Apply overrides for specified versions

This commit is contained in:
Mark Kaylor 2023-03-22 11:30:48 +01:00
parent 0798cf75f3
commit 19ce046512
5 changed files with 141 additions and 79 deletions

View File

@ -17,7 +17,7 @@ module.exports = () => ({
documentation: {
config: {
info: {
version: '2.0.0',
version: '1.0.0',
},
},
},

View File

@ -16,12 +16,11 @@ module.exports = {
name: 'Apache 2.0',
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
},
'x-strapi-generation-date': new Date().toISOString(),
},
'x-strapi-config': {
path: '/documentation',
plugins: null,
customizer: null,
mutateDocumentation: null,
},
servers: [],
externalDocs: {

View File

@ -154,13 +154,15 @@ describe('Documentation service', () => {
},
],
};
global.strapi.config.get = () => ({ ...userConfig });
const docService = documentation({ strapi: global.strapi });
await docService.generateFullDoc();
const lastMockCall = fse.writeJson.mock.calls[fse.writeJson.mock.calls.length - 1];
const mockFinalDoc = lastMockCall[1];
expect(mockFinalDoc.info).toEqual(userConfig.info);
// The generation data is dynamically added, it cannot be modified by the user
const { 'x-generation-date': generationConfig, ...mockFinalDocInfo } = mockFinalDoc.info;
expect(mockFinalDocInfo).toEqual(userConfig.info);
expect(mockFinalDoc['x-strapi-config']).toEqual(userConfig['x-strapi-config']);
expect(mockFinalDoc.externalDocs).toEqual(userConfig.externalDocs);
expect(mockFinalDoc.security).toEqual(userConfig.security);
@ -319,6 +321,66 @@ describe('Documentation service', () => {
'test-new-component'
);
});
it('overrides only the specified version', async () => {
const overrideService = override({ strapi: global.strapi });
// Simulate override from upload plugin
overrideService.registerOverride(
{
// Only override for version 1.0.0
info: { version: '1.0.0' },
components: {
schemas: {
// This component schema exists after generating with mock data, replace it
ShouldNotBeAdded: {},
},
},
},
{ pluginOrigin: 'upload' }
);
// Simulate override from upload plugin
overrideService.registerOverride(
{
// Only override for version 2.0.0
info: { version: '2.0.0' },
components: {
schemas: {
// This component schema exists after generating with mock data, replace it
ShouldBeAdded: {},
},
},
},
{ pluginOrigin: 'upload' }
);
// Simulate override from upload plugin
overrideService.registerOverride(
{
components: {
schemas: {
// This component schema exists after generating with mock data, replace it
ShouldAlsoBeAdded: {},
},
},
},
{ pluginOrigin: 'upload' }
);
global.strapi.plugins.documentation = {
service: jest.fn((name) => {
const mockServices = {
override: overrideService,
};
return mockServices[name];
}),
};
const docService = documentation({ strapi: global.strapi });
await docService.generateFullDoc('2.0.0');
const lastMockCall = fse.writeJson.mock.calls[fse.writeJson.mock.calls.length - 1];
const mockFinalDoc = lastMockCall[1];
expect(mockFinalDoc.components.schemas.ShouldNotBeAdded).toBeUndefined();
expect(mockFinalDoc.components.schemas.ShouldBeAdded).toBeDefined();
expect(mockFinalDoc.components.schemas.ShouldAlsoBeAdded).toBeDefined();
});
it('excludes apis and plugins from generation', async () => {
const overrideService = override({ strapi: global.strapi });
@ -346,12 +408,12 @@ describe('Documentation service', () => {
Object.keys(mockFinalDoc.components.schemas).find((compo) => compo.includes('Kitchensink'))
).toBeUndefined();
});
it("applies a user's customizer function", async () => {
it("applies a user's mutateDocumentation function", async () => {
global.strapi.config.get = () => ({
...defaultConfig,
'x-strapi-config': {
...defaultConfig['x-strapi-config'],
customizer(draft) {
mutateDocumentation(draft) {
draft.paths['/kitchensinks'] = { get: { responses: { 200: { description: 'test' } } } };
},
},

View File

@ -176,7 +176,6 @@ module.exports = ({ strapi }) => {
'full_documentation.json'
);
// Set config defaults
const serverUrl = getAbsoluteServerUrl(strapi.config);
const apiPath = strapi.config.get('api.rest.prefix');
const generatedDocumentation = produce(config, (draft) => {
@ -189,42 +188,49 @@ module.exports = ({ strapi }) => {
},
];
}
// Set the generated date
draft.info['x-generation-date'] = new Date().toISOString();
// Set the plugins that need documentation
draft['x-strapi-config'].plugins = pluginsThatNeedDocumentation;
// Delete it from the config so it doesn't end up in the spec
delete draft['x-strapi-config'].customizer;
delete draft['x-strapi-config'].mutateDocumentation;
// Set the generated paths
draft.paths = paths;
// Merge the generated component schemas with the defaults
draft.components = _.merge(defaultOpenApiComponents, { schemas });
// Check for overrides and then add them
if (overrideService.registeredOverrides.length > 0) {
overrideService.registeredOverrides.forEach((doc) => {
// Only run the overrrides when no override version is provided,
// or when the generated documentation version matches the override version
if (!doc?.info?.version || doc.info.version === version) {
if (doc.tags) {
// Merge override tags with the generated tags
draft.tags = draft.tags || [];
draft.tags.push(...doc.tags);
}
overrideService.registeredOverrides.forEach((doc) => {
if (doc.tags) {
// Merge override tags with the generated tags
draft.tags = draft.tags || [];
draft.tags.push(...doc.tags);
}
if (doc.paths) {
// Merge override paths with the generated paths
// The override will add a new path or replace the value of an existing path
draft.paths = { ...draft.paths, ...doc.paths };
}
if (doc.paths) {
// Merge override paths with the generated paths
// The override will add a new path or replace the value of an existing path
draft.paths = { ...draft.paths, ...doc.paths };
}
if (doc.components) {
Object.entries(doc.components).forEach(([key, val]) => {
draft.components[key] = draft.components[key] || {};
// Merge override components with the generated components,
// The override will add a new component or replace the value of an existing component
draft.components[key] = { ...draft.components[key], ...val };
});
}
});
if (doc.components) {
Object.entries(doc.components).forEach(([key, val]) => {
draft.components[key] = draft.components[key] || {};
// Merge override components with the generated components,
// The override will add a new component or replace the value of an existing component
draft.components[key] = { ...draft.components[key], ...val };
});
}
}
});
}
});
// Get the documentation customizer
const documentationCustomizer = config['x-strapi-config'].customizer;
// Escape hatch, allow the user to provide a customizer function that can manipulate
// Get the documentation mutateDocumentation
const documentationCustomizer = config['x-strapi-config'].mutateDocumentation;
// Escape hatch, allow the user to provide a mutateDocumentation function that can alter any part of
// the generated documentation before it is written to the file system
const finalDocumentation = documentationCustomizer
? produce(generatedDocumentation, documentationCustomizer)

View File

@ -5,53 +5,48 @@ const { getPluginsThatNeedDocumentation } = require('./utils/get-plugins-that-ne
module.exports = ({ strapi }) => {
const registeredOverrides = [];
const excludedFromGeneration = [];
/**
*
* @param {string | string[]} api - The name of the api or and array of apis to exclude from generation
*/
const excludeFromGeneration = (api) => {
if (Array.isArray(api)) {
excludedFromGeneration.push(...api);
return;
}
excludedFromGeneration.push(api);
};
/**
* @TODO pluginOrigin should be required in next major release
* @param {object} doc - The openapi specifcation to override
* @param {object} options - The options to override the documentation
* @param {string} options.pluginOrigin - The name of the plugin that is overriding the documentation
* @param {string[]} options.excludeFromGeneration - The name of the plugin that is overriding the documentation
*/
const registerOverride = (override, { pluginOrigin, excludeFromGeneration = [] }) => {
const pluginsThatNeedDocumentation = getPluginsThatNeedDocumentation(
strapi.config.get('plugin.documentation')
);
// Don't apply the override if the plugin is not in the list of plugins that need documentation
if (pluginOrigin && !pluginsThatNeedDocumentation.includes(pluginOrigin)) return;
if (excludeFromGeneration.length) {
strapi
.plugin('documentation')
.service('override')
.excludeFromGeneration(excludeFromGeneration);
}
let overrideToRegister = override;
// Parse yaml if we receive a string
if (typeof override === 'string') {
overrideToRegister = require('yaml').parse(overrideToRegister);
}
// receive an object we can register it directly
registeredOverrides.push(overrideToRegister);
};
return {
registeredOverrides,
registerOverride,
excludeFromGeneration,
excludedFromGeneration,
/**
*
* @param {string | string[]} api - The name of the api or and array of apis to exclude from generation
*/
excludeFromGeneration(api) {
if (Array.isArray(api)) {
excludedFromGeneration.push(...api);
return;
}
excludedFromGeneration.push(api);
},
/**
* @TODO pluginOrigin should be required in next major release
* @param {object} doc - The openapi specifcation to override
* @param {object} options - The options to override the documentation
* @param {string} options.pluginOrigin - The name of the plugin that is overriding the documentation
* @param {string[]} options.excludeFromGeneration - The name of the plugin that is overriding the documentation
*/
registerOverride(override, { pluginOrigin, excludeFromGeneration = [] }) {
const pluginsThatNeedDocumentation = getPluginsThatNeedDocumentation(
strapi.config.get('plugin.documentation')
);
// Don't apply the override if the plugin is not in the list of plugins that need documentation
if (pluginOrigin && !pluginsThatNeedDocumentation.includes(pluginOrigin)) return;
if (excludeFromGeneration.length) {
this.excludeFromGeneration(excludeFromGeneration);
}
let overrideToRegister = override;
// Parse yaml if we receive a string
if (typeof override === 'string') {
overrideToRegister = require('yaml').parse(overrideToRegister);
}
// receive an object we can register it directly
registeredOverrides.push(overrideToRegister);
},
};
};