fix(open-api): include additional fields (#3439)

* open api additional field

* lint

* lint

* falsy value

* deprecated zodschema with zodtype
This commit is contained in:
KinfeMichael Tariku
2025-07-18 10:10:17 -07:00
committed by GitHub
parent f521d7bf11
commit 5242d82aa8
3 changed files with 74 additions and 16 deletions
@@ -1 +1 @@
IDLE
RUNNING
@@ -4,10 +4,11 @@ import type {
OpenAPIParameter,
OpenAPISchemaType,
} from "better-call";
import { ZodObject, ZodOptional, ZodSchema } from "zod";
import { z, ZodOptional, ZodObject, ZodType } from "zod";
import { getEndpoints } from "../../api";
import { getAuthTables } from "../../db";
import type { AuthContext, BetterAuthOptions } from "../../types";
import type { FieldAttribute } from "../../db";
export interface Path {
get?: {
@@ -67,9 +68,9 @@ export interface Path {
};
};
}
const paths: Record<string, Path> = {};
function getTypeFromZodType(zodType: ZodSchema) {
const paths: Record<string, Path> = {};
function getTypeFromZodType(zodType: ZodType) {
switch (zodType.constructor.name) {
case "ZodString":
return "string";
@@ -86,6 +87,25 @@ function getTypeFromZodType(zodType: ZodSchema) {
}
}
function getFieldSchema(field: FieldAttribute) {
const schema: any = {
type: field.type === "date" ? "string" : field.type,
};
if (field.defaultValue !== undefined) {
schema.default =
typeof field.defaultValue === "function"
? "Generated at runtime"
: field.defaultValue;
}
if (field.input === false) {
schema.readOnly = true;
}
return schema;
}
function getParameters(options: EndpointOptions) {
const parameters: OpenAPIParameter[] = [];
if (options.metadata?.openapi?.parameters) {
@@ -94,7 +114,7 @@ function getParameters(options: EndpointOptions) {
}
if (options.query instanceof ZodObject) {
Object.entries(options.query.shape).forEach(([key, value]) => {
if (value instanceof ZodSchema) {
if (value instanceof ZodType) {
parameters.push({
name: key,
in: "query",
@@ -129,12 +149,12 @@ function getRequestBody(options: EndpointOptions): any {
const properties: Record<string, any> = {};
const required: string[] = [];
Object.entries(shape).forEach(([key, value]) => {
if (value instanceof ZodSchema) {
if (value instanceof z.ZodType) {
properties[key] = {
type: getTypeFromZodType(value),
description: value.description,
};
if (!(value instanceof ZodOptional)) {
if (!(value instanceof z.ZodOptional)) {
required.push(key);
}
}
@@ -280,18 +300,24 @@ export async function generator(ctx: AuthContext, options: BetterAuthOptions) {
const tables = getAuthTables(options);
const models = Object.entries(tables).reduce((acc, [key, value]) => {
const modelName = key.charAt(0).toUpperCase() + key.slice(1);
const fields = value.fields;
const required: string[] = [];
const properties: Record<string, any> = {
id: { type: "string" },
};
Object.entries(fields).forEach(([fieldKey, fieldValue]) => {
if (!fieldValue) return;
properties[fieldKey] = getFieldSchema(fieldValue);
if (fieldValue.required && fieldValue.input !== false) {
required.push(fieldKey);
}
});
// @ts-ignore
acc[modelName] = {
type: "object",
properties: Object.entries(value.fields).reduce(
(acc, [key, value]) => {
acc[key] = {
type: value.type,
};
return acc;
},
{ id: { type: "string" } } as Record<string, any>,
),
properties,
...(required.length > 0 ? { required } : {}),
};
return acc;
}, {});
@@ -5,6 +5,19 @@ import { openAPI } from ".";
describe("open-api", async (it) => {
const { auth } = await getTestInstance({
plugins: [openAPI()],
user: {
additionalFields: {
role: {
type: "string",
required: true,
defaultValue: "user",
},
preferences: {
type: "string",
required: false,
},
},
},
});
it("should generate OpenAPI schema", async () => {
@@ -22,4 +35,23 @@ describe("open-api", async (it) => {
type: "string",
});
});
it("should include additionalFields in the User schema", async () => {
const schema = await auth.api.generateOpenAPISchema();
const schemas = schema.components.schemas as Record<
string,
Record<string, any>
>;
expect(schemas["User"].properties.role).toEqual({
type: "string",
default: "user",
});
expect(schemas["User"].properties.preferences).toEqual({
type: "string",
});
expect(schemas["User"].required).toContain("role");
expect(schemas["User"].required).not.toContain("preferences");
});
});