@autobe/agent
Version:
AI backend server code generator
156 lines (145 loc) • 5.34 kB
text/typescript
import {
AutoBeAnalyze,
AutoBeInterfaceEndpointDesign,
} from "@autobe/interface";
import { StringUtil } from "@autobe/utils";
import { LlmTypeChecker, NamingConvention } from "@typia/utils";
import { singular } from "pluralize";
import typia, { ILlmApplication, ILlmSchema, IValidation } from "typia";
export namespace AutoBeInterfaceEndpointProgrammer {
/**
* Filter endpoint designs:
*
* - For base: Remove guest actors and login/join/refresh/session
* authorizationType
* - For action: Remove all non-null authorizationType (action endpoints must be
* null only)
*/
export const filter = (props: {
kind: "base" | "action";
design: AutoBeInterfaceEndpointDesign;
actors: AutoBeAnalyze.IActor[];
}): boolean => {
// Action endpoints: only allow authorizationType: null
if (props.kind === "action") return props.design.authorizationType === null;
else if (
props.design.endpoint.path === "/auth" ||
props.design.endpoint.path.startsWith("/auth/")
)
return false;
// remove specific auth types
if (
props.design.authorizationType === "login" ||
props.design.authorizationType === "join" ||
props.design.authorizationType === "refresh" ||
props.design.authorizationType === "withdraw"
)
return false;
else if (props.design.authorizationType === "session") {
props.design.authorizationActors =
props.design.authorizationActors.filter((name) => {
const actor: AutoBeAnalyze.IActor | undefined = props.actors.find(
(a) => a.name === name,
);
if (actor === undefined) return false;
return actor.kind !== "guest";
});
if (props.design.authorizationActors.length === 0) return false;
else if (
props.design.endpoint.method !== "get" &&
props.design.endpoint.method !== "patch"
)
return false;
return true;
}
return true;
};
export const fixDesign = (props: {
actors: AutoBeAnalyze.IActor[];
design: AutoBeInterfaceEndpointDesign;
}): AutoBeInterfaceEndpointDesign => {
props.design.endpoint.path = fixPath(props.design.endpoint.path);
if (props.design.authorizationActors.length === 1) {
const actor: AutoBeAnalyze.IActor | undefined = props.actors.find(
(a) => a.name === props.design.authorizationActors[0],
);
if (actor !== undefined && actor.kind !== "admin") {
const param: string =
NamingConvention.camel(singular(actor.name)) + "Id";
const bracket: string = `{${param}}`;
if (props.design.endpoint.path.includes(bracket) === true)
props.design.endpoint.path = props.design.endpoint.path.replace(
bracket,
"",
);
}
} else if (
props.design.authorizationActors.length > 1 &&
props.design.endpoint.path.includes("{actorId}")
) {
props.design.endpoint.path = props.design.endpoint.path.replace(
"{actorId}",
"",
);
}
return props.design;
};
export const fixPath = (path: string): string => {
return path
.split("/")
.map((s) =>
s.startsWith("{") && s.endsWith("}")
? `{${NamingConvention.camel(s.slice(1, -1).replace(/-/g, "_"))}}`
: s,
)
.join("/");
};
export const fixApplication = (props: {
application: ILlmApplication;
actors: AutoBeAnalyze.IActor[];
}): void => {
const $defs: Record<string, ILlmSchema> =
props.application.functions[0].parameters.$defs;
const design: ILlmSchema | undefined =
$defs[typia.reflect.name<AutoBeInterfaceEndpointDesign>()];
if (design === undefined)
throw new Error("AutoBeInterfaceEndpointDesign is undefined");
else if (LlmTypeChecker.isObject(design) === false)
throw new Error("AutoBeInterfaceEndpointDesign is not object type");
const property: ILlmSchema | undefined =
design.properties["authorizationActors"];
if (property === undefined)
throw new Error(
"AutoBeInterfaceEndpointDesign.authorizationActors is undefined",
);
else if (LlmTypeChecker.isArray(property) === false)
throw new Error(
"AutoBeInterfaceEndpointDesign.authorizationActors is not array type",
);
property.items = {
type: "string",
enum: props.actors.map((actor) => actor.name),
};
};
export const validateDesign = (props: {
design: AutoBeInterfaceEndpointDesign;
actors: AutoBeAnalyze.IActor[];
path: string;
errors: IValidation.IError[];
}): void => {
// if (props.actors.length === 0) props.design.authorizationActors = [];
props.design.authorizationActors.forEach((actorName, i) => {
if (props.actors.find((actor) => actor.name === actorName) === undefined)
props.errors.push({
path: `${props.path}.authorizationActors[${i}]`,
expected: props.actors.map((a) => JSON.stringify(a.name)).join(" | "),
value: actorName,
description: StringUtil.trim`
Actor "${actorName}" is not defined in the roles list.
Please select one of them below, or do not define (\`null\`):
${props.actors.map((actor) => `- ${actor.name}`).join("\n")}
`,
});
});
};
}