-
Notifications
You must be signed in to change notification settings - Fork 512
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
Support zod's infer type #1256
Comments
Hello there cgibson-swyftx 👋 Thank you for opening your very first issue in this project. We will try to get back to you as soon as we can.👀 |
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days |
Bump |
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days |
Bump |
bump |
I've labeled this to avoid the bot. Please vote instead of commenting, and feel free to open a PR to fix this. |
I have the same problem when using yup's inferred types. |
faced the same issue |
@WoH or other person, can gives me a little help. I really like trying to solve this but I need a little help to understand where I need to looking for. |
I tried this and it worked (with yup) jquense/yup#946 (comment) |
I wish I could easily help you out here, but given the things you You should be able to extract a lot of that code and reuse it. |
I'll try to solve infer, input and output from Zod when having more time. I found a trick to solve partial for output and infer types. // One schema with zod
const userSchema = z.object({
username: z.string(),
// ... what you need to exist
})
export type UserParsed = ReturnType<typeof userSchema.parse>
// use type to define body like that:
@Post()
@Middlewares<RequestHandler>(schemaValidation(userSchema))
public async create(
@Body() body: UserParsed
) {
// TODO code here
} That working on my tests... interesting. Documentation show really weird but with correct schema information. |
I assume because ReturnType is a type reference to a type that we already try to resolve via the mechanism I described. |
it created the type, but unfortunately, the validation doesn't work, I assume it' relate to #1067 |
what would it be schemaValidation(...)? |
i have same problem! Any solution? |
See above for a possible workaround, please submit a PR that properly fixes this otherwise :) |
I realized that when I use this solution, the generated documentation does not recognize the lack of fields even though they are not optional |
schemaValidation is a middleware not related with the issue, middleware to validate body with zod schemas. |
Upon some shallow investigation, I'm able to replicate the underlying problem (without zod in the loop). In the zod types they have.
and it appears that I can replicate the similar error with
Note that
I noticed that TSOA's AST processing code does not know how to handle a So later when trying to dive into |
this was the solution for me: |
Bump this, would like to see a solution that maintains validation or mention in the documentation the limitations of the framework. |
For me, the equivalent approach in Yup does create the correct type. But tsoa throws errors unless I use an interface with Some demos: With yupFails with import * as yup from "yup";
import { Controller, Get, Queries, Route } from "tsoa";
const basketSchema = yup.object({
id: yup.number().optional(),
name: yup.string().optional(),
});
type TGetBasketParams = ReturnType<typeof basketSchema.validateSync>;
@Route("basket")
export class Basket extends Controller {
@Get()
public static get(@Queries() params: TGetBasketParams) {
return;
}
} No yup, just a plain old typeFails with import { Controller, Get, Queries, Route } from "tsoa";
type TGetBasketParams = {
id?: number;
name?: string;
};
@Route("basket")
export class Basket extends Controller {
@Get()
public static get(@Queries() params: TGetBasketParams) {
return;
}
} no yup, same type as an interfaceWorks. import { Controller, Get, Queries, Route } from "tsoa";
interface TGetBasketParams {
id?: number;
name?: string;
}
@Route("basket")
export class Basket extends Controller {
@Get()
public static get(@Queries() params: TGetBasketParams) {
return;
}
} |
@daweimau Would you mind setting up a repro for this? Not sure I have the time to support 1), but 2) should be reasonable. |
@WoH What is needed for this feature and how can I help? |
This is my validation middleware for Yup but I guess with a small refactor you use it for zod as well
|
With the newer version 6.0.0, now not even this workaround is valid 😞 Also tried the default |
Any solution, its 2023 and this issue is still present. LOL |
Are you interested in adding support for this? |
Unfortunately I dont really have the time and experience ;-; |
This is just not helping the situation @ashe0047
I am actually, as I really like the idea that TSOA provides, and as I just moved all of our routes/controllers to 5.1.1 and v6 came out. |
Any update on it or for the workaround ReturnType? @boian-ivanov maybe you found something? |
Yeah, we should do something very similar to this, and maybe take that as a good reason to move the logic into a type inference based resolution for this and possibly other types that we resolve using the type checker: Basically, instead of jumping around in the AST, ask TS to give us the type and then use flags and helpers to get what we need. |
Same issue when using mongoose InferSchemaType<>
|
I had similar issues as reported by others and for tsoa 6.1.x zod.infer type does not work at all. After series of r&d here is my finding: For. folks who are trying to use zod or zod inferred types or similar solutions here is the workaround. Since Tsoa already uses decorators so, it would make sense to use |
Bump. Also tried ReturnType<> and not working on 6.1.x |
Hi, I would love for this to work, is there something I can do to help? |
I also ran into this issue when returning a response based on a zod schema. On v6.4.0 I seem to be able to workaround it if I move the type into a service layer. For example For example instead of // schema.ts
import { z } from "zod";
export const userSchema = z.object({
name: z.string(),
});
export type User = z.infer<typeof userSchema>; // testController.ts
import { Get, Controller, Route } from "tsoa";
import { User } from "./schema";
@Route("test")
export class TestController extends Controller {
@Get("/")
public getUser(): User {
return {
name: "test",
};
}
} Demo sandbox https://codesandbox.io/p/devbox/new-waterfall-3t9s9w?workspaceId=28b5e101-3593-4ce2-888e-ecba9c7ec239 run I have // schema.ts
import { z } from "zod";
export const userSchema = z.object({
name: z.string(),
});
export type User = z.infer<typeof userSchema>; // testService.ts
import { User } from "./schema";
export function getUser(): User {
return {
name: "test",
};
} // testController.ts
import { Get, Controller, Route } from "tsoa";
import { getUser } from "./testService";
@Route("test")
export class TestController extends Controller {
@Get("/")
public getUser() {
return getUser();
}
} Demo sandbox https://codesandbox.io/p/devbox/sharp-hooks-9lcgm7?workspaceId=28b5e101-3593-4ce2-888e-ecba9c7ec239 run |
@longzheng you aren't returning anything in your second example, so of course it works since tsoa isn't inferring anything |
Ah sorry that's a typo. I've updated the sandbox now and it still works. @Route("test")
export class TestController extends Controller {
@Get("/")
public getUser() {
return getUser();
}
} The generated "paths": {
"/test": {
"get": {
"operationId": "GetUser",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"type": "string"
}
},
"type": "object"
}
}
}
}
},
"security": [],
"parameters": []
}
}
}, I'm currently using this workaround in my own project extensively. |
Edit: this actually doesn't do much for me since all the validation is lost when inferring the type. I'm instead going to attempt using the zod-to-openapi library. I can confirm @longzheng's work around. For some controller endpoints, though, you need a model as a parameter. This is how I got around that: // models.ts
const orgSchema = z.object({
id: z.string(),
name: z.string(),
foundedYear: z.number(),
});
export type Org = z.infer<typeof orgSchema>;
// service.ts
import { Org } from './models.ts';
export class OrgService {
...
createOrganization(organization: Org): void {
// do create
}
}
// controller.ts
import { OrgService } from './service.ts'
// Use the service's param type instead of the model directly
type OrgParam = Parameters<typeof OrgService.prototype.createOrganization>[0];
@Route('orgs')
export class OrgController {
constructor(private orgService: OrgService) {}
@Post()
public async create(@Body() org: OrgParam): Promise<void> {
this.orgService.createOrganization(org);
}
} Not a great solution as you'd have to do that for every parameter you need for every service function. A full proper solution to this ticket would still be great. |
Many thanks @longzheng and @troncoso !!! Works perfectly |
I came up with an even easier workaround that doesn't require a separate service layer by using // schema.ts
import { z } from "zod";
export const userSchema = z.object({
name: z.string(),
});
export type User = z.infer<typeof userSchema>; // testController.ts
import { Get, Controller, Route } from "tsoa";
import { User } from "./schema";
@Route("test")
export class TestController extends Controller {
@Get("/")
public getUser(): User extends unknown ? User : never {
return {
name: "test",
};
}
} The output "paths": {
"/test": {
"get": {
"operationId": "GetUser",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"type": "string"
}
},
"type": "object"
}
} Demo sandbox https://codesandbox.io/p/devbox/intelligent-paper-2fch3m?workspaceId=28b5e101-3593-4ce2-888e-ecba9c7ec239 |
@kamit-transient you are saying that you were able to integrate class-validator and tsoa? Could you please share some details on how you did it? 🙂 |
(@WoH, @direisc, looping you in, in case you have more familiarity with this package's code than me.) Ok, I debugged the heck out of this issue, and came to the following conclusion: The error is thrown in the // ...
} else if (ts.isTypeQueryNode(arg)) {
const subTypeName = this.calcRefTypeName(arg.exprName); // <---
return `typeof ${subTypeName}`;
} else if (ts.isIndexedAccessTypeNode(arg)) {
// ... The issue seems to be, it calls All well and good, except.. the node isn't a typescript type. It's going to refer to some type of variable declaration or other, since that's what nodeIsUsable(node) {
switch (node.kind) {
case ts.SyntaxKind.InterfaceDeclaration:
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.TypeAliasDeclaration:
case ts.SyntaxKind.EnumDeclaration:
case ts.SyntaxKind.EnumMember:
return true;
default:
return false;
}
} ...when, in fact, the To see if I could get it working, I temporarily tried replacing With that said, I then hit another issue: Type export type SafeParseError<Input> = {
success: false;
error: ZodError<Input>;
data?: never;
};
export type SafeParseReturnType<Input, Output> = SafeParseSuccess<Output> | SafeParseError<Input>;
export declare abstract class ZodType< /* ---> */ Output /* <--- */ = any, Def extends ZodTypeDef = ZodTypeDef, Input = Output> {
readonly _type: Output;
readonly _output: Output;
readonly _input: Input;
readonly _def: Def;
get description(): string | undefined;
"~standard": StandardSchemaV1.Props<Input, Output>;
abstract _parse(input: ParseInput): ParseReturnType<Output>; I'm getting the impression that maybe TSOA doesn't play well with generics either, which causes this issue. To be specific, the AST node of the So, all in all, it appears that TSOA has at least two issues with Zod, but possibly more. Calling it quits for now, but if I get more time to investigate, I'll post back. Even still, the first issue at least seems like something reasonably fixable, especially since it doesn't ever make sense to try to resolve the operand of a typescript |
3 years and nothing... |
The only method I managed to make work, which in my case I had problems with data entries was like this:
|
3 years of you complaining no one did enough free work for you fast enough or? The naming issue sounds correct (not supported), the Generics problem sounds it should be added to, and resolved via, the context for these declarations. You either found a bug or there's something else going on. I think a viable path would be to resolve the type via Type Checker entirely under the name of the referencing type if available (type B = typeof a, then as B), or, possible create an unnamed type otherwise (object literal being the most likely, i.e. |
When using a Zod validator and then passing it to TSOA, it throws this error:
Error: No matching model found for referenced type infer.
Types File
Then use it in TSO
Sorting
I'm submitting a ...
I confirm that I
Expected Behavior
When running
yarn tsoa spec-and-routes
I expect TSOA to be able to use the inferred type generated by Zod.Current Behavior
It crashes with
Context (Environment)
Version of the library: "^3.14.1"
Version of NodeJS: v14.17.4
The text was updated successfully, but these errors were encountered: