UNPKG

@mintlify/validation

Version:

Validates mint.json files

258 lines (257 loc) 13.3 kB
import { BaseConverter } from './BaseConverter.js'; import { generateFirstIncrementalSchema } from './IncrementalEvaluator.js'; import { IncrementalParametersConverter, ParametersConverter } from './ParametersConverter.js'; import { SchemaConverter } from './SchemaConverter.js'; import { SecurityConverter } from './SecurityConverter.js'; import { ServersConverter } from './ServersConverter.js'; import { InvalidSchemaError } from './errors.js'; import { generateExampleFromSchema } from './generateExampleFromSchema.js'; import { EndpointLocation } from './types/endpoint.js'; import { dereference } from './utils.js'; export class BaseOpenApiToEndpointConverter extends BaseConverter { constructor(document, path, method, safeParse = false) { super(safeParse); this.document = document; this.path = path; this.method = method; this.safeParse = safeParse; this.location = new EndpointLocation(document, path); const paths = this.location.getEndpointPaths(); if (paths === undefined) { throw new InvalidSchemaError(['#'], `${this.location.type} not defined`); } const pathObject = this.location.getEndpoint(); if (pathObject === undefined) { throw new InvalidSchemaError(['#', this.location.path], `${this.location.type} not defined: ${this.path}`); } this.pathObject = pathObject; const operationObject = pathObject[this.method]; if (operationObject === undefined) { throw new InvalidSchemaError(['#', this.location.path, this.path], `operation does not exist: ${this.method}`); } this.operationObject = operationObject; } convert() { var _a, _b, _c, _d, _e; const securityRequirements = this.location.type === 'webhook' ? this.operationObject.security : (_a = this.operationObject.security) !== null && _a !== void 0 ? _a : this.document.security; const securitySchemes = (_b = this.document.components) === null || _b === void 0 ? void 0 : _b.securitySchemes; const security = SecurityConverter.convert(securityRequirements, securitySchemes, this.safeParse); const parameters = this.convertParameters(); const servers = ServersConverter.convert((_d = (_c = this.operationObject.servers) !== null && _c !== void 0 ? _c : this.pathObject.servers) !== null && _d !== void 0 ? _d : this.document.servers); // title is a bit too specific to take from the path object const title = this.operationObject.summary; const description = (_e = this.operationObject.description) !== null && _e !== void 0 ? _e : this.pathObject.description; const body = this.convertBody(); const deprecated = !!this.operationObject.deprecated; const codeSamples = this.convertCodeSamples(['#', this.location.path, this.path, this.method], this.operationObject); const xMcp = this.getXmcp(this.operationObject); const response = this.convertResponses(); return { title, description, path: this.path, method: this.method, servers, request: { security, parameters, body, codeSamples, }, response, deprecated, type: this.location.type, xMcp, }; } convertExamples(examples, example, schemaArray) { if (examples && Object.values(examples).some(({ value }) => value !== undefined)) { return Object.fromEntries(Object.entries(examples) .filter(([_, { value }]) => value !== undefined) .map(([key, example]) => [ key, { summary: example.summary, description: example.description, value: example.value, }, ])); } if (example !== undefined) { return { example: { value: example } }; } return { example: { value: generateExampleFromSchema(schemaArray[0]) } }; } convertCodeSamples(debugPath, operation) { let key; let rawCodeSamples; if ('x-codeSamples' in operation) { rawCodeSamples = operation['x-codeSamples']; key = 'x-codeSamples'; } else if ('x-code-samples' in operation) { rawCodeSamples = operation['x-code-samples']; key = 'x-code-samples'; } else { return undefined; } if (!Array.isArray(rawCodeSamples)) { this.handleNewError(InvalidSchemaError, [...debugPath, key], `${key} must be an array`); return; } const codeSamples = []; rawCodeSamples.forEach((codeSample) => { if (!codeSample || typeof codeSample !== 'object' || !('source' in codeSample) || typeof codeSample.source !== 'string' || !('lang' in codeSample) || typeof codeSample.lang !== 'string') { this.handleNewError(InvalidSchemaError, [...debugPath, key], 'invalid code sample'); return; } codeSamples.push({ label: 'label' in codeSample && typeof codeSample.label === 'string' ? codeSample.label : undefined, lang: codeSample['lang'], source: codeSample['source'], }); }); return codeSamples; } getXmcp(operation) { if ('x-mcp' in operation && typeof operation['x-mcp'] === 'object' && operation['x-mcp'] !== null) { return operation['x-mcp']; } return undefined; } } export class OpenApiToEndpointConverter extends BaseOpenApiToEndpointConverter { convertBody() { const requestBody = this.operationObject.requestBody; return this.convertContent(['#', this.location.path, this.path, this.method, 'requestBody', 'content'], requestBody === null || requestBody === void 0 ? void 0 : requestBody.content, 'request', requestBody === null || requestBody === void 0 ? void 0 : requestBody.required, requestBody === null || requestBody === void 0 ? void 0 : requestBody.description); } convertResponses() { const responses = this.operationObject.responses; if (!responses) return {}; const newEntries = Object.entries(responses).map(([statusCode, response]) => [ statusCode, this.convertContent(['#', this.location.path, this.path, this.method, 'responses', statusCode, 'content'], response.content, 'response', undefined, response.description), ]); return Object.fromEntries(newEntries); } convertContent(debugPath, content, location, required, description) { if (content === undefined) { return {}; } const newEntries = Object.entries(content).map(([contentType, mediaObject]) => { const schemaArray = SchemaConverter.convert({ schema: mediaObject.schema, path: [...debugPath, contentType, 'schema'], required, location, contentType, safeParse: this.safeParse, }); const examples = this.convertExamples(mediaObject.examples, mediaObject.example, schemaArray); return [contentType, { schemaArray, examples, description }]; }); return Object.fromEntries(newEntries); } convertParameters() { const pathParameters = this.pathObject.parameters; const operationParameters = this.operationObject.parameters; return ParametersConverter.convert(this.method, pathParameters, operationParameters, ['#', this.location.path, this.path], this.safeParse); } static convert(spec, path, method, safeParse) { return new OpenApiToEndpointConverter(spec, path, method, safeParse).convert(); } } export class OpenApiToIncrementalEndpointConverter extends BaseOpenApiToEndpointConverter { constructor(rawDocument, document, path, method, safeParse = false) { super(document, path, method, safeParse); this.rawDocument = rawDocument; this.location = new EndpointLocation(rawDocument, path); } convertParameters() { var _a; const path = this.location.getEndpoint(); const pathParameters = path === null || path === void 0 ? void 0 : path.parameters; const operationParameters = (_a = path === null || path === void 0 ? void 0 : path[this.method]) === null || _a === void 0 ? void 0 : _a.parameters; return IncrementalParametersConverter.convert(this.rawDocument, this.method, pathParameters, operationParameters, ['#', this.location.path, this.path], this.safeParse); } convertBody() { var _a, _b; const path = this.location.getEndpoint(); let rawRequestBody = (_a = path === null || path === void 0 ? void 0 : path[this.method]) === null || _a === void 0 ? void 0 : _a.requestBody; if (rawRequestBody && '$ref' in rawRequestBody) { rawRequestBody = dereference('requestBodies', rawRequestBody.$ref, (_b = this.rawDocument.components) === null || _b === void 0 ? void 0 : _b.requestBodies); } if (!rawRequestBody || '$ref' in rawRequestBody) return {}; const requestBody = this.operationObject.requestBody; return this.convertContent(['#', this.location.path, this.path, this.method, 'requestBody', 'content'], rawRequestBody.content, requestBody === null || requestBody === void 0 ? void 0 : requestBody.content, 'request', requestBody === null || requestBody === void 0 ? void 0 : requestBody.required, requestBody === null || requestBody === void 0 ? void 0 : requestBody.description); } convertResponses() { var _a; const path = this.location.getEndpoint(); const rawResponses = (_a = path === null || path === void 0 ? void 0 : path[this.method]) === null || _a === void 0 ? void 0 : _a.responses; if (!rawResponses) return {}; const newEntries = Object.entries(rawResponses).map(([statusCode, rawResponse]) => { var _a, _b; if ('$ref' in rawResponse) { const dereferencedRes = dereference('responses', rawResponse.$ref, (_a = this.rawDocument.components) === null || _a === void 0 ? void 0 : _a.responses); if (!dereferencedRes || '$ref' in dereferencedRes) throw Error(); rawResponse = dereferencedRes; } const response = (_b = this.operationObject.responses) === null || _b === void 0 ? void 0 : _b[statusCode]; return [ statusCode, this.convertContent(['#', this.location.path, this.path, this.method, 'responses', statusCode, 'content'], rawResponse.content, response === null || response === void 0 ? void 0 : response.content, 'response', undefined, response === null || response === void 0 ? void 0 : response.description), ]; }); return Object.fromEntries(newEntries); } convertContent(debugPath, rawContent, dereferencedContent, location, required, description) { if (!rawContent || !dereferencedContent) { if (description) { return { '_mintlify/placeholder': { schemaArray: [{ type: 'any', description }], examples: {}, description, }, }; } return {}; } const newEntries = Object.entries(rawContent).map(([contentType, mediaObject]) => { var _a; const incrementalSchemaArray = generateFirstIncrementalSchema(mediaObject.schema, (_a = this.rawDocument.components) === null || _a === void 0 ? void 0 : _a.schemas, required, location, contentType); const dereferencedMediaObject = dereferencedContent[contentType]; const schemaArray = SchemaConverter.convert({ schema: dereferencedMediaObject === null || dereferencedMediaObject === void 0 ? void 0 : dereferencedMediaObject.schema, path: [...debugPath, contentType, 'schema'], required, location, contentType, safeParse: this.safeParse, }); const examples = this.convertExamples(dereferencedMediaObject === null || dereferencedMediaObject === void 0 ? void 0 : dereferencedMediaObject.examples, dereferencedMediaObject === null || dereferencedMediaObject === void 0 ? void 0 : dereferencedMediaObject.example, schemaArray); return [contentType, { schemaArray: incrementalSchemaArray, examples, description }]; }); return Object.fromEntries(newEntries); } static convert(rawDocument, resolvedDocument, path, method, safeParse) { return new OpenApiToIncrementalEndpointConverter(rawDocument, resolvedDocument, path, method, safeParse).convert(); } }