Tsoa: feature: discriminator

Created on 29 Oct 2020  路  8Comments  路  Source: lukeautry/tsoa

Sorting

  • I'm submitting a ...

    • [ ] bug report
    • [x] feature request
    • [ ] support request
  • I confirm that I

    • [x] used the search to make sure that a similar issue hasn't already been submit

Description

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)
  }

Possible Solution

Maybe a decorator to define a custom pre-validation function where we will need to return the type name we need to validate against?

Pending feedback help wanted

All 8 comments

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:

  • When I define attributes: Array<ICreateAttribute | IAttribute> it only uses the ICreateAttribute and removes the ids.
  • When I define 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;
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

amcdnl picture amcdnl  路  6Comments

jfrconley picture jfrconley  路  3Comments

JimmyBjorklund picture JimmyBjorklund  路  5Comments

jeremyVignelles picture jeremyVignelles  路  3Comments

cedricboidin picture cedricboidin  路  5Comments