pagopa / openapi-codegen-ts

App IO - Utils
European Union Public License 1.2
37 stars 12 forks source link
digital-citizenship

Utilities and tools for the Digital Citizenship initiative

This package provide some tools that are used across the projects of the Digital Citizenship initiative.

To add the tools to a project:

$ yarn add -D @pagopa/openapi-codegen-ts

Commands

gen-api-models

This tool generates TypeScript definitions of OpenAPI specs.

In simple terms it converts an OpenAPI spec like this one into:

Note: the generated models requires the runtime dependency @pagopa/ts-commons.

About string pattern definition

Up until version 12.x, when handling string pattern definitions given in the OpenAPI specifications, the production of runtime types has a bug: when using a backslash (\) for escaping regular expression digits (e.g., \d), the generator drops it. Double backslashes (\\) can be used in the pattern description as a fix for this issue. Starting from version 12.x the codegen will notify you whenever it detects a \\ inside a pattern definition as this bug has been resolved for OpenAPI 3.x.

Usage

$ gen-api-models --help
Options:
  --version               Show version number                          [boolean]
  --api-spec              Path to input OpenAPI spec file    [string] [required]
  --strict                Generate strict interfaces (default: true)
                                                                 [default: true]
  --out-dir               Output directory to store generated definition files
                                                             [string] [required]
  --ts-spec-file          If defined, converts the OpenAPI specs to TypeScript
                          source and writes it to this file             [string]
  --request-types         Generate request types (default: false)
                                                                [default: false]
  --response-decoders     Generate response decoders (default:
                          false, implies --request-types)       [default: false]
  --client                Generate request client SDK           [default: false]
  --default-success-type  Default type for success responses (
                          default: 'undefined')  [string] [default: "undefined"]
  --default-error-type    Default type for error responses (
                          default: 'undefined')  [string] [default: "undefined"]
  --help                  Show help                                    [boolean]

Example:

$ gen-api-models --api-spec ./api/public_api_v1.yaml --out-dir ./lib/api/definitions --ts-spec-file ./lib/api/public_api_v1.ts
Writing TS Specs to lib/api/public_api_v1.ts
ProblemJson -> lib/api/definitions/ProblemJson.ts
NotificationChannel -> lib/api/definitions/NotificationChannel.ts
NotificationChannelStatusValue -> lib/api/definitions/NotificationChannelStatusValue.ts
...
done

Generated client

The http client is defined in client.ts module file. It exports the following:

Example
import { createClient, WithDefaultsT } from "my-api/client";

// Without withDefaults
const simpleClient: Client = createClient({
    baseUrl: `http://localhost:8080`,
    fetchApi: (nodeFetch as any) as typeof fetch
});

// myOperation is defined to accept { id: string; Bearer: string; }
const result = await simpleClient.myOperation({
    id: "id123",
    Bearer: "VALID_TOKEN"
});

// with withDefaults
const withBearer: WithDefaultsT<"Bearer"> = 
    wrappedOperation => 
        params => { // wrappedOperation and params are correctly inferred
            return wrappedOperation({
              ...params,
              Bearer: "VALID_TOKEN"
            });
          };
//  this is the same of using createClient<"Bearer">. K type is being inferred from withBearer
const clientWithGlobalToken: Client<"Bearer"> = createClient({
    baseUrl: `http://localhost:8080`,
    fetchApi: (nodeFetch as any) as typeof fetch,
    withDefaults: withBearer
});

// myOperation doesn't require "Bearer" anymore, as it's defined in "withBearer" adapter 
const result = await clientWithGlobalToken.myOperation({
    id: "id123"
});

gen-api-sdk

Bundles a generated api models and clients into a node package ready to be published into a registry. The script is expected to be executed in the root of an application exposing an API, thus it infers package attributes from the expected ./package.json file. Values can be still overridden by provinding the respective CLI argument. To avoid this behavior, use --no-infer-attrs or -N.

Usage

$ gen-api-sdk --help
Package options:
  --no-infer-attr, -N                 Infer package attributes from a
                                      package.json file present in the current
                                      directory       [boolean] [default: false]
  --package-name, -n, --name          Name of the generated package     [string]
  --package-version, -V               Version of the generated package  [string]
  --package-description, -d, --desc   Description of the package        [string]
  --package-author, -a, --author      The author of the API exposed     [string]
  --package-license, -L, --license    The license of the API Exposed    [string]
  --package-registry, -r, --registry  Url of the registry the package is
                                      published in                      [string]
  --package-access, -x, --access      Either 'public' or 'private', depending of
                                      the accessibility of the package in the
                                      registry
                                         [string] [choices: "public", "private"]

Code generation options:
  --api-spec, -i          Path to input OpenAPI spec file    [string] [required]
  --strict                Generate strict interfaces (default: true)
                                                                 [default: true]
  --out-dir, -o           Output directory to store generated definition files
                                                             [string] [required]
  --default-success-type  Default type for success responses (experimental,
                          default: 'undefined')  [string] [default: "undefined"]
  --default-error-type    Default type for error responses (experimental,
                          default: 'undefined')  [string] [default: "undefined"]
  --camel-cased           Generate camelCased properties name (default: false)
                                                                [default: false]

Options:
  --version  Show version number                                       [boolean]
  --help     Show help                                                 [boolean]

bundle-api-spec

Takes a given api spec file and resolves its esternal references by creating a new file with only internal refereces

$ bundle-api-spec --help
Code generation options:
  --api-spec, -i     Path to input OpenAPI spec file         [string] [required]
  --out-path, -o     Output path of the spec file            [string] [required]
  --api-version, -V  Version of the api. If provided, override the version in
                     the original spec file                             [string]

Options:
  --version  Show version number                                       [boolean]
  --help     Show help                                                 [boolean]

Requirements

TEST

Unit test

Run test over utils' implementation

yarn test

End-to-end test

Run test over generated files

yarn e2e

Known issues, tradeoffs and throubleshooting

A model file for a definition is not generated

When using gen-api-models against a specification file which references an external definition file, some of such remote definitions do not result in a dedicated model file. This is somehow intended and the rationale is explained here. Quick takeaway is that to have a definition to result in a model file, it must be explicitly referenced by the specification file. In short: if you need to keep the references between the generated classes, the specification file must contain all the schema definitions. See example below.

example:

if the Pets schema uses the Pet, import both into the main document

components:
  schemas:
    Pets:
        $ref: "animal.yaml#/Pets"
    Pet:
        $ref: "animal.yaml#/Pet"

animal.yaml

Pets:
  type: array
  items:
    $ref: '#/definitions/Pet'
Pet:
  type: "object"
  required:
    - name
  properties:
    name:
      type: string

Migration from old versions

Generated code is slightly different from v4 as it implements some bug fixes that result in breaking changes. Here's a list of what to be aware of:

from 4.3.0 to 5.x