Open cgibson-swyftx opened 2 years ago
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
I've labeled this to avoid the bot. Please vote instead of commenting, and feel free to open a PR to fix this.
@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) https://github.com/jquense/yup/issues/946#issuecomment-647051195
I've labeled this to avoid the bot. Please vote instead of commenting, and feel free to open a PR to fix this.
@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 wish I could easily help you out here, but given the things you infer
, the most likely answer is: Ask the type checker what that type is since you don't wanna work on the AST itself.
In code, maybe you can take a look at how we try to resolve Conditional Types, this will likely work similarly.
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.
export type UserParsed = ReturnType<typeof userSchema.parse>
it created the type, but unfortunately, the validation doesn't work, I assume it' relate to #1067
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.
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
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.
what would it be schemaValidation(...)?
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.
export declare abstract class ZodType<Output = any, Def extends ZodTypeDef = ZodTypeDef, Input = Output> {
and it appears that @tsoa/cli/src/metadataGeneration
can't handle the generics part (sort of).
I can replicate the similar error with
import { Get, Route } from 'tsoa';
@Route('/')
export class Controller {
@Get('/zodTest')
public async zodTest(): Promise<TheType> {
return '';
}
}
declare abstract class ObjBase<T = any> {
readonly _type: T;
}
class ObjWithString extends ObjBase<string> {}
type TheType = ObjWithString['_type'];
Note that
ObjWithString['_type']
TSOA fails.ObjWithString
it works.I noticed that TSOA's AST processing code does not know how to handle a <Output,
node, so the type T
does not get created/stored.
So later when trying to dive into ObjWithString['...']
you end up with No matching model found for referenced type T.
this was the solution for me: https://github.com/lukeautry/tsoa/issues/1256#issuecomment-1333814661 Thanks @direisc π
@WoH I know this may be inappropriate, but may I politely request you to take a look into this issue again?
This comment (https://github.com/lukeautry/tsoa/issues/1256#issuecomment-1414545885) seems like a good starting point.
Bump this, would like to see a solution that maintains validation or mention in the documentation the limitations of the framework.
export type UserParsed = ReturnType
For me, the equivalent approach in Yup does create the correct type.
But tsoa throws errors unless I use an interface with Queries()
, and so this approach seems to fail because it creates a type, not an interface. This seems to be a separate (apparently solved ?) issue, but still blocks this workaround in my case.
Some demos:
Fails with GenerateMetadataError: @Queries('params') only support 'refObject' or 'nestedObjectLiteral' types. If you want only one query parameter, please use the '@Query' decorator.
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;
}
}
Fails with GenerateMetadataError: @Queries('params') only support 'refObject' or 'nestedObjectLiteral' types. If you want only one query parameter, please use the '@Query' decorator.
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;
}
}
Works.
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?
i have same problem! Any solution?
This is my validation middleware for Yup but I guess with a small refactor you use it for zod as well
import { RequestHandler } from 'express';
import * as Yup from 'yup';
export function schemaValidation(schema: Yup.Schema<any>): RequestHandler {
return (req, res, next) => {
try {
schema.validateSync(req.body, { abortEarly: false });
next();
} catch (err: Yup.ValidationError | any) {
if (err instanceof Yup.ValidationError) {
log.error(`Caught Yup Validation Error for ${req.path}:`, err.errors);
return res.status(422).json({
message: 'Validation Failed',
details: err?.errors,
});
}
next();
}
};
}
export type UserParsed = ReturnType<typeof userSchema.parse>
With the newer version 6.0.0, now not even this workaround is valid π
Also tried the default z.infer<...>
approach and that still gives the same error
Any solution, its 2023 and this issue is still present. LOL
Are you interested in adding support for this?
Are you interested in adding support for this?
Unfortunately I dont really have the time and experience ;-;
Any solution, its 2023 and this issue is still present. LOL
This is just not helping the situation @ashe0047
Are you interested in adding support for this?
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.
@WoH Do you have any pointers where the issue might be lying? As the interesting thing is that with the ReturnType<...>
fix things were compiling properly on v5.1.1, so with v6 something has changed in that area π€
Can't promise that I can fix it, but I'll definitely have a look
Any update on it or for the workaround ReturnType? @boian-ivanov maybe you found something?
Any solution, its 2023 and this issue is still present. LOL
This is just not helping the situation @ashe0047
Are you interested in adding support for this?
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. @WoH Do you have any pointers where the issue might be lying? As the interesting thing is that with the
ReturnType<...>
fix things were compiling properly on v5.1.1, so with v6 something has changed in that area π€ Can't promise that I can fix it, but I'll definitely have a look
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<>
Generate routes error.
GenerateMetadataError: No matching model found for referenced type settingsSchema.
at TypeResolver.getModelTypeDeclarations (/app/node_modules/@tsoa/cli/dist/metadataGeneration/typeResolver.js:1135:19)
...
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 class-validator
and class-transformer
combination to replace Zod completely. And this is what finally working for me.
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 npm run build
in terminal
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 npm run build
in terminal
@longzheng you aren't returning anything in your second example, so of course it works since tsoa isn't inferring anything
@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 swagger.json
has
"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 extends
.
// 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 swagger.json
is
"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
Same story with any other types from inference, for example Drizzle's InferSelectModel
.
export const profiles = pgTable(
'profiles',
{
id: integer().primaryKey().generatedAlwaysAsIdentity(),
headline: varchar('headline', { length: 255 }).notNull(),
firstName: varchar('first_name', { length: 100 }).notNull(),
},
);
export type Profile = InferSelectModel<typeof profiles>;
public async createProfile(): Promise<Profile> {}
This would throw [1] There was a problem resolving type of 'Profile'. [1] Generate routes error. [1] GenerateMetadataError: No matching model found for referenced type profiles.
error.
However, if I update the code to
export type ProfileInference = InferSelectModel<typeof profiles>;
export type Profile = Required<ProfileInference>;
Tsoa will generate 2 schemas:
Required_ProfileInference_
Profile
Or tried with Omit
, Tsoa generated
Pick_ProfileInference.Exclude_keyofProfileInference.id__
Omit_ProfileInference.id_
Profile
Then I tested it with Zod with the hope that it will work
const profileSchema2 = z.object({
id: z.number(),
headline: z.string(),
summary: z.string(),
firstName: z.string(),
lastName: z.string(),
profilePicture: z.string(),
userId: z.number()
});
// export type Profile = Omit<ProfileInference, 'id'>;
export type ProfileTest = z.infer<typeof profileSchema2>;
export type Profile = Omit<ProfileTest, 'id'>;
but it didn't work correctly, although console throw no errors
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