UNPKG

@autobe/agent

Version:

AI backend server code generator

261 lines (226 loc) 9.62 kB
import { AutoBeAnalyze, AutoBeOpenApi } from "@autobe/interface"; import { StringUtil } from "@autobe/utils"; import { NamingConvention } from "@typia/utils"; import { singular } from "pluralize"; import typia, { IValidation } from "typia"; import { AutoBeInterfaceOperationProgrammer } from "./AutoBeInterfaceOperationProgrammer"; export namespace AutoBeInterfaceAuthorizationProgrammer { export const getTypeName = (props: { prefix: string | null; actor: string; }): string => { return ["I", ...(props.prefix ? [props.prefix] : []), props.actor] .map((s) => NamingConvention.pascal(singular(s))) .join(""); }; export const getSessionTypeName = (props: { prefix: string | null; actor: string; }): string => `${getTypeName(props)}Session`; export const filter = (props: { actor: string; operation: AutoBeOpenApi.IOperation; }): boolean => props.actor !== "guest" ? true : props.operation.authorizationType !== "login"; export const fixOperations = (props: { operations: AutoBeOpenApi.IOperation[]; prefix: string; }): AutoBeOpenApi.IOperation[] => { for (const op of props.operations) AutoBeInterfaceOperationProgrammer.fix(op); return props.operations .filter((op) => op.authorizationType !== null) .map((op) => { return { ...op, path: "/" + [props.prefix, ...op.path.split("/")] .filter((it) => it !== "") .join("/"), } satisfies AutoBeOpenApi.IOperation; }); }; export const validateOperation = (props: { operation: AutoBeOpenApi.IOperation; actor: AutoBeAnalyze.IActor; prefix: string | null; accessor: string; errors: IValidation.IError[]; }): void => { // common operation validations AutoBeInterfaceOperationProgrammer.validate({ errors: props.errors, accessor: props.accessor, operation: props.operation, }); // check authorization type if (props.operation.authorizationType === null) return; // check for which actor is specified if (props.operation.authorizationActor !== props.actor.name) props.errors.push({ path: `${props.accessor}.authorizationActor`, expected: JSON.stringify(props.actor.name), value: props.operation.authorizationActor, description: StringUtil.trim` The authorizationActor must match the actor associated with this authorization operation. If this is just a mistake, please change the value accordingly. Otherwise you actually made for another actor, please entirely remake the operation for the correct actor. The other actor was already defined elsewhere. - Expected actor: ${JSON.stringify(props.actor.name)} - Provided actor: ${JSON.stringify(props.operation.authorizationActor)} `, }); if ( props.operation.authorizationType !== "join" && props.operation.authorizationType !== "login" && props.operation.authorizationType !== "refresh" ) return; // path parameters must be empty if (props.operation.parameters.length !== 0) props.errors.push({ path: `${props.accessor}.parameters`, expected: "[] // (empty array)", value: props.operation.parameters, description: StringUtil.trim` Authorization operations (join/login/refresh) cannot have path parameters. All necessary data must be provided in the request body. Remove the parameters from the operation. Also ensure the following properties are correct: - AutoBeOpenApi.IOperation.specification - AutoBeOpenApi.IOperation.description - AutoBeOpenApi.IOperation.requestBody - AutoBeOpenApi.IOperation.responseBody `, }); // special authorization cases const typeName: string = getTypeName({ prefix: props.prefix, actor: props.actor.name, }); // validate request body const requestTypeName: string = `${typeName}.${ props.operation.authorizationType === "login" ? "ILogin" : props.operation.authorizationType === "join" ? "IJoin" : "IRefresh" }`; if (props.operation.requestBody === null) props.errors.push({ path: `${props.accessor}.requestBody`, expected: `AutoBeOpenApi.IRequestBody // typeName: ${requestTypeName}`, value: props.operation.requestBody, description: StringUtil.trim` Request body is required for authentication ${props.operation.authorizationType} operation. Write the requestBody with typeName exactly "${requestTypeName}". Also ensure the following properties are correct: - AutoBeOpenApi.IOperation.specification - AutoBeOpenApi.IOperation.description `, }); else if (props.operation.requestBody.typeName !== requestTypeName) props.errors.push({ path: `${props.accessor}.requestBody.typeName`, expected: `AutoBeOpenApi.IOperation with requestBody.typeName: ${requestTypeName}`, value: props.operation.requestBody.typeName, description: StringUtil.trim` Wrong request body type name: "${props.operation.requestBody.typeName}" Fix the requestBody.typeName to exactly "${requestTypeName}". Also ensure the following properties are correct: - AutoBeOpenApi.IOperation.specification - AutoBeOpenApi.IOperation.description - AutoBeOpenApi.IOperation.requestBody.description `, }); // validate response body const responseTypeName: string = `${typeName}.IAuthorized`; if (props.operation.responseBody === null) props.errors.push({ path: `${props.accessor}.responseBody`, expected: `AutoBeOpenApi.IResponseBody // typeName: ${responseTypeName}`, value: props.operation.responseBody, description: StringUtil.trim` Response body is required for authentication ${props.operation.authorizationType} operation. Write the responseBody with typeName exactly "${responseTypeName}". Also ensure the following properties are correct: - AutoBeOpenApi.IOperation.specification - AutoBeOpenApi.IOperation.description `, }); else if (props.operation.responseBody.typeName !== responseTypeName) props.errors.push({ path: `${props.accessor}.responseBody.typeName`, expected: JSON.stringify(responseTypeName), value: props.operation.responseBody.typeName, description: StringUtil.trim` Wrong response body type name: "${props.operation.responseBody.typeName}" Fix the responseBody.typeName to exactly "${responseTypeName}". Also ensure the following properties are correct: - AutoBeOpenApi.IOperation.specification - AutoBeOpenApi.IOperation.description - AutoBeOpenApi.IOperation.responseBody.description `, }); }; export const validateAuthorizationTypes = (props: { actor: AutoBeAnalyze.IActor; operations: AutoBeOpenApi.IOperation[]; accessor: string; errors: IValidation.IError[]; }): void => { type AuthorizationType = AutoBeOpenApi.IOperation["authorizationType"]; for (const type of typia.misc.literals<AuthorizationType>()) { // Skip null - these are handled by Base/Action Endpoint generators if (type === null) continue; if (props.actor.kind === "guest" && type === "login") continue; const count: number = props.operations.filter( (o) => o.authorizationType === type, ).length; if (count === 0) props.errors.push({ path: props.accessor, value: props.operations, expected: StringUtil.trim` { ...(AutoBeOpenApi.IOperation data), authorizationType: "${type}" } `, description: StringUtil.trim` There must be an operation that has defined (AutoBeOpenApi.IOperation.authorizationType := "${type}") for the "${props.actor.name}" role's authorization activity; "${type}". However, none of the operations have the (AutoBeOpenApi.IOperation.authorizationType := "${type}") value, so that the "${props.actor.name}" cannot perform the authorization ${type} activity. Please make that operation at the next function calling. You have to do it. `, }); else if (count > 1) props.errors.push({ path: props.accessor, value: props.operations, expected: `Only one operation with authorizationType "${type}"`, description: StringUtil.trim` There must be only one operation that has defined (AutoBeOpenApi.IOperation.authorizationType := "${type}") for the "${props.actor.name}" role's authorization activity; "${type}". However, multiple operations (${count} operations) have the (AutoBeOpenApi.IOperation.authorizationType := "${type}") value, so that the "${props.actor.name}" cannot determine which operation to use for the authorization ${type} activity. Please ensure that only one operation is defined for each authorizationType per actor. `, }); } }; }