@mintlify/common
Version:
Commonly shared code within Mintlify
280 lines (279 loc) • 10.1 kB
JavaScript
import isAbsoluteUrl from 'is-absolute-url';
import { getSecurityOptionsForAuthMethod } from '../../../../index.js';
import { isDocsConfig } from '../../../../isDocsConfig.js';
import { isMdxJsxAttribute, isParamFieldLocation } from '../../../lib/mdx-utils.js';
export const parseApiString = (apiString, config) => {
const components = apiString.trim().split(/\s+/);
if (!components[0] || !components[1] || components.length > 2) {
throw new Error('improperly formatted api string');
}
const [upperMethod, endpointStr] = components;
const method = upperMethod.toLowerCase();
if (!['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'].includes(method)) {
throw new Error('invalid http method');
}
const { origin, path } = parseEndpoint(endpointStr);
const servers = origin ? [{ url: origin }] : parseServers(config);
return {
path,
method: method,
servers,
};
};
const parseEndpoint = (endpoint) => {
if (!isAbsoluteUrl(endpoint)) {
return {
origin: undefined,
path: endpoint,
};
}
const url = new URL(endpoint);
return {
origin: decodeURI(url.origin),
path: decodeURI(url.pathname),
};
};
const parseServers = (config) => {
var _a, _b, _c;
const baseUrl = config && isDocsConfig(config) ? (_b = (_a = config.api) === null || _a === void 0 ? void 0 : _a.mdx) === null || _b === void 0 ? void 0 : _b.server : (_c = config === null || config === void 0 ? void 0 : config.api) === null || _c === void 0 ? void 0 : _c.baseUrl;
if (!baseUrl) {
return undefined;
}
if (typeof baseUrl === 'string') {
return [{ url: baseUrl }];
}
const servers = baseUrl.filter(Boolean).map((url) => ({ url }));
return servers.length > 0 ? servers : undefined;
};
export const parseAuthMethod = (authMethodString, config) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const method = authMethodString !== null && authMethodString !== void 0 ? authMethodString : (config && isDocsConfig(config) ? (_c = (_b = (_a = config.api) === null || _a === void 0 ? void 0 : _a.mdx) === null || _b === void 0 ? void 0 : _b.auth) === null || _c === void 0 ? void 0 : _c.method : (_e = (_d = config === null || config === void 0 ? void 0 : config.api) === null || _d === void 0 ? void 0 : _d.auth) === null || _e === void 0 ? void 0 : _e.method);
const name = config && isDocsConfig(config) ? (_h = (_g = (_f = config.api) === null || _f === void 0 ? void 0 : _f.mdx) === null || _g === void 0 ? void 0 : _g.auth) === null || _h === void 0 ? void 0 : _h.name : (_k = (_j = config === null || config === void 0 ? void 0 : config.api) === null || _j === void 0 ? void 0 : _j.auth) === null || _k === void 0 ? void 0 : _k.name;
return getSecurityOptionsForAuthMethod(method, name);
};
export const parseField = (node) => {
let locationAttr;
let schemaInfo;
let required = undefined;
let deprecated = undefined;
let placeholder = undefined;
for (const { name: attrName, value } of node.attributes.filter(isMdxJsxAttribute)) {
if (node.name !== 'ResponseField' && isParamFieldLocation(attrName)) {
// if ParamField or Param, parameter name comes from the `body`, `header`, etc attribute
if (locationAttr) {
console.error('multiple location/name pairs specified');
return;
}
if (attrName === 'body') {
if (typeof value !== 'string' && value !== null) {
console.error(`invalid body parameter name: "${value}"`);
return;
}
locationAttr = {
location: attrName,
name: value,
};
}
else {
if (typeof value !== 'string') {
console.error(`invalid parameter name: "${value}"`);
return;
}
locationAttr = {
location: attrName,
name: value,
};
}
}
else if (node.name === 'ResponseField' && attrName === 'name') {
// if ResponseField, parameter name comes from the `name` attribute
if (locationAttr) {
console.error('multiple names specified');
return;
}
if (typeof value !== 'string') {
console.error(`invalid response field name: "${value}"`);
return;
}
locationAttr = {
location: 'response',
name: value,
};
}
else if (attrName === 'type') {
if (typeof value !== 'string') {
console.error(`invalid type string: "${value}"`);
return;
}
schemaInfo = parseTypeString(value);
}
else if (attrName === 'required') {
if (value === null || (typeof value === 'object' && value.value.trim() === 'true')) {
required = true;
}
}
else if (attrName === 'deprecated') {
if (value === null || (typeof value === 'object' && value.value.trim() === 'true')) {
deprecated = true;
}
}
else if (attrName === 'placeholder') {
if (typeof value === 'string') {
placeholder = value;
}
}
}
if (locationAttr === undefined) {
console.error('no parameter name specified');
return;
}
if (schemaInfo === undefined) {
console.error(`no type specified for parameter "${locationAttr.name}"`);
return;
}
schemaInfo.schema.description = parseDescription(node);
schemaInfo.schema.required = required;
schemaInfo.schema.deprecated = deprecated;
schemaInfo.schema.placeholder = placeholder;
return Object.assign({ locationAttr }, schemaInfo);
};
export const parseDescription = (node) => {
const descriptionFragments = node.children.flatMap((child) => {
var _a;
if (child.type === 'text') {
return [child.value];
}
else if (child.type === 'element' &&
child.tagName === 'p' &&
((_a = child.children[0]) === null || _a === void 0 ? void 0 : _a.type) === 'text') {
return [child.children[0].value];
}
return [];
});
return descriptionFragments.length > 0 ? descriptionFragments.join('\n') : undefined;
};
const typeStringToSchemaType = {
// string
str: 'string',
string: 'string',
date: 'string',
text: 'string',
url: 'string',
uuid: 'string',
hash: 'string',
bytes: 'string',
xml: 'string',
// integer
int: 'integer',
integer: 'integer',
// number
num: 'number',
number: 'number',
float: 'number',
decimal: 'number',
long: 'number',
double: 'number',
// boolean
bool: 'boolean',
boolean: 'boolean',
// object
obj: 'object',
object: 'object',
record: 'object',
dict: 'object',
dictionary: 'object',
map: 'object',
// array
arr: 'array',
array: 'array',
list: 'array',
// file
file: 'file',
// any
any: 'any',
json: 'any',
// null
null: 'null',
};
const genericStringRegex = /^(\w+)<(.*)>$/;
export const parseTypeString = (typeString) => {
const lowerTypeString = typeString.toLowerCase();
const simpleSchemaType = typeStringToSchemaType[lowerTypeString];
// catch all standard type strings
if (simpleSchemaType) {
return generateSchemaWithTypeString(simpleSchemaType);
}
// catch type strings that end with []
if (lowerTypeString.endsWith('[]')) {
// recursively determine the type of the rest of the string
const subschemaInfo = parseTypeString(lowerTypeString.slice(0, lowerTypeString.length - 2));
return {
schema: {
type: 'array',
items: [subschemaInfo.schema],
},
deepestSchema: subschemaInfo.deepestSchema,
};
}
// catch type strings like foo<bar>
const regexMatch = genericStringRegex.exec(lowerTypeString);
if (regexMatch !== null) {
// unpack capture group 1 (foo) and 2 (bar)
const [_, superType, subType] = regexMatch;
if (superType && subType && typeStringToSchemaType[superType] === 'array') {
// catches type strings like array<bar>
// recursively determine the type of everything within the angle brackets
const subschemaInfo = parseTypeString(subType);
return {
schema: {
type: 'array',
items: [subschemaInfo.schema],
},
deepestSchema: subschemaInfo.deepestSchema,
};
}
}
// for any unrecognized typestring, default to object
return generateSchemaWithTypeString('object');
};
const generateSchemaWithTypeString = (type) => {
switch (type) {
case 'string':
case 'number':
case 'integer':
case 'boolean':
case 'file':
case 'null':
case 'any': {
const schema = { type };
return {
schema,
deepestSchema: schema,
};
}
case 'object': {
const schema = {
type: 'object',
properties: {},
};
return {
schema,
deepestSchema: schema,
};
}
case 'array': {
const itemsSchema = {
type: 'object',
properties: {},
};
const schema = {
type: 'array',
items: [itemsSchema],
};
return {
schema,
deepestSchema: itemsSchema,
};
}
}
};