mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor: filter whole supplement document if needed (#6085)
This commit is contained in:
parent
6dd487269d
commit
097dfcac89
4 changed files with 29 additions and 21 deletions
|
@ -3,12 +3,12 @@
|
||||||
{
|
{
|
||||||
"name": "Organization applications",
|
"name": "Organization applications",
|
||||||
"description": "Manage organization - application relationships. An application can be associated with one or more organizations in order to get access to the organization resources.\n\nCurrently, only machine-to-machine applications can be associated with organizations."
|
"description": "Manage organization - application relationships. An application can be associated with one or more organizations in order to get access to the organization resources.\n\nCurrently, only machine-to-machine applications can be associated with organizations."
|
||||||
}
|
},
|
||||||
|
{ "name": "Dev feature" }
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/organizations/{id}/applications": {
|
"/api/organizations/{id}/applications": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Get organization applications",
|
"summary": "Get organization applications",
|
||||||
"description": "Get applications associated with the organization.",
|
"description": "Get applications associated with the organization.",
|
||||||
"responses": {
|
"responses": {
|
||||||
|
@ -18,7 +18,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Add organization application",
|
"summary": "Add organization application",
|
||||||
"description": "Add an application to the organization.",
|
"description": "Add an application to the organization.",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
@ -44,7 +43,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Replace organization applications",
|
"summary": "Replace organization applications",
|
||||||
"description": "Replace all applications associated with the organization with the given data.",
|
"description": "Replace all applications associated with the organization with the given data.",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
@ -72,7 +70,6 @@
|
||||||
},
|
},
|
||||||
"/api/organizations/{id}/applications/{applicationId}": {
|
"/api/organizations/{id}/applications/{applicationId}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Remove organization application",
|
"summary": "Remove organization application",
|
||||||
"description": "Remove an application from the organization.",
|
"description": "Remove an application from the organization.",
|
||||||
"responses": {
|
"responses": {
|
||||||
|
@ -84,7 +81,6 @@
|
||||||
},
|
},
|
||||||
"/api/organizations/{id}/applications/{applicationId}/roles": {
|
"/api/organizations/{id}/applications/{applicationId}/roles": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Get organization application roles",
|
"summary": "Get organization application roles",
|
||||||
"description": "Get roles associated with the application in the organization.",
|
"description": "Get roles associated with the application in the organization.",
|
||||||
"responses": {
|
"responses": {
|
||||||
|
@ -94,7 +90,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Add organization application role",
|
"summary": "Add organization application role",
|
||||||
"description": "Add a role to the application in the organization.",
|
"description": "Add a role to the application in the organization.",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
@ -120,7 +115,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Replace organization application roles",
|
"summary": "Replace organization application roles",
|
||||||
"description": "Replace all roles associated with the application in the organization with the given data.",
|
"description": "Replace all roles associated with the application in the organization with the given data.",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
@ -148,7 +142,6 @@
|
||||||
},
|
},
|
||||||
"/api/organizations/{id}/applications/{applicationId}/roles/{organizationRoleId}": {
|
"/api/organizations/{id}/applications/{applicationId}/roles/{organizationRoleId}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Remove organization application role",
|
"summary": "Remove organization application role",
|
||||||
"description": "Remove a role from the application in the organization.",
|
"description": "Remove a role from the application in the organization.",
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
{
|
{
|
||||||
"name": "Security",
|
"name": "Security",
|
||||||
"description": "Security related endpoints."
|
"description": "Security related endpoints."
|
||||||
}
|
},
|
||||||
|
{ "name": "Dev feature" }
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/security/subject-tokens": {
|
"/api/security/subject-tokens": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["Dev feature"],
|
|
||||||
"summary": "Create a new subject token.",
|
"summary": "Create a new subject token.",
|
||||||
"description": "Create a new subject token for the use of impersonating the user.",
|
"description": "Create a new subject token for the use of impersonating the user.",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import { httpCodeToMessage } from '@logto/core-kit';
|
import { httpCodeToMessage } from '@logto/core-kit';
|
||||||
import { condString, conditionalArray, deduplicate } from '@silverhand/essentials';
|
import { condArray, condString, conditionalArray, deduplicate } from '@silverhand/essentials';
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
import { findUp } from 'find-up';
|
import { findUp } from 'find-up';
|
||||||
import type { IMiddleware } from 'koa-router';
|
import type { IMiddleware } from 'koa-router';
|
||||||
|
@ -23,6 +23,7 @@ import type { AnonymousRouter } from '../types.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildTag,
|
buildTag,
|
||||||
|
devFeatureTag,
|
||||||
findSupplementFiles,
|
findSupplementFiles,
|
||||||
normalizePath,
|
normalizePath,
|
||||||
removeUnnecessaryOperations,
|
removeUnnecessaryOperations,
|
||||||
|
@ -134,11 +135,13 @@ const identifiableEntityNames = Object.freeze([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** Additional tags that cannot be inferred from the path. */
|
/** Additional tags that cannot be inferred from the path. */
|
||||||
const additionalTags = Object.freeze([
|
const additionalTags = Object.freeze(
|
||||||
'Organization applications',
|
condArray<string>(
|
||||||
'Organization users',
|
EnvSet.values.isDevFeaturesEnabled && 'Organization applications',
|
||||||
'Security',
|
EnvSet.values.isDevFeaturesEnabled && 'Security',
|
||||||
]);
|
'Organization users'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach the `/swagger.json` route which returns the generated OpenAPI document for the
|
* Attach the `/swagger.json` route which returns the generated OpenAPI document for the
|
||||||
|
@ -201,7 +204,7 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
|
||||||
assertThat(routesDirectory, new Error('Cannot find routes directory.'));
|
assertThat(routesDirectory, new Error('Cannot find routes directory.'));
|
||||||
|
|
||||||
const supplementPaths = await findSupplementFiles(routesDirectory);
|
const supplementPaths = await findSupplementFiles(routesDirectory);
|
||||||
const supplementDocuments = await Promise.all(
|
const allSupplementDocuments = await Promise.all(
|
||||||
supplementPaths.map(async (path) =>
|
supplementPaths.map(async (path) =>
|
||||||
removeUnnecessaryOperations(
|
removeUnnecessaryOperations(
|
||||||
// eslint-disable-next-line no-restricted-syntax -- trust the type here as we'll validate it later
|
// eslint-disable-next-line no-restricted-syntax -- trust the type here as we'll validate it later
|
||||||
|
@ -210,6 +213,13 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Filter out supplement documents that are for dev features when dev features are disabled.
|
||||||
|
const supplementDocuments = allSupplementDocuments.filter(
|
||||||
|
(supplement) =>
|
||||||
|
EnvSet.values.isDevFeaturesEnabled ||
|
||||||
|
!supplement.tags?.find((tag) => tag?.name === devFeatureTag)
|
||||||
|
);
|
||||||
|
|
||||||
const baseDocument: OpenAPIV3.Document = {
|
const baseDocument: OpenAPIV3.Document = {
|
||||||
openapi: '3.0.1',
|
openapi: '3.0.1',
|
||||||
servers: [
|
servers: [
|
||||||
|
|
|
@ -15,7 +15,9 @@ const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slic
|
||||||
/** The tag name used in the supplement document to indicate that the operation is cloud only. */
|
/** The tag name used in the supplement document to indicate that the operation is cloud only. */
|
||||||
const cloudOnlyTag = 'Cloud only';
|
const cloudOnlyTag = 'Cloud only';
|
||||||
/** The tag name is used in the supplement document to indicate that the corresponding API operation is a dev feature. */
|
/** The tag name is used in the supplement document to indicate that the corresponding API operation is a dev feature. */
|
||||||
const devFeatureTag = 'Dev feature';
|
export const devFeatureTag = 'Dev feature';
|
||||||
|
|
||||||
|
const reservedTags = new Set([cloudOnlyTag, devFeatureTag]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the root component name from the given absolute path.
|
* Get the root component name from the given absolute path.
|
||||||
|
@ -145,7 +147,7 @@ export const validateSupplement = (
|
||||||
const originalTags = new Set(original.tags?.map((tag) => tag.name));
|
const originalTags = new Set(original.tags?.map((tag) => tag.name));
|
||||||
|
|
||||||
for (const { name } of supplementTags) {
|
for (const { name } of supplementTags) {
|
||||||
if (!originalTags.has(name)) {
|
if (!reservedTags.has(name) && !originalTags.has(name)) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`Supplement document contains extra tag \`${name}\`. If you want to add a new tag, please add it to the \`additionalTags\` array in the main swagger route file.`
|
`Supplement document contains extra tag \`${name}\`. If you want to add a new tag, please add it to the \`additionalTags\` array in the main swagger route file.`
|
||||||
);
|
);
|
||||||
|
@ -213,7 +215,10 @@ export const validateSwaggerDocument = (document: OpenAPIV3.Document) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tag of document.tags ?? []) {
|
for (const tag of document.tags ?? []) {
|
||||||
assert(tag.description, `Tag \`${tag.name}\` must have a description.`);
|
assert(
|
||||||
|
reservedTags.has(tag.name) || tag.description,
|
||||||
|
`Tag \`${tag.name}\` must have a description.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue