openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

Operation parameter default values should be generated in types

Open nadavhames opened this issue 1 year ago • 6 comments

Description

Lets say I have an openapi schema with an object property with a default value like this:

paths:
  /foo/bar:
    post:
      operationId: foo_bar
      parameters: []
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                anyOf:
                  - type: object
                    required:
                      - ok
                    properties:
                      ok:
                        type: boolean
                        default: true

I would expect that openapi-ts would generate approximately the following type definition containing the ok object property:

type FooBar = {
    ok: boolean = true;
}

but when I try and generate I get the following without true as the default for ok:

type FooBar = {
    ok: boolean;
}

Is there a way to achieve this with this library? Much appreciated!

OpenAPI 3.0 clarifies semantics of the default property keyword in section 4.7.25.1 of the spec:

The default value represents what would be assumed by the consumer of the input as the value of the schema if one is not provided. Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object defined at the same level. For example, if type is string, then default can be "foo" but cannot be 1.

Reproducible example or configuration

No response

OpenAPI specification (optional)

Steps to reproduce: Just add default to any property - it does not appear in the generated types.
I would expect that in the Error type for this schema, the type should generate to have property message: boolean = true;

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            maximum: 100
            format: int32
      responses:
        '200':
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:    
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Pets:
      type: array
      maxItems: 100
      items:
        $ref: "#/components/schemas/Pet"
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: boolean
          default: true

System information (optional)

No response

nadavhames avatar Aug 22 '24 01:08 nadavhames

@nadavhames What's the use case for this?

mrlubos avatar Aug 22 '24 07:08 mrlubos

@mrlubos this would be a big win as it would allow for type narrowing via Discriminated Unions. Let me give an example:

Say you have a union type like this: (Previously I incorrectly said ok: boolean = true; which is impossible to do in type declarations, my mistake)

type Foo = {
    ok: true;
    bla: string;
}

type Bar = {
    ok: false;
    baz: number;
}

type Union = Foo | Bar;

Instead of saying boolean or string like usual, we use a type literal for the ok property. The ok property needs to exist in all the types making up Union. TS then knows in our code if Union is Foo or Bar depending on ok - providing property completion and errors for irrelevant fields etc:

const abc: Union = getUnion();

if (abc.ok) {
  abc.bla is defined and completable
  abc.baz is not defined
} else {
  abc.bla is not defined
  abc.baz is defined and completable
}

This also works with string literals or any other type literal as you can see in Discriminated Unions.

This can be represented in openapi like this:

schema:
  anyOf:
    - type: object
      required:
        - ok
      properties:
        ok:
           type: boolean
           enum: 
               - true
        bla:
           type: string
    - type: object
      required:
        - ok
      properties:
        ok:
           type: boolean
           enum: 
               - false
        baz:
           type: number

(I think enum makes the most sense here to signal that we want to use a type literal instead of the regular type, but default: true/ default: false is also an option that may make sense for this. Maybe even type: true or type: false instead of type: boolean, but that might be confusing and not allowed)

There are many use cases for Discriminating Unions - I personally have a type Response = Success | BusinessError;. Success has all the data properties if the request is successful and BusinessError has some fields that only exist on some expected error cases. I check the ok field of response to narrow the types to either one so I can handle each case - it is very clean and convenient code.

nadavhames avatar Aug 22 '24 14:08 nadavhames

@nadavhames That makes sense! Although I'm not sure if I'd call it a bug, this was never a feature. I take it that you're using OpenAPI 3.1?

mrlubos avatar Aug 22 '24 15:08 mrlubos

@mrlubos yes in that case I agree that its a new feature or enhancement. Currently i'm on OpenAPI 3.0 because I am using https://typespec.io/ to generate my schema file

nadavhames avatar Aug 22 '24 15:08 nadavhames

I just figured out that it works with string literals: ok: "hello"; in typespec gets generated as

ok:
    type: string
    enum:
      - hello

in openapi and it gets generated by openapi-ts as: ok: 'hello'; Which works for my use case. I will go with this route for now!

nadavhames avatar Aug 22 '24 16:08 nadavhames

+1 this feature would be super useful for my use case. We have a successful api response type that I want success to always be the value true, but when openapi-ts generates the types it comes across as boolean. But like @nadavhames, I found it works as expected for string literals.

kylecarhart avatar Aug 30 '24 17:08 kylecarhart