mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor: remove cloud only operations when needed
This commit is contained in:
parent
77b67fbd04
commit
45a7ee17aa
3 changed files with 49 additions and 56 deletions
|
@ -191,7 +191,7 @@
|
|||
},
|
||||
"/api/configs/jwt-customizer/test": {
|
||||
"post": {
|
||||
"tags": ["cloud-only"],
|
||||
"tags": ["Cloud only"],
|
||||
"summary": "Test JWT customizer",
|
||||
"description": "Test the JWT customizer script with the given sample context and sample token payload.",
|
||||
"requestBody": {
|
||||
|
|
|
@ -7,13 +7,14 @@ import deepmerge from 'deepmerge';
|
|||
import { findUp } from 'find-up';
|
||||
import type { IMiddleware } from 'koa-router';
|
||||
import type Router from 'koa-router';
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
import { type OpenAPIV3 } from 'openapi-types';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import { isKoaAuthMiddleware } from '#src/middleware/koa-auth/index.js';
|
||||
import type { WithGuardConfig } from '#src/middleware/koa-guard.js';
|
||||
import { isGuardMiddleware } from '#src/middleware/koa-guard.js';
|
||||
import { isPaginationMiddleware } from '#src/middleware/koa-pagination.js';
|
||||
import { type DeepPartial } from '#src/test-utils/tenant.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { consoleLog } from '#src/utils/console.js';
|
||||
import { translationSchemas, zodTypeToSwagger } from '#src/utils/zod.js';
|
||||
|
@ -24,7 +25,7 @@ import {
|
|||
buildTag,
|
||||
findSupplementFiles,
|
||||
normalizePath,
|
||||
pruneSupplementPaths,
|
||||
removeCloudOnlyOperations,
|
||||
validateSupplement,
|
||||
validateSwaggerDocument,
|
||||
} from './utils/general.js';
|
||||
|
@ -193,15 +194,14 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
|
|||
assertThat(routesDirectory, new Error('Cannot find routes directory.'));
|
||||
|
||||
const supplementPaths = await findSupplementFiles(routesDirectory);
|
||||
const rawSupplementDocuments = await Promise.all(
|
||||
supplementPaths.map(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
async (path) => JSON.parse(await fs.readFile(path, 'utf8')) as Record<string, unknown>
|
||||
const supplementDocuments = await Promise.all(
|
||||
supplementPaths.map(async (path) =>
|
||||
removeCloudOnlyOperations(
|
||||
// eslint-disable-next-line no-restricted-syntax -- trust the type here as we'll validate it later
|
||||
JSON.parse(await fs.readFile(path, 'utf8')) as DeepPartial<OpenAPIV3.Document>
|
||||
)
|
||||
)
|
||||
);
|
||||
const supplementDocuments = rawSupplementDocuments.map((document) =>
|
||||
pruneSupplementPaths(document)
|
||||
);
|
||||
|
||||
const baseDocument: OpenAPIV3.Document = {
|
||||
openapi: '3.0.1',
|
||||
|
@ -232,8 +232,11 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
|
|||
tags: [...tags].map((tag) => ({ name: tag })),
|
||||
};
|
||||
|
||||
const data = supplementDocuments.reduce(
|
||||
(document, supplement) => deepmerge(document, supplement, { arrayMerge: mergeParameters }),
|
||||
const data = supplementDocuments.reduce<OpenAPIV3.Document>(
|
||||
(document, supplement) =>
|
||||
deepmerge<OpenAPIV3.Document, DeepPartial<OpenAPIV3.Document>>(document, supplement, {
|
||||
arrayMerge: mergeParameters,
|
||||
}),
|
||||
baseDocument
|
||||
);
|
||||
|
||||
|
|
|
@ -7,10 +7,14 @@ import { OpenAPIV3 } from 'openapi-types';
|
|||
import { z } from 'zod';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import { type DeepPartial } from '#src/test-utils/tenant.js';
|
||||
import { consoleLog } from '#src/utils/console.js';
|
||||
|
||||
const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1);
|
||||
|
||||
/** The tag name used in the supplement document to indicate that the operation is cloud only. */
|
||||
const cloudOnlyTag = 'Cloud only';
|
||||
|
||||
/**
|
||||
* Get the root component name from the given absolute path.
|
||||
* @example '/organizations/:id' -> 'organizations'
|
||||
|
@ -106,9 +110,14 @@ const validateSupplementPaths = (
|
|||
);
|
||||
}
|
||||
|
||||
if (isKeyInObject(operations[method], 'tags')) {
|
||||
const operation = operations[method];
|
||||
if (
|
||||
isKeyInObject(operation, 'tags') &&
|
||||
Array.isArray(operation.tags) &&
|
||||
(operation.tags.length > 1 || operation.tags[0] !== cloudOnlyTag)
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Cannot use \`tags\` in supplement document on path \`${path}\` and operation \`${method}\`. Define tags in the document root instead.`
|
||||
`Cannot use \`tags\` in supplement document on path \`${path}\` and operation \`${method}\` except for \`${cloudOnlyTag}\`. Define tags in the document root instead.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +134,7 @@ const validateSupplementPaths = (
|
|||
*/
|
||||
export const validateSupplement = (
|
||||
original: OpenAPIV3.Document,
|
||||
supplement: Record<string, unknown>
|
||||
supplement: DeepPartial<OpenAPIV3.Document>
|
||||
) => {
|
||||
if (supplement.tags) {
|
||||
const supplementTags = z.array(z.object({ name: z.string() })).parse(supplement.tags);
|
||||
|
@ -204,51 +213,32 @@ export const validateSwaggerDocument = (document: OpenAPIV3.Document) => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Some path are only available in the cloud version, so we need to prune them out in the OSS.
|
||||
* **CAUTION**: This function mutates the input document.
|
||||
*
|
||||
* Remove operations (path + method) that are tagged with `Cloud only` if the application is not
|
||||
* running in the cloud. This will prevent the swagger validation from failing in the OSS
|
||||
* environment.
|
||||
*/
|
||||
export const pruneSupplementPaths = (supplement: Record<string, unknown>) => {
|
||||
if (EnvSet.values.isCloud) {
|
||||
return supplement;
|
||||
export const removeCloudOnlyOperations = (
|
||||
document: DeepPartial<OpenAPIV3.Document>
|
||||
): DeepPartial<OpenAPIV3.Document> => {
|
||||
if (EnvSet.values.isCloud || !document.paths) {
|
||||
return document;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const supplementName = ((supplement.tags ?? []) as OpenAPIV3.TagObject[])[0]?.name;
|
||||
for (const [path, pathItem] of Object.entries(document.paths)) {
|
||||
for (const method of Object.values(OpenAPIV3.HttpMethods)) {
|
||||
if (pathItem?.[method]?.tags?.includes(cloudOnlyTag)) {
|
||||
// eslint-disable-next-line @silverhand/fp/no-delete, @typescript-eslint/no-dynamic-delete -- intended
|
||||
delete pathItem[method];
|
||||
}
|
||||
}
|
||||
|
||||
if (!supplementName) {
|
||||
return supplement;
|
||||
if (Object.keys(pathItem ?? {}).length === 0) {
|
||||
// eslint-disable-next-line @silverhand/fp/no-delete, @typescript-eslint/no-dynamic-delete -- intended
|
||||
delete document.paths[path];
|
||||
}
|
||||
}
|
||||
|
||||
if (!supplement.paths) {
|
||||
return supplement;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const supplementPaths = supplement.paths as OpenAPIV3.PathsObject;
|
||||
|
||||
if (Object.entries(supplement.paths).length === 0) {
|
||||
return supplement;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const newPaths = Object.fromEntries(
|
||||
Object.entries(supplementPaths)
|
||||
.map(([path, pathBody]) => [
|
||||
path,
|
||||
Object.fromEntries(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
Object.entries(pathBody as OpenAPIV3.PathItemObject).filter(
|
||||
([_, operationBody]) =>
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
!((operationBody as OpenAPIV3.OperationObject).tags ?? []).includes('cloud-only')
|
||||
)
|
||||
),
|
||||
])
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
.filter(([_, pathBody]) => Object.entries(pathBody as OpenAPIV3.PathItemObject).length > 0)
|
||||
) as OpenAPIV3.PathsObject;
|
||||
|
||||
return {
|
||||
...supplement,
|
||||
paths: newPaths,
|
||||
};
|
||||
return document;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue