UNPKG

rapidoc

Version:

RapiDoc - Open API spec viewer with built in console

1,087 lines (1,047 loc) 41 kB
import RandExp from 'randexp'; // Make RandExp determinist RandExp.prototype.randInt = (from) => from; // Takes a value as input and provides a printable string to replresent null values, spaces, blankstring etc export function getPrintableVal(val) { if (val === undefined) { return ''; } if (val === null) { return 'null'; } if (val === '') { return '∅'; } if (typeof val === 'boolean' || typeof val === 'number') { return `${val}`; } if (Array.isArray(val)) { return val.map((v) => (v === null ? 'null' : v === '' ? '∅' : v.toString().replace(/^ +| +$/g, (m) => '●'.repeat(m.length)) || '')).join(', '); } if (typeof val === 'object') { const keys = Object.keys(val); return `{ ${keys[0]}:${val[keys[0]]}${keys.length > 1 ? ',' : ''} ... }`; } return val.toString().replace(/^ +| +$/g, (m) => '●'.repeat(m.length)) || ''; } /* Generates an schema object containing type and constraint info */ export function getTypeInfo(schema) { if (!schema) { return; } let dataType = ''; let constrain = ''; // let examples; if (schema.$ref) { const n = schema.$ref.lastIndexOf('/'); const schemaNode = schema.$ref.substring(n + 1); dataType = `{recursive: ${schemaNode}} `; } else if (schema.type) { dataType = Array.isArray(schema.type) ? schema.type.join(schema.length === 2 ? ' or ' : '┃') : schema.type; if (schema.format || schema.enum || schema.const) { dataType = dataType.replace('string', schema.enum ? 'enum' : schema.const ? 'const' : schema.format); } if (schema.nullable) { dataType += '┃null'; } } else if (schema.const) { dataType = 'const'; } else if (Object.keys(schema).length === 0) { dataType = 'any'; } else { dataType = '{missing-type-info}'; } const info = { type: dataType, format: schema.format || '', pattern: (schema.pattern && !schema.enum) ? schema.pattern : '', readOrWriteOnly: (schema.readOnly ? '🆁' : schema.writeOnly ? '🆆' : ''), deprecated: schema.deprecated ? '❌' : '', examples: schema.examples || schema.example, default: getPrintableVal(schema.default), description: schema.description || '', constrain: '', allowedValues: '', arrayType: '', html: '', }; if (info.type === '{recursive}') { info.description = schema.$ref.substring(schema.$ref.lastIndexOf('/') + 1); } else if (info.type === '{missing-type-info}' || info.type === 'any') { info.description = info.description || ''; } // Set Allowed Values info.allowedValues = schema.const ? schema.const : Array.isArray(schema.enum) ? schema.enum.map((v) => (getPrintableVal(v))).join('┃') : ''; if (dataType === 'array' && schema.items) { const arrayItemType = schema.items?.type; const arrayItemDefault = getPrintableVal(schema.items.default); info.arrayType = `${schema.type} of ${Array.isArray(arrayItemType) ? arrayItemType.join('') : arrayItemType}`; info.default = arrayItemDefault; info.allowedValues = schema.items.const ? schema.const : Array.isArray(schema.items?.enum) ? schema.items.enum.map((v) => (getPrintableVal(v))).join('┃') : ''; } if (dataType.match(/integer|number/g)) { if (schema.minimum !== undefined || schema.exclusiveMinimum !== undefined) { constrain += schema.minimum !== undefined ? `Min ${schema.minimum}` : `More than ${schema.exclusiveMinimum}`; } if (schema.maximum !== undefined || schema.exclusiveMaximum !== undefined) { constrain += schema.maximum !== undefined ? `${constrain ? '┃' : ''}Max ${schema.maximum}` : `${constrain ? '┃' : ''}Less than ${schema.exclusiveMaximum}`; } if (schema.multipleOf !== undefined) { constrain += `${constrain ? '┃' : ''} multiple of ${schema.multipleOf}`; } } if (dataType.match(/string/g)) { if (schema.minLength !== undefined && schema.maxLength !== undefined) { constrain += `${constrain ? '┃' : ''}${schema.minLength} to ${schema.maxLength} chars`; } else if (schema.minLength !== undefined) { constrain += `${constrain ? '┃' : ''}Min ${schema.minLength} chars`; } else if (schema.maxLength !== undefined) { constrain += `Max ${constrain ? '┃' : ''}${schema.maxLength} chars`; } } info.constrain = constrain; info.html = `${info.type}~|~${info.readOrWriteOnly}~|~${info.constrain}~|~${info.default}~|~${info.allowedValues}~|~${info.pattern}~|~${info.description}~|~${schema.title || ''}~|~${info.deprecated ? 'deprecated' : ''}`; return info; } /** * * @param {*} ex if the value * - Is an Object with 'value' property like * { 'value': 'example_val1', 'description': 'some description' } * Returns >>> * { * 'Example': { 'value' : 'example_val1', 'description': 'some description' }, * } * - Is an object where each key represents a valid example object (i,e has a value property) * { * 'example1': { 'value' : 'example_val1', 'description': 'some description' }, * 'example2': { 'value' : 'example_val2', 'description': 'some other description' }, * 'invalid': { 'description': 'invalid example object without any value property' } * } * Returns >>> * { * 'example1': { 'value' : 'example_val1', 'description': 'some description' }, * 'example2': { 'value' : 'example_val2', 'description': 'some other description' } * } * if none of the keys represents an object with 'value' property then return undefined * - Is an array of premitive values * ['example_val1', 'example_val2'] * Returns >>> * { * 'Example1': {value:'value1'} * 'Example2': {value:'value2'} * } * - Is a premitive value * 'example_val1' * Returns >>> * { * 'Example': { 'value': 'example_val1' } * } * - Is undefined * returns undefined * @returns */ export function standardizeExample(ex) { if (typeof ex === 'object' && !Array.isArray(ex)) { if (ex.value !== undefined) { // Case 1: Single object with 'value' property return { Example: { ...ex } }; } // Case 2: Object where each key is an object with a 'value' property const filteredEntries = Object.entries(ex).filter(([_, obj]) => obj.value !== undefined); // eslint-disable-line // If no valid entries found, return JSON.stringify of the input if (filteredEntries.length === 0) { return undefined; } return Object.fromEntries(filteredEntries); } if (Array.isArray(ex)) { // Case 3: Array of primitive values return ex.reduce((acc, value, index) => { acc[`Example${index + 1}`] = { value }; return acc; }, {}); } // Case 4: Single primitive value return ex ? { Example: { value: ex } } : undefined; } /** * Normalize example object in the following format (List of object which is used to render example links and fill the input boxes) * [{ * exampleVal : 'value to be rendered on the input control (text-box)', * exampleList : [ * value : '', * printableValue: '', * summary : '', * description : '' * ] * }] * */ export function normalizeExamples(examples, dataType = 'string') { if (!examples) { return { exampleVal: '', exampleList: [], }; } if (examples.constructor === Object) { const exampleList = Object.values(examples) .filter((v) => (v['x-example-show-value'] !== false)) .map((v) => ({ value: (typeof v.value === 'boolean' || typeof v.value === 'number' ? `${v.value}` : (v.value || '')), printableValue: getPrintableVal(v.value), summary: v.summary || '', description: v.description || '', })); const exampleVal = exampleList.length > 0 ? exampleList[0].value : ''; return { exampleVal, exampleList }; } // This is non-standard way to provide example but will support for now if (!Array.isArray(examples)) { examples = examples ? [examples] : []; } if (examples.length === 0) { return { exampleVal: '', exampleList: [], }; } if (dataType === 'array') { const [exampleVal] = examples; const exampleList = examples.map((v) => ({ value: v, printableValue: getPrintableVal(v), })); return { exampleVal, exampleList }; } const exampleVal = examples[0].toString(); const exampleList = examples.map((v) => ({ value: v.toString(), printableValue: getPrintableVal(v), })); return { exampleVal, exampleList }; } export function anyExampleWithSummaryOrDescription(examples) { return examples.some((x) => x.summary?.length > 0 || x.description?.length > 0); } export function getSampleValueByType(schemaObj) { const example = schemaObj.examples ? schemaObj.examples[0] : schemaObj.example === null ? null : schemaObj.example || undefined; if (example === '') { return ''; } if (example === null) { return null; } if (example === 0) { return 0; } if (example === false) { return false; } if (example instanceof Date) { switch (schemaObj.format.toLowerCase()) { case 'date': return example.toISOString().split('T')[0]; case 'time': return example.toISOString().split('T')[1]; default: return example.toISOString(); } } if (example) { return example; } if (Object.keys(schemaObj).length === 0) { return null; } if (schemaObj.$ref) { // Indicates a Circular ref return {}; } if (schemaObj.const === false || schemaObj.const === 0 || schemaObj.const === null || schemaObj.const === '') { return schemaObj.const; } if (schemaObj.const) { return schemaObj.const; } if (schemaObj.default) { return schemaObj.default; } const typeValue = Array.isArray(schemaObj.type) ? schemaObj.type[0] : schemaObj.type; if (!typeValue) { return null; } if (typeValue.match(/^integer|^number/g)) { const multipleOf = Number.isNaN(Number(schemaObj.multipleOf)) ? undefined : Number(schemaObj.multipleOf); const maximum = Number.isNaN(Number(schemaObj.maximum)) ? undefined : Number(schemaObj.maximum); const minimumPossibleVal = Number.isNaN(Number(schemaObj.minimum)) ? Number.isNaN(Number(schemaObj.exclusiveMinimum)) ? maximum || 0 : Number(schemaObj.exclusiveMinimum) + (typeValue.startsWith('integer') ? 1 : 0.001) : Number(schemaObj.minimum); const finalVal = multipleOf ? multipleOf >= minimumPossibleVal ? multipleOf : minimumPossibleVal % multipleOf === 0 ? minimumPossibleVal : Math.ceil(minimumPossibleVal / multipleOf) * multipleOf : minimumPossibleVal; return finalVal; } if (typeValue.match(/^boolean/g)) { return false; } if (typeValue.match(/^null/g)) { return null; } if (typeValue.match(/^string/g)) { if (schemaObj.enum) { return schemaObj.enum[0]; } if (schemaObj.const) { return schemaObj.const; } if (schemaObj.pattern) { try { return new RandExp(schemaObj.pattern).gen(); } catch { return schemaObj.pattern; } } if (schemaObj.format) { const u = `${Date.now().toString(16)}${Math.random().toString(16)}0`.repeat(16); switch (schemaObj.format.toLowerCase()) { case 'url': case 'uri': return 'http://example.com'; case 'date': return (new Date(0)).toISOString().split('T')[0]; case 'time': return (new Date(0)).toISOString().split('T')[1]; case 'date-time': return (new Date(0)).toISOString(); case 'duration': return 'P3Y6M4DT12H30M5S'; // P=Period 3-Years 6-Months 4-Days 12-Hours 30-Minutes 5-Seconds case 'email': case 'idn-email': return 'user@example.com'; case 'hostname': case 'idn-hostname': return 'www.example.com'; case 'ipv4': return '198.51.100.42'; case 'ipv6': return '2001:0db8:5b96:0000:0000:426f:8e17:642a'; case 'uuid': return [u.substring(0, 8), u.substring(8, 12), `4000-8${u.substring(13, 16)}`, u.substring(16, 28)].join('-'); case 'byte': return 'ZXhhbXBsZQ=='; // 'example' base64 encoded. See https://spec.openapis.org/oas/v3.0.0#data-types default: return ''; } } else { const minLength = Number.isNaN(schemaObj.minLength) ? undefined : Number(schemaObj.minLength); const maxLength = Number.isNaN(schemaObj.maxLength) ? undefined : Number(schemaObj.maxLength); const finalLength = minLength || (maxLength > 6 ? 6 : maxLength || undefined); return finalLength ? 'A'.repeat(finalLength) : 'string'; } } // If type cannot be determined return null; } /* json2xml- TestCase { 'prop1' : 'one', 'prop2' : 'two', 'prop3' : [ 'a', 'b', 'c' ], 'prop4' : { 'ob1' : 'val-1', 'ob2' : 'val-2' } } <root> <prop1>simple</prop1> <prop2> <0>a</0> <1>b</1> <2>c</2> </prop2> <prop3> <ob1>val-1</ob1> <ob2>val-2</ob2> </prop3> </root> */ export function json2xml(obj, level = 1) { const indent = ' '.repeat(level); let xmlText = ''; if (level === 1 && typeof obj !== 'object') { return `\n${indent}${obj.toString()}`; } for (const prop in obj) { const tagNameOrProp = (obj[prop]['::XML_TAG'] || prop); let tagName = ''; if (Array.isArray(obj[prop])) { tagName = tagNameOrProp[0]['::XML_TAG'] || `${prop}`; } else { tagName = tagNameOrProp; } if (prop.startsWith('::')) { continue; } if (Array.isArray(obj[prop])) { xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(obj[prop], level + 1)}\n${indent}</${tagName}>`; } else if (typeof obj[prop] === 'object') { xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(obj[prop], level + 1)}\n${indent}</${tagName}>`; } else { xmlText = `${xmlText}\n${indent}<${tagName}>${obj[prop].toString()}</${tagName}>`; } } return xmlText; } function addSchemaInfoToExample(schema, obj) { if (typeof obj !== 'object' || obj === null) { return; } if (schema.title) { obj['::TITLE'] = schema.title; } if (schema.description) { obj['::DESCRIPTION'] = schema.description; } if (schema.xml?.name) { obj['::XML_TAG'] = schema.xml?.name; } if (schema.xml?.wrapped) { obj['::XML_WRAP'] = schema.xml?.wrapped.toString(); } } function removeTitlesAndDescriptions(obj) { if (typeof obj !== 'object' || obj === null) { return; } delete obj['::TITLE']; delete obj['::DESCRIPTION']; delete obj['::XML_TAG']; delete obj['::XML_WRAP']; for (const k in obj) { removeTitlesAndDescriptions(obj[k]); } } function addPropertyExampleToObjectExamples(example, obj, propertyKey) { for (const key in obj) { obj[key][propertyKey] = example; } } function mergePropertyExamples(obj, propertyName, propExamples) { // Create an example for each variant of the propertyExample, merging them with the current (parent) example let i = 0; const maxCombinations = 10; const mergedObj = {}; for (const exampleKey in obj) { for (const propExampleKey in propExamples) { mergedObj[`example-${i}`] = { ...obj[exampleKey] }; mergedObj[`example-${i}`][propertyName] = propExamples[propExampleKey]; i++; if (i >= maxCombinations) { break; } } if (i >= maxCombinations) { break; } } return mergedObj; } /* For changing JSON-Schema to a Sample Object, as per the schema (to generate examples based on schema) */ export function schemaToSampleObj(schema, config = { }) { let obj = {}; if (!schema) { return; } if (schema.allOf) { const objWithAllProps = {}; if (schema.allOf.length === 1 && !schema.allOf[0]?.properties && !schema.allOf[0]?.items) { // If allOf has single item and the type is not an object or array, then its a primitive if (schema.allOf[0].$ref) { return '{ }'; } if (schema.allOf[0].readOnly && config.includeReadOnly) { const tempSchema = schema.allOf[0]; return getSampleValueByType(tempSchema); } return; } schema.allOf.forEach((v) => { if (v.type === 'object' || v.properties || v.allOf || v.anyOf || v.oneOf) { const partialObj = schemaToSampleObj(v, config); Object.assign(objWithAllProps, partialObj); } else if (v.type === 'array' || v.items) { const partialObj = [schemaToSampleObj(v, config)]; Object.assign(objWithAllProps, partialObj); } else if (v.type) { const prop = `prop${Object.keys(objWithAllProps).length}`; objWithAllProps[prop] = getSampleValueByType(v); } else { return ''; } }); obj = objWithAllProps; } else if (schema.oneOf) { // 1. First create example with scheme.properties const objWithSchemaProps = {}; if (schema.properties) { for (const propertyName in schema.properties) { if (schema.properties[propertyName].properties || schema.properties[propertyName].properties?.items) { objWithSchemaProps[propertyName] = schemaToSampleObj(schema.properties[propertyName], config); } else { objWithSchemaProps[propertyName] = getSampleValueByType(schema.properties[propertyName]); } } } if (schema.oneOf.length > 0) { /* oneOf: - type: object properties: option1_PropA: type: string option1_PropB: type: string - type: object properties: option2_PropX: type: string properties: prop1: type: string prop2: type: string minLength: 10 The aboove Schem should generate the following 2 examples Example-1 { prop1: 'string', prop2: 'AAAAAAAAAA', <-- min-length 10 option1_PropA: 'string', option1_PropB: 'string' } Example-2 { prop1: 'string', prop2: 'AAAAAAAAAA', <-- min-length 10 option2_PropX: 'string' } */ let i = 0; // Merge all examples of each oneOf-schema for (const key in schema.oneOf) { const oneOfSamples = schemaToSampleObj(schema.oneOf[key], config); for (const sampleKey in oneOfSamples) { // 2. In the final example include a one-of item along with properties let finalExample; if (Object.keys(objWithSchemaProps).length > 0) { if (oneOfSamples[sampleKey] === null || typeof oneOfSamples[sampleKey] !== 'object') { // This doesn't really make sense since every oneOf schema _should_ be an object if there are common properties, so we'll skip this continue; } else { finalExample = Object.assign(oneOfSamples[sampleKey], objWithSchemaProps); } } else { finalExample = oneOfSamples[sampleKey]; } obj[`example-${i}`] = finalExample; addSchemaInfoToExample(schema.oneOf[key], obj[`example-${i}`]); i++; } } } } else if (schema.anyOf) { // First generate values for regular properties let commonObj; if (schema.type === 'object' || schema.properties) { commonObj = { 'example-0': {} }; for (const propertyName in schema.properties) { if (schema.example) { commonObj = schema; break; } if (schema.properties[propertyName].deprecated && !config.includeDeprecated) { continue; } if (schema.properties[propertyName].readOnly && !config.includeReadOnly) { continue; } if (schema.properties[propertyName].writeOnly && !config.includeWriteOnly) { continue; } commonObj = mergePropertyExamples(commonObj, propertyName, schemaToSampleObj(schema.properties[propertyName], config)); } } // Combine every variant of the regular properties with every variant of the anyOf samples let i = 0; for (const key in schema.anyOf) { const anyOfSamples = schemaToSampleObj(schema.anyOf[key], config); for (const sampleKey in anyOfSamples) { if (typeof commonObj !== 'undefined') { for (const commonKey in commonObj) { obj[`example-${i}`] = { ...commonObj[commonKey], ...anyOfSamples[sampleKey] }; } } else { obj[`example-${i}`] = anyOfSamples[sampleKey]; } addSchemaInfoToExample(schema.anyOf[key], obj[`example-${i}`]); i++; } } } else if (schema.type === 'object' || schema.properties) { obj['example-0'] = {}; addSchemaInfoToExample(schema, obj['example-0']); if (schema.example) { obj['example-0'] = schema.example; } else { for (const propertyName in schema.properties) { if (schema.properties[propertyName]?.deprecated && !config.includeDeprecated) { continue; } if (schema.properties[propertyName]?.readOnly && !config.includeReadOnly) { continue; } if (schema.properties[propertyName]?.writeOnly && !config.includeWriteOnly) { continue; } if (schema.properties[propertyName]?.type === 'array' || schema.properties[propertyName]?.items) { if (schema.properties[propertyName].example) { addPropertyExampleToObjectExamples(schema.properties[propertyName].example, obj, propertyName); } else if (schema.properties[propertyName]?.items?.example) { // schemas and properties support single example but not multiple examples. addPropertyExampleToObjectExamples([schema.properties[propertyName].items.example], obj, propertyName); } else { const itemSamples = schemaToSampleObj(schema.properties[propertyName].items, config); if (config.useXmlTagForProp) { const xmlTagName = schema.properties[propertyName].xml?.name || propertyName; if (schema.properties[propertyName].xml?.wrapped) { const wrappedItemSample = JSON.parse(`{ "${xmlTagName}" : { "${xmlTagName}" : ${JSON.stringify(itemSamples['example-0'])} } }`); obj = mergePropertyExamples(obj, xmlTagName, wrappedItemSample); } else { obj = mergePropertyExamples(obj, xmlTagName, itemSamples); } } else { const arraySamples = []; for (const key in itemSamples) { arraySamples[key] = [itemSamples[key]]; } obj = mergePropertyExamples(obj, propertyName, arraySamples); } } continue; } obj = mergePropertyExamples(obj, propertyName, schemaToSampleObj(schema.properties[propertyName], config)); } if (typeof schema.additionalProperties === 'object') { const propertyName = schema.additionalProperties['x-additionalPropertiesName'] || 'property'; obj = mergePropertyExamples(obj, `${propertyName}1`, schemaToSampleObj(schema.additionalProperties, config)); obj = mergePropertyExamples(obj, `${propertyName}2`, schemaToSampleObj(schema.additionalProperties, config)); } } } else if (schema.type === 'array' || schema.items) { if (schema.items || schema.example) { if (schema.example) { obj['example-0'] = schema.example; } else if (schema.items?.example) { // schemas and properties support single example but not multiple examples. obj['example-0'] = [schema.items.example]; } else { const samples = schemaToSampleObj(schema.items, config); let i = 0; for (const key in samples) { obj[`example-${i}`] = [samples[key]]; addSchemaInfoToExample(schema.items, obj[`example-${i}`]); i++; } } } else { obj['example-0'] = []; } } else { return { 'example-0': getSampleValueByType(schema) }; } return obj; } function generateMarkdownForArrayAndObjectDescription(schema, level = 0) { let markdown = ((schema.description || schema.title) && (schema.minItems || schema.maxItems)) ? '<span class="descr-expand-toggle">➔</span>' : ''; if (schema.title) { if (schema.description) { markdown = `${markdown} <b>${schema.title}:</b> ${schema.description}<br/>`; } else { markdown = `${markdown} ${schema.title}<br/>`; } } else if (schema.description) { markdown = `${markdown} ${schema.description}<br/>`; } if (schema.minItems) { markdown = `${markdown} <b>Min Items:</b> ${schema.minItems}`; } if (schema.maxItems) { markdown = `${markdown} <b>Max Items:</b> ${schema.maxItems}`; } if (level > 0 && schema.items?.description) { let itemsMarkdown = ''; if (schema.items.minProperties) { itemsMarkdown = `<b>Min Properties:</b> ${schema.items.minProperties}`; } if (schema.items.maxProperties) { itemsMarkdown = `${itemsMarkdown} <b>Max Properties:</b> ${schema.items.maxProperties}`; } markdown = `${markdown}${itemsMarkdown} [ ${schema.items.description} ] `; } return markdown; } /** * For changing OpenAPI-Schema to an Object Notation, * This Object would further be an input to UI Components to generate an Object-Tree * @param {object} schema - Schema object from OpenAPI spec * @param {object} obj - recursivly pass this object to generate object notation * @param {number} level - recursion level * @param {string} suffix - used for suffixing property names to avoid duplicate props during object composion */ export function schemaInObjectNotation(schema, obj, level = 0, suffix = '') { if (!schema) { return; } if (schema.allOf) { const objWithAllProps = {}; if (schema.allOf.length === 1 && !schema.allOf[0].properties && !schema.allOf[0].items) { // If allOf has single item and the type is not an object or array, then its a primitive const tempSchema = schema.allOf[0]; return `${getTypeInfo(tempSchema).html}`; } // If allOf is an array of multiple elements, then all the keys makes a single object schema.allOf.map((v, i) => { if (v.type === 'object' || v.properties || v.allOf || v.anyOf || v.oneOf) { const propSuffix = (v.anyOf || v.oneOf) && i > 0 ? i : ''; const partialObj = schemaInObjectNotation(v, {}, (level + 1), propSuffix); Object.assign(objWithAllProps, partialObj); } else if (v.type === 'array' || v.items) { const partialObj = schemaInObjectNotation(v, {}, (level + 1)); Object.assign(objWithAllProps, partialObj); } else if (v.type) { const prop = `prop${Object.keys(objWithAllProps).length}`; const typeObj = getTypeInfo(v); objWithAllProps[prop] = `${typeObj.html}`; } else { return ''; } }); obj = objWithAllProps; } else if (schema.anyOf || schema.oneOf) { obj['::description'] = schema.description || ''; // 1. First iterate the regular properties if (schema.type === 'object' || schema.properties) { obj['::description'] = schema.description || ''; obj['::type'] = 'object'; // obj['::deprecated'] = schema.deprecated || false; for (const key in schema.properties) { if (schema.required && schema.required.includes(key)) { obj[`${key}*`] = schemaInObjectNotation(schema.properties[key], {}, (level + 1)); } else { obj[key] = schemaInObjectNotation(schema.properties[key], {}, (level + 1)); } } } // 2. Then show allof/anyof objects const objWithAnyOfProps = {}; const xxxOf = schema.anyOf ? 'anyOf' : 'oneOf'; schema[xxxOf].forEach((v, index) => { if (v.type === 'object' || v.properties || v.allOf || v.anyOf || v.oneOf) { const partialObj = schemaInObjectNotation(v, {}); objWithAnyOfProps[`::OPTION~${index + 1}${v.title ? `~${v.title}` : ''}`] = partialObj; objWithAnyOfProps[`::OPTION~${index + 1}${v.title ? `~${v.title}` : ''}`]['::readwrite'] = ''; // xxx-options cannot be read or write only objWithAnyOfProps['::type'] = 'xxx-of-option'; } else if (v.type === 'array' || v.items) { // This else-if block never seems to get executed const partialObj = schemaInObjectNotation(v, {}); objWithAnyOfProps[`::OPTION~${index + 1}${v.title ? `~${v.title}` : ''}`] = partialObj; objWithAnyOfProps[`::OPTION~${index + 1}${v.title ? `~${v.title}` : ''}`]['::readwrite'] = ''; // xxx-options cannot be read or write only objWithAnyOfProps['::type'] = 'xxx-of-array'; } else { const prop = `::OPTION~${index + 1}${v.title ? `~${v.title}` : ''}`; objWithAnyOfProps[prop] = `${getTypeInfo(v).html}`; objWithAnyOfProps['::type'] = 'xxx-of-option'; } }); obj[(schema.anyOf ? `::ANY~OF ${suffix}` : `::ONE~OF ${suffix}`)] = objWithAnyOfProps; // obj['::type'] = 'object'; obj['::type'] = 'object'; } else if (Array.isArray(schema.type)) { // When a property has multiple types, then check further if any of the types are array or object, if yes then modify the schema using one-of // Clone the schema - as it will be modified to replace multi-data-types with one-of; const subSchema = JSON.parse(JSON.stringify(schema)); const primitiveType = []; const complexTypes = []; subSchema.type.forEach((v) => { if (v.match(/integer|number|string|null|boolean/g)) { primitiveType.push(v); } else if (v === 'array' && typeof subSchema.items?.type === 'string' && subSchema.items?.type.match(/integer|number|string|null|boolean/g)) { // Array with primitive types should also be treated as primitive type if (subSchema.items.type === 'string' && subSchema.items.format) { primitiveType.push(`[${subSchema.items.format}]`); } else { primitiveType.push(`[${subSchema.items.type}]`); } } else { complexTypes.push(v); } }); let multiPrimitiveTypes; if (primitiveType.length > 0) { subSchema.type = primitiveType.join(primitiveType.length === 2 ? ' or ' : '┃'); multiPrimitiveTypes = getTypeInfo(subSchema); if (complexTypes.length === 0) { return `${multiPrimitiveTypes?.html || ''}`; } } if (complexTypes.length > 0) { obj['::type'] = 'object'; const multiTypeOptions = { '::type': 'xxx-of-option', }; // Generate ONE-OF options for complexTypes complexTypes.forEach((v, i) => { if (v === 'null') { multiTypeOptions[`::OPTION~${i + 1}`] = 'NULL~|~~|~~|~~|~~|~~|~~|~~|~'; } else if ('integer, number, string, boolean,'.includes(`${v},`)) { subSchema.type = Array.isArray(v) ? v.join('┃') : v; const primitiveTypeInfo = getTypeInfo(subSchema); multiTypeOptions[`::OPTION~${i + 1}`] = primitiveTypeInfo.html; } else if (v === 'object') { // If object type iterate all the properties and create an object-type-option const objTypeOption = { '::title': schema.title || '', '::description': schema.description || '', '::type': 'object', '::deprecated': schema.deprecated || false, }; for (const key in schema.properties) { if (schema.required && schema.required.includes(key)) { objTypeOption[`${key}*`] = schemaInObjectNotation(schema.properties[key], {}, (level + 1)); } else { objTypeOption[key] = schemaInObjectNotation(schema.properties[key], {}, (level + 1)); } } multiTypeOptions[`::OPTION~${i + 1}`] = objTypeOption; } else if (v === 'array') { multiTypeOptions[`::OPTION~${i + 1}`] = { '::title': schema.title || '', '::description': schema.description || '', '::type': 'array', '::props': schemaInObjectNotation(schema.items, {}, (level + 1)), }; } }); multiTypeOptions[`::OPTION~${complexTypes.length + 1}`] = multiPrimitiveTypes?.html || ''; obj['::ONE~OF'] = multiTypeOptions; } } else if (schema.type === 'object' || schema.properties) { // If Object obj['::title'] = schema.title || ''; obj['::description'] = generateMarkdownForArrayAndObjectDescription(schema, level); obj['::type'] = 'object'; if ((Array.isArray(schema.type) && schema.type.includes('null')) || schema.nullable) { obj['::dataTypeLabel'] = 'object or null'; obj['::nullable'] = true; } obj['::deprecated'] = schema.deprecated || false; obj['::readwrite'] = schema.readOnly ? 'readonly' : schema.writeOnly ? 'writeonly' : ''; for (const key in schema.properties) { if (schema.required && schema.required.includes(key)) { obj[`${key}*`] = schemaInObjectNotation(schema.properties[key], {}, (level + 1)); } else { obj[key] = schemaInObjectNotation(schema.properties[key], {}, (level + 1)); } } for (const key in schema.patternProperties) { obj[`[pattern: ${key}]`] = schemaInObjectNotation(schema.patternProperties[key], obj, (level + 1)); } if (schema.additionalProperties) { obj['[any-key]'] = schemaInObjectNotation(schema.additionalProperties, {}); } } else if (schema.type === 'array' || schema.items) { // If Array obj['::title'] = schema.title || ''; obj['::description'] = generateMarkdownForArrayAndObjectDescription(schema, level); obj['::type'] = 'array'; if ((Array.isArray(schema.type) && schema.type.includes('null')) || schema.nullable) { obj['::dataTypeLabel'] = 'array or null'; obj['::nullable'] = true; } obj['::deprecated'] = schema.deprecated || false; obj['::readwrite'] = schema.readOnly ? 'readonly' : schema.writeOnly ? 'writeonly' : ''; if (schema.items?.items) { obj['::array-type'] = schema.items.items.type; } obj['::props'] = schemaInObjectNotation(schema.items, {}, (level + 1)); } else { const typeObj = getTypeInfo(schema); if (typeObj?.html) { return `${typeObj.html}`; } return ''; } return obj; } /* Create Example object */ export function generateExample(schema, mimeType, examples = {}, example = {}, includeReadOnly = true, includeWriteOnly = true, outputType = 'json', includeGeneratedExample = false) { const finalExamples = []; // First check if examples is provided if (examples) { for (const eg in examples) { let egContent = ''; let egFormat = 'json'; if (mimeType?.toLowerCase().includes('json')) { if (outputType === 'text') { egContent = typeof examples[eg].value === 'string' ? examples[eg].value : JSON.stringify(examples[eg].value, undefined, 2); egFormat = 'text'; } else { egContent = examples[eg].value; if (typeof examples[eg].value === 'string') { try { // const fixedJsonString = examples[eg].value.replace((/([\w]+)(:)/g), '"$1"$2').replace((/'/g), '"'); const fixedJsonString = examples[eg].value; egContent = JSON.parse(fixedJsonString); egFormat = 'json'; } catch { egFormat = 'text'; egContent = examples[eg].value; } } } } else { egContent = examples[eg].value; egFormat = 'text'; } finalExamples.push({ exampleId: eg, exampleSummary: examples[eg].summary || eg, exampleDescription: examples[eg].description || '', exampleType: mimeType, exampleValue: egContent, exampleFormat: egFormat, }); } } else if (example) { let egContent = ''; let egFormat = 'json'; if (mimeType?.toLowerCase().includes('json')) { if (outputType === 'text') { egContent = typeof example === 'string' ? example : JSON.stringify(example, undefined, 2); egFormat = 'text'; } else if (typeof example === 'object') { egContent = example; egFormat = 'json'; } else if (typeof example === 'string') { try { egContent = JSON.parse(example); egFormat = 'json'; } catch { egFormat = 'text'; egContent = example; } } } else { egContent = example; egFormat = 'text'; } finalExamples.push({ exampleId: 'Example', exampleSummary: '', exampleDescription: '', exampleType: mimeType, exampleValue: egContent, exampleFormat: egFormat, }); } // If schema-level examples are not provided or includeGeneratedExample === true then generate one based on the schema field types if (finalExamples.length === 0 || includeGeneratedExample === true) { if (schema) { if (schema.example) { // Note: Deprecated: The 'example' property has been deprecated in 3.1.0 in favor of the JSON Schema 'examples' keyword finalExamples.push({ exampleId: 'Example', exampleSummary: '', exampleDescription: '', exampleType: mimeType, exampleValue: schema.example, exampleFormat: ((mimeType?.toLowerCase().includes('json') && typeof schema.example === 'object') ? 'json' : 'text'), }); } else if (mimeType?.toLowerCase().includes('json') || mimeType?.toLowerCase().includes('text') || mimeType?.toLowerCase().includes('*/*') || mimeType?.toLowerCase().includes('xml')) { let xmlRootStart = ''; let xmlRootEnd = ''; let exampleFormat = ''; let exampleValue = ''; if (mimeType?.toLowerCase().includes('xml')) { xmlRootStart = schema.xml?.name ? `<${schema.xml.name} ${schema.xml.namespace ? `xmlns="${schema.xml.namespace}"` : ''}>` : '<root>'; xmlRootEnd = schema.xml?.name ? `</${schema.xml.name}>` : '</root>'; exampleFormat = 'text'; } else { exampleFormat = outputType; } const samples = schemaToSampleObj(schema, { includeReadOnly, includeWriteOnly, deprecated: true, useXmlTagForProp: mimeType?.toLowerCase().includes('xml') }); let i = 0; for (const samplesKey in samples) { if (!samples[samplesKey]) { continue; } const summary = samples[samplesKey]['::TITLE'] || `Example ${++i}`; const description = samples[samplesKey]['::DESCRIPTION'] || ''; if (mimeType?.toLowerCase().includes('xml')) { exampleValue = `<?xml version="1.0" encoding="UTF-8"?>\n${xmlRootStart}${json2xml(samples[samplesKey], 1)}\n${xmlRootEnd}`; } else { removeTitlesAndDescriptions(samples[samplesKey]); exampleValue = outputType === 'text' ? JSON.stringify(samples[samplesKey], null, 2) : samples[samplesKey]; } finalExamples.push({ exampleId: samplesKey, exampleSummary: summary, exampleDescription: description, exampleType: mimeType, exampleFormat, exampleValue, }); } } else if (mimeType?.toLowerCase().includes('jose')) { finalExamples.push({ exampleId: 'Example', exampleSummary: 'Base64 Encoded', exampleDescription: '', exampleType: mimeType, exampleValue: schema.pattern || 'bXJpbg==', exampleFormat: 'text', }); } else { finalExamples.push({ exampleId: 'Example', exampleSummary: '', exampleDescription: '', exampleType: mimeType, exampleValue: '', exampleFormat: 'text', }); } } else { // No Example or Schema provided (should never reach here) finalExamples.push({ exampleId: 'Example', exampleSummary: '', exampleDescription: '', exampleType: mimeType, exampleValue: '', exampleFormat: 'text', }); } } return finalExamples; } function getSerializeStyleForContentType(contentType) { if (contentType === 'application/json') { return 'json'; } if (contentType === 'application/xml') { return 'xml'; } return null; } export function getSchemaFromParam(param) { if (param.schema) { return [param.schema, null, null]; } if (param.content) { // we gonna use the first content-encoding for (const contentType of Object.keys(param.content)) { if (param.content[contentType].schema) { return [param.content[contentType].schema, getSerializeStyleForContentType(contentType), param.content[contentType]]; } } } return [null, null, null]; }