DEV Community

Cover image for Development of TypeScript Code Generator for OpenAPI Specification using AST
HimeNyan
HimeNyan

Posted on

Development of TypeScript Code Generator for OpenAPI Specification using AST

OpenAPI TypeScript Code Generator

@himenon/openapi-typescript-code-generator

Motivation

There were several libraries available for generating TypeScript code from the OpenAPI Specification (OAS), so I tried using these at first.

However, as the complexity of the system increased, there were points where it did not work well.

It worked well enough when the OAS was simple, but when the number of split files exceeded 50 and they were aggregated into a single typedef, the typedef became very difficult to use.
However, when the number of split files exceeded 50 and they were aggregated into a single type definition, it became very difficult to use.

Also, most of the libraries generate API clients that depend on specific libraries, which can be a concern when implementing them in a real project. For example, if your team is unified on fetch, you may not want to use axios, and vice versa.

In addition, when I was told that I could write my own templates, I actually tried to do so, but the learning cost for this was high because of the partial changes to the API Client and the various formats provided, such as Mustash notation, and it was very difficult to disseminate from the perspective of maintenance cost.

In order to solve these problems, we have developed a library.

Design Concept

As described in the README on GitHub, we designed and developed the library using the following guidelines.

  • Be typedef first.
  • Typedefs should not contain any entities (file size should be 0 when typedefs are converted to .js)
  • The directory structure should be mapped to the typedef structure.
  • No dependency on any API client library.
  • Can be extended by TypeScript AST.
  • Conform to the OpenAPI specification.
  • It should be a single file to maintain portability.

To make this happen, I made full use of TypeScript AST to create a Code Generator.
Using TypeScript AST, it was also (fairly) easy to convert oneOf and allOf to Union Type and Intersection Type.

Also, by converting the directory structure to namespace and mapping that structure to the typedef structure, the reference structure became much clearer.
I put a lot of effort into the implementation of this reference resolution, and it is one of the features that you should definitely try.

Example

Here are a couple of examples. You can see how powerful it is when you actually use it.

Case 1 - oneOf

# spec.yml
components:
  schemas:
    OneOfType:
      oneOf:
        - type: string
        - type: number
        - type: object
Enter fullscreen mode Exit fullscreen mode

After conversion

export namespace Schemas {
  export type OneOfType = string | number | {};
}
Enter fullscreen mode Exit fullscreen mode

Case 2 - layered structure

# spec.yml
components:
  schemas:
    RemoteRefString:
      $ref: "./components/schemas/Level1/RemoteBoolean.yml"
Enter fullscreen mode Exit fullscreen mode
# ./components/schemas/Level1/RemoteBoolean.yml
type: boolean
Enter fullscreen mode Exit fullscreen mode

After conversion

export namespace Schemas {
  export namespace Level1 {
    export type RemoteBoolean = boolean;
  }
  export type RemoteRefBoolean = Schemas.Level1.RemoteBoolean;
}
Enter fullscreen mode Exit fullscreen mode

Limitation

By putting the directory structure into the implementation, a limitation arises.

Only the names available in the TypeScript namespace declarations can be used for directory names.
This is a minus point in terms of freedom, but it has the advantage of passively organizing the namespace.

Maybe there is a way to change the internal implementation in a flexible way, such as playing with validation for naming that is not available within TypeScript, or converting to camelCase.

There are other limitations, so please take a look at the REAMDE if you are interested.

Template function

You can use TypeScript AST to extend your code. At first glance, AST is difficult, but by using a very good tool called ts-ast-viewer, you can instantly enjoy the benefits of AST.

The implementation of the output language can be changed in the same language, so the learning cost is low, and the presence of a playground like ts-ast-viewer makes it easy to take the first step and guarantees the freedom of expansion.

Dependency Injection

The default API Template is designed to inject dependencies. Similarly, the formatting of QueryParmaeter is done externally, and no real processing is described.
There are many different formats for the Parameter system, and these are separated as separate libraries.

The sample project shows how to use these, so please have a look!

About the future

I'm planning to modify it as much as I notice, but I'm not confident that I've covered everything. The $ref resolution algorithm is working well, but it is recursive, so I can't guarantee that it covers everything.
If you find any, please throw a bug report to the issue.

Also, if you want to make contributions to the implementation, read here first.

Finally, thank you for reading this far. If you'd like, you can give me a Start on GitHub!

Top comments (0)