UNPKG

rapipdf

Version:

RapiPdf - Generate PDF from Open API spec

371 lines (352 loc) 13.2 kB
/* Generates an object containing type and constraint info */ export function getTypeInfo(schema) { if (!schema) { return; } const info = { type: schema.$ref ? '{recursive}' : schema.enum ? 'enum' : schema.format ? schema.format : schema.type, format: schema.format ? schema.format : '', pattern: (schema.pattern && !schema.enum) ? schema.pattern : '', readOrWriteOnly: schema.readOnly ? 'READ-ONLY' : schema.writeOnly ? 'WRITE-ONLY' : '', deprecated: schema.deprecated ? 'DEPRECATED' : '', default: schema.default === 0 ? '0' : (schema.default ? schema.default : ''), description: schema.description ? schema.description : '', allowedValues: '', constrain: '', arrayType: '', typeInfoText: '', }; if (info.type === '{recursive}') { info.description = schema.$ref.substring(schema.$ref.lastIndexOf('/') + 1); } // Set the Type if (schema.enum) { let opt = ''; schema.enum.map((v) => { opt += `${v}, `; }); info.type = 'enum'; info.allowedValues = opt.slice(0, -2); } else if (schema.type) { info.type = schema.type; } if (schema.type === 'array' || schema.items) { const arraySchema = schema.items; info.arrayType = `${schema.type} of ${arraySchema.type}`; info.default = arraySchema.default === 0 ? '0 ' : (arraySchema.default ? arraySchema.default : ''); if (arraySchema.enum) { let opt = ''; arraySchema.enum.map((v) => { opt += `${v}, `; }); info.allowedValues = opt.slice(0, -2); } } else if (schema.type === 'integer' || schema.type === 'number') { if (schema.minimum !== undefined && schema.maximum !== undefined) { info.constrain = `${schema.exclusiveMinimum ? '>' : 'between '}${schema.minimum} and ${schema.exclusiveMaximum ? '<' : ''} ${schema.maximum}`; } else if (schema.minimum !== undefined && schema.maximum === undefined) { info.constrain = `${schema.exclusiveMinimum ? '>' : '>='}${schema.minimum}`; } else if (schema.minimum === undefined && schema.maximum !== undefined) { info.constrain = `${schema.exclusiveMaximum ? '<' : '<='}${schema.maximum}`; } if (schema.multipleOf !== undefined) { info.constrain = `multiple of ${schema.multipleOf}`; } } else if (schema.type === 'string') { if (schema.minLength !== undefined && schema.maxLength !== undefined) { info.constrain = `${schema.minLength} to ${schema.maxLength} chars`; } else if (schema.minLength !== undefined && schema.maxLength === undefined) { info.constrain = `min:${schema.minLength} chars`; } else if (schema.minLength === undefined && schema.maxLength !== undefined) { info.constrain = `max:${schema.maxLength} chars`; } } info.typeInfoText = `${info.type}~|~${info.readOrWriteOnly} ${info.deprecated}~|~${info.constrain}~|~${info.default}~|~${info.allowedValues}~|~${info.pattern}~|~${info.description}`; return info; } function generatePropDescription(propDescrArray, localize) { const descrStack = []; // Set Read or Write only if (propDescrArray[1].trim()) { descrStack.push({ text: `${propDescrArray[1]}`, style: ['sub', 'b', 'darkGray'], margin: [0, 3, 0, 0], }); } // Set Constraints if (propDescrArray[2]) { descrStack.push({ text: `${propDescrArray[2]}`, style: ['small', 'mono', 'darkGray'], }); } // Set Default if (propDescrArray[3]) { descrStack.push({ text: [ { text: `${localize.default}:`, style: ['sub', 'b', 'darkGray'] }, { text: propDescrArray[3], style: ['small', 'darkGray', 'mono'] }, ], }); } // Set Allowed if (propDescrArray[4]) { descrStack.push({ text: [ { text: `${localize.allowed}:`, style: ['sub', 'b', 'darkGray'] }, { text: propDescrArray[4], style: ['small', 'lightGray', 'mono'] }, ], }); } // Set Pattern if (propDescrArray[5]) { descrStack.push({ text: [ { text: `${localize.pattern}:`, style: ['sub', 'b', 'darkGray'] }, { text: propDescrArray[5], style: ['small', 'lightGray', 'mono'] }, ], }); } if (propDescrArray[6]) { descrStack.push({ text: `${propDescrArray[6]}`, style: ['sub', 'lightGray'], margin: [0, 3, 0, 0], }); } return descrStack; } /** * For changing OpenAPI-Schema to an Object Notation, * This Object would further be an input to `objectToTree()` to generate a pdfDefs representing 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 - contains the recursion depth */ export function schemaInObjectNotation(schema, obj = {}, level = 0) { if (!schema) { return; } if (schema.type === 'object' || schema.properties) { // If Object obj['::description'] = schema.description ? schema.description : ''; obj['::type'] = 'object'; 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)); } } } else if (schema.items) { // If Array obj['::description'] = schema.description ? schema.description : ''; obj['::type'] = 'array'; obj['::props'] = schemaInObjectNotation(schema.items, {}, (level + 1)); } else 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).typeInfoText}`; } // If allOf is an array of multiple elements, then all the keys makes a single object schema.allOf.map((v) => { if (v.type === 'object' || v.properties || v.allOf || v.anyOf || v.oneOf) { const partialObj = schemaInObjectNotation(v, {}, (level + 1)); 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.typeInfoText}`; } else { return ''; } }); obj = objWithAllProps; } else if (schema.anyOf || schema.oneOf) { let i = 1; const objWithAnyOfProps = {}; const xxxOf = schema.anyOf ? 'anyOf' : 'oneOf'; schema[xxxOf].map((v) => { if (v.type === 'object' || v.properties || v.allOf || v.anyOf || v.oneOf) { const partialObj = schemaInObjectNotation(v, {}, (level + 1)); objWithAnyOfProps[`OPTION:${i}`] = partialObj; i++; } else if (v.type === 'array' || v.items) { const partialObj = [schemaInObjectNotation(v, {}, (level + 1))]; Object.assign(objWithAnyOfProps, partialObj); } else { const prop = `prop${Object.keys(objWithAnyOfProps).length}`; objWithAnyOfProps[prop] = `${getTypeInfo(v).typeInfoText}`; } }); obj[(schema.anyOf ? 'ANY:OF' : 'ONE:OF')] = objWithAnyOfProps; } else { const typeObj = getTypeInfo(schema); if (typeObj.typeInfoText) { return `${typeObj.typeInfoText}`; } return ''; } return obj; } /** * For changing an object to Tree (array of pdfDefs, which when feeded to pdfMake would produce a indented object tree) * @param {object} obj - keys to iterate * @param {string} prevKeyDataType - data-type of previous key, it is either 'primitive', 'object' or 'array', based on this appropriate braces are used * @param {string} prevKey - name of the key from previous recursive call stack */ export function objectToTree(obj, localize, prevKeyDataType = 'object', prevKey = '') { if (typeof obj !== 'object') { const propDescrArray = obj.split('~|~'); if (prevKeyDataType === 'array') { propDescrArray[0] = `[${propDescrArray[0]}]`; } const descrStack = generatePropDescription(propDescrArray, localize); return [ { text: prevKey, style: ['small', 'mono'], margin: 0 }, { text: (propDescrArray[0] ? propDescrArray[0] : ''), style: ['small', 'mono', 'lightGray'], margin: 0 }, { stack: descrStack, margin: 0 }, ]; } // Important - pdfMake needs one row with all the cells for colSpan to work properly const rows = [ [{ text: '', margin: 0 }, { text: '', margin: 0 }, { text: '', margin: 0 }], ]; for (const key in obj) { // If ANY_OF or ONE_OF if (key === 'ANY:OF' || key === 'ONE:OF') { const allOptions = []; for (const k in obj[key]) { allOptions.push(objectToTree(obj[key][k], localize, 'object', k)); } return [ { colSpan: 3, stack: [ { text: `${prevKey}`, style: ['small', 'mono'] }, { margin: [10, 0, 0, 0], stack: [ { text: `${key.replace(':', ' ')}`, style: ['sub', 'blue', 'b'], margin: [0, 5, 0, 0] }, ...allOptions, ], }, ], }, ]; } if (typeof obj[key] === 'object' && obj[key]['::type']) { let objectDef; if (obj[key]['::type'] === 'array') { objectDef = objectToTree(obj[key]['::props'], localize, obj[key]['::type'], key, 'array'); } else { objectDef = objectToTree(obj[key], localize, obj[key]['::type'], key); } rows.push(objectDef); } else if (key.startsWith('::') === false) { const primitiveDef = objectToTree(obj[key], localize, 'primitive', key); rows.push(primitiveDef); } } let keyDef; if (prevKey.startsWith('OPTION:')) { keyDef = { text: [ { text: `${prevKey.replace(':', ' ')}`, style: ['sub', 'b', 'blue'] }, { text: `${prevKeyDataType === 'array' ? '[{' : '{'}`, style: ['small', 'mono'] }, ], }; } else { keyDef = { stack: [ { text: `${prevKey} ${prevKeyDataType === 'array' ? '[{' : '{'}`, style: ['small', 'mono'] }, ], }; if (obj['::description'] || prevKeyDataType === 'array') { keyDef.stack.push( { text: `${prevKeyDataType === 'array' ? 'Array of object: ' : ''} ${obj['::description'] ? obj['::description'] : ''}`, style: ['sub', 'gray'], margin: [0, 0, 0, 4], }, ); } } return [{ colSpan: 3, stack: [ keyDef, { margin: [10, 0, 0, 0], layout: { defaultBorder: false, hLineWidth() { return 0; }, vLineWidth() { return 0; }, paddingTop() { return 0; }, paddingBottom() { return 0; }, }, table: { headerRows: 0, widths: ['auto', 'auto', '*'], dontBreakRows: false, body: rows, }, }, { text: `${prevKeyDataType === 'array' ? '}]' : '}'}`, style: ['small', 'mono'] }, ], }]; } /** * For changing an object to Table-Tree (array of pdfDefs, which when feeded to pdfMake would produce a indented table tree) * @param {object} obj - keys to iterate * @param {string} keyDataType - is 'primitive', 'object' or 'array', based on this appropriate braces are used * @param {string} keyName - name of the key from previous recursive call stack * @param {string} keyDescr - description of the key */ export function objectToTableTree(obj, localize, allRows = [], level = 0) { const leftMargin = level * 10; if (!obj || typeof obj === 'string') { return [{ text: '' }]; } for (const key in obj) { if (typeof obj[key] === 'object') { let objType; if (obj[key]['::type'] === 'array') { objType = 'array'; } else { objType = 'object'; } const objRow = [ { text: key, style: ['small', 'b'], margin: [leftMargin, 0, 0, 0] }, { text: objType, style: ['small', 'mono', 'lightGray'], margin: 0 }, { text: '', margin: 0 }, ]; allRows.push(objRow); if (obj[key]['::type'] === 'array') { objectToTableTree(obj[key]['::props'], localize, allRows, (level + 1)); } else { objectToTableTree(obj[key], localize, allRows, (level + 1)); } } else if (typeof obj[key] === 'string' && (key.startsWith('::') === false)) { const typeAndDescr = obj[key].split('~|~'); const descrStack = generatePropDescription(typeAndDescr, localize); allRows.push([ { text: key, style: ['small'], margin: [leftMargin, 0, 0, 0] }, { text: (typeAndDescr[0] ? typeAndDescr[0] : ''), style: ['small', 'mono', 'lightGray'], margin: 0 }, { stack: ((descrStack && descrStack.length) > 0 ? descrStack : [{ text: '' }]), margin: 0 }, ]); } } return allRows; }