I'm submitting a ...
I confirm that I
I'm using mongoose discriminators for my models, so I have 1 principal model and 3 children.
In my controllers, I have a PATCH method to update my models, in this PATCH method all properties are optionals and I can use this method to update any of my children models.
BUT, tsoa need to validate the right schema depending on which model I'm updating.
It will be nice if we can have a solution to declare a pre-validation function to tell tsoa which schema to validate against.
Currently, I'm updating the handlebars template to call my custom method (see below) in the patch route instead of validating schema:
public async validate (validate: (args: any, request: any, response: any) => any[], args: Args, req: express.Request, res: express.Response): Promise<any[]> {
const dt = await req.models.Datasource.findOne({ _id: new ObjectId(req.params.id) }, 'mode').exec()
if (!dt) {
throw new httpErrors.DatasourceNotFound({ id: req.params.id })
}
switch (dt.mode) {
case DatasourceMode.WORKER:
args.body.ref = 'PatchWorkerDt'
break
case DatasourceMode.INGESTER:
args.body.ref = 'PatchIngesterDt'
break
case DatasourceMode.PROXY:
args.body.ref = 'PatchProxyDt'
break
}
return validate(args, req, res)
}
Maybe a decorator to define a custom pre-validation function where we will need to return the type name we need to validate against?
The discriminator is also needed in the case of inheritance : https://swagger.io/specification/#composition-and-inheritance-polymorphism
Related: We should be able to tell which "children classes" we need to include in the generated schema. NSwag does that through the [KnownType(typeof(ChildType))]. It's not perfect (as you can't annotate external types), but it's better than nothing. See https://github.com/RicoSuter/NSwag/blob/e2e920fca79d1fda848bcd339908aa2b3d423dd4/src/NSwag.CodeGeneration.TypeScript.Tests/TypeScriptDiscriminatorTests.cs#L15-L17
Regarding discriminators etc, my understanding if that this would "Just Work 鈩笍 " if you use union types as accepted models in your controller.
Imagine you have :
interface Type1{
discriminator: 'type1';
propertyOfType1: string;
}
interface Type2{
discriminator: 'type2';
propertyOfType2: number;
}
interface Type3{
discriminator: 'type3';
propertyOfType3: string[];
}
export type ExposedType = Type1 | Type2 | Type3;
then implicitly the discriminator property would be used to distinguish between each type, bothen in the OpenApi spec and in typescript.
I might be missing something totally though ...
That would work with typescript, but if you're generating open api definitions to be consumed in any language, some generators might rely on the discriminator property to resolve the type really used.
Oh right, I didn't take this into account 馃憤
Didn't see this when I made my issue (#875). I'm happy to attempt to implement this, but I do need a bit of guidance as to where to start in the codebase.
Since TypeScript does this (discriminating based on properties) without explicitly specifying a discriminator, I'd find it helpful if you could work out a proposal as to how you would declare a discriminator.
Since both interfaces and type declarations don't exist at runtime, decorators aren't allowed on them. As a result, I think the only option would be jsdoc on the union type that specifies the name of the discriminator.
I have another case that this feature might solve. When I save a layer, it can have an existing and newly created attributes:
attributes: Array<ICreateAttribute | IAttribute> it only uses the ICreateAttribute and removes the ids.attributes: Array<IAttribute | ICreateAttribute> it only uses the IAttribute and I get validation errors, because of missing ids.@Post("/{id}")
public async save(@Path() id: number, @Body() body: ISaveLayer): Promise<ILayer> {
// body.attributes are ICreateAttribute[] (but could also be IAttribute)
// and the id's are always undefined - not correct
}
@Post("")
public async create(@Body() body: ICreateLayer): Promise<ILayer> {
// body.attributes are ICreateAttribute[] - correct
}
interface ICreateLayer {
name: string;
attributes: ICreateAttribute[];
}
interface ISaveLayer extends ICreateLayer {
id: number;
attributes: Array<ICreateAttribute | IAttribute>;
}
interface ILayer extends ISaveLayer {
attributes: IAttribute[];
}
interface ICreateAttribute {
name: string;
dataType: DataTypes;
geometryType?: GeometryType;
isSearchable?: boolean;
maxLength?: number;
minLength?: number;
position?: number;
}
interface IAttribute extends ICreateAttribute {
id: number;
saveDate: Date;
geometryType: GeometryType;
isSearchable: boolean;
maxLength: number;
minLength: number;
position: number;
}