@scalar/oas-utils
Version:
Open API spec and Yaml handling utilities
169 lines (166 loc) • 7.16 kB
JavaScript
import { json2xml } from '../helpers/json2xml.js';
import { normalizeMimeTypeObject } from '../helpers/normalize-mime-type-object.js';
import { prettyPrintJson } from '../helpers/pretty-print-json.js';
import { getExampleFromSchema } from './get-example-from-schema.js';
import { getParametersFromOperation } from './get-parameters-from-operation.js';
/**
* Transform the object into a nested array of objects
* that represent the key-value pairs of the object.
*/
function getParamsFromObject(obj, prefix = '') {
return Object.entries(obj).flatMap(([key, value]) => {
const newKey = prefix ? `${prefix}[${key}]` : key;
if (typeof value === 'object' && value !== null) {
return getParamsFromObject(value, newKey);
}
return [{ name: newKey, value }];
});
}
// Define preferred standard mime types (order indicates preference)
const standardMimeTypes = [
'application/json',
'application/octet-stream',
'application/x-www-form-urlencoded',
'application/xml',
'multipart/form-data',
'text/plain',
];
/**
* Get the request body from the operation.
*/
function getRequestBodyFromOperation(operation, selectedExampleKey, omitEmptyAndOptionalProperties) {
const originalContent = operation.information?.requestBody?.content;
const content = normalizeMimeTypeObject(originalContent);
// First try to find a standard mime type
const mimeType = standardMimeTypes.find((currentMimeType) => !!content?.[currentMimeType]) ??
(Object.keys(content ?? {})[0] || 'application/json');
// Handle JSON-like content types (e.g., application/vnd.github+json)
const isJsonLike = mimeType.includes('json') || mimeType.endsWith('+json');
/** Examples */
const examples = content?.[mimeType]?.examples ?? content?.['application/json']?.examples;
// Let’s use the first example
const selectedExample = examples?.[selectedExampleKey ?? Object.keys(examples ?? {})[0] ?? ''];
if (selectedExample) {
return {
mimeType,
text: prettyPrintJson(selectedExample?.value),
};
}
/**
* Body Parameters (Swagger 2.0)
*
* ”The payload that's appended to the HTTP request. Since there can only be one payload, there can only
* be one body parameter. The name of the body parameter has no effect on the parameter itself and is used
* for documentation purposes only. Since Form parameters are also in the payload, body and form
* parameters cannot exist together for the same operation.”
*/
const bodyParameters = getParametersFromOperation(operation, 'body', false);
if (bodyParameters.length > 0) {
return {
mimeType: 'application/json',
text: prettyPrintJson(bodyParameters[0]?.value ?? ''),
};
}
/**
* FormData Parameters (Swagger 2.0)
*
* ”Form - Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded,
* multipart/form-data or both are used as the content type of the request (in Swagger's definition, the
* consumes property of an operation). This is the only parameter type that can be used to send files,
* thus supporting the file type. Since form parameters are sent in the payload, they cannot be declared
* together with a body parameter for the same operation. Form parameters have a different format based on
* the content-type used (for further details, consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4):
* - application/x-www-form-urlencoded - Similar to the format of Query parameters but as a payload.
* For example, foo=1&bar=swagger - both foo and bar are form parameters. This is normally used for simple
* parameters that are being transferred.
* - multipart/form-data - each parameter takes a section in the payload with an internal header.
* For example, for the header Content-Disposition: form-data; name="submit-name" the name of the parameter is
* submit-name. This type of form parameters is more commonly used for file transfers.”
*/
const formDataParameters = getParametersFromOperation(operation, 'formData', false);
if (formDataParameters.length > 0) {
return {
mimeType: 'application/x-www-form-urlencoded',
params: formDataParameters.map((parameter) => ({
name: parameter.name,
/**
* TODO: This value MUST be a string
* Figure out why this is not always a string
*
* JSON.stringify is a TEMPORARY fix
*/
value: typeof parameter.value === 'string' ? parameter.value : JSON.stringify(parameter.value),
})),
};
}
// If no mime type is supported, exit early
if (!mimeType) {
return null;
}
// Get the request body object for the mime type
const requestBodyObject = content?.[mimeType];
// Get example from operation
const example = requestBodyObject?.example ? requestBodyObject?.example : undefined;
// Update the JSON handling section
if (isJsonLike) {
const exampleFromSchema = requestBodyObject?.schema
? getExampleFromSchema(requestBodyObject?.schema, {
mode: 'write',
omitEmptyAndOptionalProperties: omitEmptyAndOptionalProperties ?? false,
})
: null;
const body = example ?? exampleFromSchema;
return {
mimeType,
text: body ? (typeof body === 'string' ? body : JSON.stringify(body, null, 2)) : undefined,
};
}
// XML
if (mimeType === 'application/xml') {
const exampleFromSchema = requestBodyObject?.schema
? getExampleFromSchema(requestBodyObject?.schema, {
xml: true,
mode: 'write',
})
: null;
return {
mimeType,
text: example ?? json2xml(exampleFromSchema, ' '),
};
}
// Binary data
if (mimeType === 'application/octet-stream') {
return {
mimeType,
text: 'BINARY',
};
}
// Plain text
if (mimeType === 'text/plain') {
const exampleFromSchema = requestBodyObject?.schema
? getExampleFromSchema(requestBodyObject?.schema, {
xml: true,
mode: 'write',
})
: null;
return {
mimeType,
text: example ?? exampleFromSchema ?? '',
};
}
// URL encoded data
if (mimeType === 'multipart/form-data' || mimeType === 'application/x-www-form-urlencoded') {
const exampleFromSchema = requestBodyObject?.schema
? getExampleFromSchema(requestBodyObject?.schema, {
xml: true,
mode: 'write',
})
: null;
return {
mimeType,
params: getParamsFromObject(example ?? exampleFromSchema ?? {}),
};
}
return null;
}
export { getRequestBodyFromOperation };