diff --git a/packages/plugins/documentation/server/services/helpers/build-component-schema.js b/packages/plugins/documentation/server/services/helpers/build-component-schema.js index e80fd64d0e..8d5de19c9d 100644 --- a/packages/plugins/documentation/server/services/helpers/build-component-schema.js +++ b/packages/plugins/documentation/server/services/helpers/build-component-schema.js @@ -20,6 +20,18 @@ const { hasFindMethod, isLocalizedPath } = require('./utils/routes'); const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => { // Store response and request schemas in an object let schemas = {}; + let componentSchemas = {}; + // adds a ComponentSchema to the Schemas so it can be used as Ref + const addComponentSchema = (schemaName, schema) => { + if (!Object.keys(schema) || !Object.keys(schema.properties)) { + return false; + } + componentSchemas = { + ...componentSchemas, + [schemaName]: schema, + }; + return true; + }; // Get all the route methods const routeMethods = routeInfo.routes.map((route) => route.method); // Check for localized paths @@ -56,7 +68,7 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => { [`${pascalCase(uniqueName)}LocalizationRequest`]: { required: [...requiredAttributes, 'locale'], type: 'object', - properties: cleanSchemaAttributes(attributesForRequest, { isRequest: true }), + properties: cleanSchemaAttributes(attributesForRequest, { isRequest: true, addComponentSchema }), }, }; } @@ -71,7 +83,7 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => { data: { required: requiredAttributes, type: 'object', - properties: cleanSchemaAttributes(attributesForRequest, { isRequest: true }), + properties: cleanSchemaAttributes(attributesForRequest, { isRequest: true, addComponentSchema }), }, }, }, @@ -85,7 +97,7 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => { type: 'object', properties: { id: { type: 'string' }, - ...cleanSchemaAttributes(attributes), + ...cleanSchemaAttributes(attributes, { addComponentSchema }), }, }, }; @@ -97,17 +109,37 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => { // Build the list response schema schemas = { ...schemas, - [`${pascalCase(uniqueName)}ListResponse`]: { + [`${pascalCase(uniqueName)}ListResponseDataItem`]: { type: 'object', + properties: { + id: { type: 'string' }, + attributes: { + type: 'object', + properties: cleanSchemaAttributes(attributes, { + addComponentSchema, + componentSchemaRefName: `#/components/schemas/${pascalCase( + uniqueName + )}ListResponseDataItemLocalized`, + }), + }, + }, + }, + [`${pascalCase(uniqueName)}ListResponseDataItemLocalized`]: { + type: 'object', + properties: { + id: { type: 'string' }, + attributes: { + type: 'object', + properties: cleanSchemaAttributes(attributes, { addComponentSchema }), + }, + }, + }, + [`${pascalCase(uniqueName)}ListResponse`]: { properties: { data: { type: 'array', items: { - type: 'object', - properties: { - id: { type: 'string' }, - attributes: { type: 'object', properties: cleanSchemaAttributes(attributes) }, - }, + $ref: `#/components/schemas/${pascalCase(uniqueName)}ListResponseDataItem`, }, }, meta: { @@ -131,22 +163,41 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => { // Build the response schema schemas = { ...schemas, - [`${pascalCase(uniqueName)}Response`]: { + [`${pascalCase(uniqueName)}ResponseDataObject`]: { type: 'object', properties: { - data: { + id: { type: 'string' }, + attributes: { type: 'object', - properties: { - id: { type: 'string' }, - attributes: { type: 'object', properties: cleanSchemaAttributes(attributes) }, - }, + properties: cleanSchemaAttributes(attributes, { + addComponentSchema, + componentSchemaRefName: `#/components/schemas/${pascalCase( + uniqueName + )}ResponseDataObjectLocalized`, + }), + }, + }, + }, + [`${pascalCase(uniqueName)}ResponseDataObjectLocalized`]: { + type: 'object', + properties: { + id: { type: 'string' }, + attributes: { + type: 'object', + properties: cleanSchemaAttributes(attributes, { addComponentSchema }), + }, + }, + }, + [`${pascalCase(uniqueName)}Response`]: { + properties: { + data: { + $ref: `#/components/schemas/${pascalCase(uniqueName)}ResponseDataObject`, }, meta: { type: 'object' }, }, }, }; - - return schemas; + return { ...schemas, ...componentSchemas }; }; const buildComponentSchema = (api) => { diff --git a/packages/plugins/documentation/server/services/helpers/utils/clean-schema-attributes.js b/packages/plugins/documentation/server/services/helpers/utils/clean-schema-attributes.js index f952ff3ef5..3f3089766a 100644 --- a/packages/plugins/documentation/server/services/helpers/utils/clean-schema-attributes.js +++ b/packages/plugins/documentation/server/services/helpers/utils/clean-schema-attributes.js @@ -2,15 +2,18 @@ const _ = require('lodash'); const getSchemaData = require('./get-schema-data'); - +const pascalCase = require('./pascal-case'); /** * @description - Converts types found on attributes to OpenAPI acceptable data types * * @param {object} attributes - The attributes found on a contentType - * @param {{ typeMap: Map, isRequest: boolean }} opts + * @param {{ typeMap: Map, isRequest: boolean, addComponentSchema: function, componentSchemaRefName: string }} opts * @returns Attributes using OpenAPI acceptable data types */ -const cleanSchemaAttributes = (attributes, { typeMap = new Map(), isRequest = false } = {}) => { +const cleanSchemaAttributes = ( + attributes, + { typeMap = new Map(), isRequest = false, addComponentSchema = () => {}, componentSchemaRefName = '' } = {} +) => { const attributesCopy = _.cloneDeep(attributes); for (const prop of Object.keys(attributesCopy)) { @@ -86,43 +89,49 @@ const cleanSchemaAttributes = (attributes, { typeMap = new Map(), isRequest = fa } case 'component': { const componentAttributes = strapi.components[attribute.component].attributes; - + const rawComponentSchema = { + type: 'object', + properties: { + ...(isRequest ? {} : { id: { type: 'string' } }), + ...cleanSchemaAttributes(componentAttributes, { + typeMap, + isRequest, + }), + }, + }; + const refComponentSchema = { + $ref: `#/components/schemas/${pascalCase(attribute.component)}Component`, + }; + const componentExists = addComponentSchema( + `${pascalCase(attribute.component)}Component`, + rawComponentSchema + ); + const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema; if (attribute.repeatable) { attributesCopy[prop] = { type: 'array', - items: { - type: 'object', - properties: { - ...(isRequest ? {} : { id: { type: 'string' } }), - ...cleanSchemaAttributes(componentAttributes, { typeMap, isRequest }), - }, - }, + items: finalComponentSchema, }; } else { - attributesCopy[prop] = { - type: 'object', - properties: { - ...(isRequest ? {} : { id: { type: 'string' } }), - ...cleanSchemaAttributes(componentAttributes, { - typeMap, - isRequest, - }), - }, - }; + attributesCopy[prop] = finalComponentSchema; } break; } case 'dynamiczone': { const components = attribute.components.map((component) => { const componentAttributes = strapi.components[component].attributes; - return { + const rawComponentSchema = { type: 'object', properties: { ...(isRequest ? {} : { id: { type: 'string' } }), __component: { type: 'string' }, - ...cleanSchemaAttributes(componentAttributes, { typeMap, isRequest }), + ...cleanSchemaAttributes(componentAttributes, { typeMap, isRequest, addComponentSchema }), }, }; + const refComponentSchema = { $ref: `#/components/schemas/${pascalCase(component)}` }; + const componentExists = addComponentSchema(pascalCase(component), rawComponentSchema); + const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema; + return finalComponentSchema; }); attributesCopy[prop] = { @@ -171,8 +180,13 @@ const cleanSchemaAttributes = (attributes, { typeMap = new Map(), isRequest = fa if (prop === 'localizations') { attributesCopy[prop] = { - type: 'array', - items: { type: 'object', properties: {} }, + type: 'object', + properties: { + data: { + type: 'array', + items: componentSchemaRefName.length ? { $ref: componentSchemaRefName } : {}, + }, + }, }; break; }