@autobe/agent
Version:
AI backend server code generator
261 lines (226 loc) • 9.62 kB
text/typescript
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.
`,
});
}
};
}