diff --git a/README.md b/README.md index 9a70710e..9586daaf 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - 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](#node-fetch-support), [Axios](#axios-support) and XHR http clients +- Supports generations of Fetch, [Node-Fetch](#node-fetch-support), [Axios](#axios-support), [Angular](#angular-support) 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 @@ -22,6 +22,7 @@ - Supports aborting of requests (cancelable promise pattern) - Supports external references using [json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser/) + ## Install ``` @@ -40,7 +41,7 @@ $ openapi --help -V, --version output the version number -i, --input OpenAPI specification, can be a path, url or string content (required) -o, --output Output directory (required) - -c, --client HTTP client to generate [fetch, xhr, axios, node] (default: "fetch") + -c, --client HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch") --name Custom client class name --useOptions Use options instead of arguments --useUnionTypes Use union types instead of enums @@ -54,467 +55,31 @@ $ openapi --help -h, --help display help for command Examples - $ openapi --input ./spec.json - $ openapi --input ./spec.json --output ./dist - $ openapi --input ./spec.json --output ./dist --client xhr + $ openapi --input ./spec.json --output ./generated + $ openapi --input ./spec.json --output ./generated --client xhr ``` -## Example - -**package.json** -```json -{ - "scripts": { - "generate": "openapi --input ./spec.json --output ./dist" - } -} -``` - -**NPX** - -``` -npx openapi-typescript-codegen --input ./spec.json --output ./dist -``` - -**Node.js API** - -```javascript -const OpenAPI = require('openapi-typescript-codegen'); - -OpenAPI.generate({ - input: './spec.json', - output: './dist' -}); - -// Or by providing the content of the spec directly 🚀 -OpenAPI.generate({ - input: require('./spec.json'), - output: './dist' -}); -``` - - -## Features - - -### Generate client instance with `--name` option -The OpenAPI generator allows creation of client instances to support the multiple backend services use case. -The generated client uses an instance of the server configuration and not the global `OpenAPI` constant. -To generate a client instance, set a custom name to the client class, use `--name` option. - -``` -openapi --input ./spec.json --output ./dist ---name AppClient -``` - -The generated client will be exported from the `index` file and can be used as shown below: - -```typescript -// Create the client instance with server and authentication details -const appClient = new AppClient({ - BASE: 'http://server-host.com', - TOKEN: '1234' -}); - -// Use the client instance to make the API call -const response = await appClient.organizations.createOrganization({ - name: 'OrgName', - description: 'OrgDescription', -}); -``` - - -### Argument style vs. Object style `--useOptions` -There's no [named parameter](https://en.wikipedia.org/wiki/Named_parameter) in JavaScript or TypeScript, because of -that, we offer the flag `--useOptions` to generate code in two different styles. - -**Argument-style:** -```typescript -function createUser(name: string, password: string, type?: string, address?: string) { - // ... -} - -// Usage -createUser('Jack', '123456', undefined, 'NY US'); -``` - -**Object-style:** -```typescript -function createUser({ name, password, type, address }: { - name: string, - password: string, - type?: string - address?: string -}) { - // ... -} - -// Usage -createUser({ - name: 'Jack', - password: '123456', - address: 'NY US' -}); -``` - -### Enums vs. Union Types `--useUnionTypes` -The OpenAPI spec allows you to define [enums](https://swagger.io/docs/specification/data-models/enums/) inside the -data model. By default, we convert these enums definitions to [TypeScript enums](https://www.typescriptlang.org/docs/handbook/enums.html). -However, these enums are merged inside the namespace of the model, this is unsupported by Babel, [see docs](https://babeljs.io/docs/en/babel-plugin-transform-typescript#impartial-namespace-support). -Because we also want to support projects that use Babel [@babel/plugin-transform-typescript](https://babeljs.io/docs/en/babel-plugin-transform-typescript), -we offer the flag `--useUnionTypes` to generate [union types](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types) -instead of the traditional enums. The difference can be seen below: - -**Enums:** -```typescript -// Model -export interface Order { - id?: number; - quantity?: number; - status?: Order.status; -} - -export namespace Order { - export enum status { - PLACED = 'placed', - APPROVED = 'approved', - DELIVERED = 'delivered', - } -} - -// Usage -const order: Order = { - id: 1, - quantity: 40, - status: Order.status.PLACED -} -``` - -**Union Types:** -```typescript -// Model -export interface Order { - id?: number; - quantity?: number; - status?: 'placed' | 'approved' | 'delivered'; -} - -// Usage -const order: Order = { - id: 1, - quantity: 40, - status: 'placed' -} -``` - -### Runtime schemas `--exportSchemas` -By default, the OpenAPI generator only exports interfaces for your models. These interfaces will help you during -development, but will not be available in JavaScript during runtime. However, Swagger allows you to define properties -that can be useful during runtime, for instance: `maxLength` of a string or a `pattern` to match, etc. Let's say -we have the following model: - -```json -{ - "MyModel": { - "required": [ - "key", - "name" - ], - "type": "object", - "properties": { - "key": { - "maxLength": 64, - "pattern": "^[a-zA-Z0-9_]*$", - "type": "string" - }, - "name": { - "maxLength": 255, - "type": "string" - }, - "enabled": { - "type": "boolean", - "readOnly": true - }, - "modified": { - "type": "string", - "format": "date-time", - "readOnly": true - } - } - } -} -``` - -This will generate the following interface: - -```typescript -export interface MyModel { - key: string; - name: string; - readonly enabled?: boolean; - readonly modified?: string; -} -``` - -The interface does not contain any properties like `maxLength` or `pattern`. However, they could be useful -if we wanted to create some form where a user could create such a model. In that form you would iterate -over the properties to render form fields based on their type and validate the input based on the `maxLength` -or `pattern` property. This requires us to have this information somewhere... For this we can use the -flag `--exportSchemas` to generate a runtime model next to the normal interface: - -```typescript -export const $MyModel = { - properties: { - key: { - type: 'string', - isRequired: true, - maxLength: 64, - pattern: '^[a-zA-Z0-9_]*$', - }, - name: { - type: 'string', - isRequired: true, - maxLength: 255, - }, - enabled: { - type: 'boolean', - isReadOnly: true, - }, - modified: { - type: 'string', - isReadOnly: true, - format: 'date-time', - }, - }, -} as const; -``` - -These runtime object are prefixed with a `$` character and expose all the interesting attributes of a model -and its properties. We can now use this object to generate the form: - -```typescript jsx -import { $MyModel } from './generated'; - -// Some pseudo code to iterate over the properties and return a form field -// the form field could be some abstract component that renders the correct -// field type and validation rules based on the given input. -const formFields = Object.entries($MyModel.properties).map(([key, value]) => ( - -)); - -const MyForm = () => ( -
- {formFields} -
-); - -``` - - -### Enum with custom names and descriptions -You can use `x-enum-varnames` and `x-enum-descriptions` in your spec to generate enum with custom names and descriptions. -It's not in official [spec](https://github.com/OAI/OpenAPI-Specification/issues/681) yet. But it's a supported extension -that can help developers use more meaningful enumerators. -```json -{ - "EnumWithStrings": { - "description": "This is a simple enum with strings", - "enum": [ - 0, - 1, - 2 - ], - "x-enum-varnames": [ - "Success", - "Warning", - "Error" - ], - "x-enum-descriptions": [ - "Used when the status of something is successful", - "Used when the status of something has a warning", - "Used when the status of something has an error" - ] - } -} -``` - -Generated code: -```typescript -enum EnumWithStrings { - /* - * Used when the status of something is successful - */ - Success = 0, - /* - * Used when the status of something has a warning - */ - Waring = 1, - /* - * Used when the status of something has an error - */ - Error = 2, -} -``` - - -### Nullable in OpenAPI v2 -In the OpenAPI v3 spec you can create properties that can be NULL, by providing a `nullable: true` in your schema. -However, the v2 spec does not allow you to do this. You can use the unofficial `x-nullable` in your specification -to generate nullable properties in OpenApi v2. - -```json -{ - "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 - } - } - } -} -``` - -Generated code: -```typescript -interface ModelWithNullableString { - prop?: string | null, - requiredProp: string | null, -} -``` - - -### Authorization -The OpenAPI generator supports Bearer Token authorization. In order to enable the sending -of tokens in each request you can set the token using the global OpenAPI configuration: - -```typescript -import { OpenAPI } from './generated'; - -OpenAPI.TOKEN = 'some-bearer-token'; -``` - -Alternatively, we also support an async method that provides the token for each request. -You can simply assign this method to the same `TOKEN `property in the global OpenAPI object. - -```typescript -import { OpenAPI } from './generated'; - -const getToken = async () => { - // Some code that requests a token... - return 'SOME_TOKEN'; -} - -OpenAPI.TOKEN = getToken; -``` - -### References - -Local references to schema definitions (those beginning with `#/definitions/schemas/`) -will be converted to type references to the equivalent, generated top-level type. - -The OpenAPI generator also supports external references, which allows you to break -down your openapi.yml into multiple sub-files, or incorporate third-party schemas -as part of your types to ensure everything is able to be TypeScript generated. - -External references may be: -* *relative references* - references to other files at the same location e.g. - `{ $ref: 'schemas/customer.yml' }` -* *remote references* - fully qualified references to another remote location - e.g. `{ $ref: 'https://myexampledomain.com/schemas/customer_schema.yml' }` - - For remote references, both files (when the file is on the current filesystem) - and http(s) URLs are supported. - -External references may also contain internal paths in the external schema (e.g. -`schemas/collection.yml#/definitions/schemas/Customer`) and back-references to -the base openapi file or between files (so that you can reference another -schema in the main file as a type of an object or array property, for example). - -At start-up, an OpenAPI or Swagger file with external references will be "bundled", -so that all external references and back-references will be resolved (but local -references preserved). - - -FAQ +Documentation === - -### Babel support -If you use enums inside your models / definitions then those enums are by default inside a namespace with the same name -as your model. This is called declaration merging. However, the [@babel/plugin-transform-typescript](https://babeljs.io/docs/en/babel-plugin-transform-typescript) -does not support these namespaces, so if you are using babel in your project please use the `--useUnionTypes` flag -to generate union types instead of traditional enums. More info can be found here: [Enums vs. Union Types](#enums-vs-union-types---useuniontypes). - -**Note:** If you are using Babel 7 and Typescript 3.8 (or higher) then you should enable the `onlyRemoveTypeImports` to -ignore any 'type only' imports, see https://babeljs.io/docs/en/babel-preset-typescript#onlyremovetypeimports for more info - -```javascript -module.exports = { - presets: [ - ['@babel/preset-typescript', { - onlyRemoveTypeImports: true, - }], - ], -}; -``` - -### Axios support -This tool allows you to generate a client based on the [`axios`](https://www.npmjs.com/package/axios) client. -The advantage of the Axios client is that it works in both NodeJS and Browser based environments. -If you want to generate the Axios based client then you can specify `--client axios` in the openapi call: - -`openapi --input ./spec.json --output ./dist --client axios` - -The only downside is that this client needs some additional dependencies to work (due to the missing Blob and FormData -classes in NodeJS). - -``` -npm install axios --save-dev -npm install form-data@4.x --save-dev -``` - -In order to compile the project and resolve the imports, you will need to enable the `allowSyntheticDefaultImports` -in your `tsconfig.json` file. +- [Basic usage](docs/basic-usage.md) +- [OpenAPI object](docs/openapi-object.md) +- [Client instances](docs/client-instances.md) `--name` +- [Argument vs. Object style](docs/arguments-vs-object-style.md) `--useOptions` +- [Enums vs. Union types](docs/enum-vs-union-types.md) `--useUnionTypes` +- [Runtime schemas](docs/runtime-schemas.md) `--exportSchemas` +- [Enum with custom names and descriptions](docs/custom-enums.md) +- [Nullable props (OpenAPI v2)](docs/nullable-props.md) +- [Authorization](docs/authorization.md) +- [External references](docs/external-references.md) -### Node-Fetch support -By default, this tool will generate a client that is compatible with the (browser based) Fetch API. -However, this client will not work inside the Node.js environment. If you want to generate the Node.js compatible -client then you can specify `--client node` in the openapi call: - -`openapi --input ./spec.json --output ./dist --client node` - -This will generate a client that uses [`node-fetch`](https://www.npmjs.com/package/node-fetch) internally. However, -in order to compile and run this client, you might need to install the `node-fetch@2.x` dependencies. - -> Since version 3.x [`node-fetch`](https://www.npmjs.com/package/node-fetch) switched to ESM only, -> breaking many CommonJS based toolchains (like Jest). Right now we do not support this new version! - -``` -npm install @types/node-fetch@2.x --save-dev -npm install abort-controller@3.x --save-dev -npm install form-data@4.x --save-dev -npm install node-fetch@2.x --save-dev -``` - -In order to compile the project and resolve the imports, you will need to enable the `allowSyntheticDefaultImports` -in your `tsconfig.json` file. +Support +=== +- [Babel support](docs/babel-support.md) +- [Axios support](docs/axios-support.md) +- [Angular support](docs/angular-support.md) +- [Node-Fetch support](docs/node-fetch-support.md) [npm-url]: https://npmjs.org/package/openapi-typescript-codegen diff --git a/docs/angular-support.md b/docs/angular-support.md new file mode 100644 index 00000000..faf013ea --- /dev/null +++ b/docs/angular-support.md @@ -0,0 +1,13 @@ +# Angular support + +Lorem + +`openapi --input ./spec.json --output ./generated --client angular` + +This has been tested with the following versions: + +``` +"@angular/common": "13.1.3", +"@angular/core": "13.1.3", +"rxjs": "7.5.2", +``` diff --git a/docs/arguments-vs-object-style.md b/docs/arguments-vs-object-style.md new file mode 100644 index 00000000..85acb504 --- /dev/null +++ b/docs/arguments-vs-object-style.md @@ -0,0 +1,35 @@ +# Arguments vs. Object style + +**Flag:** `--useOptions` + +There's no [named parameter](https://en.wikipedia.org/wiki/Named_parameter) in JavaScript or TypeScript, because of +that, we offer the flag `--useOptions` to generate code in two different styles. + +**Argument style:** +```typescript +const createUser = (name: string, password: string, type?: string, address?: string) => { + // ... +}; + +// Usage +createUser('Jack', '123456', undefined, 'NY US'); +``` + +**Object style:** +```typescript +const createUser = ({ name, password, type, address }: { + name: string, + password: string, + type?: string + address?: string +}) => { + // ... +}; + +// Usage +createUser({ + name: 'Jack', + password: '123456', + address: 'NY US' +}); +``` diff --git a/docs/authorization.md b/docs/authorization.md new file mode 100644 index 00000000..3682aff3 --- /dev/null +++ b/docs/authorization.md @@ -0,0 +1,24 @@ +# Authorization + +The OpenAPI generator supports Bearer Token authorization. In order to enable the sending +of tokens in each request you can set the token using the global OpenAPI configuration: + +```typescript +import { OpenAPI } from './generated'; + +OpenAPI.TOKEN = 'some-bearer-token'; +``` + +Alternatively, we also support an async method that provides the token for each request. +You can simply assign this method to the same `TOKEN `property in the global OpenAPI object. + +```typescript +import { OpenAPI } from './generated'; + +const getToken = async () => { + // Some code that requests a token... + return 'SOME_TOKEN'; +}; + +OpenAPI.TOKEN = getToken; +``` diff --git a/docs/axios-support.md b/docs/axios-support.md new file mode 100644 index 00000000..f4dab243 --- /dev/null +++ b/docs/axios-support.md @@ -0,0 +1,18 @@ +# Axios support + +This tool allows you to generate a client based on the [`Axios`](https://www.npmjs.com/package/axios) client. +The advantage of the Axios client is that it works in both Node.js and Browser based environments. +If you want to generate the Axios based client then you can specify `--client axios` in the openapi call: + +`openapi --input ./spec.json --output ./generated --client axios` + +The only downside is that this client needs some additional dependencies to work (due to the missing FormData +classes in Node.js). + +``` +npm install axios --save-dev +npm install form-data@4.x --save-dev +``` + +In order to compile the project and resolve the imports, you will need to enable the `allowSyntheticDefaultImports` +in your `tsconfig.json` file. diff --git a/docs/babel-support.md b/docs/babel-support.md new file mode 100644 index 00000000..def12252 --- /dev/null +++ b/docs/babel-support.md @@ -0,0 +1,19 @@ +# Babel support + +If you use enums inside your models / definitions then those enums are by default inside a namespace with the same name +as your model. This is called declaration merging. However, the [@babel/plugin-transform-typescript](https://babeljs.io/docs/en/babel-plugin-transform-typescript) +does not support these namespaces, so if you are using babel in your project please use the `--useUnionTypes` flag +to generate union types instead of traditional enums. More info can be found here: [Enums vs. Union Types](#enums-vs-union-types---useuniontypes). + +**Note:** If you are using Babel 7 and Typescript 3.8 (or higher) then you should enable the `onlyRemoveTypeImports` to +ignore any 'type only' imports, see https://babeljs.io/docs/en/babel-preset-typescript#onlyremovetypeimports for more info + +```javascript +module.exports = { + presets: [ + ['@babel/preset-typescript', { + onlyRemoveTypeImports: true, + }], + ], +}; +``` diff --git a/docs/basic-usage.md b/docs/basic-usage.md new file mode 100644 index 00000000..ee834087 --- /dev/null +++ b/docs/basic-usage.md @@ -0,0 +1,62 @@ +# Basic usage + +``` +$ openapi --help + + Usage: openapi [options] + + Options: + -V, --version output the version number + -i, --input OpenAPI specification, can be a path, url or string content (required) + -o, --output Output directory (required) + -c, --client HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch") + --name Custom client class name + --useOptions Use options instead of arguments + --useUnionTypes Use union types instead of enums + --exportCore Write core files to disk (default: true) + --exportServices Write services to disk (default: true) + --exportModels Write models to disk (default: true) + --exportSchemas Write schemas to disk (default: false) + --indent Indentation options [4, 2, tab] (default: "5") + --postfix Service name postfix (default: "Service") + --request Path to custom request file + -h, --help display help for command + + Examples + $ openapi --input ./spec.json --output ./generated +``` + + +## Example + +**package.json** +```json +{ + "scripts": { + "generate": "openapi --input ./spec.json --output ./generated" + } +} +``` + +**NPX** + +``` +npx openapi-typescript-codegen --input ./spec.json --output ./generated +``` + +**Node.js** + +```javascript +const OpenAPI = require('openapi-typescript-codegen'); + +OpenAPI.generate({ + input: './spec.json', + output: './generated', +}); + +// Or by providing the content of the spec directly 🚀 +OpenAPI.generate({ + input: require('./spec.json'), + output: './generated', +}); +``` diff --git a/docs/client-instances.md b/docs/client-instances.md new file mode 100644 index 00000000..33e9c936 --- /dev/null +++ b/docs/client-instances.md @@ -0,0 +1,27 @@ +# Client instances + +**Flag:** `--name` + +The OpenAPI generator allows creation of client instances to support the multiple backend services use case. +The generated client uses an instance of the server configuration and not the global `OpenAPI` constant. +To generate a client instance, set a custom name to the client class, use `--name` option. + +``` +openapi --input ./spec.json --output ./generated ---name AppClient +``` + +The generated client will be exported from the `index` file and can be used as shown below: + +```typescript +// Create the client instance with server and authentication details +const appClient = new AppClient({ + BASE: 'http://server-host.com', + TOKEN: '1234', +}); + +// Use the client instance to make the API call +const response = await appClient.organizations.createOrganization({ + name: 'OrgName', + description: 'OrgDescription', +}); +``` diff --git a/docs/custom-enums.md b/docs/custom-enums.md new file mode 100644 index 00000000..a53d6faa --- /dev/null +++ b/docs/custom-enums.md @@ -0,0 +1,45 @@ +# Enum with custom names and descriptions + +You can use `x-enum-varnames` and `x-enum-descriptions` in your spec to generate enum with custom names and descriptions. +It's not in official [spec](https://github.com/OAI/OpenAPI-Specification/issues/681) yet. But it's a supported extension +that can help developers use more meaningful enumerators. +```json +{ + "EnumWithStrings": { + "description": "This is a simple enum with strings", + "enum": [ + 0, + 1, + 2 + ], + "x-enum-varnames": [ + "Success", + "Warning", + "Error" + ], + "x-enum-descriptions": [ + "Used when the status of something is successful", + "Used when the status of something has a warning", + "Used when the status of something has an error" + ] + } +} +``` + +Generated code: +```typescript +enum EnumWithStrings { + /* + * Used when the status of something is successful + */ + Success = 0, + /* + * Used when the status of something has a warning + */ + Waring = 1, + /* + * Used when the status of something has an error + */ + Error = 2, +} +``` diff --git a/docs/enum-vs-union-types.md b/docs/enum-vs-union-types.md new file mode 100644 index 00000000..129bbdc2 --- /dev/null +++ b/docs/enum-vs-union-types.md @@ -0,0 +1,52 @@ +# Enums vs. Union types + +**Flag:** `--useUnionTypes` + +The OpenAPI spec allows you to define [enums](https://swagger.io/docs/specification/data-models/enums/) inside the +data model. By default, we convert these enums definitions to [TypeScript enums](https://www.typescriptlang.org/docs/handbook/enums.html). +However, these enums are merged inside the namespace of the model, this is unsupported by Babel, [see docs](https://babeljs.io/docs/en/babel-plugin-transform-typescript#impartial-namespace-support). +Because we also want to support projects that use Babel [@babel/plugin-transform-typescript](https://babeljs.io/docs/en/babel-plugin-transform-typescript), +we offer the flag `--useUnionTypes` to generate [union types](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types) +instead of the traditional enums. The difference can be seen below: + +**Enums:** +```typescript +// Model +export type Order = { + id?: number; + quantity?: number; + status?: Order.status; +}; + +export namespace Order { + export enum status { + PLACED = 'placed', + APPROVED = 'approved', + DELIVERED = 'delivered', + } +} + +// Usage +const order: Order = { + id: 1, + quantity: 40, + status: Order.status.PLACED, +}; +``` + +**Union Types:** +```typescript +// Model +export type Order = { + id?: number; + quantity?: number; + status?: 'placed' | 'approved' | 'delivered'; +}; + +// Usage +const order: Order = { + id: 1, + quantity: 40, + status: 'placed', +}; +``` diff --git a/docs/external-references.md b/docs/external-references.md new file mode 100644 index 00000000..ce0544b5 --- /dev/null +++ b/docs/external-references.md @@ -0,0 +1,26 @@ +# External references + +Local references to schema definitions (those beginning with `#/definitions/schemas/`) +will be converted to type references to the equivalent, generated top-level type. + +The OpenAPI generator also supports external references, which allows you to break +down your openapi.yml into multiple sub-files, or incorporate third-party schemas +as part of your types to ensure everything is able to be TypeScript generated. + +External references may be: +* *relative references* - references to other files at the same location e.g. + `{ $ref: 'schemas/customer.yml' }` +* *remote references* - fully qualified references to another remote location + e.g. `{ $ref: 'https://myexampledomain.com/schemas/customer_schema.yml' }` + + For remote references, both files (when the file is on the current filesystem) + and http(s) URLs are supported. + +External references may also contain internal paths in the external schema (e.g. +`schemas/collection.yml#/definitions/schemas/Customer`) and back-references to +the base openapi file or between files (so that you can reference another +schema in the main file as a type of an object or array property, for example). + +At start-up, an OpenAPI or Swagger file with external references will be "bundled", +so that all external references and back-references will be resolved (but local +references preserved). diff --git a/docs/node-fetch-support.md b/docs/node-fetch-support.md new file mode 100644 index 00000000..2b24035b --- /dev/null +++ b/docs/node-fetch-support.md @@ -0,0 +1,23 @@ +# Node-Fetch support + +By default, this tool will generate a client that is compatible with the (browser based) Fetch API. +However, this client will not work inside the Node.js environment. If you want to generate the Node.js compatible +client then you can specify `--client node` in the openapi call: + +`openapi --input ./spec.json --output ./generated --client node` + +This will generate a client that uses [`node-fetch`](https://www.npmjs.com/package/node-fetch) internally. However, +in order to compile and run this client, you might need to install the `node-fetch@2.x` dependencies. + +> Since version 3.x [`node-fetch`](https://www.npmjs.com/package/node-fetch) switched to ESM only, +> breaking many CommonJS based toolchains (like Jest). Right now we do not support this new version! + +``` +npm install @types/node-fetch@2.x --save-dev +npm install abort-controller@3.x --save-dev +npm install form-data@4.x --save-dev +npm install node-fetch@2.x --save-dev +``` + +In order to compile the project and resolve the imports, you will need to enable the `allowSyntheticDefaultImports` +in your `tsconfig.json` file. diff --git a/docs/nullable-props.md b/docs/nullable-props.md new file mode 100644 index 00000000..21d1bb48 --- /dev/null +++ b/docs/nullable-props.md @@ -0,0 +1,37 @@ +# Nullable props (OpenAPI v2) + +In the OpenAPI v3 spec you can create properties that can be `NULL`, by providing a `nullable: true` in your schema. +However, the v2 spec does not allow you to do this. You can use the unofficial `x-nullable` in your specification +to generate nullable properties in OpenApi v2. + +```json +{ + "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 + } + } + } +} +``` + +Generated code: +```typescript +export type ModelWithNullableString = { + prop?: string | null; + requiredProp: string | null; +}; +``` diff --git a/docs/openapi-object.md b/docs/openapi-object.md new file mode 100644 index 00000000..8417fcbd --- /dev/null +++ b/docs/openapi-object.md @@ -0,0 +1,49 @@ +# OpenAPI object + +The library exposes a global OpenAPI object that can be used to configure the requests, +below you can find the properties and their usage. + +**Example:** + +```typescript +export const OpenAPI: OpenAPIConfig = { + BASE: 'http://localhost:3000/my-api', + VERSION: '1.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; +``` + +## Properties + +`OpenAPI.BASE` +Test + +`OpenAPI.VERSION` +Test + +`OpenAPI.WITH_CREDENTIALS` +Test + +`OpenAPI.CREDENTIALS` +Test + +`OpenAPI.TOKEN` +Test + +`OpenAPI.USERNAME` +Test + +`OpenAPI.PASSWORD` +Test + +`OpenAPI.HEADERS` +Test + +`OpenAPI.ENCODE_PATH` +Test diff --git a/docs/runtime-schemas.md b/docs/runtime-schemas.md new file mode 100644 index 00000000..f40be5c7 --- /dev/null +++ b/docs/runtime-schemas.md @@ -0,0 +1,113 @@ +# Runtime schemas + +**Flag:** `--exportSchemas` + +By default, the OpenAPI generator only exports interfaces for your models. These interfaces will help you during +development, but will not be available in JavaScript during runtime. However, Swagger allows you to define properties +that can be useful during runtime, for instance: `maxLength` of a string or a `pattern` to match, etc. Let's say +we have the following model: + +```json +{ + "MyModel": { + "required": [ + "key", + "name" + ], + "type": "object", + "properties": { + "key": { + "maxLength": 64, + "pattern": "^[a-zA-Z0-9_]*$", + "type": "string" + }, + "name": { + "maxLength": 255, + "type": "string" + }, + "enabled": { + "type": "boolean", + "readOnly": true + }, + "modified": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + } +} +``` + +This will generate the following interface: + +```typescript +export type MyModel = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; +} +``` + +The interface does not contain any properties like `maxLength` or `pattern`. However, they could be useful +if we wanted to create some form where a user could create such a model. In that form you would iterate +over the properties to render form fields based on their type and validate the input based on the `maxLength` +or `pattern` property. This requires us to have this information somewhere... For this we can use the +flag `--exportSchemas` to generate a runtime model next to the normal interface: + +```typescript +export const $MyModel = { + properties: { + key: { + type: 'string', + isRequired: true, + maxLength: 64, + pattern: '^[a-zA-Z0-9_]*$', + }, + name: { + type: 'string', + isRequired: true, + maxLength: 255, + }, + enabled: { + type: 'boolean', + isReadOnly: true, + }, + modified: { + type: 'string', + isReadOnly: true, + format: 'date-time', + }, + }, +} as const; +``` + +These runtime object are prefixed with a `$` character and expose all the interesting attributes of a model +and its properties. We can now use this object to generate the form: + +```typescript jsx +import { $MyModel } from './generated'; + +// Some pseudo code to iterate over the properties and return a form field +// the form field could be some abstract component that renders the correct +// field type and validation rules based on the given input. +const formFields = Object.entries($MyModel.properties) + .map(([key, value]) => ( + + )); + +const MyForm = () => ( +
+ {formFields} +
+); + +``` diff --git a/test/index.js b/test/index.js index 548506a4..e644b5eb 100644 --- a/test/index.js +++ b/test/index.js @@ -57,8 +57,8 @@ const generateRealWorldSpecs = async () => { }; const main = async () => { - await generate('./test/spec/v2.json', './test/generated/v2/'); - await generate('./test/spec/v3.json', './test/generated/v3/'); + await generate('./test/spec/aap.json', './test/generated/aap/'); + // await generate('./test/spec/v3.json', './test/generated/v3/'); // await generateRealWorldSpecs(); }; diff --git a/test/spec/aap.json b/test/spec/aap.json new file mode 100644 index 00000000..00b10459 --- /dev/null +++ b/test/spec/aap.json @@ -0,0 +1,28 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Header Problem", + "version": "1.0.0" + }, + "paths": {}, + "components": { + "headers": { + "Generic-Header": { + "description": "Generic-Header description", + "schema": { + "type": "string" + }, + "example": "123", + "required": true + } + }, + "schemas": { + "Generic-Schema": { + "description": "Generic-Schema description", + "type": "string", + "example": "234", + "required": true + } + } + } +}