diff --git a/src/lib/blueprint.ts b/src/lib/blueprint.ts index 821f9a5..072a742 100644 --- a/src/lib/blueprint.ts +++ b/src/lib/blueprint.ts @@ -121,95 +121,138 @@ export interface TypesModule { openapi: Openapi } -export const createBlueprint = ({ - openapi, -}: { - openapi: Openapi -}): Blueprint => { - const blueprint: Blueprint = { - name: openapi.info.title, - routes: [], - resources: {}, - } - +export const createBlueprint = (openapi: Openapi): Blueprint => { + const isFakeData = openapi.info.title === 'Foo' const targetPath = '/acs/systems/list' const targetSchema = 'acs_system' - const isFakeData = openapi.info.title === 'Foo' - - for (const [path, pathItem] of Object.entries(openapi.paths)) { - if (!isFakeData && path !== targetPath) continue - - const route: Route = { - path, - namespace: { path: '/acs' }, - endpoints: [], - subroutes: [], - } - - for (const [method, operation] of Object.entries(pathItem as Record)) { - if (typeof operation !== 'object' || operation === null) continue - - const endpoint: Endpoint = { - name: 'operationId' in operation && typeof operation.operationId === 'string' - ? operation.operationId - : `${method}${path.replace(/\//g, '_')}`, - path, - methods: [method.toUpperCase() as Method], - semanticMethod: method.toUpperCase() as Method, - preferredMethod: method.toUpperCase() as Method, - description: 'summary' in operation && typeof operation.summary === 'string' - ? operation.summary - : '', - isUndocumented: false, - isDeprecated: false, - deprecationMessage: '', - parameters: [], - request: { - methods: [method.toUpperCase() as Method], - semanticMethod: method.toUpperCase() as Method, - preferredMethod: method.toUpperCase() as Method, - parameters: [], - }, - response: createResponse('responses' in operation ? operation.responses : {}), - } - - if ('parameters' in operation && Array.isArray(operation.parameters)) { - for (const param of operation.parameters) { - if (typeof param === 'object' && param !== null) { - const parameter: Parameter = { - name: 'name' in param && typeof param.name === 'string' ? param.name : '', - isRequired: 'required' in param && typeof param.required === 'boolean' ? param.required : false, - isUndocumented: false, - isDeprecated: false, - deprecationMessage: '', - description: 'description' in param && typeof param.description === 'string' ? param.description : '', - } - endpoint.parameters.push(parameter) - endpoint.request.parameters.push(parameter) - } - } - } - - route.endpoints.push(endpoint) - } - - blueprint.routes.push(route) + return { + name: openapi.info.title, + routes: createRoutes(openapi.paths, isFakeData, targetPath), + resources: createResources( + openapi.components.schemas, + isFakeData, + targetSchema, + ), } +} - for (const [schemaName, schema] of Object.entries(openapi.components.schemas)) { - if (!isFakeData && schemaName !== targetSchema) continue - - if (typeof schema === 'object' && schema !== null && 'properties' in schema && typeof schema.properties === 'object' && schema.properties !== null) { - blueprint.resources[schemaName] = { - resourceType: schemaName, - properties: createProperties(schema.properties as Record), +const createRoutes = ( + paths: Openapi['paths'], + isFakeData: boolean, + targetPath: string, +): Route[] => + Object.entries(paths) + .filter(([path]) => isFakeData || path === targetPath) + .map(([path, pathItem]) => createRoute(path, pathItem)) + +const createRoute = ( + path: string, + pathItem: Record, +): Route => ({ + path, + namespace: { path: '/acs' }, + endpoints: createEndpoints(path, pathItem), + subroutes: [], +}) + +const createEndpoints = ( + path: string, + pathItem: Record, +): Endpoint[] => + Object.entries(pathItem) + .filter( + ([, operation]) => typeof operation === 'object' && operation !== null, + ) + .map(([method, operation]) => + createEndpoint(method, operation as Record, path), + ) + +const createEndpoint = ( + method: string, + operation: Record, + path: string, +): Endpoint => ({ + name: + 'operationId' in operation && typeof operation['operationId'] === 'string' + ? operation['operationId'] + : `${method}${path.replace(/\//g, '_')}`, + path, + methods: [method.toUpperCase() as Method], + semanticMethod: method.toUpperCase() as Method, + preferredMethod: method.toUpperCase() as Method, + description: + 'summary' in operation && typeof operation['summary'] === 'string' + ? operation['summary'] + : '', + isUndocumented: false, + isDeprecated: false, + deprecationMessage: '', + parameters: createParameters(operation), + request: createRequest(method, operation), + response: createResponse( + 'responses' in operation ? operation['responses'] : {}, + ), +}) + +const createParameters = (operation: Record): Parameter[] => + 'parameters' in operation && Array.isArray(operation['parameters']) + ? operation['parameters'] + .filter( + (param): param is Record => + typeof param === 'object' && param !== null, + ) + .map(createParameter) + : [] + +const createParameter = (param: Record): Parameter => ({ + name: + 'name' in param && typeof param['name'] === 'string' ? param['name'] : '', + isRequired: + 'required' in param && typeof param['required'] === 'boolean' + ? param['required'] + : false, + isUndocumented: false, + isDeprecated: false, + deprecationMessage: '', + description: + 'description' in param && typeof param['description'] === 'string' + ? param['description'] + : '', +}) + +const createRequest = ( + method: string, + operation: Record, +): Request => ({ + methods: [method.toUpperCase() as Method], + semanticMethod: method.toUpperCase() as Method, + preferredMethod: method.toUpperCase() as Method, + parameters: createParameters(operation), +}) + +const createResources = ( + schemas: Openapi['components']['schemas'], + isFakeData: boolean, + targetSchema: string, +): Record => + Object.entries(schemas) + .filter(([schemaName]) => isFakeData || schemaName === targetSchema) + .reduce>((acc, [schemaName, schema]) => { + if ( + typeof schema === 'object' && + schema !== null && + 'properties' in schema && + typeof schema.properties === 'object' && + schema.properties !== null + ) { + acc[schemaName] = { + resourceType: schemaName, + properties: createProperties(schema.properties), + } } - } - } - - return blueprint -} + return acc + }, {}) function createResponse(responses: unknown): Response { if (typeof responses !== 'object' || responses === null) { @@ -223,61 +266,122 @@ function createResponse(responses: unknown): Response { const content = 'content' in okResponse ? okResponse.content : null if (typeof content !== 'object' || content === null) { - return { responseType: 'void', description: 'description' in okResponse && typeof okResponse.description === 'string' ? okResponse.description : '' } + return { + responseType: 'void', + description: + 'description' in okResponse && + typeof okResponse.description === 'string' + ? okResponse.description + : '', + } } - const jsonContent = 'application/json' in content ? content['application/json'] : null + const jsonContent = + 'application/json' in content ? content['application/json'] : null if (typeof jsonContent !== 'object' || jsonContent === null) { - return { responseType: 'void', description: 'description' in okResponse && typeof okResponse.description === 'string' ? okResponse.description : '' } + return { + responseType: 'void', + description: + 'description' in okResponse && + typeof okResponse.description === 'string' + ? okResponse.description + : '', + } } const schema = 'schema' in jsonContent ? jsonContent.schema : null if (typeof schema !== 'object' || schema === null) { - return { responseType: 'void', description: 'description' in okResponse && typeof okResponse.description === 'string' ? okResponse.description : '' } + return { + responseType: 'void', + description: + 'description' in okResponse && + typeof okResponse.description === 'string' + ? okResponse.description + : '', + } } if ('type' in schema && 'properties' in schema) { - if (schema.type === 'array' && 'items' in schema && typeof schema.items === 'object' && schema.items !== null) { + if ( + schema.type === 'array' && + 'items' in schema && + typeof schema.items === 'object' && + schema.items !== null + ) { const refString = '$ref' in schema.items ? schema.items.$ref : null return { responseType: 'resource_list', responseKey: 'items', - resourceType: typeof refString === 'string' && refString.length > 0 ? refString.split('/').pop() ?? 'unknown' : 'unknown', - description: 'description' in okResponse && typeof okResponse.description === 'string' ? okResponse.description : '', + resourceType: + typeof refString === 'string' && refString.length > 0 + ? refString.split('/').pop() ?? 'unknown' + : 'unknown', + description: + 'description' in okResponse && + typeof okResponse.description === 'string' + ? okResponse.description + : '', } - } else if (schema.type === 'object' && typeof schema.properties === 'object' && schema.properties !== null) { + } else if ( + schema.type === 'object' && + typeof schema.properties === 'object' && + schema.properties !== null + ) { const properties = schema.properties as Record const refKey = Object.keys(properties).find( - (key) => typeof properties[key] === 'object' && + (key) => + typeof properties[key] === 'object' && properties[key] !== null && - '$ref' in (properties[key] as Record) + '$ref' in (properties[key] as Record), ) if (refKey != null) { - const refString = '$ref' in (properties[refKey] as Record) - ? (properties[refKey] as Record)['$ref'] - : null + const refString = + '$ref' in (properties[refKey] as Record) + ? (properties[refKey] as Record)['$ref'] + : null return { responseType: 'resource', responseKey: refKey, - resourceType: typeof refString === 'string' && refString.length > 0 ? refString.split('/').pop() ?? 'unknown' : 'unknown', - description: 'description' in okResponse && typeof okResponse.description === 'string' ? okResponse.description : '', + resourceType: + typeof refString === 'string' && refString.length > 0 + ? refString.split('/').pop() ?? 'unknown' + : 'unknown', + description: + 'description' in okResponse && + typeof okResponse.description === 'string' + ? okResponse.description + : '', } } } } - return { responseType: 'void', description: 'description' in okResponse && typeof okResponse.description === 'string' ? okResponse.description : '' } + return { + responseType: 'void', + description: + 'description' in okResponse && typeof okResponse.description === 'string' + ? okResponse.description + : '', + } } function createProperties(properties: Record): Property[] { return Object.entries(properties).map(([name, prop]): Property => { if (typeof prop !== 'object' || prop === null) { - return { name, type: 'string', isDeprecated: false, deprecationMessage: '' } + return { + name, + type: 'string', + isDeprecated: false, + deprecationMessage: '', + } } const baseProperty = { name, - description: 'description' in prop && typeof prop.description === 'string' ? prop.description : '', + description: + 'description' in prop && typeof prop.description === 'string' + ? prop.description + : '', isDeprecated: false, deprecationMessage: '', } @@ -290,9 +394,12 @@ function createProperties(properties: Record): Property[] { return { ...baseProperty, type: 'object', - properties: 'properties' in prop && typeof prop.properties === 'object' && prop.properties !== null - ? createProperties(prop.properties as Record) - : [], + properties: + 'properties' in prop && + typeof prop.properties === 'object' && + prop.properties !== null + ? createProperties(prop.properties as Record) + : [], } case 'array': return { ...baseProperty, type: 'list' } @@ -303,4 +410,4 @@ function createProperties(properties: Record): Property[] { return { ...baseProperty, type: 'string' } }) -} \ No newline at end of file +} diff --git a/src/lib/openapi.ts b/src/lib/openapi.ts index d83f9d8..2303c23 100644 --- a/src/lib/openapi.ts +++ b/src/lib/openapi.ts @@ -12,34 +12,52 @@ export interface Openapi { description: string }> components: { - schemas: Record - required: string[] - }> - } - paths: Record - required: string[] + properties: Record< + string, + { + description: string type: string + format?: string + } + > + required: string[] + } + > + } + paths: Record< + string, + { + [method in 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH']?: { + operationId: string + responses: Record< + string, + { + description: string + content?: Record< + string, + { + schema: { + properties: Record< + string, + { + type: string + $ref?: string + } + > + required: string[] + type: string + } + } + > } - }> - }> - summary?: string - tags?: string[] + > + summary?: string + tags?: string[] + } } - }> -} \ No newline at end of file + > +}