@mintlify/validation
Version:
Validates mint.json files
258 lines (257 loc) • 13.3 kB
JavaScript
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();
}
}