-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API improvement suggestion #99
Comments
Hi @EmilIvanov thank you for the kind words. I am not quite sure what you'd want to achieve by doing that 🤔? Is this meant to be an alternative to the current If that is the case then how would you go about passing all schemas into the |
Thank you for the quick response. Here's the The OpenApi definitions only change when your API changes. This means they can be generated just once at build time and then served statically. Imagine that you have And so, if I can put the const { paths, components } = generatePaths({ paths, components }); As an added bonus, you can have a schema just lying in a file. If you need to register it at definition time, then you need to have a registry instance available and so now you need to export a function of the registry to a schema. Hopefully at least some of this makes sense. |
I think I was trying to achieve something like that earlier on but I never could envision it. I think I get it though. The idea it seems would be declaring your schema eg. const name = z.string().openapi({schemaRef: "Name"}); and then when we use it: const someObj = z.object({firstName: name}); When we run into something using schemaRef we immediately "register" it and chuck it into the components. We could generate as someObj:
properties:
name:
$ref: "#/components/schemas/Name" Without needing to register it. It might be possible. I can probably have a go at this over the weekend to see if it's possible or how much work it would take, seems like a fun challenge. That way we could bypass needing to create a registry at runtime which I think is what he's trying to say. This is possibly a little bit cleaner than |
I am going to mention @samchungy comment from the PR here so that we can all discuss:
We are already doing this in some sense 🤔. The only difference is that we are only "traversing" schemas that we've passed to the generator. And from what I understood the idea of @EmilIvanov was to just use what was already passed through the paths, right 🤔? If that is the case I think the overall approach should be something along the lines of:
Is that the target state? If so I don't think that would be too hard to implement. However that would break some previous encapsulation ideas. Right now everything that is internal to the work of I would like to share how we've setup stuff for our project and how it works:
From what I've gathered what you're trying to solve/implement is a way to just pass in paths and not the schemas. And reference them based on the presence of |
cc: @georgyangelov I imagine you'd also have thoughts on that. |
Let me try to explain everything. There are multiple things going on here and this is turning out to be something more than the original issue. First and foremost - metadataIntro.
Since this is not the case, I checked if you had support for a key that would hold the schema name. I found this example in your documentation: z.string().nullable().openapi(refId: 'name'); Unfortunately, This is the original request. And so, I thought I'd try asking you to allow for an additional key in your metadata object where I could put the schema name. Why does this matter?
Second - ease of useNow, even though this wasn't in the original request, you kinda caught on to it probably since it doesn't make sense to add that new key and not use it. Here is be my suggestion without providing specifics. In addition to: const schema = ...;
const registry = new Registry();
const registeredSchema = registry.register(schema);
const route = {
request: {
params: registeredSchema
},
...
};
registry.registerPath(route);
const generator = new OpenAPIGenerator(registry.definitions, version);
const document = generator.generateDocument(openApiConfigObject); Provide an API similar to: const schema = ...;
const route = {
request: {
params: registeredSchema
},
...
};
// -------------------- THIS LINE IS THE IMPORTANT PART -----------------
const document = generateDocument({
schemas: [schema],
paths: [route],
webhooks: ...,
...rest of the definition types
}, version, openApiConfigObject) Now with the addition of the new meta key, you wouldn't need to pass |
By runtime I mean - when we call For my projects in particular the generated schema isn't required at runtime. Technically, runtime includes when everything is being what I believe you are calling build time. So we don't need a registry or the generator for that matter, just the Zod types really and adding eg. The ability to set
This should also be possible by being able to specify |
Psst, hey, want some zod-metadata? Probably, not production ready, though. |
Hi all, first thank you all for the great discussion. This is an interesting suggestion for sure. I'm wondering whether the important part here is "not having to call registry.register(...)" or "let's figure out how to make zod-to-openapi a dev-only dependency". In my mind it's the second thing that's really the point. Let me explain why I think that:
Maybe we should be thinking in that direction - how to make it possible to have zod-to-openapi as a dev dependency. I'm not sure I like completely removing the registry - it gives us a lot of flexibility. Maybe something like a separate package called As far as the refId itself, I'm a bit on the fence of having just metadata keys control what gets extracted and what doesn't - we'll have to think about stuff like two different schemas with the same name, how objects are wrapped (nullable, extends, etc.). |
I think being able to specify the I'm fine with keeping the registry around but would love the be able to specify Performance aside, I chose this repo over https://github.com/anatine/zod-plugins/blob/main/packages/zod-openapi/README.md mainly because of the elegance of |
In case anyone is reading this and waiting for the update, here's how you can achieve this on your own in the meanwhile.
declare module '@asteasolutions/zod-to-openapi' {
interface ZodOpenAPIMetadata {
schemaName: string;
}
}
import { Schema } from 'zod';
import { OpenAPIGenerator, OpenAPIRegistry, RouteConfig } from '@asteasolutions/zod-to-openapi';
import { OpenApiVersion } from '@asteasolutions/zod-to-openapi/dist/openapi-generator';
const schemaName = (schema: Schema) =>
schema._def.openapi?.metadata?.schemaName;
const registerSchema = (registry: OpenAPIRegistry, schema: Schema) => {
const name = schemaName(schema);
if (!name) {
return;
}
const registered = !!registry.definitions.find(
(d) => d.type === 'schema' && name === schemaName(d.schema)
);
if (registered) {
return;
}
registry.register(name, schema);
};
const registerRoutes = (registry: OpenAPIRegistry, routes: RouteConfig[]) => {
for (const route of routes) {
if (route.request?.query) {
registerSchema(registry, route.request.query);
}
// register headers, params, body and responses
registry.registerPath(route)
}
};
export const generateDocument = (version: OpenApiVersion, routes: RouteConfig[]) => {
const registry = new OpenAPIRegistry();
registerRoutes(registry, routes);
const generator = new OpenAPIGenerator(registry.definitions, version);
return generator.generateDocument({ /** your document info */ })
}; |
@samchungy just to mention - the decoupling is still possible. You can just use |
Hi everyone, Thank you so much for the awesome library! It's exactly what I was looking for. I am currently working on incorporating zod-to-openapi into my TypeScript monorepo. I love the idea of using RouteConfig instances as API definitions in a shared package and utilizing them in both the frontend and backend for consistency and validation. In my use case, it would be really helpful to have the flexibility to define and export route configs as static objects without the need to register() related schemas before connecting them. My current workaround is using
Just trying to make a point for decoupling definitions from registry. |
@EmilIvanov I got some extra time and tried to play a little bit with this idea. This is a test that I got working and I think this is what your idea was, right? I.e register any schema upon its occurrence and reference it. This is definitely not in a completed state, but at least it showcases the API design that we can try and work towards. |
…b.com:asteasolutions/zod-to-openapi into enhancement/#99-automatic-registration
@EmilIvanov @samchungy @fomin-alexander I just opened a PR where I've implemented pretty much all of the work for the suggested new API. I'd be more than happy to get some feedback from you guys. The most relevant change (API wise) that you should be looking at seems to be the auto-register.spec.ts file where I've added various test cases for how I imagined the API should work. |
Thank you, @AGalabov, for your work on this! I noticed that your PR introduces the concept of automatic registration of request and response body schemas based on registered paths. However, as a library user, I would suggest leaving it up to the user to decide which schemas to register, for example, by only auto-registering schemas with |
Just curious about this, is there a scenario where you would add a Oh my, I just had a lightbulb moment 💡. I think I understand what you want and how to achieve it. Will come back in like a week 😄 |
@fomin-alexander thank you for the kind words. I think you've missed some parts. I totally agree with your statement and actually the library works exactly as you said in the PR 😄 . If you take a look at the {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Person"
}
}
} If we remove the {
application/json": {"schema": {"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
]
} Was that what you were suggesting or is there more to it? |
@AGalabov, ah now I see that your changes are exactly what I was hoping for, sorry for being slow to understand :) @samchungy, I can't imagine a situation where we have a refId but wouldn't want to register it as a schema. Thanks guys for the feedback, and I'm looking forward to the new release! |
…o enhancement/#99-automatic-registration
…o enhancement/#99-automatic-registration
…-registration Draft #99 automatic registration
@AGalabov I think as long as we have For example if you have Either that or |
@jedwards1211 although I do get your general idea that ideally you would fully separate the two I don't think I get how that would work. (I saw your comment here as well). Overall the idea was that wherever you use that schema you'd want it to have its reference to be reused => I don't think we would be changing that, sorry. |
@AGalabov when are you planning on releasing this feature, or was it already released and we can close this issue? |
@EmilIvanov I just merged the other PR I was waiting for and was writing release and migration notes. I should release this as 5.0.0 in the next 1-2 hours max 🤞 I'll close the issue with a comment then |
Present in v5.0.0 🎉 . I'd be happy to hear your thoughts on how it works. Thank you for the idea and the patience on this. |
Hi, thank you for the great library. I'm playing around with it and I think it would be useful to be able to specify the schema name in the meta, e.g.:
This way schema registration can be done separately.
The text was updated successfully, but these errors were encountered: