diff --git a/README.md b/README.md index b851329a8..b16bc6d8e 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,19 @@ -# OpenAPI Typescript Codegen +# OpenAPI TypeScript Codegen -[![NPM][npm-image]][npm-url] -[![License][license-image]][license-url] -[![Coverage][coverage-image]][coverage-url] -[![Coverage][coverage-image]][coverage-url] -[![Downloads][downloads-image]][downloads-url] -[![Build][build-image]][build-url] +Fork of [ferdikoomen/openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen). See that repository for more documentation. -> Node.js library that generates Typescript clients based on the OpenAPI specification. +## List of changes +1. Headers are always optional, whether the OpenAPI JSON says they are required or not. +2. Added an optional config property called `ERROR_CALLBACK` which is a function that will run before API errors are thrown. +3. Ability to run code before or after each request using `BEFORE_REQUEST` and `BEFORE_REQUEST` config -## Why? -- Frontend ❤️ OpenAPI, but we do not want to use JAVA codegen in our builds -- Quick, lightweight, robust and framework-agnostic 🚀 -- Supports generation of TypeScript clients -- Supports generations of Fetch, Node-Fetch, Axios, Angular and XHR http clients -- Supports OpenAPI specification v2.0 and v3.0 -- Supports JSON and YAML files for input -- Supports generation through CLI, Node.js and NPX -- Supports tsc and @babel/plugin-transform-typescript -- Supports aborting of requests (cancelable promise pattern) -- Supports external references using [json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser/) +## Testing +1. Run `npm test` to run automated tests +2. If a snapshot test fails due to an intentional change, run `npm run test:update` to update the snapshots +3. To test local changes with your application, first run `npm run build`. Then, replace `npx @jegan321/openapi-typescript-codegen` with `node ../openapi-typescript-codegen/bin/index.js` in your application-side script. -## Install - -``` -npm install openapi-typescript-codegen --save-dev -``` - -## Usage - -``` -$ openapi --help - - Usage: openapi [options] - - Options: - -V, --version output the version number - -i, --input OpenAPI specification, can be a path, url or string content (required) - -o, --output Output directory (required) - -c, --client HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch") - --name Custom client class name - --useOptions Use options instead of arguments - --useUnionTypes Use union types instead of enums - --exportCore Write core files to disk (default: true) - --exportServices Write services to disk (default: true) - --exportModels Write models to disk (default: true) - --exportSchemas Write schemas to disk (default: false) - --indent Indentation options [4, 2, tab] (default: "4") - --postfixServices Service name postfix (default: "Service") - --postfixModels Model name postfix - --request Path to custom request file - -h, --help display help for command - - Examples - $ openapi --input ./spec.json --output ./generated - $ openapi --input ./spec.json --output ./generated --client xhr -``` - -## Docker usage - -To build the Docker container, execute the following command: - -``` -docker build . --tag openapi-typescript-codegen -``` - -After this is done, you can execute the CLI commands: - -``` -docker run openapi-typescript-codegen --help -docker run openapi-typescript-codegen --input sample.yaml --output client -``` - -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) -- [Canceling requests](docs/canceling-requests.md) -- [Custom request file](docs/custom-request-file.md) - -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 -[license-url]: LICENSE -[license-image]: http://img.shields.io/npm/l/openapi-typescript-codegen.svg -[coverage-url]: https://codecov.io/gh/ferdikoomen/openapi-typescript-codegen -[coverage-image]: https://img.shields.io/codecov/c/github/ferdikoomen/openapi-typescript-codegen.svg -[downloads-url]: http://npm-stat.com/charts.html?package=openapi-typescript-codegen -[downloads-image]: http://img.shields.io/npm/dm/openapi-typescript-codegen.svg -[build-url]: https://circleci.com/gh/ferdikoomen/openapi-typescript-codegen/tree/master -[build-image]: https://circleci.com/gh/ferdikoomen/openapi-typescript-codegen/tree/master.svg?style=svg +## Release +1. Update package.json with a new version +2. `npm run release` +3. `npm publish` +4. Commit changes diff --git a/package-lock.json b/package-lock.json index f9369c33e..798f8ec6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "openapi-typescript-codegen", + "name": "@jegan321/openapi-typescript-codegen", "version": "0.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "openapi-typescript-codegen", + "name": "@jegan321/openapi-typescript-codegen", "version": "0.25.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1f5bb214a..f0baf3df9 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,18 @@ { - "name": "openapi-typescript-codegen", - "version": "0.25.0", - "description": "Library that generates Typescript clients based on the OpenAPI specification.", - "author": "Ferdi Koomen", - "homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen", + "name": "@jegan321/openapi-typescript-codegen", + "version": "0.25.3", + "description": "Library that generates Typescript clients based on the OpenAPI specification. Fork of ferdikoomen/openapi-typescript-codegen", + "author": "John Egan", + "homepage": "https://github.com/jegan321/openapi-typescript-codegen", "repository": { "type": "git", - "url": "git+https://github.com/ferdikoomen/openapi-typescript-codegen.git" + "url": "git+https://github.com/jegan321/openapi-typescript-codegen.git" }, "bugs": { - "url": "https://github.com/ferdikoomen/openapi-typescript-codegen/issues" + "url": "https://github.com/jegan321/openapi-typescript-codegen/issues" + }, + "publishConfig": { + "access": "public" }, "license": "MIT", "keywords": [ @@ -25,12 +28,6 @@ "angular", "node" ], - "maintainers": [ - { - "name": "Ferdi Koomen", - "email": "info@madebyferdi.com" - } - ], "main": "dist/index.js", "types": "types/index.d.ts", "bin": { diff --git a/src/openApi/v2/parser/getOperationParameter.ts b/src/openApi/v2/parser/getOperationParameter.ts index 6b68c9d5a..1f9688ecb 100644 --- a/src/openApi/v2/parser/getOperationParameter.ts +++ b/src/openApi/v2/parser/getOperationParameter.ts @@ -24,7 +24,7 @@ export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParame description: parameter.description || null, isDefinition: false, isReadOnly: false, - isRequired: parameter.required === true, + isRequired: parameter.in === 'header' ? false : parameter.required === true, // Headers are always optional isNullable: parameter['x-nullable'] === true, format: parameter.format, maximum: parameter.maximum, diff --git a/src/openApi/v3/parser/getOperationParameter.ts b/src/openApi/v3/parser/getOperationParameter.ts index 97a719c96..a454652c7 100644 --- a/src/openApi/v3/parser/getOperationParameter.ts +++ b/src/openApi/v3/parser/getOperationParameter.ts @@ -23,7 +23,7 @@ export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParame deprecated: parameter.deprecated === true, isDefinition: false, isReadOnly: false, - isRequired: parameter.required === true, + isRequired: parameter.in === 'header' ? false : parameter.required === true, // Headers are always optional isNullable: parameter.nullable === true, imports: [], enum: [], diff --git a/src/templates/core/OpenAPI.hbs b/src/templates/core/OpenAPI.hbs index 7b9560a26..1a273fdfc 100644 --- a/src/templates/core/OpenAPI.hbs +++ b/src/templates/core/OpenAPI.hbs @@ -1,6 +1,8 @@ {{>header}} import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { ApiError } from './ApiError'; type Resolver = (options: ApiRequestOptions) => Promise; type Headers = Record; @@ -15,6 +17,9 @@ export type OpenAPIConfig = { PASSWORD?: string | Resolver | undefined; HEADERS?: Headers | Resolver | undefined; ENCODE_PATH?: ((path: string) => string) | undefined; + ERROR_CALLBACK?: (error: ApiError) => void; + BEFORE_REQUEST?: (options: ApiRequestOptions) => void; + AFTER_REQUEST?: (apiResult: ApiResult) => void; }; export const OpenAPI: OpenAPIConfig = { diff --git a/src/templates/core/angular/request.hbs b/src/templates/core/angular/request.hbs index 57c98516e..4ba61ee66 100644 --- a/src/templates/core/angular/request.hbs +++ b/src/templates/core/angular/request.hbs @@ -72,6 +72,10 @@ export const request = (config: OpenAPIConfig, http: HttpClient, options: Api const formData = getFormData(options); const body = getRequestBody(options); + if (config.BEFORE_REQUEST) { + config.BEFORE_REQUEST(options); + } + return getHeaders(config, options).pipe( switchMap(headers => { return sendRequest(config, options, http, url, formData, body, headers); @@ -79,13 +83,17 @@ export const request = (config: OpenAPIConfig, http: HttpClient, options: Api map(response => { const responseBody = getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); - return { + const result = { url, ok: response.ok, status: response.status, statusText: response.statusText, body: responseHeader ?? responseBody, } as ApiResult; + if (config.AFTER_REQUEST) { + config.AFTER_REQUEST(result); + } + return result; }), catchError((error: HttpErrorResponse) => { if (!error.status) { @@ -100,7 +108,7 @@ export const request = (config: OpenAPIConfig, http: HttpClient, options: Api } as ApiResult); }), map(result => { - catchErrorCodes(options, result); + catchErrorCodes(config, options, result); return result.body as T; }), catchError((error: ApiError) => { diff --git a/src/templates/core/axios/request.hbs b/src/templates/core/axios/request.hbs index 6612f1614..39944ecb8 100644 --- a/src/templates/core/axios/request.hbs +++ b/src/templates/core/axios/request.hbs @@ -73,6 +73,9 @@ import type { OpenAPIConfig } from './OpenAPI'; export const request = (config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { try { + if (config.BEFORE_REQUEST) { + config.BEFORE_REQUEST(options); + } const url = getUrl(config, options); const formData = getFormData(options); const body = getRequestBody(options); @@ -91,7 +94,11 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions, ax body: responseHeader ?? responseBody, }; - catchErrorCodes(options, result); + if (config.AFTER_REQUEST) { + config.AFTER_REQUEST(result); + } + + catchErrorCodes(config, options, result); resolve(result.body); } diff --git a/src/templates/core/fetch/request.hbs b/src/templates/core/fetch/request.hbs index 4af6f9440..ff253083b 100644 --- a/src/templates/core/fetch/request.hbs +++ b/src/templates/core/fetch/request.hbs @@ -65,6 +65,9 @@ import type { OpenAPIConfig } from './OpenAPI'; export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { try { + if (config.BEFORE_REQUEST) { + config.BEFORE_REQUEST(options); + } const url = getUrl(config, options); const formData = getFormData(options); const body = getRequestBody(options); @@ -83,7 +86,11 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions): C body: responseHeader ?? responseBody, }; - catchErrorCodes(options, result); + if (config.AFTER_REQUEST) { + config.AFTER_REQUEST(result); + } + + catchErrorCodes(config, options, result); resolve(result.body); } diff --git a/src/templates/core/functions/catchErrorCodes.hbs b/src/templates/core/functions/catchErrorCodes.hbs index 42f69d071..2c5c02fb6 100644 --- a/src/templates/core/functions/catchErrorCodes.hbs +++ b/src/templates/core/functions/catchErrorCodes.hbs @@ -1,6 +1,6 @@ -export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { +export const catchErrorCodes = (config: OpenAPIConfig, options: ApiRequestOptions, result: ApiResult): void => { const errors: Record = { 400: 'Bad Request', 401: 'Unauthorized', @@ -14,7 +14,12 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): const error = errors[result.status]; if (error) { - throw new ApiError(options, result, error); + const apiError = new ApiError(options, result, error); + const errorCallback = config.ERROR_CALLBACK; + if (errorCallback) { + errorCallback(apiError); + } + throw apiError; } if (!result.ok) { diff --git a/src/templates/core/node/request.hbs b/src/templates/core/node/request.hbs index 8e6f6110e..5d72596b9 100644 --- a/src/templates/core/node/request.hbs +++ b/src/templates/core/node/request.hbs @@ -70,6 +70,9 @@ import type { OpenAPIConfig } from './OpenAPI'; export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { try { + if (config.BEFORE_REQUEST) { + config.BEFORE_REQUEST(options); + } const url = getUrl(config, options); const formData = getFormData(options); const body = getRequestBody(options); @@ -88,7 +91,11 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions): C body: responseHeader ?? responseBody, }; - catchErrorCodes(options, result); + if (config.AFTER_REQUEST) { + config.AFTER_REQUEST(result); + } + + catchErrorCodes(config, options, result); resolve(result.body); } diff --git a/src/templates/core/xhr/request.hbs b/src/templates/core/xhr/request.hbs index 47f92870b..bb62e78c5 100644 --- a/src/templates/core/xhr/request.hbs +++ b/src/templates/core/xhr/request.hbs @@ -68,6 +68,9 @@ import type { OpenAPIConfig } from './OpenAPI'; export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { try { + if (config.BEFORE_REQUEST) { + config.BEFORE_REQUEST(options); + } const url = getUrl(config, options); const formData = getFormData(options); const body = getRequestBody(options); @@ -86,7 +89,11 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions): C body: responseHeader ?? responseBody, }; - catchErrorCodes(options, result); + if (config.AFTER_REQUEST) { + config.AFTER_REQUEST(result); + } + + catchErrorCodes(config, options, result); resolve(result.body); } diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index ef6b18554..9f32216b2 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -206,6 +206,8 @@ exports[`v2 should generate: test/generated/v2/core/OpenAPI.ts 1`] = ` /* tslint:disable */ /* eslint-disable */ import type { ApiRequestOptions } from './ApiRequestOptions'; +import { ApiError } from './ApiError'; +import { ApiResult } from './ApiResult'; type Resolver = (options: ApiRequestOptions) => Promise; type Headers = Record; @@ -220,6 +222,9 @@ export type OpenAPIConfig = { PASSWORD?: string | Resolver | undefined; HEADERS?: Headers | Resolver | undefined; ENCODE_PATH?: ((path: string) => string) | undefined; + ERROR_CALLBACK?: (error: ApiError) => void; + BEFORE_REQUEST?: (options: ApiRequestOptions) => void; + AFTER_REQUEST?: (apiResult: ApiResult) => void; }; export const OpenAPI: OpenAPIConfig = { @@ -486,7 +491,7 @@ export const getResponseBody = async (response: Response): Promise => { return undefined; }; -export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { +export const catchErrorCodes = (config: OpenAPIConfig, options: ApiRequestOptions, result: ApiResult): void => { const errors: Record = { 400: 'Bad Request', 401: 'Unauthorized', @@ -500,7 +505,12 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): const error = errors[result.status]; if (error) { - throw new ApiError(options, result, error); + const apiError = new ApiError(options, result, error); + const errorCallback = config.ERROR_CALLBACK; + if (errorCallback) { + errorCallback(apiError); + } + throw apiError; } if (!result.ok) { @@ -530,6 +540,9 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { try { + if (config.BEFORE_REQUEST) { + config.BEFORE_REQUEST(options); + } const url = getUrl(config, options); const formData = getFormData(options); const body = getRequestBody(options); @@ -548,7 +561,11 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions): C body: responseHeader ?? responseBody, }; - catchErrorCodes(options, result); + if (config.AFTER_REQUEST) { + config.AFTER_REQUEST(result); + } + + catchErrorCodes(config, options, result); resolve(result.body); } @@ -2821,19 +2838,19 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class ParametersService { /** - * @param parameterHeader This is the parameter that goes into the header * @param parameterQuery This is the parameter that goes into the query params * @param parameterForm This is the parameter that goes into the form data * @param parameterBody This is the parameter that is sent as request body * @param parameterPath This is the parameter that goes into the path + * @param parameterHeader This is the parameter that goes into the header * @throws ApiError */ public static callWithParameters( - parameterHeader: string, parameterQuery: string, parameterForm: string, parameterBody: string, parameterPath: string, + parameterHeader?: string, ): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -2854,7 +2871,6 @@ export class ParametersService { }); } /** - * @param parameterHeader This is the parameter that goes into the request header * @param parameterQuery This is the parameter that goes into the request query params * @param parameterForm This is the parameter that goes into the request form data * @param parameterBody This is the parameter that is sent as request body @@ -2862,10 +2878,10 @@ export class ParametersService { * @param parameterPath2 This is the parameter that goes into the path * @param parameterPath3 This is the parameter that goes into the path * @param _default This is the parameter with a reserved keyword + * @param parameterHeader This is the parameter that goes into the request header * @throws ApiError */ public static callWithWeirdParameterNames( - parameterHeader: string, parameterQuery: string, parameterForm: string, parameterBody: string, @@ -2873,6 +2889,7 @@ export class ParametersService { parameterPath2?: string, parameterPath3?: string, _default?: string, + parameterHeader?: string, ): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -3299,6 +3316,8 @@ exports[`v3 should generate: test/generated/v3/core/OpenAPI.ts 1`] = ` /* tslint:disable */ /* eslint-disable */ import type { ApiRequestOptions } from './ApiRequestOptions'; +import { ApiError } from './ApiError'; +import { ApiResult } from './ApiResult'; type Resolver = (options: ApiRequestOptions) => Promise; type Headers = Record; @@ -3313,6 +3332,9 @@ export type OpenAPIConfig = { PASSWORD?: string | Resolver | undefined; HEADERS?: Headers | Resolver | undefined; ENCODE_PATH?: ((path: string) => string) | undefined; + ERROR_CALLBACK?: (error: ApiError) => void; + BEFORE_REQUEST?: (options: ApiRequestOptions) => void; + AFTER_REQUEST?: (apiResult: ApiResult) => void; }; export const OpenAPI: OpenAPIConfig = { @@ -3579,7 +3601,7 @@ export const getResponseBody = async (response: Response): Promise => { return undefined; }; -export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { +export const catchErrorCodes = (config: OpenAPIConfig, options: ApiRequestOptions, result: ApiResult): void => { const errors: Record = { 400: 'Bad Request', 401: 'Unauthorized', @@ -3593,7 +3615,12 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): const error = errors[result.status]; if (error) { - throw new ApiError(options, result, error); + const apiError = new ApiError(options, result, error); + const errorCallback = config.ERROR_CALLBACK; + if (errorCallback) { + errorCallback(apiError); + } + throw apiError; } if (!result.ok) { @@ -3623,6 +3650,9 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { return new CancelablePromise(async (resolve, reject, onCancel) => { try { + if (config.BEFORE_REQUEST) { + config.BEFORE_REQUEST(options); + } const url = getUrl(config, options); const formData = getFormData(options); const body = getRequestBody(options); @@ -3641,7 +3671,11 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions): C body: responseHeader ?? responseBody, }; - catchErrorCodes(options, result); + if (config.AFTER_REQUEST) { + config.AFTER_REQUEST(result); + } + + catchErrorCodes(config, options, result); resolve(result.body); } @@ -6621,7 +6655,7 @@ export class DeprecatedService { * @throws ApiError */ public static deprecatedCall( - parameter: DeprecatedModel | null, + parameter?: DeprecatedModel | null, ): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -6997,21 +7031,21 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class ParametersService { /** - * @param parameterHeader This is the parameter that goes into the header * @param parameterQuery This is the parameter that goes into the query params * @param parameterForm This is the parameter that goes into the form data * @param parameterCookie This is the parameter that goes into the cookie * @param parameterPath This is the parameter that goes into the path * @param requestBody This is the parameter that goes into the body + * @param parameterHeader This is the parameter that goes into the header * @throws ApiError */ public static callWithParameters( - parameterHeader: string | null, parameterQuery: string | null, parameterForm: string | null, parameterCookie: string | null, parameterPath: string | null, requestBody: ModelWithString | null, + parameterHeader?: string | null, ): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -7036,7 +7070,6 @@ export class ParametersService { }); } /** - * @param parameterHeader This is the parameter that goes into the request header * @param parameterQuery This is the parameter that goes into the request query params * @param parameterForm This is the parameter that goes into the request form data * @param parameterCookie This is the parameter that goes into the cookie @@ -7045,10 +7078,10 @@ export class ParametersService { * @param parameterPath2 This is the parameter that goes into the path * @param parameterPath3 This is the parameter that goes into the path * @param _default This is the parameter with a reserved keyword + * @param parameterHeader This is the parameter that goes into the request header * @throws ApiError */ public static callWithWeirdParameterNames( - parameterHeader: string | null, parameterQuery: string | null, parameterForm: string | null, parameterCookie: string | null, @@ -7057,6 +7090,7 @@ export class ParametersService { parameterPath2?: string, parameterPath3?: string, _default?: string, + parameterHeader?: string | null, ): CancelablePromise { return __request(OpenAPI, { method: 'POST',