Closed robotkutya closed 2 years ago
hey, this is intended behavior as the main purpose of this lib is to get an api client (using zodios) which means we will only export schemas used by the openapi PathsObject
(doc.paths
)
however, using the exported API from openapi-zod-client
I think you can achieve what you want:
// ./example/petstore-components-generator.ts
import SwaggerParser from "@apidevtools/swagger-parser";
import { OpenAPIObject, SchemaObject } from "openapi3-ts";
import { ConversionTypeContext, getZodSchema } from "../src/openApiToZod";
const main = async () => {
const openApiDoc = (await SwaggerParser.parse("./example/petstore.yaml")) as OpenAPIObject;
const schemas = openApiDoc.components?.schemas ?? {};
const ctx: ConversionTypeContext = {
getSchemaByRef: (ref) => schemas[ref.split("/").at(-1)!] as SchemaObject,
zodSchemaByHash: {},
schemaHashByRef: {},
codeMetaByRef: {},
hashByVariableName: {},
circularTokenByRef: {},
};
const zodSchemas = Object.fromEntries(
Object.entries(schemas).map(([name, schema]) => [name, getZodSchema({ schema, ctx }).toString()])
);
console.log(zodSchemas);
};
main();
❯ pnpm tsx ./example/petstore-components-generator.ts
{
Order: 'z.object({ id: z.number(), petId: z.number(), quantity: z.number(), shipDate: z.string(), status: z.enum(["placed", "approved", "delivered"]), complete: z.boolean() }).partial()',
Customer: 'z.object({ id: z.number(), username: z.string(), address: z.array(@ref__vjRfEADnJZ8__) }).partial()',
Address: 'z.object({ street: z.string(), city: z.string(), state: z.string(), zip: z.string() }).partial()',
Category: 'z.object({ id: z.number(), name: z.string() }).partial()',
User: 'z.object({ id: z.number(), username: z.string(), firstName: z.string(), lastName: z.string(), email: z.string(), password: z.string(), phone: z.string(), userStatus: z.number() }).partial()',
Tag: 'z.object({ id: z.number(), name: z.string() }).partial()',
Pet: 'z.object({ id: z.number().optional(), name: z.string(), category: @ref__vR1x0k5qaLk__.optional(), photoUrls: z.array(z.string()), tags: z.array(@ref__vR1x0k5qaLk__).optional(), status: z.enum(["available", "pending", "sold"]).optional() })',
ApiResponse: 'z.object({ code: z.number(), type: z.string(), message: z.string() }).partial()'
}
is that ok ?
btw I forgot to talk about the @ref_xxx
tokens still here
tl;dr, a naive solution: add something like this inlineRefTokenWithActualSchema
const inlineRefTokenWithActualSchema = (code: string) =>
code.replaceAll(tokens.refTokenHashRegex, (match) => ctx.zodSchemaByHash[match]);
const zodSchemas = Object.fromEntries(
Object.entries(schemas).map(([name, schema]) => [
name,
inlineRefTokenWithActualSchema(getZodSchema({ schema, ctx }).toString()),
])
);
output:
❯ pnpm tsx ./example/petstore-components-generator.ts
{
Order: 'z.object({ id: z.number(), petId: z.number(), quantity: z.number(), shipDate: z.string(), status: z.enum(["placed", "approved", "delivered"]), complete: z.boolean() }).partial()',
Customer: 'z.object({ id: z.number(), username: z.string(), address: z.array(z.object({ street: z.string(), city: z.string(), state: z.string(), zip: z.string() }).partial()) }).partial()',
Address: 'z.object({ street: z.string(), city: z.string(), state: z.string(), zip: z.string() }).partial()',
Category: 'z.object({ id: z.number(), name: z.string() }).partial()',
User: 'z.object({ id: z.number(), username: z.string(), firstName: z.string(), lastName: z.string(), email: z.string(), password: z.string(), phone: z.string(), userStatus: z.number() }).partial()',
Tag: 'z.object({ id: z.number(), name: z.string() }).partial()',
Pet: 'z.object({ id: z.number().optional(), name: z.string(), category: z.object({ id: z.number(), name: z.string() }).partial().optional(), photoUrls: z.array(z.string()), tags: z.array(z.object({ id: z.number(), name: z.string() }).partial()).optional(), status: z.enum(["available", "pending", "sold"]).optional() })',
ApiResponse: 'z.object({ code: z.number(), type: z.string(), message: z.string() }).partial()'
}
the thing is that there might be more complex cases than the petstore one, with recursive refs etc, or multiple schemas referencing another, which leads to duplicated code
which is why I went the @ref_xxx
tokens way, even tho the code has become quite a mess (sorry for that I was in a hurry, what started as a quick PoC somehow got a bit of attention so I tried to deliver ASAP)
Thanks, that looks very promising! I'll take a look at the code after some sleep :P
Yeah, I figured that this was intended behavior. Maybe consider adding this as a feature behind a flag? Something similar to the --exportModels
and --exportSchemas
flags in openapi-typescript-codege[]
yeah ok this definitely makes sense, I just published a new version (v0.2.2) with that feature (--export-schemas
option), tell me if everything works fine for you ! 🙏
Daaaaamn, that was fast! Beautiful!
Seems to work perfectly.
Thank you so much 🖖
Is there a way to generate all OpenApi component schemas and export them (without hashed names)?
In the Pet Store example, there are 8 component schemas. https://github.com/astahmer/openapi-zod-client/blob/main/example/petstore.yaml
Only 4 of them are added to
export const schemas {...}
. https://github.com/astahmer/openapi-zod-client/blob/main/example/petstore-schemas.ts