mirror of
https://github.com/ferdikoomen/openapi-typescript-codegen.git
synced 2025-12-08 20:16:21 +00:00
Merge pull request #944 from ferdikoomen/feature/angular
Feature/angular
This commit is contained in:
commit
8d5e458c08
482
README.md
482
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
|
||||
@ -28,7 +28,6 @@
|
||||
npm install openapi-typescript-codegen --save-dev
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
@ -40,7 +39,7 @@ $ openapi --help
|
||||
-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, axios, node] (default: "fetch")
|
||||
-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
|
||||
@ -54,468 +53,29 @@ $ 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]) => (
|
||||
<FormField
|
||||
name={key}
|
||||
type={value.type}
|
||||
format={value.format}
|
||||
maxLength={value.maxLength}
|
||||
pattern={value.pattern}
|
||||
isReadOnly={value.isReadOnly}
|
||||
/>
|
||||
));
|
||||
|
||||
const MyForm = () => (
|
||||
<form>
|
||||
{formFields}
|
||||
</form>
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
|
||||
### 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
|
||||
===
|
||||
- [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)
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
### 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
|
||||
[npm-image]: https://img.shields.io/npm/v/openapi-typescript-codegen.svg
|
||||
|
||||
@ -12,7 +12,7 @@ const params = program
|
||||
.version(pkg.version)
|
||||
.requiredOption('-i, --input <value>', 'OpenAPI specification, can be a path, url or string content (required)')
|
||||
.requiredOption('-o, --output <value>', 'Output directory (required)')
|
||||
.option('-c, --client <value>', 'HTTP client to generate [fetch, xhr, node, axios]', 'fetch')
|
||||
.option('-c, --client <value>', 'HTTP client to generate [fetch, xhr, node, axios, angular]', 'fetch')
|
||||
.option('--name <value>', 'Custom client class name')
|
||||
.option('--useOptions', 'Use options instead of arguments')
|
||||
.option('--useUnionTypes', 'Use union types instead of enums')
|
||||
|
||||
84
docs/angular-support.md
Normal file
84
docs/angular-support.md
Normal file
@ -0,0 +1,84 @@
|
||||
# Angular support
|
||||
|
||||
This tool allows you to generate a client based on the [`Angular HttpClient`](https://angular.io/guide/http).
|
||||
The generated services are fully injectable and make use of the [RxJS](https://rxjs.dev/) Observer pattern.
|
||||
If you want to generate the Angular based client then you can specify `--client angular` in the openapi call:
|
||||
|
||||
`openapi --input ./spec.json --output ./generated --client angular`
|
||||
|
||||
The Angular client has been tested with the following versions:
|
||||
|
||||
```
|
||||
"@angular/common": "13.1.3",
|
||||
"@angular/core": "13.1.3",
|
||||
"rxjs": "7.5.2",
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
In the AppModule you can import the services and add them to the list of injectable services:
|
||||
|
||||
```typescript
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { OrganizationService } from './generated/services/OrganizationService';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
HttpClientModule,
|
||||
],
|
||||
providers: [
|
||||
OrganizationService,
|
||||
],
|
||||
bootstrap: [
|
||||
AppComponent,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
platformBrowserDynamic()
|
||||
.bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
```
|
||||
|
||||
Inside the component you can inject the service and just use it as you would with any observable:
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import type { OrganizationService } from './generated/services/OrganizationService';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `<div>Angular is ready</div>`,
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor(private readonly organizationService: OrganizationService) {
|
||||
|
||||
// Make a call
|
||||
this.organizationService
|
||||
.createOrganization({
|
||||
name: 'OrgName',
|
||||
description: 'OrgDescription',
|
||||
})
|
||||
.subscribe(organization => {
|
||||
console.log(organization);
|
||||
});
|
||||
|
||||
// Or even map result and retry on error
|
||||
this.organizationService
|
||||
.getOrganizations()
|
||||
.pipe(
|
||||
map(organizations => organizations[0]),
|
||||
retryWhen(error => error)
|
||||
)
|
||||
.subscribe(organization => {
|
||||
console.log(organization);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
37
docs/arguments-vs-object-style.md
Normal file
37
docs/arguments-vs-object-style.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 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'
|
||||
});
|
||||
```
|
||||
24
docs/authorization.md
Normal file
24
docs/authorization.md
Normal file
@ -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;
|
||||
```
|
||||
18
docs/axios-support.md
Normal file
18
docs/axios-support.md
Normal file
@ -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.
|
||||
19
docs/babel-support.md
Normal file
19
docs/babel-support.md
Normal file
@ -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,
|
||||
}],
|
||||
],
|
||||
};
|
||||
```
|
||||
61
docs/basic-usage.md
Normal file
61
docs/basic-usage.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Basic 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: "5")
|
||||
--postfix <value> Service name postfix (default: "Service")
|
||||
--request <value> 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',
|
||||
});
|
||||
```
|
||||
27
docs/client-instances.md
Normal file
27
docs/client-instances.md
Normal file
@ -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',
|
||||
});
|
||||
```
|
||||
47
docs/custom-enums.md
Normal file
47
docs/custom-enums.md
Normal file
@ -0,0 +1,47 @@
|
||||
# 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,
|
||||
}
|
||||
```
|
||||
54
docs/enum-vs-union-types.md
Normal file
54
docs/enum-vs-union-types.md
Normal file
@ -0,0 +1,54 @@
|
||||
# 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',
|
||||
};
|
||||
```
|
||||
28
docs/external-references.md
Normal file
28
docs/external-references.md
Normal file
@ -0,0 +1,28 @@
|
||||
# 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).
|
||||
23
docs/node-fetch-support.md
Normal file
23
docs/node-fetch-support.md
Normal file
@ -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.
|
||||
38
docs/nullable-props.md
Normal file
38
docs/nullable-props.md
Normal file
@ -0,0 +1,38 @@
|
||||
# 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;
|
||||
};
|
||||
```
|
||||
144
docs/openapi-object.md
Normal file
144
docs/openapi-object.md
Normal file
@ -0,0 +1,144 @@
|
||||
# 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/api',
|
||||
VERSION: '2.0',
|
||||
WITH_CREDENTIALS: false,
|
||||
CREDENTIALS: 'include',
|
||||
TOKEN: undefined,
|
||||
USERNAME: undefined,
|
||||
PASSWORD: undefined,
|
||||
HEADERS: undefined,
|
||||
ENCODE_PATH: undefined,
|
||||
};
|
||||
```
|
||||
|
||||
Properties
|
||||
===
|
||||
|
||||
### `OpenAPI.BASE`
|
||||
|
||||
The base path of the OpenAPI server, this is generated from the spec,
|
||||
but can be overwritten to switch servers.
|
||||
|
||||
```typescript
|
||||
if (process.env === 'development') {
|
||||
OpenAPI.BASE = 'http://staging.company.com:3000/api';
|
||||
}
|
||||
if (process.env === 'production') {
|
||||
OpenAPI.BASE = '/api';
|
||||
}
|
||||
```
|
||||
|
||||
### `OpenAPI.VERSION`
|
||||
|
||||
The version param in the OpenAPI paths `{api-version}`. The version is taken from the spec,
|
||||
but can be updated to call multiple versions of the same OpenAPI backend.
|
||||
|
||||
### `OpenAPI.WITH_CREDENTIALS`
|
||||
|
||||
Similar to the [withCredentials](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)
|
||||
property of the XHR specification. When set to true, cross-site requests should be made
|
||||
using credentials such as cookies, authorization headers, etc.
|
||||
|
||||
### `OpenAPI.CREDENTIALS`
|
||||
|
||||
Similar to the [credentials](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included)
|
||||
property of the Fetch specification. When `OpenAPI.WITH_CREDENTIALS` is set to true,
|
||||
this property controls the specific implementation for Fetch and Node-Fetch clients.
|
||||
Valid values are: `include`, `omit` and `same-origin`.
|
||||
|
||||
### `OpenAPI.TOKEN`
|
||||
|
||||
Set the Bearer authentication token to use for the requests. This needs to be a valid
|
||||
(non-expired) token, otherwise the request will fail. The property can be updated as often
|
||||
as you want, this is useful for scenario's where the token would automatically refresh
|
||||
after x minutes. This property also allows you to use an `async` method that will be resolved
|
||||
before requests are made.
|
||||
|
||||
```typescript
|
||||
OpenAPI.TOKEN = 'MY_TOKEN';
|
||||
|
||||
OpenAPI.TOKEN = async () => {
|
||||
// Note: loading this from a JSON file is not recommended ;-)
|
||||
const response = await fetch('configuration.json');
|
||||
const { token } = response.json();
|
||||
return token;
|
||||
};
|
||||
```
|
||||
|
||||
### `OpenAPI.USERNAME`
|
||||
|
||||
Set the basic authentication username, although not recommended, the basic authentication
|
||||
header is still supported. The username and password hash will be calculated by the client
|
||||
before sending the request. This property also allows you to use an `async` method that
|
||||
will be resolved before requests are made.
|
||||
|
||||
```typescript
|
||||
OpenAPI.USERNAME = 'john';
|
||||
|
||||
OpenAPI.USERNAME = async () => {
|
||||
// Note: loading this from a JSON file is not recommended ;-)
|
||||
const response = await fetch('configuration.json');
|
||||
const { username } = response.json();
|
||||
return username;
|
||||
};
|
||||
```
|
||||
|
||||
### `OpenAPI.PASSWORD`
|
||||
|
||||
Set the basic authentication password. See `OpenAPI.USERNAME` for more info.
|
||||
|
||||
```typescript
|
||||
OpenAPI.PASSWORD = 'welcome123';
|
||||
|
||||
OpenAPI.PASSWORD = async () => {
|
||||
// Note: loading this from a JSON file is not recommended ;-)
|
||||
const response = await fetch('configuration.json');
|
||||
const { password } = response.json();
|
||||
return password;
|
||||
};
|
||||
```
|
||||
|
||||
### `OpenAPI.HEADERS`
|
||||
|
||||
This property allows you to specify additional headers to send for each request. This can be useful
|
||||
for adding headers that are not generated through the spec. Or adding headers for tracking purposes.
|
||||
This property also allows you to use an `async` method that will be resolved before requests are made.
|
||||
|
||||
```typescript
|
||||
OpenAPI.HEADERS = {
|
||||
'x-navigator': window.navigator.appVersion,
|
||||
'x-environment': process.env,
|
||||
'last-modified': 'Wed, 21 Oct 2015 07:28:00 GMT',
|
||||
};
|
||||
|
||||
OpenAPI.HEADERS = async () => {
|
||||
// Note: loading this from a JSON file is not recommended ;-)
|
||||
const response = await fetch('configuration.json');
|
||||
const { headers } = response.json();
|
||||
return headers;
|
||||
};
|
||||
```
|
||||
|
||||
### `OpenAPI.ENCODE_PATH`
|
||||
|
||||
By default, all path parameters are encoded using the `encodeURI` method. This will convert invalid
|
||||
URL characters, for example spaces, backslashes, etc. However, you might want to make the encoding
|
||||
more strict due to security restrictions. So you can set this to `encodeURIComponent` to encode
|
||||
most non-alphanumerical characters to percentage encoding. Or set a customer encoder that just
|
||||
replaces some special characters.
|
||||
|
||||
```typescript
|
||||
OpenAPI.ENCODE_PATH = encodeURIComponent;
|
||||
|
||||
OpenAPI.ENCODE_PATH = (value: string) => {
|
||||
return value.replace(':', '_');
|
||||
};
|
||||
```
|
||||
113
docs/runtime-schemas.md
Normal file
113
docs/runtime-schemas.md
Normal file
@ -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]) => (
|
||||
<FormField
|
||||
name={key}
|
||||
type={value.type}
|
||||
format={value.format}
|
||||
maxLength={value.maxLength}
|
||||
pattern={value.pattern}
|
||||
isReadOnly={value.isReadOnly}
|
||||
/>
|
||||
));
|
||||
|
||||
const MyForm = () => (
|
||||
<form>
|
||||
{formFields}
|
||||
</form>
|
||||
);
|
||||
|
||||
```
|
||||
@ -20,18 +20,20 @@ const config: Config.InitialOptions = {
|
||||
'<rootDir>/test/e2e/v2.node.spec.ts',
|
||||
'<rootDir>/test/e2e/v2.axios.spec.ts',
|
||||
'<rootDir>/test/e2e/v2.babel.spec.ts',
|
||||
'<rootDir>/test/e2e/v2.angular.spec.ts',
|
||||
'<rootDir>/test/e2e/v3.fetch.spec.ts',
|
||||
'<rootDir>/test/e2e/v3.xhr.spec.ts',
|
||||
'<rootDir>/test/e2e/v3.node.spec.ts',
|
||||
'<rootDir>/test/e2e/v3.axios.spec.ts',
|
||||
'<rootDir>/test/e2e/v3.babel.spec.ts',
|
||||
'<rootDir>/test/e2e/v3.angular.spec.ts',
|
||||
'<rootDir>/test/e2e/client.fetch.spec.ts',
|
||||
'<rootDir>/test/e2e/client.xhr.spec.ts',
|
||||
'<rootDir>/test/e2e/client.node.spec.ts',
|
||||
'<rootDir>/test/e2e/client.axios.spec.ts',
|
||||
'<rootDir>/test/e2e/client.babel.spec.ts',
|
||||
],
|
||||
modulePathIgnorePatterns: ['<rootDir>/test/e2e/generated'],
|
||||
modulePathIgnorePatterns: ['<rootDir>/test/e2e/generated'],
|
||||
},
|
||||
],
|
||||
collectCoverageFrom: ['<rootDir>/src/**/*.ts', '!<rootDir>/src/**/*.d.ts', '!<rootDir>/bin', '!<rootDir>/dist'],
|
||||
|
||||
31
package.json
31
package.json
@ -51,24 +51,30 @@
|
||||
"test:update": "jest --selectProjects UNIT --updateSnapshot",
|
||||
"test:watch": "jest --selectProjects UNIT --watch",
|
||||
"test:coverage": "jest --selectProjects UNIT --coverage",
|
||||
"test:e2e": "jest --selectProjects E2E --runInBand",
|
||||
"test:e2e": "jest --selectProjects E2E --runInBand --verbose",
|
||||
"eslint": "eslint .",
|
||||
"eslint:fix": "eslint . --fix",
|
||||
"prepublishOnly": "yarn run clean && yarn run release",
|
||||
"codecov": "codecov --token=66c30c23-8954-4892-bef9-fbaed0a2e42b"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"abort-controller": "^3.0.0",
|
||||
"axios": "^0.25.0",
|
||||
"camelcase": "^6.3.0",
|
||||
"commander": "^8.3.0",
|
||||
"form-data": "^4.0.0",
|
||||
"handlebars": "^4.7.6",
|
||||
"json-schema-ref-parser": "^9.0.7",
|
||||
"node-fetch": "^2.6.6"
|
||||
"json-schema-ref-parser": "^9.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "13.1.4",
|
||||
"@angular/animations": "13.1.3",
|
||||
"@angular/cli": "13.1.4",
|
||||
"@angular/common": "13.1.3",
|
||||
"@angular/compiler": "13.1.3",
|
||||
"@angular/compiler-cli": "13.1.3",
|
||||
"@angular/core": "13.1.3",
|
||||
"@angular/forms": "13.1.3",
|
||||
"@angular/platform-browser": "13.1.3",
|
||||
"@angular/platform-browser-dynamic": "13.1.3",
|
||||
"@angular/router": "13.1.3",
|
||||
"@babel/cli": "7.16.8",
|
||||
"@babel/core": "7.16.12",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
@ -76,22 +82,29 @@
|
||||
"@rollup/plugin-commonjs": "21.0.1",
|
||||
"@rollup/plugin-node-resolve": "13.1.3",
|
||||
"@rollup/plugin-typescript": "8.3.0",
|
||||
"@types/cross-spawn": "6.0.2",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/glob": "7.2.0",
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/node": "17.0.12",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/qs": "6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "5.10.1",
|
||||
"@typescript-eslint/parser": "5.10.1",
|
||||
"abort-controller": "^3.0.0",
|
||||
"axios": "^0.25.0",
|
||||
"codecov": "3.8.3",
|
||||
"cross-spawn": "7.0.3",
|
||||
"eslint": "8.7.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"express": "4.17.2",
|
||||
"form-data": "^4.0.0",
|
||||
"glob": "7.2.0",
|
||||
"jest": "27.4.7",
|
||||
"jest-cli": "27.4.7",
|
||||
"node-fetch": "^2.6.6",
|
||||
"prettier": "2.5.1",
|
||||
"puppeteer": "13.1.2",
|
||||
"qs": "6.10.3",
|
||||
@ -99,8 +112,10 @@
|
||||
"rollup": "2.66.1",
|
||||
"rollup-plugin-node-externals": "3.1.2",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rxjs": "7.5.2",
|
||||
"ts-node": "10.4.0",
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.5.5"
|
||||
"typescript": "4.5.5",
|
||||
"zone.js": "0.11.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,4 +3,5 @@ export enum HttpClient {
|
||||
XHR = 'xhr',
|
||||
NODE = 'node',
|
||||
AXIOS = 'axios',
|
||||
ANGULAR = 'angular',
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { parse as parseV2 } from './openApi/v2';
|
||||
import { parse as parseV3 } from './openApi/v3';
|
||||
import { getOpenApiSpec } from './utils/getOpenApiSpec';
|
||||
import { getOpenApiVersion, OpenApiVersion } from './utils/getOpenApiVersion';
|
||||
import { isDefined } from './utils/isDefined';
|
||||
import { isString } from './utils/isString';
|
||||
import { postProcessClient } from './utils/postProcessClient';
|
||||
import { registerHandlebarTemplates } from './utils/registerHandlebarTemplates';
|
||||
@ -64,6 +65,10 @@ export const generate = async ({
|
||||
request,
|
||||
write = true,
|
||||
}: Options): Promise<void> => {
|
||||
if (httpClient === HttpClient.ANGULAR && isDefined(clientName)) {
|
||||
throw new Error('Angular client does not support --name property');
|
||||
}
|
||||
|
||||
const openApi = isString(input) ? await getOpenApiSpec(input) : input;
|
||||
const openApiVersion = getOpenApiVersion(openApi);
|
||||
const templates = registerHandlebarTemplates({
|
||||
|
||||
44
src/templates/core/angular/getHeaders.hbs
Normal file
44
src/templates/core/angular/getHeaders.hbs
Normal file
@ -0,0 +1,44 @@
|
||||
const getHeaders = (config: OpenAPIConfig, options: ApiRequestOptions): Observable<HttpHeaders> => {
|
||||
return forkJoin({
|
||||
token: resolve(options, config.TOKEN),
|
||||
username: resolve(options, config.USERNAME),
|
||||
password: resolve(options, config.PASSWORD),
|
||||
additionalHeaders: resolve(options, config.HEADERS),
|
||||
}).pipe(
|
||||
map(({ token, username, password, additionalHeaders }) => {
|
||||
const headers = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
})
|
||||
.filter(([_, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: String(value),
|
||||
}), {} as Record<string, string>);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers['Authorization'] = `Basic ${credentials}`;
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (options.mediaType) {
|
||||
headers['Content-Type'] = options.mediaType;
|
||||
} else if (isBlob(options.body)) {
|
||||
headers['Content-Type'] = options.body.type || 'application/octet-stream';
|
||||
} else if (isString(options.body)) {
|
||||
headers['Content-Type'] = 'text/plain';
|
||||
} else if (!isFormData(options.body)) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
return new HttpHeaders(headers);
|
||||
}),
|
||||
);
|
||||
};
|
||||
12
src/templates/core/angular/getRequestBody.hbs
Normal file
12
src/templates/core/angular/getRequestBody.hbs
Normal file
@ -0,0 +1,12 @@
|
||||
const getRequestBody = (options: ApiRequestOptions): any => {
|
||||
if (options.body) {
|
||||
if (options.mediaType?.includes('/json')) {
|
||||
return JSON.stringify(options.body)
|
||||
} else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) {
|
||||
return options.body;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
6
src/templates/core/angular/getResponseBody.hbs
Normal file
6
src/templates/core/angular/getResponseBody.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
const getResponseBody = <T>(response: HttpResponse<T>): T | undefined => {
|
||||
if (response.status !== 204 && response.body !== null) {
|
||||
return response.body;
|
||||
}
|
||||
return;
|
||||
};
|
||||
9
src/templates/core/angular/getResponseHeader.hbs
Normal file
9
src/templates/core/angular/getResponseHeader.hbs
Normal file
@ -0,0 +1,9 @@
|
||||
const getResponseHeader = <T>(response: HttpResponse<T>, responseHeader?: string): string | undefined => {
|
||||
if (responseHeader) {
|
||||
const value = response.headers.get(responseHeader);
|
||||
if (isString(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
109
src/templates/core/angular/request.hbs
Normal file
109
src/templates/core/angular/request.hbs
Normal file
@ -0,0 +1,109 @@
|
||||
{{>header}}
|
||||
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import type { HttpResponse, HttpErrorResponse } from '@angular/common/http';
|
||||
import { catchError, forkJoin, map, switchMap, of, throwError } from 'rxjs';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import { ApiError } from './ApiError';
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import type { ApiResult } from './ApiResult';
|
||||
import type { OpenAPIConfig } from './OpenAPI';
|
||||
|
||||
{{>functions/isDefined}}
|
||||
|
||||
|
||||
{{>functions/isString}}
|
||||
|
||||
|
||||
{{>functions/isStringWithValue}}
|
||||
|
||||
|
||||
{{>functions/isBlob}}
|
||||
|
||||
|
||||
{{>functions/isFormData}}
|
||||
|
||||
|
||||
{{>functions/base64}}
|
||||
|
||||
|
||||
{{>functions/getQueryString}}
|
||||
|
||||
|
||||
{{>functions/getUrl}}
|
||||
|
||||
|
||||
{{>functions/getFormData}}
|
||||
|
||||
|
||||
{{>functions/resolve}}
|
||||
|
||||
|
||||
{{>angular/getHeaders}}
|
||||
|
||||
|
||||
{{>angular/getRequestBody}}
|
||||
|
||||
|
||||
{{>angular/sendRequest}}
|
||||
|
||||
|
||||
{{>angular/getResponseHeader}}
|
||||
|
||||
|
||||
{{>angular/getResponseBody}}
|
||||
|
||||
|
||||
{{>functions/catchErrorCodes}}
|
||||
|
||||
|
||||
/**
|
||||
* Request method
|
||||
* @param config The OpenAPI configuration object
|
||||
* @param http The Angular HTTP client
|
||||
* @param options The request options from the service
|
||||
* @returns Observable<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
export const request = <T>(config: OpenAPIConfig, http: HttpClient, options: ApiRequestOptions): Observable<T> => {
|
||||
const url = getUrl(config, options);
|
||||
const formData = getFormData(options);
|
||||
const body = getRequestBody(options);
|
||||
|
||||
return getHeaders(config, options).pipe(
|
||||
switchMap(headers => {
|
||||
return sendRequest<T>(config, options, http, url, formData, body, headers);
|
||||
}),
|
||||
map(response => {
|
||||
const responseBody = getResponseBody(response);
|
||||
const responseHeader = getResponseHeader(response, options.responseHeader);
|
||||
return {
|
||||
url,
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: responseHeader ?? responseBody,
|
||||
} as ApiResult;
|
||||
}),
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
if (!error.status) {
|
||||
return throwError(error);
|
||||
}
|
||||
return of({
|
||||
url,
|
||||
ok: error.ok,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.error ?? error.statusText,
|
||||
} as ApiResult);
|
||||
}),
|
||||
map(result => {
|
||||
catchErrorCodes(options, result);
|
||||
return result.body as T;
|
||||
}),
|
||||
catchError((error: ApiError) => {
|
||||
return throwError(error);
|
||||
}),
|
||||
);
|
||||
};
|
||||
16
src/templates/core/angular/sendRequest.hbs
Normal file
16
src/templates/core/angular/sendRequest.hbs
Normal file
@ -0,0 +1,16 @@
|
||||
export const sendRequest = <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions,
|
||||
http: HttpClient,
|
||||
url: string,
|
||||
body: any,
|
||||
formData: FormData | undefined,
|
||||
headers: HttpHeaders
|
||||
): Observable<HttpResponse<T>> => {
|
||||
return http.request<T>(options.method, url, {
|
||||
headers,
|
||||
body: body ?? formData,
|
||||
withCredentials: config.WITH_CREDENTIALS,
|
||||
observe: 'response',
|
||||
});
|
||||
};
|
||||
@ -55,7 +55,7 @@ import type { OpenAPIConfig } from './OpenAPI';
|
||||
{{>axios/getResponseBody}}
|
||||
|
||||
|
||||
{{>functions/catchErrors}}
|
||||
{{>functions/catchErrorCodes}}
|
||||
|
||||
|
||||
/**
|
||||
@ -86,7 +86,7 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): C
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrors(options, result);
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ import type { OpenAPIConfig } from './OpenAPI';
|
||||
{{>fetch/getResponseBody}}
|
||||
|
||||
|
||||
{{>functions/catchErrors}}
|
||||
{{>functions/catchErrorCodes}}
|
||||
|
||||
|
||||
/**
|
||||
@ -83,7 +83,7 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): C
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrors(options, result);
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const catchErrors = (options: ApiRequestOptions, result: ApiResult): void => {
|
||||
const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
|
||||
const errors: Record<number, string> = {
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
@ -56,7 +56,7 @@ import type { OpenAPIConfig } from './OpenAPI';
|
||||
{{>node/getResponseBody}}
|
||||
|
||||
|
||||
{{>functions/catchErrors}}
|
||||
{{>functions/catchErrorCodes}}
|
||||
|
||||
|
||||
/**
|
||||
@ -87,7 +87,7 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): C
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrors(options, result);
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{{~#equals @root.httpClient 'fetch'}}{{>fetch/request}}{{/equals~}}
|
||||
{{~#equals @root.httpClient 'xhr'}}{{>xhr/request}}{{/equals~}}
|
||||
{{~#equals @root.httpClient 'axios'}}{{>axios/request}}{{/equals~}}
|
||||
{{~#equals @root.httpClient 'angular'}}{{>angular/request}}{{/equals~}}
|
||||
{{~#equals @root.httpClient 'node'}}{{>node/request}}{{/equals~}}
|
||||
|
||||
@ -55,7 +55,7 @@ import type { OpenAPIConfig } from './OpenAPI';
|
||||
{{>xhr/getResponseBody}}
|
||||
|
||||
|
||||
{{>functions/catchErrors}}
|
||||
{{>functions/catchErrorCodes}}
|
||||
|
||||
|
||||
/**
|
||||
@ -86,7 +86,7 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): C
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrors(options, result);
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
{{>header}}
|
||||
|
||||
{{#equals @root.httpClient 'angular'}}
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
{{/equals}}
|
||||
{{#if imports}}
|
||||
{{#each imports}}
|
||||
import type { {{{this}}} } from '../models/{{{this}}}';
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
{{#notEquals @root.httpClient 'angular'}}
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
{{/notEquals}}
|
||||
{{#if @root.exportClient}}
|
||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||
{{else}}
|
||||
@ -14,11 +22,18 @@ import { OpenAPI } from '../core/OpenAPI';
|
||||
import { request as __request } from '../core/request';
|
||||
{{/if}}
|
||||
|
||||
{{#equals @root.httpClient 'angular'}}
|
||||
@Injectable()
|
||||
{{/equals}}
|
||||
export class {{{name}}}{{{@root.postfix}}} {
|
||||
{{#if @root.exportClient}}
|
||||
|
||||
constructor(private readonly httpRequest: BaseHttpRequest) {}
|
||||
{{/if}}
|
||||
{{#equals @root.httpClient 'angular'}}
|
||||
|
||||
constructor(private readonly http: HttpClient) {}
|
||||
{{/equals}}
|
||||
|
||||
{{#each operations}}
|
||||
/**
|
||||
@ -47,8 +62,13 @@ export class {{{name}}}{{{@root.postfix}}} {
|
||||
public {{{name}}}({{>parameters}}): CancelablePromise<{{>result}}> {
|
||||
return this.httpRequest.request({
|
||||
{{else}}
|
||||
{{#equals @root.httpClient 'angular'}}
|
||||
public {{{name}}}({{>parameters}}): Observable<{{>result}}> {
|
||||
return __request(OpenAPI, this.http, {
|
||||
{{else}}
|
||||
public static {{{name}}}({{>parameters}}): CancelablePromise<{{>result}}> {
|
||||
return __request(OpenAPI, {
|
||||
{{/equals}}
|
||||
{{/if}}
|
||||
method: '{{{method}}}',
|
||||
url: '{{{path}}}',
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
{{~#equals @root.httpClient 'fetch'}}Blob{{/equals~}}
|
||||
{{~#equals @root.httpClient 'xhr'}}Blob{{/equals~}}
|
||||
{{~#equals @root.httpClient 'axios'}}Blob{{/equals~}}
|
||||
{{~#equals @root.httpClient 'angular'}}Blob{{/equals~}}
|
||||
{{~#equals @root.httpClient 'node'}}Blob{{/equals~}}
|
||||
{{~else~}}
|
||||
{{{base}}}
|
||||
|
||||
@ -14,5 +14,7 @@ export const getHttpRequestName = (httpClient: HttpClient): string => {
|
||||
return 'NodeHttpRequest';
|
||||
case HttpClient.AXIOS:
|
||||
return 'AxiosHttpRequest';
|
||||
case HttpClient.ANGULAR:
|
||||
return 'AngularHttpRequest';
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,6 +2,12 @@ import Handlebars from 'handlebars/runtime';
|
||||
|
||||
import { HttpClient } from '../HttpClient';
|
||||
import templateClient from '../templates/client.hbs';
|
||||
import angularGetHeaders from '../templates/core/angular/getHeaders.hbs';
|
||||
import angularGetRequestBody from '../templates/core/angular/getRequestBody.hbs';
|
||||
import angularGetResponseBody from '../templates/core/angular/getResponseBody.hbs';
|
||||
import angularGetResponseHeader from '../templates/core/angular/getResponseHeader.hbs';
|
||||
import angularRequest from '../templates/core/angular/request.hbs';
|
||||
import angularSendRequest from '../templates/core/angular/sendRequest.hbs';
|
||||
import templateCoreApiError from '../templates/core/ApiError.hbs';
|
||||
import templateCoreApiRequestOptions from '../templates/core/ApiRequestOptions.hbs';
|
||||
import templateCoreApiResult from '../templates/core/ApiResult.hbs';
|
||||
@ -20,7 +26,7 @@ import fetchGetResponseHeader from '../templates/core/fetch/getResponseHeader.hb
|
||||
import fetchRequest from '../templates/core/fetch/request.hbs';
|
||||
import fetchSendRequest from '../templates/core/fetch/sendRequest.hbs';
|
||||
import functionBase64 from '../templates/core/functions/base64.hbs';
|
||||
import functionCatchErrors from '../templates/core/functions/catchErrors.hbs';
|
||||
import functionCatchErrorCodes from '../templates/core/functions/catchErrorCodes.hbs';
|
||||
import functionGetFormData from '../templates/core/functions/getFormData.hbs';
|
||||
import functionGetQueryString from '../templates/core/functions/getQueryString.hbs';
|
||||
import functionGetUrl from '../templates/core/functions/getUrl.hbs';
|
||||
@ -161,7 +167,7 @@ export const registerHandlebarTemplates = (root: {
|
||||
Handlebars.registerPartial('base', Handlebars.template(partialBase));
|
||||
|
||||
// Generic functions used in 'request' file @see src/templates/core/request.hbs for more info
|
||||
Handlebars.registerPartial('functions/catchErrors', Handlebars.template(functionCatchErrors));
|
||||
Handlebars.registerPartial('functions/catchErrorCodes', Handlebars.template(functionCatchErrorCodes));
|
||||
Handlebars.registerPartial('functions/getFormData', Handlebars.template(functionGetFormData));
|
||||
Handlebars.registerPartial('functions/getQueryString', Handlebars.template(functionGetQueryString));
|
||||
Handlebars.registerPartial('functions/getUrl', Handlebars.template(functionGetUrl));
|
||||
@ -206,5 +212,13 @@ export const registerHandlebarTemplates = (root: {
|
||||
Handlebars.registerPartial('axios/sendRequest', Handlebars.template(axiosSendRequest));
|
||||
Handlebars.registerPartial('axios/request', Handlebars.template(axiosRequest));
|
||||
|
||||
// Specific files for the angular client implementation
|
||||
Handlebars.registerPartial('angular/getHeaders', Handlebars.template(angularGetHeaders));
|
||||
Handlebars.registerPartial('angular/getRequestBody', Handlebars.template(angularGetRequestBody));
|
||||
Handlebars.registerPartial('angular/getResponseBody', Handlebars.template(angularGetResponseBody));
|
||||
Handlebars.registerPartial('angular/getResponseHeader', Handlebars.template(angularGetResponseHeader));
|
||||
Handlebars.registerPartial('angular/sendRequest', Handlebars.template(angularSendRequest));
|
||||
Handlebars.registerPartial('angular/request', Handlebars.template(angularRequest));
|
||||
|
||||
return templates;
|
||||
};
|
||||
|
||||
@ -469,7 +469,7 @@ const getResponseBody = async (response: Response): Promise<any> => {
|
||||
return;
|
||||
};
|
||||
|
||||
const catchErrors = (options: ApiRequestOptions, result: ApiResult): void => {
|
||||
const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
|
||||
const errors: Record<number, string> = {
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
@ -519,7 +519,7 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): C
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrors(options, result);
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
@ -3370,7 +3370,7 @@ const getResponseBody = async (response: Response): Promise<any> => {
|
||||
return;
|
||||
};
|
||||
|
||||
const catchErrors = (options: ApiRequestOptions, result: ApiResult): void => {
|
||||
const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
|
||||
const errors: Record<number, string> = {
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
@ -3420,7 +3420,7 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): C
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrors(options, result);
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
|
||||
91
test/e2e/assets/main-angular.ts
Normal file
91
test/e2e/assets/main-angular.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { OpenAPI } from './core/OpenAPI';
|
||||
import { CollectionFormatService } from './services/CollectionFormatService';
|
||||
import { ComplexService } from './services/ComplexService';
|
||||
import { DefaultService } from './services/DefaultService';
|
||||
import { DefaultsService } from './services/DefaultsService';
|
||||
import { DuplicateService } from './services/DuplicateService';
|
||||
import { ErrorService } from './services/ErrorService';
|
||||
import { HeaderService } from './services/HeaderService';
|
||||
import { MultipleTags1Service } from './services/MultipleTags1Service';
|
||||
import { MultipleTags2Service } from './services/MultipleTags2Service';
|
||||
import { MultipleTags3Service } from './services/MultipleTags3Service';
|
||||
import { NoContentService } from './services/NoContentService';
|
||||
import { ParametersService } from './services/ParametersService';
|
||||
import { ResponseService } from './services/ResponseService';
|
||||
import { SimpleService } from './services/SimpleService';
|
||||
import { TypesService } from './services/TypesService';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `<div>Angular is ready</div>`,
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor(
|
||||
private readonly collectionFormatService: CollectionFormatService,
|
||||
private readonly complexService: ComplexService,
|
||||
private readonly defaultService: DefaultService,
|
||||
private readonly defaultsService: DefaultsService,
|
||||
private readonly duplicateService: DuplicateService,
|
||||
private readonly errorService: ErrorService,
|
||||
private readonly headerService: HeaderService,
|
||||
private readonly multipleTags1Service: MultipleTags1Service,
|
||||
private readonly multipleTags2Service: MultipleTags2Service,
|
||||
private readonly multipleTags3Service: MultipleTags3Service,
|
||||
private readonly noContentService: NoContentService,
|
||||
private readonly parametersService: ParametersService,
|
||||
private readonly responseService: ResponseService,
|
||||
private readonly simpleService: SimpleService,
|
||||
private readonly typesService: TypesService
|
||||
) {
|
||||
(window as any).api = {
|
||||
OpenAPI,
|
||||
CollectionFormatService: collectionFormatService,
|
||||
ComplexService: complexService,
|
||||
DefaultService: defaultService,
|
||||
DefaultsService: defaultsService,
|
||||
DuplicateService: duplicateService,
|
||||
ErrorService: errorService,
|
||||
HeaderService: headerService,
|
||||
MultipleTags1Service: multipleTags1Service,
|
||||
MultipleTags2Service: multipleTags2Service,
|
||||
MultipleTags3Service: multipleTags3Service,
|
||||
NoContentService: noContentService,
|
||||
ParametersService: parametersService,
|
||||
ResponseService: responseService,
|
||||
SimpleService: simpleService,
|
||||
TypesService: typesService,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, HttpClientModule],
|
||||
providers: [
|
||||
CollectionFormatService,
|
||||
ComplexService,
|
||||
DefaultService,
|
||||
DefaultsService,
|
||||
DuplicateService,
|
||||
ErrorService,
|
||||
HeaderService,
|
||||
MultipleTags1Service,
|
||||
MultipleTags2Service,
|
||||
MultipleTags3Service,
|
||||
NoContentService,
|
||||
ParametersService,
|
||||
ResponseService,
|
||||
SimpleService,
|
||||
TypesService,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
platformBrowserDynamic()
|
||||
.bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
@ -3,7 +3,7 @@ import { compileWithTypescript } from './scripts/compileWithTypescript';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v3.node', () => {
|
||||
describe('client.axios', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('client/axios');
|
||||
await generateClient('client/axios', 'v3', 'axios', false, false, 'AppClient');
|
||||
@ -83,4 +83,68 @@ describe('v3.node', () => {
|
||||
}
|
||||
expect(error).toContain('Request aborted');
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
let error;
|
||||
try {
|
||||
const { AppClient } = require('./generated/client/axios/index.js');
|
||||
const client = new AppClient();
|
||||
await client.error.testErrorCode(500);
|
||||
} catch (e) {
|
||||
const err = e as any;
|
||||
error = JSON.stringify({
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
url: err.url,
|
||||
status: err.status,
|
||||
statusText: err.statusText,
|
||||
body: err.body,
|
||||
});
|
||||
}
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
let error;
|
||||
try {
|
||||
const { AppClient } = require('./generated/client/axios/index.js');
|
||||
const client = new AppClient();
|
||||
await client.error.testErrorCode(409);
|
||||
} catch (e) {
|
||||
const err = e as any;
|
||||
error = JSON.stringify({
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
url: err.url,
|
||||
status: err.status,
|
||||
statusText: err.statusText,
|
||||
body: err.body,
|
||||
});
|
||||
}
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import { copyAsset } from './scripts/copyAsset';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v3.babel', () => {
|
||||
describe('client.babel', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('client/babel');
|
||||
await generateClient('client/babel', 'v3', 'fetch', true, true, 'AppClient');
|
||||
@ -53,13 +53,123 @@ describe('v3.babel', () => {
|
||||
const { AppClient } = (window as any).api;
|
||||
const client = new AppClient();
|
||||
return await client.complex.complexTypes({
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
parameterObject: {
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('support form data', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
const { AppClient } = (window as any).api;
|
||||
const client = new AppClient();
|
||||
return await client.parameters.callWithParameters({
|
||||
parameterHeader: 'valueHeader',
|
||||
parameterQuery: 'valueQuery',
|
||||
parameterForm: 'valueForm',
|
||||
parameterCookie: 'valueCookie',
|
||||
parameterPath: 'valuePath',
|
||||
requestBody: {
|
||||
prop: 'valueBody',
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
let error;
|
||||
try {
|
||||
await browser.evaluate(async () => {
|
||||
const { AppClient } = (window as any).api;
|
||||
const client = new AppClient();
|
||||
const promise = client.simple.getCallWithoutParametersAndResponse();
|
||||
setTimeout(() => {
|
||||
promise.cancel();
|
||||
}, 10);
|
||||
await promise;
|
||||
});
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
}
|
||||
expect(error).toContain('CancelError: Request aborted');
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { AppClient } = (window as any).api;
|
||||
const client = new AppClient();
|
||||
await client.error.testErrorCode({
|
||||
status: 500,
|
||||
});
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { AppClient } = (window as any).api;
|
||||
const client = new AppClient();
|
||||
await client.error.testErrorCode({
|
||||
status: 409,
|
||||
});
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import { copyAsset } from './scripts/copyAsset';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v3.fetch', () => {
|
||||
describe('client.fetch', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('client/fetch');
|
||||
await generateClient('client/fetch', 'v3', 'fetch', false, false, 'AppClient');
|
||||
@ -126,7 +126,10 @@ describe('v3.fetch', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -157,7 +160,10 @@ describe('v3.fetch', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import { compileWithTypescript } from './scripts/compileWithTypescript';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v3.node', () => {
|
||||
describe('client.node', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('client/node');
|
||||
await generateClient('client/node', 'v3', 'node', false, false, 'AppClient');
|
||||
@ -108,7 +108,10 @@ describe('v3.node', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -137,7 +140,10 @@ describe('v3.node', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import { copyAsset } from './scripts/copyAsset';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v3.xhr', () => {
|
||||
describe('client.xhr', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('client/xhr');
|
||||
await generateClient('client/xhr', 'v3', 'xhr', false, false, 'AppClient');
|
||||
@ -63,6 +63,24 @@ describe('v3.xhr', () => {
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('support form data', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
const { AppClient } = (window as any).api;
|
||||
const client = new AppClient();
|
||||
return await client.parameters.callWithParameters(
|
||||
'valueHeader',
|
||||
'valueQuery',
|
||||
'valueForm',
|
||||
'valueCookie',
|
||||
'valuePath',
|
||||
{
|
||||
prop: 'valueBody',
|
||||
}
|
||||
);
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
let error;
|
||||
try {
|
||||
@ -107,7 +125,10 @@ describe('v3.xhr', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -138,7 +159,10 @@ describe('v3.xhr', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
24
test/e2e/scripts/buildAngularProject.ts
Normal file
24
test/e2e/scripts/buildAngularProject.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { sync } from 'cross-spawn';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
|
||||
export const buildAngularProject = (dir: string, name: string, output: string) => {
|
||||
const cwd = `./test/e2e/generated/${dir}/${name}/`;
|
||||
sync(
|
||||
'ng',
|
||||
[
|
||||
'build',
|
||||
'--output-path',
|
||||
output,
|
||||
'--optimization',
|
||||
'false',
|
||||
'--configuration',
|
||||
'development',
|
||||
'--source-map',
|
||||
'false',
|
||||
],
|
||||
{
|
||||
cwd: resolvePath(cwd),
|
||||
stdio: 'inherit',
|
||||
}
|
||||
);
|
||||
};
|
||||
43
test/e2e/scripts/createAngularProject.ts
Normal file
43
test/e2e/scripts/createAngularProject.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { sync } from 'cross-spawn';
|
||||
import { mkdirSync, rmSync } from 'fs';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
|
||||
export const createAngularProject = (dir: string, name: string) => {
|
||||
const cwd = `./test/e2e/generated/${dir}/`;
|
||||
mkdirSync(cwd, {
|
||||
recursive: true,
|
||||
});
|
||||
sync(
|
||||
'ng',
|
||||
[
|
||||
'new',
|
||||
name,
|
||||
'--minimal',
|
||||
'true',
|
||||
'--style',
|
||||
'css',
|
||||
'--inline-style',
|
||||
'true',
|
||||
'--inline-template',
|
||||
'true',
|
||||
'--routing',
|
||||
'false',
|
||||
'--skip-tests',
|
||||
'true',
|
||||
'--skip-install',
|
||||
'true',
|
||||
'--skip-git',
|
||||
'true',
|
||||
'--commit',
|
||||
'false',
|
||||
'--force',
|
||||
],
|
||||
{
|
||||
cwd: resolvePath(cwd),
|
||||
stdio: 'inherit',
|
||||
}
|
||||
);
|
||||
rmSync(`${cwd}/${name}/src/app/`, {
|
||||
recursive: true,
|
||||
});
|
||||
};
|
||||
@ -28,6 +28,25 @@ const start = async (dir: string) => {
|
||||
})
|
||||
);
|
||||
|
||||
// Serve static assets
|
||||
_app.get('/runtime.js', (req, res) => {
|
||||
res.sendFile(resolvePath(`./test/e2e/generated/${dir}/runtime.js`));
|
||||
});
|
||||
_app.get('/polyfills.js', (req, res) => {
|
||||
res.sendFile(resolvePath(`./test/e2e/generated/${dir}/polyfills.js`));
|
||||
});
|
||||
_app.get('/vendor.js', (req, res) => {
|
||||
res.sendFile(resolvePath(`./test/e2e/generated/${dir}/vendor.js`));
|
||||
});
|
||||
_app.get('/main.js', (req, res) => {
|
||||
res.sendFile(resolvePath(`./test/e2e/generated/${dir}/main.js`));
|
||||
});
|
||||
_app.get('/styles.css', (req, res) => {
|
||||
res.sendFile(resolvePath(`./test/e2e/generated/${dir}/styles.css`));
|
||||
});
|
||||
_app.get('/favicon.ico', (req, res) => {
|
||||
res.sendFile(resolvePath(`./test/e2e/generated/${dir}/favicon.ico`));
|
||||
});
|
||||
_app.get('/', (req, res) => {
|
||||
res.sendFile(resolvePath(`./test/e2e/generated/${dir}/index.html`));
|
||||
});
|
||||
@ -37,7 +56,10 @@ const start = async (dir: string) => {
|
||||
// See the spec files for more information.
|
||||
_app.all('/base/api/v1.0/error', (req, res) => {
|
||||
const status = parseInt(String(req.query.status));
|
||||
res.sendStatus(status);
|
||||
res.status(status).json({
|
||||
status,
|
||||
message: 'hello world',
|
||||
});
|
||||
});
|
||||
|
||||
// Register an 'echo' server that just returns all data from the API calls.
|
||||
|
||||
52
test/e2e/v2.angular.spec.ts
Normal file
52
test/e2e/v2.angular.spec.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import browser from './scripts/browser';
|
||||
import { buildAngularProject } from './scripts/buildAngularProject';
|
||||
import { cleanup } from './scripts/cleanup';
|
||||
import { copyAsset } from './scripts/copyAsset';
|
||||
import { createAngularProject } from './scripts/createAngularProject';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v2.angular', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('v2/angular');
|
||||
createAngularProject('v2/angular', 'app');
|
||||
await generateClient('v2/angular/app/src', 'v2', 'angular');
|
||||
copyAsset('main-angular.ts', 'v2/angular/app/src/main.ts');
|
||||
buildAngularProject('v2/angular', 'app', 'dist');
|
||||
await server.start('v2/angular/app/dist');
|
||||
await browser.start();
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN'));
|
||||
const result = await browser.evaluate(async () => {
|
||||
return await new Promise<any>(resolve => {
|
||||
const { OpenAPI, SimpleService } = (window as any).api;
|
||||
OpenAPI.TOKEN = (window as any).tokenRequest;
|
||||
SimpleService.getCallWithoutParametersAndResponse().subscribe(resolve);
|
||||
});
|
||||
});
|
||||
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
|
||||
});
|
||||
|
||||
it('supports complex params', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
return await new Promise<any>(resolve => {
|
||||
const { ComplexService } = (window as any).api;
|
||||
ComplexService.complexTypes({
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
},
|
||||
},
|
||||
}).subscribe(resolve);
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -3,7 +3,7 @@ import { compileWithTypescript } from './scripts/compileWithTypescript';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v2.node', () => {
|
||||
describe('v2.axios', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('v2/axios');
|
||||
await generateClient('v2/axios', 'v2', 'axios');
|
||||
@ -35,19 +35,4 @@ describe('v2.node', () => {
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
let error;
|
||||
try {
|
||||
const { SimpleService } = require('./generated/v2/axios/index.js');
|
||||
const promise = SimpleService.getCallWithoutParametersAndResponse();
|
||||
setTimeout(() => {
|
||||
promise.cancel();
|
||||
}, 10);
|
||||
await promise;
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
}
|
||||
expect(error).toContain('Request aborted');
|
||||
});
|
||||
});
|
||||
|
||||
@ -35,9 +35,11 @@ describe('v2.babel', () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
const { ComplexService } = (window as any).api;
|
||||
return await ComplexService.complexTypes({
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
parameterObject: {
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -44,82 +44,4 @@ describe('v2.fetch', () => {
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
let error;
|
||||
try {
|
||||
await browser.evaluate(async () => {
|
||||
const { SimpleService } = (window as any).api;
|
||||
const promise = SimpleService.getCallWithoutParametersAndResponse();
|
||||
setTimeout(() => {
|
||||
promise.cancel();
|
||||
}, 10);
|
||||
await promise;
|
||||
});
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
}
|
||||
expect(error).toContain('CancelError: Request aborted');
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { ErrorService } = (window as any).api;
|
||||
await ErrorService.testErrorCode(500);
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { ErrorService } = (window as any).api;
|
||||
await ErrorService.testErrorCode(409);
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -50,60 +50,4 @@ describe('v2.node', () => {
|
||||
}
|
||||
expect(error).toContain('Request aborted');
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
let error;
|
||||
try {
|
||||
const { ErrorService } = require('./generated/v2/node/index.js');
|
||||
await ErrorService.testErrorCode(500);
|
||||
} catch (e) {
|
||||
const err = e as any;
|
||||
error = JSON.stringify({
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
url: err.url,
|
||||
status: err.status,
|
||||
statusText: err.statusText,
|
||||
body: err.body,
|
||||
});
|
||||
}
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
let error;
|
||||
try {
|
||||
const { ErrorService } = require('./generated/v2/node/index.js');
|
||||
await ErrorService.testErrorCode(409);
|
||||
} catch (e) {
|
||||
const err = e as any;
|
||||
error = JSON.stringify({
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
url: err.url,
|
||||
status: err.status,
|
||||
statusText: err.statusText,
|
||||
body: err.body,
|
||||
});
|
||||
}
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -44,82 +44,4 @@ describe('v2.xhr', () => {
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
let error;
|
||||
try {
|
||||
await browser.evaluate(async () => {
|
||||
const { SimpleService } = (window as any).api;
|
||||
const promise = SimpleService.getCallWithoutParametersAndResponse();
|
||||
setTimeout(() => {
|
||||
promise.cancel();
|
||||
}, 10);
|
||||
await promise;
|
||||
});
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
}
|
||||
expect(error).toContain('CancelError: Request aborted');
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { ErrorService } = (window as any).api;
|
||||
await ErrorService.testErrorCode(500);
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { ErrorService } = (window as any).api;
|
||||
await ErrorService.testErrorCode(409);
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
158
test/e2e/v3.angular.spec.ts
Normal file
158
test/e2e/v3.angular.spec.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import browser from './scripts/browser';
|
||||
import { buildAngularProject } from './scripts/buildAngularProject';
|
||||
import { cleanup } from './scripts/cleanup';
|
||||
import { copyAsset } from './scripts/copyAsset';
|
||||
import { createAngularProject } from './scripts/createAngularProject';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v3.angular', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('v3/angular');
|
||||
createAngularProject('v3/angular', 'app');
|
||||
await generateClient('v3/angular/app/src', 'v3', 'angular');
|
||||
copyAsset('main-angular.ts', 'v3/angular/app/src/main.ts');
|
||||
buildAngularProject('v3/angular', 'app', 'dist');
|
||||
await server.start('v3/angular/app/dist');
|
||||
await browser.start();
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
await browser.exposeFunction('tokenRequest', jest.fn().mockResolvedValue('MY_TOKEN'));
|
||||
const result = await browser.evaluate(async () => {
|
||||
return await new Promise<any>(resolve => {
|
||||
const { OpenAPI, SimpleService } = (window as any).api;
|
||||
OpenAPI.TOKEN = (window as any).tokenRequest;
|
||||
OpenAPI.USERNAME = undefined;
|
||||
OpenAPI.PASSWORD = undefined;
|
||||
SimpleService.getCallWithoutParametersAndResponse().subscribe(resolve);
|
||||
});
|
||||
});
|
||||
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
|
||||
});
|
||||
|
||||
it('uses credentials', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
return await new Promise<any>(resolve => {
|
||||
const { OpenAPI, SimpleService } = (window as any).api;
|
||||
OpenAPI.TOKEN = undefined;
|
||||
OpenAPI.USERNAME = 'username';
|
||||
OpenAPI.PASSWORD = 'password';
|
||||
SimpleService.getCallWithoutParametersAndResponse().subscribe(resolve);
|
||||
});
|
||||
});
|
||||
expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ=');
|
||||
});
|
||||
|
||||
it('supports complex params', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
return await new Promise<any>(resolve => {
|
||||
const { ComplexService } = (window as any).api;
|
||||
ComplexService.complexTypes({
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
},
|
||||
},
|
||||
}).subscribe(resolve);
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('support form data', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
return await new Promise<any>(resolve => {
|
||||
const { ParametersService } = (window as any).api;
|
||||
ParametersService.callWithParameters(
|
||||
'valueHeader',
|
||||
'valueQuery',
|
||||
'valueForm',
|
||||
'valueCookie',
|
||||
'valuePath',
|
||||
{
|
||||
prop: 'valueBody',
|
||||
}
|
||||
).subscribe(resolve);
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
await new Promise<any>((resolve, reject) => {
|
||||
const { ErrorService } = (window as any).api;
|
||||
ErrorService.testErrorCode(500).subscribe(resolve, reject);
|
||||
});
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
const { ErrorService } = (window as any).api;
|
||||
ErrorService.testErrorCode(409).subscribe(console.log, console.log);
|
||||
try {
|
||||
await new Promise<any>((resolve, reject) => {
|
||||
// const { ErrorService } = (window as any).api;
|
||||
ErrorService.testErrorCode(409).subscribe(resolve, reject);
|
||||
});
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -3,7 +3,7 @@ import { compileWithTypescript } from './scripts/compileWithTypescript';
|
||||
import { generateClient } from './scripts/generateClient';
|
||||
import server from './scripts/server';
|
||||
|
||||
describe('v3.node', () => {
|
||||
describe('v3.axios', () => {
|
||||
beforeAll(async () => {
|
||||
cleanup('v3/axios');
|
||||
await generateClient('v3/axios', 'v3', 'axios');
|
||||
@ -76,4 +76,66 @@ describe('v3.node', () => {
|
||||
}
|
||||
expect(error).toContain('Request aborted');
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
let error;
|
||||
try {
|
||||
const { ErrorService } = require('./generated/v3/axios/index.js');
|
||||
await ErrorService.testErrorCode(500);
|
||||
} catch (e) {
|
||||
const err = e as any;
|
||||
error = JSON.stringify({
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
url: err.url,
|
||||
status: err.status,
|
||||
statusText: err.statusText,
|
||||
body: err.body,
|
||||
});
|
||||
}
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
let error;
|
||||
try {
|
||||
const { ErrorService } = require('./generated/v3/axios/index.js');
|
||||
await ErrorService.testErrorCode(409);
|
||||
} catch (e) {
|
||||
const err = e as any;
|
||||
error = JSON.stringify({
|
||||
name: err.name,
|
||||
message: err.message,
|
||||
url: err.url,
|
||||
status: err.status,
|
||||
statusText: err.statusText,
|
||||
body: err.body,
|
||||
});
|
||||
}
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -48,13 +48,120 @@ describe('v3.babel', () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
const { ComplexService } = (window as any).api;
|
||||
return await ComplexService.complexTypes({
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
parameterObject: {
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('support form data', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
const { ParametersService } = (window as any).api;
|
||||
return await ParametersService.callWithParameters({
|
||||
parameterHeader: 'valueHeader',
|
||||
parameterQuery: 'valueQuery',
|
||||
parameterForm: 'valueForm',
|
||||
parameterCookie: 'valueCookie',
|
||||
parameterPath: 'valuePath',
|
||||
requestBody: {
|
||||
prop: 'valueBody',
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
let error;
|
||||
try {
|
||||
await browser.evaluate(async () => {
|
||||
const { SimpleService } = (window as any).api;
|
||||
const promise = SimpleService.getCallWithoutParametersAndResponse();
|
||||
setTimeout(() => {
|
||||
promise.cancel();
|
||||
}, 10);
|
||||
await promise;
|
||||
});
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
}
|
||||
expect(error).toContain('CancelError: Request aborted');
|
||||
});
|
||||
|
||||
it('should throw known error (500)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { ErrorService } = (window as any).api;
|
||||
await ErrorService.testErrorCode({
|
||||
status: 500,
|
||||
});
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Custom message: Internal Server Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw unknown error (409)', async () => {
|
||||
const error = await browser.evaluate(async () => {
|
||||
try {
|
||||
const { ErrorService } = (window as any).api;
|
||||
await ErrorService.testErrorCode({
|
||||
status: 409,
|
||||
});
|
||||
} catch (e) {
|
||||
const error = e as any;
|
||||
return JSON.stringify({
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
url: error.url,
|
||||
status: error.status,
|
||||
statusText: error.statusText,
|
||||
body: error.body,
|
||||
});
|
||||
}
|
||||
return;
|
||||
});
|
||||
expect(error).toBe(
|
||||
JSON.stringify({
|
||||
name: 'ApiError',
|
||||
message: 'Generic Error',
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -118,7 +118,10 @@ describe('v3.fetch', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -148,7 +151,10 @@ describe('v3.fetch', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@ -100,7 +100,10 @@ describe('v3.node', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -128,7 +131,10 @@ describe('v3.node', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@ -58,6 +58,23 @@ describe('v3.xhr', () => {
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('support form data', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
const { ParametersService } = (window as any).api;
|
||||
return await ParametersService.callWithParameters(
|
||||
'valueHeader',
|
||||
'valueQuery',
|
||||
'valueForm',
|
||||
'valueCookie',
|
||||
'valuePath',
|
||||
{
|
||||
prop: 'valueBody',
|
||||
}
|
||||
);
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
let error;
|
||||
try {
|
||||
@ -100,7 +117,10 @@ describe('v3.xhr', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=500',
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
body: 'Internal Server Error',
|
||||
body: {
|
||||
status: 500,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -130,7 +150,10 @@ describe('v3.xhr', () => {
|
||||
url: 'http://localhost:3000/base/api/v1.0/error?status=409',
|
||||
status: 409,
|
||||
statusText: 'Conflict',
|
||||
body: 'Conflict',
|
||||
body: {
|
||||
status: 409,
|
||||
message: 'hello world',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@ -14,7 +14,8 @@
|
||||
"noImplicitAny": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
|
||||
"files": [
|
||||
|
||||
3
types/index.d.ts
vendored
3
types/index.d.ts
vendored
@ -3,6 +3,7 @@ export declare enum HttpClient {
|
||||
XHR = 'xhr',
|
||||
NODE = 'node',
|
||||
AXIOS = 'axios',
|
||||
ANGULAR = 'angular',
|
||||
}
|
||||
|
||||
export declare enum Indent {
|
||||
@ -14,7 +15,7 @@ export declare enum Indent {
|
||||
export type Options = {
|
||||
input: string | Record<string, any>;
|
||||
output: string;
|
||||
httpClient?: HttpClient | 'fetch' | 'xhr' | 'node' | 'axios';
|
||||
httpClient?: HttpClient | 'fetch' | 'xhr' | 'node' | 'axios' | 'angular';
|
||||
clientName?: string;
|
||||
useOptions?: boolean;
|
||||
useUnionTypes?: boolean;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user