orval-labs/orval

Fetch: generator does not seem to support multiple response types

Closed this issue · 5 comments

Description

If you use an openAPI schema with multiple responses like this:

  "/api/user": {
    "get": {
      "operationId": "get_user",
      "summary": "Get User",
      "parameters": [],
      "responses": {
        "200": {
          "description": "OK",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UserResponse" }
            }
          }
        },
        "400": {
          "description": "Bad Request",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ApiErrorResponse" }
            }
          }
        }

The generated fetch will only have the 200 response type (in this example UserResponse). It does not seem to generate a union type for the method return type.

I tried to work around this using customFetch so I can union with the alternative response type:
https://github.com/orval-labs/orval/blob/master/samples/next-app-with-fetch/custom-fetch.ts#L56

However, this wouldn't work as the result type is generated by Orval.

Some maybe loosely related issues: #420, #935

I wonder if fetch needs generateEachHttpStatus similar to mock and zod #1380

Hello @soartec-lab do you have any guidance on how to work on this issue? Currently it is hard to handle non-200 responses and errors in the generated code without this case.

@jamesleeht

There are no plans at the moment.
Does this mean that you want to include patterns other than 200 such as 404 in the data of responseType?

@soartec-lab pretty sure that's exactly what he means. I have similar case
There is a piece from my OpenAPI docs

 "/books/{id}": {
      "get": {
        "operationId": "BooksController_getBook",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Id",
            "schema": {
              "format": "uuid",
              "example": "00000000-0000-0000-0000-000000000000",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Book",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BookResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BookNotFoundError"
                    },
                    {
                      "$ref": "#/components/schemas/ValidationError"
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/UnknownError"
                    }
                  ]
                }
              }
            }
          }
        },
        "summary": "Get book by id",
        "tags": [
          "books"
        ]
      }
    },

I expect to get any of these responses from fetch, but it generates only success 200 response

export const booksControllerGetBook = async (id: string, options?: RequestInit): Promise<BookResponse> => {
  const res = await fetch(getBooksControllerGetBookUrl(id), { ...options, method: 'GET' });
  const data = await res.json();
  return data as BookResponse;
}

export interface BookResponse {
  author: string;
  description: string;
  discountPrice: BookResponseDiscountPrice;
  id: string;
  imagePath: string;
  name: string;
  price: number;
}

It would be great to add possibility to generate types like this by some config flag and use them instead of BookResponse

export type BooksControllerGetBookResponse = BookResponse | BookNotFoundError | ValidationError | UnknownError;
// or
export type BooksControllerGetBookResponse = BookResponse | BooksControllerGetBook400 | UnknownError;

Thanks. well, I think that makes sense.