Code Monkey home page Code Monkey logo

openapi-typescript-codegen's Introduction

Important announcement

Important

Please migrate your projects to use @hey-api/openapi-ts

Due to time limitations on my end, this project has been unmaintained for a while now. The @hey-api/openapi-ts project started as a fork with the goal to resolve the most pressing issues. going forward they are planning to maintain the OpenAPI generator and give it the love it deserves. Please support them with their work and make sure to migrate your projects: https://heyapi.vercel.app/openapi-ts/migrating.html

  • All open PR's and issues will be archived on the 1st of May 2024
  • All versions of this package will be deprecated in NPM

๐Ÿ‘‹ Thanks for all the support, downloads and love! Cheers Ferdi.


OpenAPI Typescript Codegen

NPM License Downloads Build

Node.js library that generates Typescript clients based on the OpenAPI specification.

Why?

  • Frontend โค๏ธ OpenAPI, but we do not want to use JAVA codegen in our builds
  • Quick, lightweight, robust and framework-agnostic ๐Ÿš€
  • Supports generation of TypeScript clients
  • Supports generations of Fetch, Node-Fetch, Axios, Angular and XHR http clients
  • Supports OpenAPI specification v2.0 and v3.0
  • Supports JSON and YAML files for input
  • Supports generation through CLI, Node.js and NPX
  • Supports tsc and @babel/plugin-transform-typescript
  • Supports aborting of requests (cancelable promise pattern)
  • Supports external references using json-schema-ref-parser

Install

npm install openapi-typescript-codegen --save-dev

Usage

$ openapi --help

  Usage: openapi [options]

  Options:
    -V, --version             output the version number
    -i, --input <value>       OpenAPI specification, can be a path, url or string content (required)
    -o, --output <value>      Output directory (required)
    -c, --client <value>      HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch")
    --name <value>            Custom client class name
    --useOptions              Use options instead of arguments
    --useUnionTypes           Use union types instead of enums
    --exportCore <value>      Write core files to disk (default: true)
    --exportServices <value>  Write services to disk (default: true)
    --exportModels <value>    Write models to disk (default: true)
    --exportSchemas <value>   Write schemas to disk (default: false)
    --indent <value>          Indentation options [4, 2, tab] (default: "4")
    --postfixServices         Service name postfix (default: "Service")
    --postfixModels           Model name postfix
    --request <value>         Path to custom request file
    -h, --help                display help for command

  Examples
    $ openapi --input ./spec.json --output ./generated
    $ openapi --input ./spec.json --output ./generated --client xhr

Documentation

The main documentation can be found in the openapi-typescript-codegen/wiki

Sponsors

If you or your company use the OpenAPI Typescript Codegen, please consider supporting me. By sponsoring I can free up time to give this project some love! Details can be found here: https://github.com/sponsors/ferdikoomen

If you're from an enterprise looking for a fully managed SDK generation, please consider our sponsor:

speakeasy

openapi-typescript-codegen's People

Contributors

budde377 avatar dbo avatar dependabot-preview[bot] avatar dependabot[bot] avatar edwinveldhuizen avatar ferdikoomen avatar jurgenbelien avatar kraenhansen avatar krokettenkoal avatar littleumbrella avatar markbrockhoff avatar mb21 avatar mlaps-gafe avatar mrtnvh avatar nandorojo avatar nikopavlica avatar npwork avatar or-zarchi-forter avatar pitmulleringka avatar qqilihq avatar raman-savitski-exadel avatar ronaldvdh-isaac avatar rossille avatar seivan avatar sheshnathverma avatar tajnymag avatar todesstoss avatar troglotit avatar vitalybaev avatar vyobukhov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openapi-typescript-codegen's Issues

Wrong code generated when using model named `File`

I have a model named File and another named FilePagingResponse,here's the generated code:

/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */

/**
 * Abstract base class for generic types.
 * A generic type is typically declared by inheriting from
 * this class parameterized with one or more type variables.
 * For example, a generic mapping type might be defined as::
 * class Mapping(Generic[KT, VT]):
 * def __getitem__(self, key: KT) -> VT:
 * ...
 * # Etc.
 * This class can then be used as follows::
 * def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
 * try:
 * return mapping[key]
 * except KeyError:
 * return default
 */
export interface FilePagingResponse {
  items: Array<File>
  total: number
  has_more?: boolean
}

So it's just using the builtin File,which is not my File model. And other code is OK.So, there must be something wrong in here.

Generating Enums inside of namespaces doesn't work, alternative method possible?

Here is a hypothetical generated typescript generated code from your tool.

export interface Response {
    name: string;
    type: Response.type;
}

export namespace Response {
  export enum type {
    ERROR = 'ERROR',
    SUCCESS = 'SUCCESS',
  }
}

We realize this a known issue with Babel 7. However if the enum was generated this way then it works great with Babel 7.

export interface Response {
    name: string;
    type: ResponseType;
}

export enum ResponseType {
  ERROR = 'ERROR',
  SUCCESS = 'SUCCESS',
}

Is there a reason why you chose to generate the code using the former method. Is it possible to add a flag to generate enums in the proposed style. The difference is the enum would append Type to the name of the interface.

Thanks, Louis

Custom `requestUsingXX` function

Supports using custom requestUsingXX function besides requestUsingXHR and requestUsingFetch.So users can using their custom network stack, for example there is a sort of plaform called 'mini program' in China, which is basically browers-based, but has it's own APIs eg. request.
It's easy to implement:

export let CLIENT = '{{{httpClient}}}';

Could be refactored to:

import { Result } from './Result'
// ...
export let CLIENT:
 | string
 | ((url: string, request: Readonly<RequestInit>) => Promise<Result>) 
 = '{{{httpClient}}}'

And in here add more conditions

try {
switch (OpenAPI.CLIENT) {
case 'xhr':
return await requestUsingXHR(url, request);
default:
return await requestUsingFetch(url, request);
}

Put all generated function's parameters into one object

Here's the problem I've met.Because JS/TS hasn't named parameter.The normal solution is

interface Names {
    name1: boolean
    name2: boolean
    name3: boolean
    name4: boolean
}

function myFunction({name1, name2, name3, name4}: Names) {
    // name1, etc. are boolean
}

And currrently the generated code is like

function myFunction(name1:boolean, name2:boolean, name3:boolean, name4:boolean) {
    // name1, etc. are boolean
}

It's frustrating that if I want to pass just one parameter which is in the rear,I have to write a bunch of undefined before it.

So if we use the former way,the problom will be solved.And we have named parameter passing(by the object literal).And the codegen don't need to sort them by required,maybe could in alphabetical order?Or we just put optional parameters into a object.

Anyway the former way seems more idiomatic,so I hope you can change to it.Honestly your work is very great,the official ts-codegen sucks ๐Ÿ˜‚.Thanks for your wonderful work.

[Feature] Support enumNames-like setting to generate enum with custom names

There's basically tow options.The first is use another field likeenumNames:string[] in OpenApiSchema to generate enums with custom names.
Schema like this:

"state": {
    "title": "State",
    "enum": [
        0,
        1,
        2
    ],
    "type": "integer",
    "default": 0,
    "enumNames": ["initial", "working", "finished"]
}

Generated code:

enum state {
  INITIAL = 0,
  WORKING = 1,
  FINISHED = 2
}

But enumNames is not a valid openapi schema field.
Another option is expanding the getEnumFromDescription.

Smart naming when generating services

Hi,

I'm consuming an API that has this path: /api/v1/management/organizations

This generates a OrganizationsService, which is fine, but there is no mention of management, or even v1 anywhere.

If we assume that the service name should be whatever comes after /api We could get V1ManagementOrganizationsService

It's horrible, but it works.

Another option is to generate those services in folders following the path, so:

/core
/models
/services
   /v1
       /management
            OrganizationsService.ts

v1 and management don't have anything so we could create just a placeholder object to have the reference to the children:

export class V1{
   public static Management: Management = Management
}

export class Management{
   public static Organizations: OrganizationsService = OrganizationsService 
}

//we have this already
export class OrganizationsService {
}

Following the previous snippet, I could now just V1.Management.Organizations.getAll()

I'm doing this currently by hand, and would be nice to automate it :)

Cheers

Feature request: Option to change how cookies are sent with requests

To allow us to use cookie based authentication and make the browser pass cookies to the server on each request we need to tell fetch to allow it. This is done by setting credentials to "same-origin" or "include" when doing requests. Right now this is hardcoded to "same-origin" which only works if the client and API has the same origin.

I would like to set it to "include" by passing some option so I can have the client and API on different origins and still use cookie authentication.

Generated barrelfile incomplete

When I generate using these options:

exportCore: false, exportModels: true, exportSchemas: true, exportServices: false,

Then the barrel file has the following problems:

  • it contains a reference to a service, which is explicitly not generated
  • it contains no references to the models

string is generated as an interface

Describe the bug
For some reason the following interface is generated when the command is run against the spec

/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */


export interface string {
}

Use union types instead of enums

Description

Instead of generating enum types I would like to have an option to generate union types instead.
If you like the idea I am very happy to implement this and create a PR.

Example

YAML model:

    [...]
    schemas:
        Rating:
            type: object
            properties:
                id:
                    type: string
                value:
                    type: string
                    enum:
                        - negative
                        - neutral
                        - positive

Current behaviour

Generated model

export interface Rating {
    id?: string;
    value?: Rating.value;
}

export namespace Rating {
    export enum value {
        NEGATIVE = 'negative',
        NEUTRAL = 'neutral',
        POSITIVE = 'positive',
    }
}

Usage

If I want to use this in my code I have to do this:

const exampleRating: Rating = {
  id: '364363313',
  value: Rating.value.NEUTRAL,
}

Proposed change

Generated model

export interface Rating {
    id?: string;
    value?: 'positive' | 'neutral'  | 'negative'; // no enum anymore, instead we use union types
}

Usage

If I want to use this in my code I can do it much simpler now:

const exampleRating: Rating = {
  id: '364363313',
  value: 'neutral', // much simpler in my opinion, TS would still complain if I would use something that is not allowed
}

Explanation

Of course this would be behind a feature flag. At the beginning I thought useUnionTypes is doing that, but it is not, so I have to find another name for my feature. I thought about useUnionTypesForEnums, but I'm open for better suggestions.

[Bug] Wrong code generated in options mode

A path takes 2 parameters a and b, b is not required and has a default value.
Generated code by now:

function add({ a, b = 1 }: { a: number; b: number }): number {
  return a + b
}

Which is making b's default value useless because we must give it a value to pass the type check.
Correct code should be:

function add({ a, b = 1 }: { a: number; b?: number }): number {
  return a + b
}

It has been tested, if call add like add({a:1}), b will set as it's default value.

ESLint Parsing error: Declaration or statement expected

I'm getting multiple linting errors from the script created by running

npx openapi-typescript-codegen -i http://localhost:8080/openapi.json -o ./src/api

The first error I get is

TypeScript error in ./src/api/core/ApiError.ts(4,13):
'=' expected.  TS1005

    2 | /* tslint:disable */
    3 | /* eslint-disable */
  > 4 | import type { ApiResult } from './ApiResult';
      |             ^
    5 | 
    6 | export class ApiError extends Error {
    7 |     public readonly url: string;

I'm also getting Parsing error: Declaration or statement expected in VSCode when looking in ./src/api/index.ts on the export type statements. I'm clueless whats actually the issue here?

Support export RequestOptions

Describe the solution you'd like
A clear and concise description of what you want to happen. Ideally with a small example of the proposed changes.
ๅธŒๆœ›ๅขžๅŠ ่พ“ๅ‡บ RequestOptions, ๆฏ”ๅฆ‚๏ผš
export const AppRequestOptions = {
url: '/api/v1/getApps',
}

่ฟ™ๆ ทๅฏไปฅไฝฟ็”จ่ฟ™ไธชๆ•ฐๆฎ๏ผŒ่‡ชๅทฑ้€‰ๆ‹ฉ ajax client ๅฐ่ฃ…requestๅฎž็Žฐ

Feature request: Allow urls in input parameter

It would be very useful if you could specify an url as input when generating the client instead of just a local file. I always use an automatically generated OpenAPI spec together with Swagger UI and pointing it to the http served specification file is very natural.

Sure you can easily download it and generate a new client but I would prefer to just point it to the url of our test enviroment and fire away everytime we need to update the client.

Thanks for an awesome tool btw!

Apparently no way of disabling `exportCore` and `exportServices` in command line

I'm tried to use openapi-typescript-codegen from command line to generate only the models as I want to use a custom implementation for the requests themselves. So, I don't need core or services, just models.

However, passing --exportCore false or --exportCore 0 or similar doesn't seem to work. It will still generate all files.

Expected Behavior
The following command should only generate models but no core or services code:

npx openapi-typescript-codegen \
  --input https://example.com/swagger.json \
  --exportCore false --exportServices false --exportModules true

Support Axios as client

Describe the solution you'd like
Currently, only XHR and Fetch are supported as clients. What would be required to add Axios as the request manager?

OpenAPI v3: Form parameters not parsed from requestBody

One breaking change of OpenAPI 3.0 is that form and body parameters are both described using requestBody instead of using the old parameter with formdata as in value. See https://swagger.io/docs/specification/describing-request-body/ and https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject

Unfortunately this doesn't work with this library since it still uses the old way 2.0 of looking for form data among the parameter list. See:


So generated clients just passes the data in the body.

Instead, it should take form parameters from request body if it uses the content type multipart/form-data or application/x-www-form-urlencoded.

Here are two examples where the OpenAPI spec describes form data that does not work:
Binary form content under the "file" name:

"requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "nullable": true
                  }
                }
              },
              "encoding": {
                "file": {
                  "style": "form"
                }
              }
            }
          }
        }

(This is from my own swagger.json and the reason I write this ๐Ÿ˜„ )

A pet form with two string string values under the names "name" and "status":

"requestBody": {
    "content": {
      "application/x-www-form-urlencoded": {
        "schema": {
          "type": "object",
           "properties": {
              "name": { 
                "description": "Updated name of the pet",
                "type": "string"
              },
              "status": {
                "description": "Updated status of the pet",
                "type": "string"
             }
           },
        "required": ["status"] 
        }
      }
    }

(Taken directly from the OpenAPI 3.0 spec file: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject)

I think my conclusions above are correct but you have way better understanding of the OpenAPI spec and this library than I do so don't take it for a fact ๐Ÿ˜„

Type "integer" default value not converted properly

Expect:

"/api": {
            "get": {
                ...
                "parameters": [
                    {
                        "name": "ProcessFlag",
                        "in": "query",
                        "description": "Format - int32. Identifies added processing:  0 = no additional, 1 = run verification first",
                        "type": "integer",
                        "default": "0"
                    }
                ],

To be converted to:

public static async getApi(
    processFlag: number = 0,
  )

But in result:

public static async getApi(
    processFlag: number = '0',
  )

I think, JSON.parse(default) can resolve this

[Bug] requestBody in parameters isn't sorted by required

if (op.requestBody) {
const requestBody = getOperationRequestBody(openApi, op.requestBody);
operation.imports.push(...requestBody.imports);
operation.parameters.push(requestBody);
operation.parametersBody = requestBody;
}

The requestBody is always the last of parameters.If there's any optional parameter in front of it,there will be an TS error 1016: A required parameter cannot follow an optional parameter.
My simple solution is

operation.parameters.push(requestBody);
operation.parameters = operation.parameters.sort(sortByRequired);

Add custom headers

Is it possible to add a way to add custom headers?

Basically a dictionary that custom headers can be added to (in the config file), and just traverse and add them in the request? It is not uncommon for applications to provide additional headers.

Im happy to provide a pull-request if that makes it easier for you:)

Using `required` in a parent for a ref doesn't make it required

Given the yaml OpenAPI 3.0:

SomeAbstractDto:
    properties:
      name:
        type: string
      age:
        type: number

SomeDto:
      required:
        - name
      allOf:
        - $ref: '#/components/schemas/SomeAbstractDto'

This generates:

interface SomeAbstractDto {
  name?: string;
  age?: number;
}

interface SomeDto extends SomeAbstractDto {}

Expected is:

interface SomeAbstractDto {
  name?: string;
  age?: number;
}

interface SomeDto extends SomeAbstractDto {
  name: string;
}

I hope this can be resolved!

path parameter in the service is not created, only referenced

Describe the bug

Following method is generated as part of the Service.ts for an endpoint defined like this: /manage/applications/{applicationId}/addons/{addonSystemKey}. There are two path parameters. The snippet references variables but such variables are not present in the method signature.

public static async installAddonCommand(
        requestBody: InstallAddonCommand,
    ): Promise<AddonInstanceValue> {

        const result = await __request({
            method: 'post',
            path: `/manage/applications/${applicationId}/addons/${addonKey}`,
            body: requestBody,
        });

Generated project fails to build in create-react-app project

Experienced behavior

I generated types definitions with CLI openapi --input ./api_spec.yaml --output ./openapi, but it fails to compile in unejected react app created with create-react-app CLI.

There are 2 problems:

Compilation error due to :

C:/xxx/openapi/index.ts
TypeScript error in C:/xxx/openapi/index.ts(10,10):
Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'.  TS1205
Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'

     8 | export { OpenAPI } from './core/OpenAPI';
     9 |
  > 10 | export { MyModel } from './models/MyModel';

After commenting out reexported modules I encountered second problem:

Failed to compile.

./openapi/core/ApiError.ts
SyntaxError: C:\xxx\openapi\core\ApiError.ts: Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel. To enable and review caveats see: https://babeljs.io/docs/en/babel-plugin-transform-typescript
  24 | }
  25 |
> 26 | export namespace ApiError {
     |                  ^^^^^^^^
  27 |     export enum Message {
  28 |         BAD_REQUEST = 'Bad Request',
  29 |         UNAUTHORIZED = 'Unauthorized',

Expected behavior

Generated code compiles properly without warnings.

[Bug] the required and nullable field in parameter object is overrided by the schema field

operationParameter.isRequired = model.default || model.isRequired;

If a operation parameter object like this(in test/sepc.json)

{
    "description": "This is the parameter that goes into the request header",
    "name": "parameterHeader",
    "in": "header",
    "required": true,
    "nullable": true,
    "schema": {
        "type": "string"
    }
}

The required and nullable are true.But the isRequired and isNullable will be false after executing the code above.Which is wrong in my opinion.
I think the code should be like:

 operationParameter.isRequired =  operationParameter.isRequired || model.default || model.isRequired; 

Lowercase PATCH methods are not fetch spec conform

We're having an issue with PATCH requests in our application using the generated API functions. The request just fails with a net::ERR_HTTP2_PROTOCOL_ERROR error message.

After digging around a bit I found out that methods are defined as all-lowercase (probably as they're coming from openapi spec?), which works for all other methods thanks to normalization, but patch is exempt as per the fetch spec. See here: https://fetch.spec.whatwg.org/#concept-method-normalize

Proposed fix

Update method definitions to be all-uppercase

OpenAPIV3 - Description not emitted for model properties defined via $ref

Thank you for a great bit of work!

In the case where the property of a model is defined directly:

"properties": {
                    "active": {
                        "description": "Active",
                        "type": "boolean"
                    },
}

The emitted code for the model includes the description as a comment:

    /**
     * Active
     */
    active?: boolean;

However, when the property is defined via $ref, no comment is emitted:

"properties": {
  "updated": {
                        "$ref": "#/components/schemas/dateTime"
                    },

where dateTime is defined:

    "components": {
        "schemas": {
            "dateTime": {
                "description": "Accept RFC3339, RFC3339_EXTENDED or timestamp - Defaults to RFC3339_EXTENDED",
                "type": "string",
                "format": "date-time"
            },
   }

The emitted code is:

    updated?: dateTime;

I think I would expect the description from the references schema to appear in the output.

[0.5.0-beta] "File" model name turns into "any"

I just tried the 0.5.0 beta and came across a generation bug where the File model name is turned into any. Typescript will complain about Interface name cannot be 'any'

Here's a minimum spec to reproduce the behavior:

swagger: "2.0"
info:
  version: v1
definitions:
  File:
    type: object

Generated code does not compile

Describe the bug
The templates reference types that are not imported which results in typescript complaining when using the generated code.

{
  "scripts": "ts-node src/client/http/index.ts"
}

Screen Shot 2020-08-11 at 3 51 24 PM

You can see this in the code today: https://github.com/ferdikoomen/openapi-typescript-codegen/blob/master/src/templates/core/request.ts#L26 (this uses Headers but does not import it).

What is expected

I figure that this error would happen to anyone using bare tsc to compile, so I assume there must be something I'm missing. Perhaps a "User Guide" section in the README.md would suffice to explain the expectations of the compile-time environment. (I'm guessing that this would work just fine if I were using Babel 7 -- and potentially webpack or rollup).

If it is that the library doesn't support tsc, then perhaps we can move this to a feature request to support a simple tsc backed use-case.

Notes
I was able to reproduce a compiled version following your test/index.js approach -- which successfully compiled the generated code in my repo: https://github.com/ferdikoomen/openapi-typescript-codegen/blob/master/test/index.js
I'm not clean on why using this particular configuration of tsc would work while my version would not.

This still only gets one step further -- at runtime this still fails.

This time with an error when constructing the new Header in request.js -- which gets called when you do MyGeneratedService.staticMethodName(body):

 ReferenceError: Headers is not defined

I have included my tsconfig.json here

{
	"compileOnSave": true,
	"compilerOptions": {
		"rootDir": ".",
		"types": ["node"],
		"strict": true,
		"sourceMap": true,
		"declaration": false,
		"esModuleInterop": true,
		"allowSyntheticDefaultImports": true,
		"moduleResolution": "node",
		"importHelpers": true,
		"target": "es6",
		"module": "commonjs",
		"typeRoots": ["node_modules/@types"],
		"lib": ["es2020", "es6"],
		"skipLibCheck": true,
		"skipDefaultLibCheck": true,
		"outDir": "dist",
		"baseUrl": ".",
		"noImplicitAny": false,
	},
	"exclude": ["node_modules", "tmp", "dist"],
	"include": ["src/**/*.ts"],
}

Support anyOf in response

Right now we don't support anyOf in the response (i think):

"responses": {
	"200": {
		"description": "The request was succesfull",
		"schema": {
		"anyOf": [
			{ "$ref": "#/definitions/Dog" },
			{ "$ref": "#/definitions/Cat" },
			{ "$ref": "#/definitions/Animal" }
		]
	}
}

OpenAPI3: Support Reusable Request Bodies

Thank you team for the great work!

OpenAPI 3.0 supports reusable request bodies as shown in the below example. Right now the generator does not support this.

paths:
  /pets:
    post:
      summary: Add a new pet
      requestBody:
        $ref: '#/components/requestBodies/PetBody'
  /pets/{petId}
    put:
      summary: Update a pet
      parameters: [ ... ]
      requestBody:
        $ref: '#/components/requestBodies/PetBody'
components:
  requestBodies:
    PetBody:
      description: A JSON object containing pet information
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Pet'

https://swagger.io/docs/specification/describing-request-body/

Creating services includes imports not in use

import { MessageDetailsDto } from '../models/MessageDetailsDto';
import { ApiError, catchGenericError } from '../core/ApiError';
import { request as __request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';

export class MessageService {

ApiError and OpenAPI is not being used in the generated service, and causes a TS6133 error when building it.

[Bug] Operation path generation

return path.replace(/{api-version}/g, '{OpenAPI.VERSION}').replace(/\{(.*?)\}/g, '${$1}');

If there's a url like /users/{user_id}/,the parameter name in generated code will be userId,but in path it still is user_id.Which is a bug.
Here's my fix:

import camelCase from 'camelcase';
/**
 * Get the final service path, this replaces the "{api-version}" placeholder
 * with a new template string placeholder so we can dynamically inject the
 * OpenAPI version without the need to hardcode this in the URL.
 * @param path
 */
export function getOperationPath(path: string): string {
    return path.replace(/{api-version}/g, '{OpenAPI.VERSION}').replace(/\{(.*?)\}/g, (_, w) => `\$\{${camelCase(w)}\}`);
}

Broken enum

Describe the bug
Such an enum creates broken typescript

"ResponseEntity": {
      "type": "object",
      "properties": {
        "body": { "type": "object" },
        "statusCode": {
          "type": "string",
          "enum": [
            "100 CONTINUE",
            "101 SWITCHING_PROTOCOLS",
            "102 PROCESSING",
            "103 CHECKPOINT",
            "200 OK",
            "201 CREATED",
            "202 ACCEPTED",
            "203 NON_AUTHORITATIVE_INFORMATION",
            "204 NO_CONTENT",
            "205 RESET_CONTENT",
            "206 PARTIAL_CONTENT",
            "207 MULTI_STATUS",
            "208 ALREADY_REPORTED",
            "226 IM_USED",
            "300 MULTIPLE_CHOICES",
            "301 MOVED_PERMANENTLY",
            "302 FOUND",
            "302 MOVED_TEMPORARILY",
            "303 SEE_OTHER",
            "304 NOT_MODIFIED",
            "305 USE_PROXY",
            "307 TEMPORARY_REDIRECT",
            "308 PERMANENT_REDIRECT",
            "400 BAD_REQUEST",
            "401 UNAUTHORIZED",
            "402 PAYMENT_REQUIRED",
            "403 FORBIDDEN",
            "404 NOT_FOUND",
            "405 METHOD_NOT_ALLOWED",
            "406 NOT_ACCEPTABLE",
            "407 PROXY_AUTHENTICATION_REQUIRED",
            "408 REQUEST_TIMEOUT",
            "409 CONFLICT",
            "410 GONE",
            "411 LENGTH_REQUIRED",
            "412 PRECONDITION_FAILED",
            "413 PAYLOAD_TOO_LARGE",
            "413 REQUEST_ENTITY_TOO_LARGE",
            "414 URI_TOO_LONG",
            "414 REQUEST_URI_TOO_LONG",
            "415 UNSUPPORTED_MEDIA_TYPE",
            "416 REQUESTED_RANGE_NOT_SATISFIABLE",
            "417 EXPECTATION_FAILED",
            "418 I_AM_A_TEAPOT",
            "419 INSUFFICIENT_SPACE_ON_RESOURCE",
            "420 METHOD_FAILURE",
            "421 DESTINATION_LOCKED",
            "422 UNPROCESSABLE_ENTITY",
            "423 LOCKED",
            "424 FAILED_DEPENDENCY",
            "426 UPGRADE_REQUIRED",
            "428 PRECONDITION_REQUIRED",
            "429 TOO_MANY_REQUESTS",
            "431 REQUEST_HEADER_FIELDS_TOO_LARGE",
            "451 UNAVAILABLE_FOR_LEGAL_REASONS",
            "500 INTERNAL_SERVER_ERROR",
            "501 NOT_IMPLEMENTED",
            "502 BAD_GATEWAY",
            "503 SERVICE_UNAVAILABLE",
            "504 GATEWAY_TIMEOUT",
            "505 HTTP_VERSION_NOT_SUPPORTED",
            "506 VARIANT_ALSO_NEGOTIATES",
            "507 INSUFFICIENT_STORAGE",
            "508 LOOP_DETECTED",
            "509 BANDWIDTH_LIMIT_EXCEEDED",
            "510 NOT_EXTENDED",
            "511 NETWORK_AUTHENTICATION_REQUIRED"
          ]
        },
        "statusCodeValue": { "type": "integer", "format": "int32" }
      },
      "title": "ResponseEntity"
    },

Results in this TypeScript

export enum statusCode {
        100 CONTINUE = '100 CONTINUE',
        101 SWITCHING_PROTOCOLS = '101 SWITCHING_PROTOCOLS',
        102 PROCESSING = '102 PROCESSING',
        103 CHECKPOINT = '103 CHECKPOINT',
        200 OK = '200 OK',
        201 CREATED = '201 CREATED',
        202 ACCEPTED = '202 ACCEPTED',
        203 NON_AUTHORITATIVE_INFORMATION = '203 NON_AUTHORITATIVE_INFORMATION',
        204 NO_CONTENT = '204 NO_CONTENT',
        205 RESET_CONTENT = '205 RESET_CONTENT',
        206 PARTIAL_CONTENT = '206 PARTIAL_CONTENT',
        207 MULTI_STATUS = '207 MULTI_STATUS',
        208 ALREADY_REPORTED = '208 ALREADY_REPORTED',
        226 IM_USED = '226 IM_USED',
        300 MULTIPLE_CHOICES = '300 MULTIPLE_CHOICES',
        301 MOVED_PERMANENTLY = '301 MOVED_PERMANENTLY',
        302 FOUND = '302 FOUND',
        302 MOVED_TEMPORARILY = '302 MOVED_TEMPORARILY',
        303 SEE_OTHER = '303 SEE_OTHER',
        304 NOT_MODIFIED = '304 NOT_MODIFIED',
        305 USE_PROXY = '305 USE_PROXY',
        307 TEMPORARY_REDIRECT = '307 TEMPORARY_REDIRECT',
        308 PERMANENT_REDIRECT = '308 PERMANENT_REDIRECT',
        400 BAD_REQUEST = '400 BAD_REQUEST',
        401 UNAUTHORIZED = '401 UNAUTHORIZED',
        402 PAYMENT_REQUIRED = '402 PAYMENT_REQUIRED',
        403 FORBIDDEN = '403 FORBIDDEN',
        404 NOT_FOUND = '404 NOT_FOUND',
        405 METHOD_NOT_ALLOWED = '405 METHOD_NOT_ALLOWED',
        406 NOT_ACCEPTABLE = '406 NOT_ACCEPTABLE',
        407 PROXY_AUTHENTICATION_REQUIRED = '407 PROXY_AUTHENTICATION_REQUIRED',
        408 REQUEST_TIMEOUT = '408 REQUEST_TIMEOUT',
        409 CONFLICT = '409 CONFLICT',
        410 GONE = '410 GONE',
        411 LENGTH_REQUIRED = '411 LENGTH_REQUIRED',
        412 PRECONDITION_FAILED = '412 PRECONDITION_FAILED',
        413 PAYLOAD_TOO_LARGE = '413 PAYLOAD_TOO_LARGE',
        413 REQUEST_ENTITY_TOO_LARGE = '413 REQUEST_ENTITY_TOO_LARGE',
        414 URI_TOO_LONG = '414 URI_TOO_LONG',
        414 REQUEST_URI_TOO_LONG = '414 REQUEST_URI_TOO_LONG',
        415 UNSUPPORTED_MEDIA_TYPE = '415 UNSUPPORTED_MEDIA_TYPE',
        416 REQUESTED_RANGE_NOT_SATISFIABLE = '416 REQUESTED_RANGE_NOT_SATISFIABLE',
        417 EXPECTATION_FAILED = '417 EXPECTATION_FAILED',
        418 I_AM_A_TEAPOT = '418 I_AM_A_TEAPOT',
        419 INSUFFICIENT_SPACE_ON_RESOURCE = '419 INSUFFICIENT_SPACE_ON_RESOURCE',
        420 METHOD_FAILURE = '420 METHOD_FAILURE',
        421 DESTINATION_LOCKED = '421 DESTINATION_LOCKED',
        422 UNPROCESSABLE_ENTITY = '422 UNPROCESSABLE_ENTITY',
        423 LOCKED = '423 LOCKED',
        424 FAILED_DEPENDENCY = '424 FAILED_DEPENDENCY',
        426 UPGRADE_REQUIRED = '426 UPGRADE_REQUIRED',
        428 PRECONDITION_REQUIRED = '428 PRECONDITION_REQUIRED',
        429 TOO_MANY_REQUESTS = '429 TOO_MANY_REQUESTS',
        431 REQUEST_HEADER_FIELDS_TOO_LARGE = '431 REQUEST_HEADER_FIELDS_TOO_LARGE',
        451 UNAVAILABLE_FOR_LEGAL_REASONS = '451 UNAVAILABLE_FOR_LEGAL_REASONS',
        500 INTERNAL_SERVER_ERROR = '500 INTERNAL_SERVER_ERROR',
        501 NOT_IMPLEMENTED = '501 NOT_IMPLEMENTED',
        502 BAD_GATEWAY = '502 BAD_GATEWAY',
        503 SERVICE_UNAVAILABLE = '503 SERVICE_UNAVAILABLE',
        504 GATEWAY_TIMEOUT = '504 GATEWAY_TIMEOUT',
        505 HTTP_VERSION_NOT_SUPPORTED = '505 HTTP_VERSION_NOT_SUPPORTED',
        506 VARIANT_ALSO_NEGOTIATES = '506 VARIANT_ALSO_NEGOTIATES',
        507 INSUFFICIENT_STORAGE = '507 INSUFFICIENT_STORAGE',
        508 LOOP_DETECTED = '508 LOOP_DETECTED',
        509 BANDWIDTH_LIMIT_EXCEEDED = '509 BANDWIDTH_LIMIT_EXCEEDED',
        510 NOT_EXTENDED = '510 NOT_EXTENDED',
        511 NETWORK_AUTHENTICATION_REQUIRED = '511 NETWORK_AUTHENTICATION_REQUIRED',
    }

Which is not valid

Custom Headers

Is it possible to add custom headers?

Currently looking to add an Authorization header: Basic XXXXXXXXXX - can only see a way documented for adding Bearer tokens ~ is there a way to add custom headers?

Non primitives loose their meaning in schemas

Openapi 3.0

First of all, nice generator. Models and ApiServices seem to work fine. But for the schemas, I do have some issues, and generating Schemas is something that is really beneficial, above others.

Here is an example:
image

The array is defined like:

        issues:
          type: array
          items:
            $ref: '#/components/schemas/Issue'

Let's assume we use these schemas for frontend validations for input fields:

  • name is fine, I see it's a type. Any props like isRequired, pattern, I can use.
  • entity is less fine, but I could just import * as Schemas from index-of-schemaand doSchemas[${type}]`, and check that object.
  • issues is an issue :) . I see it's an array type, but this is where it stops.

Suggestion:

import {$EntityDto} from './$EntityDto';
import {$Issue} from './$IssueDto';


export const $SomeDto = {
    properties: {
        name: {
            type: 'string',
        },
        entity: {
            type: $EntityDto,
        },
        issues: {
            type: $Issue,
            array: true,
        },
    },
};

This way code should be able to dynamically determine what to do for settings properties on input fields, how to validate them..

Fire promise globally before any request

First off, thanks so much for this library. This is really helpful.

Problem

I am currently using the OpenAPI.TOKEN syntax to set my auth token. This works well, but it isn't perfect for my use case. The reason is that my token is accessed asynchronously.

Ideally, I could wrap the fetch function used by this lib, such that it gets my token asynchronously before every call.

I'm using Firebase auth, which 1) refreshes the token often, and 2) has an asynchronous function to get the current token.

Currently, I do this:

onAuthStateChanged(async (user) => {
  if (user) {
   // the user is set as signed in here, so I update the app's auth state
   setIsSignedIn(true) // <- this is pseudo-code. This is what Firebase does for me, for illustration.
   // thus, they're now seeing screens that require an auth token

  // it's possible to be stuck here, where the app state has updated to show authenticated screens
  // but the token promise hasn't resolved yet

   // next, I set the user's token for request
   OpenAPI.TOKEN = await user.getIdToken() 
  } else {
    OpenAPI.TOKEN = ''
  }
})

The problem with this is that await user.getIdToken() is async. Since I render authenticated screens right when user exists, then it's possible that the user will see certain screens before I have set the OpenAPI.TOKEN. This means requests that should have a token will be sent without one.

Proposed solution

At the root of my app, it would nice to be able to do something like this:

import { OpenAPI } from 'openapi-typescript-codegen'

OpenAPI.onBeforeRequest = async () => {
   OpenAPI.TOKEN = await firebase.auth().currentUser?.getIdToken() 
}

This way, before any request is sent, I know with certainty that the latest auth token is injected in my requests.

My current work-around

Currently, I have to manually call this before every request:

import { SomeService } from '../generated'

export const callServer = async () => {
   OpenAPI.TOKEN = await firebase.auth().currentUser?.getIdToken() 
   return SomeService.someCall()
}

Doing this manually isn't ideal, since I might forget some.

I'm definitely open to a better solution, and am happy to explain better if this is unclear. Thank you!

Accept PR to separate Enums into own files?

Describe the solution you'd like
I'd like to have an option to write enums to their own files (off by default to maintain backward compatibility).

With this option on, this currently generated code:

export interface SomeModel {
    status: SomeModel.status;
}

export namespace SomeModel {
    export enum status {
        started = 'started',
        finished = 'finished'
    }
}

would instead generate this

// SomeModel.ts
export interface SomeModel {
    status: Status;
}

// Status.ts
export enum Status {
    started = 'started',
    finished = 'finished'
}

If you would accept this, any guidance on how to go about it would be appreciated. I looked through the code and think it can be done with a new post-processor and template, but won't know for sure until I start.

Support x-nullable vendor extension

Describe the solution you'd like
Since nullable isn't supported in OpenApi v2, some frameworks (e.g., drf-yasg) are using the x-nullable vendor extension to indicate if e.g. property is nullable. I believe we should use this to mark a property as nullable:

The specification

{
    "ModelWithNullableString": {
        "required": ["requiredProp"],
        "description": "This is a model with one string property",
        "type": "object",
        "properties": {
            "prop": {
                "description": "This is a simple string property",
                "type": "string",
                "x-nullable": true
            },
            "requiredProp": {
                "description": "This is a simple string property",
                "type": "string",
                "x-nullable": true,
            }
        }
    }
}

should yield

enum ModelWithNullableString {
    prop?: string | null,
    requiredProp: string | null
}

at sign (@) causing compilation error in generated files

When running the code generation script against a C# oData API, the properties preceded with an "@" are not properly quoted, and cause compilation errors.

Please see this gist for an example of the output, and how I've manually fixed the issue, by quoting the properties beginning with an "@"

Screenshot of the error thrown when running tsc against the generated code:
image

No properties for schemas created for root components

OpenAPI 3.0

This:
image

Generates:
image

image

Hence:

  • The SomethingElse schema is missing pattern and required
  • The pattern that is being generated for the Something.props.name, is not escaped properly (should become '^\\w+$', double back-slashed)

Expected:
image

image

Also, the schemas are not typed.
Adding turtle: true to the openapi spec, is not valid, so the typing could be made, because it shouldn't contain random keys

Support 'text/plain' in request body

Nice gen. My requests are working great except those that have the body to be sent as text/plain.

request.body = JSON.stringify(options.body);

This line in request.ts adds extra quotes to the body string.
Suggestion, something like this (maybe, works for my case):

        if (options.body instanceof Blob) {
            request.body = options.body;
            if (options.body.type) {
                headers.append('Content-Type', options.body.type);
            }
        } else if (typeof options.body === 'string') {
            request.body = options.body;
            headers.append('Content-Type', 'text/plain');
        } else {
            request.body = JSON.stringify(options.body);
            headers.append('Content-Type', 'application/json');
        }

Content-Type Requires Splitting before Check

The generated getResponseBody() function does not account for OData metadata values set in the Content-Type header.

This is defined in the OData standard which is an ISO/IEC International Standard.

The example where I'm encountering this has the content-type followed by some additional information which should be ignored when determining the content-type.

The code currently does this:

const contentType = response.headers.get('Content-Type'); // 'application/json; odata.metadata=minimal; odata.streaming=true'

if (contentType) {
	switch (contentType.toLowerCase()) {
		case 'application/json':
    	case 'application/json; charset=utf-8':
    		return await response.json();
		default:
			return await response.text();
	}
}
}

Whereas it might be better off doing something like this:

	const contentType = response.headers.get('Content-Type'); // 'application/json; odata.metadata=minimal; odata.streaming=true'

	const mediaType = contentType?.split(';')[0].toLowerCase() // 'application/json'

	if (mediaType === 'application/json') {
		return await response.json();
	}

	return await response.text();
}

Example in editor:

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.