Skip to content

Commit

Permalink
More refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
bourdakos1 committed Dec 13, 2021
1 parent ef16675 commit 7000f44
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 77 deletions.
4 changes: 3 additions & 1 deletion packages/docusaurus-plugin-openapi/src/openapi/load.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,7 @@ it("hello world", async () => {
"@<routeBasePath>"
);

expect(actual).toStrictEqual(expected);
expect(JSON.parse(JSON.stringify(actual))).toStrictEqual(
JSON.parse(JSON.stringify(expected))
);
});
250 changes: 174 additions & 76 deletions packages/docusaurus-plugin-openapi/src/openapi/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,17 @@ export async function _loadOpenapi(
return order;
}

/**
* Finds any reference objects in the OpenAPI definition and resolves them to a finalized value.
*/
async function resolveRefs(openapiData: OpenApiObjectWithRef) {
const { resolved } = await JsonRefs.resolveRefs(openapiData);
return resolved as OpenApiObject;
}

/**
* Convenience function for converting raw JSON to a Postman Collection object.
*/
function jsonToCollection(data: OpenApiObject): Promise<Collection> {
return new Promise((resolve, reject) => {
Converter.convert(
Expand All @@ -270,6 +276,9 @@ function jsonToCollection(data: OpenApiObject): Promise<Collection> {
});
}

/**
* Creates a Postman Collection object from an OpenAPI definition.
*/
async function createPostmanCollection(
openapiData: OpenApiObject
): Promise<Collection> {
Expand All @@ -292,104 +301,193 @@ async function createPostmanCollection(
return await jsonToCollection(data);
}

function findOperationObject(
openapiData: OpenApiObject,
method: string,
path: string
) {
//type narrowing
switch (method) {
case "get":
case "put":
case "post":
case "delete":
case "options":
case "head":
case "patch":
case "trace":
return openapiData.paths[path]?.[method];
/**
* Find the ApiItem, given the method and path.
*/
function findApiItem(sections: ApiSection[], method: string, path: string) {
for (const section of sections) {
const found = section.items.find(
(item) => item.path === path && item.method === method
);
if (found) {
return found;
}
}

return undefined;
return;
}

export async function _newLoadOpenapi(
openapiDataWithRefs: OpenApiObjectWithRef,
baseUrl: string,
routeBasePath: string
) {
const openapiData = await resolveRefs(openapiDataWithRefs);

// Attach a postman request object to the openapi spec.
const postmanCollection = await createPostmanCollection(openapiData);

postmanCollection.forEachItem((item) => {
const method = item.request.method.toLowerCase();
const path = item.request.url
.getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
.replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
function createItems(openapiData: OpenApiObject): ApiItem[] {
const seen: { [key: string]: number } = {};

const operationObject = findOperationObject(openapiData, method, path);
if (operationObject) {
// TODO
(operationObject as any).postman = item.request;
}
});
// TODO
let items: Omit<ApiItem, "permalink" | "next" | "previous">[] = [];

for (let [path, pathObject] of Object.entries(openapiData.paths)) {
const { $ref, description, parameters, servers, summary, ...rest } =
pathObject;
for (let [method, operationObject] of Object.entries({ ...rest })) {
const title =
operationObject.summary ??
operationObject.operationId ??
"Missing summary";
if (operationObject.description === undefined) {
operationObject.description =
operationObject.summary ?? operationObject.operationId ?? "";
}

const order = organizeSpec(openapiData);
const baseId = kebabCase(title);
let count = seen[baseId];

order.forEach((category, i) => {
category.items.forEach((item, ii) => {
// don't override already defined servers.
if (item.servers === undefined) {
item.servers = openapiData.servers;
let id;
if (count) {
id = `${baseId}-${count}`;
seen[baseId] = count + 1;
} else {
id = baseId;
seen[baseId] = 1;
}

if (item.security === undefined) {
item.security = openapiData.security;
}
const servers =
operationObject.servers ?? pathObject.servers ?? openapiData.servers;

const security = operationObject.security ?? openapiData.security;

// Add security schemes so we know how to handle security.
item.securitySchemes = openapiData.components?.securitySchemes;
const securitySchemes = openapiData.components?.securitySchemes;

// Make sure schemes are lowercase. See: https://github.com/cloud-annotations/docusaurus-plugin-openapi/issues/79
Object.values(item.securitySchemes ?? {}).forEach((auth) => {
if (auth.type === "http") {
auth.scheme = auth.scheme.toLowerCase();
if (securitySchemes) {
for (let securityScheme of Object.values(securitySchemes)) {
if (securityScheme.type === "http") {
securityScheme.scheme = securityScheme.scheme.toLowerCase();
}
}
}

let jsonRequestBodyExample;
const body = operationObject.requestBody?.content?.["application/json"];
if (body?.schema) {
jsonRequestBodyExample = sampleFromSchema(body.schema);
}

// TODO: Don't include summary temporarilly
const { summary, ...defaults } = operationObject;

items.push({
...defaults,
id,
title,
method,
path,
servers,
security,
securitySchemes,
jsonRequestBodyExample,
});
}
}

item.permalink = normalizeUrl([baseUrl, routeBasePath, item.id]);
// TODO
return items as ApiItem[];
}

const prev =
order[i].items[ii - 1] ||
order[i - 1]?.items[order[i - 1].items.length - 1];
const next =
order[i].items[ii + 1] || (order[i + 1] ? order[i + 1].items[0] : null);
function createSections(spec: OpenApiObject) {
const paths = createItems(spec);

if (prev) {
item.previous = {
title: prev.title,
permalink: normalizeUrl([baseUrl, routeBasePath, prev.id]),
let tagNames: string[] = [];
let tagged: ApiSection[] = [];
if (spec.tags) {
tagged = spec.tags
.map((tag) => {
return {
title: tag.name,
description: tag.description || "",
items: paths.filter((p) => p.tags && p.tags.includes(tag.name)),
};
}
})
.filter((i) => i.items.length > 0);
tagNames = tagged.map((t) => t.title);
}

if (next) {
item.next = {
title: next.title,
permalink: normalizeUrl([baseUrl, routeBasePath, next.id]),
};
}
const all = [
...tagged,
{
title: "API",
description: "",
items: paths.filter((p) => {
if (p.tags === undefined || p.tags.length === 0) {
return true;
}
for (let tag of p.tags) {
if (tagNames.includes(tag)) {
return false;
}
}
return true;
}),
},
];

const content = item.requestBody?.content;
const schema = content?.["application/json"]?.schema;
if (schema) {
item.jsonRequestBodyExample = sampleFromSchema(schema);
}
});
return all;
}

/**
* Attach Postman Request objects to the corresponding ApiItems.
*/
function bindCollectionToSections(
sections: ApiSection[],
postmanCollection: sdk.Collection
) {
postmanCollection.forEachItem((item) => {
const method = item.request.method.toLowerCase();
const path = item.request.url
.getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
.replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"

const apiItem = findApiItem(sections, method, path);
if (apiItem) {
apiItem.postman = item.request;
}
});
}

return order;
export async function _newLoadOpenapi(
openapiDataWithRefs: OpenApiObjectWithRef,
baseUrl: string,
routeBasePath: string
) {
const openapiData = await resolveRefs(openapiDataWithRefs);
const postmanCollection = await createPostmanCollection(openapiData);

const sections = createSections(openapiData);

bindCollectionToSections(sections, postmanCollection);

// flatten the references to make creating the previous/next data easier.
const items = sections.flatMap((s) => s.items);
for (let i = 0; i < items.length; i++) {
const current = items[i];
const prev = items[i - 1];
const next = items[i + 1];

current.permalink = normalizeUrl([baseUrl, routeBasePath, current.id]);

if (prev) {
current.previous = {
title: prev.title,
permalink: normalizeUrl([baseUrl, routeBasePath, prev.id]),
};
}

if (next) {
current.next = {
title: next.title,
permalink: normalizeUrl([baseUrl, routeBasePath, next.id]),
};
}
}

return sections;
}

export async function loadOpenapi(
Expand Down

0 comments on commit 7000f44

Please sign in to comment.