Merge branch 'master' into CancellablePromise

This commit is contained in:
Michal 2023-03-01 12:34:13 +01:00 committed by GitHub
commit 9845ce7d3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 10209 additions and 8238 deletions

View File

@ -1,20 +1,21 @@
version: 2
jobs:
build:
working_directory: ~/repo
docker:
- image: cimg/node:lts-browsers
resource_class: large
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-{{ checksum "package-lock.json" }}
- v1-dependencies-
- run:
name: Install dependencies
command: npm install
- save_cache:
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ checksum "package-lock.json" }}
paths:
- node_modules
- run:
@ -22,10 +23,10 @@ jobs:
command: npm run release
- run:
name: Run unit tests
command: npm run test:coverage
- run:
name: Run e2e tests
command: npm run test:e2e
command: npm run test
# - run:
# name: Run e2e tests
# command: npm run test:e2e
- run:
name: Submit to Codecov
command: npm run codecov

View File

@ -3,9 +3,20 @@ updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: monthly
time: "04:00"
open-pull-requests-limit: 10
interval: "daily"
ignore:
- dependency-name: "@types/node-fetch"
- dependency-name: "node-fetch"
- dependency-name: "camelcase"
- dependency-name: "@angular-devkit/build-angular"
- dependency-name: "@angular/animations"
- dependency-name: "@angular/cli"
- dependency-name: "@angular/common"
- dependency-name: "@angular/compiler"
- dependency-name: "@angular/compiler-cli"
- dependency-name: "@angular/core"
- dependency-name: "@angular/forms"
- dependency-name: "@angular/platform-browser"
- dependency-name: "@angular/platform-browser-dynamic"
- dependency-name: "@angular/router"
- dependency-name: "typescript"

29
.github/workflows/auto-merge.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: auto-merge
on: pull_request_target
permissions:
pull-requests: write
contents: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Fetch Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Approve PR
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Merge PR
if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

20
.licrc Normal file
View File

@ -0,0 +1,20 @@
[licenses]
accepted = [
"MIT",
"BSD",
"0BSD",
"BSD-2-Clause",
"BSD-3-Clause",
"Apache-2.0",
"CC-BY-3.0",
"CC-BY-4.0",
"CC0-1.0",
"ISC"
]
[dependencies]
ignored = []
[behavior]
run_only_on_dependency_modification = true
do_not_block_pr = true

View File

@ -1,6 +1,42 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.23.0] - 2022-06-02
### Fixed
- Upgraded dependencies
- Added blank line at the end of generated files
- Added support for Node.js v12
### Added
- Added `request` property inside `ApiError`
- Added support for `@depricated` inside models and operations
## [0.22.0] - 2022-04-26
### Fixed
- Upgraded dependencies
- Fixed issue with `null` value inside comments for OpenAPI v2 enums
- Fixed issue with compatibility for latest version of Axios (0.27.x)
### Removed
- Removed deprecated enum model generation
## [0.21.0] - 2022-04-06
### Fixed
- Return `undefined` to match `noImplicitReturns` rule
- Made `BaseHttpRequest` class abstract
- Removed private fields using `#` inside `CancelablePromise`
- Removed unneeded import `AbortController` from `node-fetch` client
- Filter out wrong enum values
## [0.20.1] - 2022-02-25
### Fixed
- Support enums with single quotes in names for V2
## [0.20.0] - 2022-02-25
### Fixed
- Updated dependencies
- Support enums with single quotes in names for V3
- Generating better names when `operationId` is not given (breaking change)
- Fixed issue where `x-enum` flags where breaking due to non-string values
## [0.19.0] - 2022-02-02
### Added
- Support for Angular client with `--name` option

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Ferdi Koomen
Copyright (c) Ferdi Koomen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -48,7 +48,8 @@ $ openapi --help
--exportModels <value> Write models to disk (default: true)
--exportSchemas <value> Write schemas to disk (default: false)
--indent <value> Indentation options [4, 2, tab] (default: "4")
--postfix <value> Service name postfix (default: "Service")
--postfixServices Service name postfix (default: "Service")
--postfixModels Model name postfix
--request <value> Path to custom request file
-h, --help display help for command
@ -69,6 +70,8 @@ Documentation
- [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
===

View File

@ -4,7 +4,7 @@
"@babel/preset-env",
{
"targets": {
"node": true
"node": "12"
}
}
],

View File

@ -21,7 +21,9 @@ const params = program
.option('--exportModels <value>', 'Write models to disk', true)
.option('--exportSchemas <value>', 'Write schemas to disk', false)
.option('--indent <value>', 'Indentation options [4, 2, tabs]', '4')
.option('--postfix <value>', 'Service name postfix', 'Service')
.option('--postfix <value>', 'Deprecated: Use --postfixServices instead. Service name postfix', 'Service')
.option('--postfixServices <value>', 'Service name postfix', 'Service')
.option('--postfixModels <value>', 'Model name postfix')
.option('--request <value>', 'Path to custom request file')
.parse(process.argv)
.opts();
@ -41,7 +43,8 @@ if (OpenAPI) {
exportModels: JSON.parse(params.exportModels) === true,
exportSchemas: JSON.parse(params.exportSchemas) === true,
indent: params.indent,
postfix: params.postfix,
postfixServices: params.postfixServices ?? params.postfix,
postfixModels: params.postfixModels,
request: params.request,
})
.then(() => {

View File

@ -34,8 +34,10 @@ describe('bin', () => {
'true',
'--indent',
'4',
'--postfix',
'--postfixServices',
'Service',
'--postfixModels',
'Dto',
]);
expect(result.stdout.toString()).toBe('');
expect(result.stderr.toString()).toBe('');
@ -67,4 +69,18 @@ describe('bin', () => {
expect(result.stdout.toString()).toContain(`-o, --output <value>`);
expect(result.stderr.toString()).toBe('');
});
it('should still support the deprecated --postfix parameter', () => {
const result = crossSpawn.sync('node', [
'./bin/index.js',
'--input',
'./test/spec/v3.json',
'--output',
'./test/generated/bin',
'--postfix',
'Service',
]);
expect(result.stdout.toString()).toBe('');
expect(result.stderr.toString()).toBe('');
});
});

View File

@ -9,8 +9,8 @@ If you want to generate the Angular based client then you can specify `--client
The Angular client has been tested with the following versions:
```
"@angular/common": "13.1.x",
"@angular/core": "13.1.x",
"@angular/common": "14.0.x",
"@angular/core": "14.0.x",
"rxjs": "7.5.x",
```

View File

@ -14,5 +14,13 @@ 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.
In order to compile the project and resolve the imports, you will need to add the following properties
in your `tsconfig.json` file:
```json
{
"compilerOptions": {
"lib": ["...", "dom"],
"allowSyntheticDefaultImports": true
}
}
```

View File

@ -18,7 +18,8 @@ $ openapi --help
--exportModels <value> Write models to disk (default: true)
--exportSchemas <value> Write schemas to disk (default: false)
--indent <value> Indentation options [4, 2, tab] (default: "4")
--postfix <value> Service name postfix (default: "Service")
--postfixServices Service name postfix (default: "Service")
--postfixModels Model name postfix
--request <value> Path to custom request file
-h, --help display help for command

View File

@ -0,0 +1,42 @@
# Canceling requests
The generated clients support canceling of requests, this works by canceling the promise that
is returned from the request. Each method inside a service (operation) returns a `CancelablePromise`
object. This promise can be canceled by calling the `cancel()` method.
Below is an example of canceling the request after a certain timeout:
```typescript
import { UserService } from './myClient';
const getAllUsers = async () => {
const request = UserService.getAllUsers();
setTimeout(() => {
if (!request.isResolved() && !request.isRejected()) {
console.warn('Canceling request due to timeout');
request.cancel();
}
}, 1000);
await request;
};
```
The API of the `CancelablePromise` is similar to a regular `Promise`, but it adds the
`cancel()` method and some additional properties:
```typescript
interface CancelablePromise<TResult> extends Promise<TResult> {
readonly isResolved: boolean;
readonly isRejected: boolean;
readonly isCancelled: boolean;
cancel: () => void;
}
```
- `isResolved`: Indicates if the promise was resolved.
- `isRejected`: Indicates if the promise was rejected.
- `isCancelled`: Indicates if the promise was canceled.
- `cancel()`: Cancels the promise (and request) and throws a rejection error: `Request aborted`.

View File

@ -7,7 +7,7 @@ The generated client uses an instance of the server configuration and not the gl
To generate a client instance, set a custom name to the client class, use `--name` option.
```
openapi --input ./spec.json --output ./generated ---name AppClient
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:

View File

@ -0,0 +1,61 @@
# Custom request file
If you want to implement custom logic on the request level,
or implement a client based on a different library, then
one option is to write your own request file and tell
the generator to use this.
The request file (`request.ts`) can be found inside the
`/core` folder of the generated client. You can modify
that file and use it, or alternatively, you can write
your own. Below is a very simplified example of an Axios
based request file:
```typescript
import axios from 'axios';
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import type { ApiRequestOptions } from './ApiRequestOptions';
import { CancelablePromise } from './CancelablePromise';
import type { OpenAPIConfig } from './OpenAPI';
const axiosInstance = axios.create({
// Your custom Axios instance config
});
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise<T> => {
return new CancelablePromise((resolve, reject, onCancel) => {
// Get the request URL. Depending on your needs, this might need additional processing,
// @see ./src/templates/core/functions/getUrl.hbs
const url = `${config.BASE}${options.path}`;
// Optional: Get and link the cancelation token, so the request can be aborted.
const source = axiosInstance.CancelToken.source();
onCancel(() => source.cancel('The user aborted a request.'));
// Execute the request. This is a minimal example, in real world scenarios
// you will need to add headers, process form data, etc.
// @see ./src/templates/core/axios/request.hbs
axiosInstance.request({
url,
data: options.body,
method: options.method,
cancelToken: source.token,
}).then(data => {
resolve(data);
}).catch(error => {
reject(error);
});
});
};
```
To use this request file in your generated code you can execute the
following command:
```
npx openapi-typescript-codegen --input ./spec.json --output ./generated --request ./request.ts
```
The `--request` parameter will tell the generator to not generate the default
`request.ts` file, but instead copy over the custom file that was specified.

15641
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "openapi-typescript-codegen",
"version": "0.19.0",
"version": "0.23.0",
"description": "Library that generates Typescript clients based on the OpenAPI specification.",
"author": "Ferdi Koomen",
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",
@ -60,63 +60,67 @@
},
"dependencies": {
"camelcase": "^6.3.0",
"commander": "^9.0.0",
"handlebars": "^4.7.6",
"json-schema-ref-parser": "^9.0.7"
"commander": "^9.4.1",
"fs-extra": "^10.1.0",
"handlebars": "^4.7.7",
"json-schema-ref-parser": "^9.0.9"
},
"devDependencies": {
"@angular-devkit/build-angular": "13.2.1",
"@angular/animations": "13.2.1",
"@angular/cli": "13.2.1",
"@angular/common": "13.2.1",
"@angular/compiler": "13.2.1",
"@angular/compiler-cli": "13.2.1",
"@angular/core": "13.2.1",
"@angular/forms": "13.2.1",
"@angular/platform-browser": "13.2.1",
"@angular/platform-browser-dynamic": "13.2.1",
"@angular/router": "13.2.1",
"@babel/cli": "7.16.8",
"@babel/core": "7.16.12",
"@babel/preset-env": "7.16.11",
"@babel/preset-typescript": "7.16.7",
"@rollup/plugin-commonjs": "21.0.1",
"@rollup/plugin-node-resolve": "13.1.3",
"@rollup/plugin-typescript": "8.3.0",
"@angular-devkit/build-angular": "14.2.7",
"@angular/animations": "14.2.8",
"@angular/cli": "14.2.7",
"@angular/common": "14.2.8",
"@angular/compiler": "14.2.8",
"@angular/compiler-cli": "14.2.8",
"@angular/core": "14.2.8",
"@angular/forms": "14.2.8",
"@angular/platform-browser": "14.2.8",
"@angular/platform-browser-dynamic": "14.2.8",
"@angular/router": "14.2.8",
"@babel/cli": "7.20.7",
"@babel/core": "7.20.12",
"@babel/preset-env": "7.20.2",
"@babel/preset-typescript": "7.18.6",
"@rollup/plugin-commonjs": "23.0.5",
"@rollup/plugin-node-resolve": "15.0.1",
"@rollup/plugin-typescript": "9.0.2",
"@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.14",
"@types/node-fetch": "2.5.12",
"@types/express": "4.17.15",
"@types/fs-extra": "^9.0.13",
"@types/glob": "8.0.1",
"@types/jest": "29.2.5",
"@types/node": "18.11.18",
"@types/node-fetch": "2.6.2",
"@types/qs": "6.9.7",
"@typescript-eslint/eslint-plugin": "5.10.2",
"@typescript-eslint/parser": "5.10.2",
"@typescript-eslint/eslint-plugin": "5.49.0",
"@typescript-eslint/parser": "5.48.2",
"abort-controller": "3.0.0",
"axios": "0.25.0",
"axios": "1.2.3",
"codecov": "3.8.3",
"cross-spawn": "7.0.3",
"eslint": "8.8.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",
"eslint": "8.32.0",
"eslint-config-prettier": "8.6.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-simple-import-sort": "8.0.0",
"express": "4.18.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.3",
"qs": "6.10.3",
"glob": "8.1.0",
"jest": "29.3.1",
"jest-cli": "29.3.1",
"node-fetch": "2.6.7",
"prettier": "2.8.3",
"puppeteer": "19.5.2",
"qs": "6.11.0",
"rimraf": "3.0.2",
"rollup": "2.67.0",
"rollup-plugin-node-externals": "3.1.2",
"rollup": "3.2.3",
"rollup-plugin-terser": "7.0.2",
"rxjs": "7.5.2",
"ts-node": "10.4.0",
"tslib": "2.3.1",
"typescript": "4.5.5",
"zone.js": "0.11.4"
"rxjs": "7.8.0",
"ts-node": "10.9.1",
"tslib": "2.4.1",
"typescript": "4.8.4",
"zone.js": "0.11.8"
},
"overrides" : {
"rollup": "3.2.3"
}
}

View File

@ -2,11 +2,12 @@ import commonjs from '@rollup/plugin-commonjs';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import { readFileSync } from 'fs';
import { precompile } from 'handlebars';
import handlebars from 'handlebars';
import { dirname, extname, resolve } from 'path';
import externals from 'rollup-plugin-node-externals';
import { terser } from 'rollup-plugin-terser';
const { precompile } = handlebars;
/**
* Custom plugin to parse handlebar imports and precompile
* the template on the fly. This reduces runtime by about
@ -28,6 +29,7 @@ const handlebarsPlugin = () => ({
preventIndent: true,
knownHelpersOnly: true,
knownHelpers: {
ifdef: true,
equals: true,
notEquals: true,
containsSpaces: true,
@ -47,9 +49,6 @@ const handlebarsPlugin = () => ({
const getPlugins = () => {
const plugins = [
externals({
deps: true,
}),
nodeResolve(),
commonjs({
sourceMap: false,
@ -72,5 +71,6 @@ export default {
file: './dist/index.js',
format: 'cjs',
},
external: ['camelcase', 'commander', 'fs-extra', 'handlebars', 'json-schema-ref-parser'],
plugins: getPlugins(),
};

View File

@ -9,6 +9,7 @@ export interface Model extends Schema {
template: string | null;
link: Model | null;
description: string | null;
deprecated?: boolean;
default?: string;
imports: string[];
enum: Enum[];

View File

@ -24,7 +24,8 @@ export type Options = {
exportModels?: boolean;
exportSchemas?: boolean;
indent?: Indent;
postfix?: string;
postfixServices?: string;
postfixModels?: string;
request?: string;
write?: boolean;
};
@ -44,7 +45,8 @@ export type Options = {
* @param exportModels Generate models
* @param exportSchemas Generate schemas
* @param indent Indentation options (4, 2 or tab)
* @param postfix Service name postfix
* @param postfixServices Service name postfix
* @param postfixModels Model name postfix
* @param request Path to custom request file
* @param write Write the files to disk (true or false)
*/
@ -60,7 +62,8 @@ export const generate = async ({
exportModels = true,
exportSchemas = false,
indent = Indent.SPACE_4,
postfix = 'Service',
postfixServices = 'Service',
postfixModels = '',
request,
write = true,
}: Options): Promise<void> => {
@ -89,7 +92,8 @@ export const generate = async ({
exportModels,
exportSchemas,
indent,
postfix,
postfixServices,
postfixModels,
clientName,
request
);
@ -112,7 +116,8 @@ export const generate = async ({
exportModels,
exportSchemas,
indent,
postfix,
postfixServices,
postfixModels,
clientName,
request
);

View File

@ -2,7 +2,7 @@ import { escapeName } from './escapeName';
describe('escapeName', () => {
it('should escape', () => {
expect(escapeName('')).toEqual('');
expect(escapeName('')).toEqual("''");
expect(escapeName('fooBar')).toEqual('fooBar');
expect(escapeName('Foo Bar')).toEqual(`'Foo Bar'`);
expect(escapeName('foo bar')).toEqual(`'foo bar'`);

View File

@ -1,4 +1,5 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { isString } from '../../../utils/isString';
import type { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtension';
/**
@ -8,8 +9,8 @@ import type { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtensi
* @param definition
*/
export const extendEnum = (enumerators: Enum[], definition: WithEnumExtension): Enum[] => {
const names = definition['x-enum-varnames'];
const descriptions = definition['x-enum-descriptions'];
const names = definition['x-enum-varnames']?.filter(isString);
const descriptions = definition['x-enum-descriptions']?.filter(isString);
return enumerators.map((enumerator, index) => ({
name: names?.[index] || enumerator.name,

View File

@ -1,5 +1,4 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { isDefined } from '../../../utils/isDefined';
export const getEnum = (values?: (string | number)[]): Enum[] => {
if (Array.isArray(values)) {
@ -7,7 +6,9 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
.filter((value, index, arr) => {
return arr.indexOf(value) === index;
})
.filter(isDefined)
.filter((value: any) => {
return typeof value === 'number' || typeof value === 'string';
})
.map(value => {
if (typeof value === 'number') {
return {
@ -23,7 +24,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
.replace(/^(\d+)/g, '_$1')
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: `'${value}'`,
value: `'${value.replace(/'/g, "\\'")}'`,
type: 'string',
description: null,
};

View File

@ -1,39 +0,0 @@
import type { Enum } from '../../../client/interfaces/Enum';
/**
* @deprecated
*/
export const getEnumFromDescription = (description: string): Enum[] => {
// Check if we can find this special format string:
// None=0,Something=1,AnotherThing=2
if (/^(\w+=[0-9]+)/g.test(description)) {
const matches = description.match(/(\w+=[0-9]+,?)/g);
if (matches) {
// Grab the values from the description
const symbols: Enum[] = [];
matches.forEach(match => {
const name = match.split('=')[0];
const value = parseInt(match.split('=')[1].replace(/[^0-9]/g, ''));
if (name && Number.isInteger(value)) {
symbols.push({
name: name
.replace(/\W+/g, '_')
.replace(/^(\d+)/g, '_$1')
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: String(value),
type: 'number',
description: null,
});
}
});
// Filter out any duplicate names
return symbols.filter((symbol, index, arr) => {
return arr.map(item => item.name).indexOf(symbol.name) === index;
});
}
}
return [];
};

View File

@ -4,7 +4,6 @@ import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { extendEnum } from './extendEnum';
import { getEnum } from './getEnum';
import { getEnumFromDescription } from './getEnumFromDescription';
import { getModelComposition } from './getModelComposition';
import { getModelProperties } from './getModelProperties';
import { getType } from './getType';
@ -69,17 +68,6 @@ export const getModel = (
}
}
if ((definition.type === 'int' || definition.type === 'integer') && definition.description) {
const enumerators = getEnumFromDescription(definition.description);
if (enumerators.length) {
model.export = 'enum';
model.type = 'number';
model.base = 'number';
model.enum.push(...enumerators);
return model;
}
}
if (definition.type === 'array' && definition.items) {
if (definition.items.$ref) {
const arrayItems = getType(definition.items.$ref);

View File

@ -1,4 +1,5 @@
import type { Model } from '../../../client/interfaces/Model';
import { reservedWords } from '../../v3/parser/getOperationParameterName';
import type { OpenApi } from '../interfaces/OpenApi';
import { getModel } from './getModel';
import { getType } from './getType';
@ -9,7 +10,7 @@ export const getModels = (openApi: OpenApi): Model[] => {
if (openApi.definitions.hasOwnProperty(definitionName)) {
const definition = openApi.definitions[definitionName];
const definitionType = getType(definitionName);
const model = getModel(openApi, definition, true, definitionType.base);
const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1'));
models.push(model);
}
}

View File

@ -20,7 +20,7 @@ export const getOperation = (
pathParams: OperationParameters
): Operation => {
const serviceName = getServiceName(tag);
const operationName = getOperationName(op.operationId || `${method}`);
const operationName = getOperationName(url, method, op.operationId);
// Create a new operation object for this method.
const operation: Operation = {

View File

@ -2,17 +2,26 @@ import { getOperationName } from './getOperationName';
describe('getOperationName', () => {
it('should produce correct result', () => {
expect(getOperationName('')).toEqual('');
expect(getOperationName('FooBar')).toEqual('fooBar');
expect(getOperationName('Foo Bar')).toEqual('fooBar');
expect(getOperationName('foo bar')).toEqual('fooBar');
expect(getOperationName('foo-bar')).toEqual('fooBar');
expect(getOperationName('foo_bar')).toEqual('fooBar');
expect(getOperationName('foo.bar')).toEqual('fooBar');
expect(getOperationName('@foo.bar')).toEqual('fooBar');
expect(getOperationName('$foo.bar')).toEqual('fooBar');
expect(getOperationName('_foo.bar')).toEqual('fooBar');
expect(getOperationName('-foo.bar')).toEqual('fooBar');
expect(getOperationName('123.foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v{api-version}/users', 'GET', undefined)).toEqual('getApiUsers');
expect(getOperationName('/api/v{api-version}/users', 'POST', undefined)).toEqual('postApiUsers');
expect(getOperationName('/api/v1/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v1/users', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users', 'POST', undefined)).toEqual('postApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'POST', undefined)).toEqual('postApiV1Users');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'fooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'FooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'Foo Bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo-bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo_bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '@foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '$foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '_foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '-foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '123.foo.bar')).toEqual('fooBar');
});
});

View File

@ -2,13 +2,23 @@ import camelCase from 'camelcase';
/**
* Convert the input value to a correct operation (method) classname.
* This converts the input string to camelCase, so the method name follows
* the most popular Javascript and Typescript writing style.
* This will use the operation ID - if available - and otherwise fallback
* on a generated name from the URL
*/
export const getOperationName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
return camelCase(clean);
export const getOperationName = (url: string, method: string, operationId?: string): string => {
if (operationId) {
return camelCase(
operationId
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim()
);
}
const urlWithoutPlaceholders = url
.replace(/[^/]*?{api-version}.*?\//g, '')
.replace(/{(.*?)}/g, '')
.replace(/\//g, '-');
return camelCase(`${method}-${urlWithoutPlaceholders}`);
};

View File

@ -5,7 +5,6 @@ import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { extendEnum } from './extendEnum';
import { getEnum } from './getEnum';
import { getEnumFromDescription } from './getEnumFromDescription';
import { getModel } from './getModel';
import { getOperationParameterDefault } from './getOperationParameterDefault';
import { getOperationParameterName } from './getOperationParameterName';
@ -70,18 +69,6 @@ export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParame
}
}
if ((parameter.type === 'int' || parameter.type === 'integer') && parameter.description) {
const enumerators = getEnumFromDescription(parameter.description);
if (enumerators.length) {
operationParameter.export = 'enum';
operationParameter.type = 'number';
operationParameter.base = 'number';
operationParameter.enum.push(...enumerators);
operationParameter.default = getOperationParameterDefault(parameter, operationParameter);
return operationParameter;
}
}
if (parameter.type === 'array' && parameter.items) {
const items = getType(parameter.items.type, parameter.items.format);
operationParameter.export = 'array';

View File

@ -6,7 +6,7 @@ export const getOperationParameterDefault = (
operationParameter: OperationParameter
): string | undefined => {
if (parameter.default === undefined) {
return;
return undefined;
}
if (parameter.default === null) {
@ -38,5 +38,5 @@ export const getOperationParameterDefault = (
}
}
return;
return undefined;
};

View File

@ -1,4 +1,5 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { isString } from '../../../utils/isString';
import type { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtension';
/**
@ -8,8 +9,8 @@ import type { WithEnumExtension } from '../interfaces/Extensions/WithEnumExtensi
* @param definition
*/
export const extendEnum = (enumerators: Enum[], definition: WithEnumExtension): Enum[] => {
const names = definition['x-enum-varnames'];
const descriptions = definition['x-enum-descriptions'];
const names = definition['x-enum-varnames']?.filter(isString);
const descriptions = definition['x-enum-descriptions']?.filter(isString);
return enumerators.map((enumerator, index) => ({
name: names?.[index] || enumerator.name,

View File

@ -1,5 +1,4 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { isDefined } from '../../../utils/isDefined';
export const getEnum = (values?: (string | number)[]): Enum[] => {
if (Array.isArray(values)) {
@ -7,7 +6,9 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
.filter((value, index, arr) => {
return arr.indexOf(value) === index;
})
.filter(isDefined)
.filter((value: any) => {
return typeof value === 'number' || typeof value === 'string';
})
.map(value => {
if (typeof value === 'number') {
return {
@ -23,7 +24,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
.replace(/^(\d+)/g, '_$1')
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: `'${value}'`,
value: `'${value.replace(/'/g, "\\'")}'`,
type: 'string',
description: null,
};

View File

@ -1,39 +0,0 @@
import type { Enum } from '../../../client/interfaces/Enum';
/**
* @deprecated
*/
export const getEnumFromDescription = (description: string): Enum[] => {
// Check if we can find this special format string:
// None=0,Something=1,AnotherThing=2
if (/^(\w+=[0-9]+)/g.test(description)) {
const matches = description.match(/(\w+=[0-9]+,?)/g);
if (matches) {
// Grab the values from the description
const symbols: Enum[] = [];
matches.forEach(match => {
const name = match.split('=')[0];
const value = parseInt(match.split('=')[1].replace(/[^0-9]/g, ''));
if (name && Number.isInteger(value)) {
symbols.push({
name: name
.replace(/\W+/g, '_')
.replace(/^(\d+)/g, '_$1')
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: String(value),
type: 'number',
description: null,
});
}
});
// Filter out any duplicate names
return symbols.filter((symbol, index, arr) => {
return arr.map(item => item.name).indexOf(symbol.name) === index;
});
}
}
return [];
};

View File

@ -4,7 +4,6 @@ import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { extendEnum } from './extendEnum';
import { getEnum } from './getEnum';
import { getEnumFromDescription } from './getEnumFromDescription';
import { getModelComposition } from './getModelComposition';
import { getModelDefault } from './getModelDefault';
import { getModelProperties } from './getModelProperties';
@ -24,6 +23,7 @@ export const getModel = (
template: null,
link: null,
description: definition.description || null,
deprecated: definition.deprecated === true,
isDefinition,
isReadOnly: definition.readOnly === true,
isNullable: definition.nullable === true,
@ -72,18 +72,6 @@ export const getModel = (
}
}
if ((definition.type === 'int' || definition.type === 'integer') && definition.description) {
const enumerators = getEnumFromDescription(definition.description);
if (enumerators.length) {
model.export = 'enum';
model.type = 'number';
model.base = 'number';
model.enum.push(...enumerators);
model.default = getModelDefault(definition, model);
return model;
}
}
if (definition.type === 'array' && definition.items) {
if (definition.items.$ref) {
const arrayItems = getType(definition.items.$ref);
@ -107,9 +95,13 @@ export const getModel = (
}
}
if (definition.type === 'object' && typeof definition.additionalProperties === 'object') {
if (definition.additionalProperties.$ref) {
const additionalProperties = getType(definition.additionalProperties.$ref);
if (
definition.type === 'object' &&
(typeof definition.additionalProperties === 'object' || definition.additionalProperties === true)
) {
const ap = typeof definition.additionalProperties === 'object' ? definition.additionalProperties : {};
if (ap.$ref) {
const additionalProperties = getType(ap.$ref);
model.export = 'dictionary';
model.type = additionalProperties.type;
model.base = additionalProperties.base;
@ -118,7 +110,7 @@ export const getModel = (
model.default = getModelDefault(definition, model);
return model;
} else {
const additionalProperties = getModel(openApi, definition.additionalProperties);
const additionalProperties = getModel(openApi, ap);
model.export = 'dictionary';
model.type = additionalProperties.type;
model.base = additionalProperties.base;
@ -158,12 +150,12 @@ export const getModel = (
}
if (definition.type === 'object') {
model.export = 'interface';
model.type = 'any';
model.base = 'any';
model.default = getModelDefault(definition, model);
if (definition.properties) {
model.export = 'interface';
model.type = 'any';
model.base = 'any';
model.default = getModelDefault(definition, model);
const modelProperties = getModelProperties(openApi, definition, getModel, model);
modelProperties.forEach(modelProperty => {
model.imports.push(...modelProperty.imports);
@ -173,8 +165,18 @@ export const getModel = (
model.enums.push(modelProperty);
}
});
return model;
} else {
const additionalProperties = getModel(openApi, {});
model.export = 'dictionary';
model.type = additionalProperties.type;
model.base = additionalProperties.base;
model.template = additionalProperties.template;
model.link = additionalProperties;
model.imports.push(...additionalProperties.imports);
model.default = getModelDefault(definition, model);
return model;
}
return model;
}
// If the schema has a type than it can be a basic or generic type.

View File

@ -31,8 +31,9 @@ export const getModelComposition = (
const hasProperties = model.properties.length;
const hasEnums = model.enums.length;
const isObject = model.type === 'any';
const isDictionary = model.export === 'dictionary';
const isEmpty = isObject && !hasProperties && !hasEnums;
return !isEmpty;
return !isEmpty || isDictionary;
})
.forEach(model => {
composition.imports.push(...model.imports);

View File

@ -3,7 +3,7 @@ import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
export const getModelDefault = (definition: OpenApiSchema, model?: Model): string | undefined => {
if (definition.default === undefined) {
return;
return undefined;
}
if (definition.default === null) {
@ -35,5 +35,5 @@ export const getModelDefault = (definition: OpenApiSchema, model?: Model): strin
}
}
return;
return undefined;
};

View File

@ -37,6 +37,7 @@ export const getModelProperties = (
> = {
name: escapeName(propertyName),
description: property.description || null,
deprecated: property.deprecated === true,
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired,

View File

@ -1,6 +1,7 @@
import type { Model } from '../../../client/interfaces/Model';
import type { OpenApi } from '../interfaces/OpenApi';
import { getModel } from './getModel';
import { reservedWords } from './getOperationParameterName';
import { getType } from './getType';
export const getModels = (openApi: OpenApi): Model[] => {
@ -10,7 +11,7 @@ export const getModels = (openApi: OpenApi): Model[] => {
if (openApi.components.schemas.hasOwnProperty(definitionName)) {
const definition = openApi.components.schemas[definitionName];
const definitionType = getType(definitionName);
const model = getModel(openApi, definition, true, definitionType.base);
const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1'));
models.push(model);
}
}

View File

@ -23,7 +23,7 @@ export const getOperation = (
pathParams: OperationParameters
): Operation => {
const serviceName = getServiceName(tag);
const operationName = getOperationName(op.operationId || `${method}`);
const operationName = getOperationName(url, method, op.operationId);
// Create a new operation object for this method.
const operation: Operation = {

View File

@ -2,17 +2,26 @@ import { getOperationName } from './getOperationName';
describe('getOperationName', () => {
it('should produce correct result', () => {
expect(getOperationName('')).toEqual('');
expect(getOperationName('FooBar')).toEqual('fooBar');
expect(getOperationName('Foo Bar')).toEqual('fooBar');
expect(getOperationName('foo bar')).toEqual('fooBar');
expect(getOperationName('foo-bar')).toEqual('fooBar');
expect(getOperationName('foo_bar')).toEqual('fooBar');
expect(getOperationName('foo.bar')).toEqual('fooBar');
expect(getOperationName('@foo.bar')).toEqual('fooBar');
expect(getOperationName('$foo.bar')).toEqual('fooBar');
expect(getOperationName('_foo.bar')).toEqual('fooBar');
expect(getOperationName('-foo.bar')).toEqual('fooBar');
expect(getOperationName('123.foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v{api-version}/users', 'GET', undefined)).toEqual('getApiUsers');
expect(getOperationName('/api/v{api-version}/users', 'POST', undefined)).toEqual('postApiUsers');
expect(getOperationName('/api/v1/users', 'GET', 'GetAllUsers')).toEqual('getAllUsers');
expect(getOperationName('/api/v1/users', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users', 'POST', undefined)).toEqual('postApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'GET', undefined)).toEqual('getApiV1Users');
expect(getOperationName('/api/v1/users/{id}', 'POST', undefined)).toEqual('postApiV1Users');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'fooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'FooBar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'Foo Bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo-bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo_bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', 'foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '@foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '$foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '_foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '-foo.bar')).toEqual('fooBar');
expect(getOperationName('/api/v{api-version}/users', 'GET', '123.foo.bar')).toEqual('fooBar');
});
});

View File

@ -2,13 +2,23 @@ import camelCase from 'camelcase';
/**
* Convert the input value to a correct operation (method) classname.
* This converts the input string to camelCase, so the method name follows
* the most popular Javascript and Typescript writing style.
* This will use the operation ID - if available - and otherwise fallback
* on a generated name from the URL
*/
export const getOperationName = (value: string): string => {
const clean = value
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim();
return camelCase(clean);
export const getOperationName = (url: string, method: string, operationId?: string): string => {
if (operationId) {
return camelCase(
operationId
.replace(/^[^a-zA-Z]+/g, '')
.replace(/[^\w\-]+/g, '-')
.trim()
);
}
const urlWithoutPlaceholders = url
.replace(/[^/]*?{api-version}.*?\//g, '')
.replace(/{(.*?)}/g, '')
.replace(/\//g, '-');
return camelCase(`${method}-${urlWithoutPlaceholders}`);
};

View File

@ -20,6 +20,7 @@ export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParame
template: null,
link: null,
description: parameter.description || null,
deprecated: parameter.deprecated === true,
isDefinition: false,
isReadOnly: false,
isRequired: parameter.required === true,

View File

@ -1,6 +1,6 @@
import camelCase from 'camelcase';
const reservedWords =
export const reservedWords =
/^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g;
/**

View File

@ -1,5 +1,6 @@
{{>header}}
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
export class ApiError extends Error {
@ -7,8 +8,9 @@ export class ApiError extends Error {
public readonly status: number;
public readonly statusText: string;
public readonly body: any;
public readonly request: ApiRequestOptions;
constructor(response: ApiResult, message: string) {
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
super(message);
this.name = 'ApiError';
@ -16,5 +18,6 @@ export class ApiError extends Error {
this.status = response.status;
this.statusText = response.statusText;
this.body = response.body;
this.request = request;
}
}

View File

@ -12,7 +12,7 @@ import type { CancelablePromise } from './CancelablePromise';
import type { OpenAPIConfig } from './OpenAPI';
{{/equals}}
export class BaseHttpRequest {
export abstract class BaseHttpRequest {
{{#equals @root.httpClient 'angular'}}
constructor(
@ -24,12 +24,8 @@ export class BaseHttpRequest {
{{/equals}}
{{#equals @root.httpClient 'angular'}}
public request<T>(options: ApiRequestOptions): Observable<T> {
throw new Error('Not Implemented');
}
public abstract request<T>(options: ApiRequestOptions): Observable<T>;
{{else}}
public request<T>(options: ApiRequestOptions): CancelablePromise<T> {
throw new Error('Not Implemented');
}
public abstract request<T>(options: ApiRequestOptions): CancelablePromise<T>;
{{/equals}}
}

View File

@ -36,47 +36,47 @@ export class CancelablePromise<T> implements Promise<T> {
onCancel: OnCancel
) => void
) {
this.#isResolved = false;
this.#isRejected = false;
this.#isCancelled = false;
this.#cancelHandlers = [];
this.#promise = new Promise<T>((resolve, reject) => {
this.#resolve = resolve;
this.#reject = reject;
this._isResolved = false;
this._isRejected = false;
this._isCancelled = false;
this._cancelHandlers = [];
this._promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
const onResolve = (value: T | PromiseLike<T>): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this.#isResolved = true;
this.#resolve?.(value);
this._isResolved = true;
this._resolve?.(value);
};
const onReject = (reason?: any): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this.#isRejected = true;
this.#reject?.(reason);
this._isRejected = true;
this._reject?.(reason);
};
const onCancel = (cancelHandler: () => void): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this.#cancelHandlers.push(cancelHandler);
this._cancelHandlers.push(cancelHandler);
};
Object.defineProperty(onCancel, 'isResolved', {
get: (): boolean => this.#isResolved,
get: (): boolean => this._isResolved,
});
Object.defineProperty(onCancel, 'isRejected', {
get: (): boolean => this.#isRejected,
get: (): boolean => this._isRejected,
});
Object.defineProperty(onCancel, 'isCancelled', {
get: (): boolean => this.#isCancelled,
get: (): boolean => this._isCancelled,
});
return executor(onResolve, onReject, onCancel as OnCancel);
@ -91,27 +91,27 @@ export class CancelablePromise<T> implements Promise<T> {
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2> {
return this.#promise.then(onFulfilled, onRejected);
return this._promise.then(onFulfilled, onRejected);
}
public catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> {
return this.#promise.catch(onRejected);
return this._promise.catch(onRejected);
}
public finally(onFinally?: (() => void) | null): Promise<T> {
return this.#promise.finally(onFinally);
return this._promise.finally(onFinally);
}
public cancel(): void {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this.#isCancelled = true;
if (this.#cancelHandlers.length) {
this._isCancelled = true;
if (this._cancelHandlers.length) {
try {
for (const cancelHandler of this.#cancelHandlers) {
for (const cancelHandler of this._cancelHandlers) {
cancelHandler();
}
} catch (error) {
@ -119,11 +119,11 @@ export class CancelablePromise<T> implements Promise<T> {
return;
}
}
this.#cancelHandlers.length = 0;
this.#reject?.(new CancelError('Request aborted'));
this._cancelHandlers.length = 0;
this._reject?.(new CancelError('Request aborted'));
}
public get isCancelled(): boolean {
return this.#isCancelled;
return this._isCancelled;
}
}

View File

@ -8,5 +8,5 @@ const getRequestBody = (options: ApiRequestOptions): any => {
return JSON.stringify(options.body);
}
}
return;
return undefined;
};

View File

@ -2,5 +2,5 @@ const getResponseBody = <T>(response: HttpResponse<T>): T | undefined => {
if (response.status !== 204 && response.body !== null) {
return response.body;
}
return;
return undefined;
};

View File

@ -5,5 +5,5 @@ const getResponseHeader = <T>(response: HttpResponse<T>, responseHeader?: string
return value;
}
}
return;
return undefined;
};

View File

@ -26,5 +26,17 @@ const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, for
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 headers;
};

View File

@ -2,5 +2,5 @@ const getRequestBody = (options: ApiRequestOptions): any => {
if (options.body) {
return options.body;
}
return;
return undefined;
};

View File

@ -2,5 +2,5 @@ const getResponseBody = (response: AxiosResponse<any>): any => {
if (response.status !== 204) {
return response.data;
}
return;
return undefined;
};

View File

@ -5,5 +5,5 @@ const getResponseHeader = (response: AxiosResponse<any>, responseHeader?: string
return content;
}
}
return;
return undefined;
};

View File

@ -1,6 +1,7 @@
{{>header}}
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import FormData from 'form-data';
import { ApiError } from './ApiError';
@ -22,6 +23,9 @@ import type { OpenAPIConfig } from './OpenAPI';
{{>functions/isBlob}}
{{>functions/isFormData}}
{{>functions/isSuccess}}

View File

@ -23,7 +23,7 @@ const sendRequest = async <T>(
try {
return await axios.request(requestConfig);
} catch (error) {
const axiosError = error as AxiosError;
const axiosError = error as AxiosError<T>;
if (axiosError.response) {
return axiosError.response;
}

View File

@ -1,5 +1,5 @@
const getRequestBody = (options: ApiRequestOptions): any => {
if (options.body) {
if (options.body !== undefined) {
if (options.mediaType?.includes('/json')) {
return JSON.stringify(options.body)
} else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) {
@ -8,5 +8,5 @@ const getRequestBody = (options: ApiRequestOptions): any => {
return JSON.stringify(options.body);
}
}
return;
return undefined;
};

View File

@ -3,7 +3,8 @@ const getResponseBody = async (response: Response): Promise<any> => {
try {
const contentType = response.headers.get('Content-Type');
if (contentType) {
const isJSON = contentType.toLowerCase().startsWith('application/json');
const jsonTypes = ['application/json', 'application/problem+json']
const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type));
if (isJSON) {
return await response.json();
} else {
@ -14,5 +15,5 @@ const getResponseBody = async (response: Response): Promise<any> => {
console.error(error);
}
}
return;
return undefined;
};

View File

@ -5,5 +5,5 @@ const getResponseHeader = (response: Response, responseHeader?: string): string
return content;
}
}
return;
return undefined;
};

View File

@ -12,10 +12,10 @@ const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void =>
const error = errors[result.status];
if (error) {
throw new ApiError(result, error);
throw new ApiError(options, result, error);
}
if (!result.ok) {
throw new ApiError(result, 'Generic Error');
throw new ApiError(options, result, 'Generic Error');
}
};

View File

@ -22,5 +22,5 @@ const getFormData = (options: ApiRequestOptions): FormData | undefined => {
return formData;
}
return;
return undefined;
};

View File

@ -1,5 +1,5 @@
const getRequestBody = (options: ApiRequestOptions): any => {
if (options.body) {
if (options.body !== undefined) {
if (options.mediaType?.includes('/json')) {
return JSON.stringify(options.body)
} else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) {
@ -8,5 +8,5 @@ const getRequestBody = (options: ApiRequestOptions): any => {
return JSON.stringify(options.body);
}
}
return;
return undefined;
};

View File

@ -3,7 +3,8 @@ const getResponseBody = async (response: Response): Promise<any> => {
try {
const contentType = response.headers.get('Content-Type');
if (contentType) {
const isJSON = contentType.toLowerCase().startsWith('application/json');
const jsonTypes = ['application/json', 'application/problem+json']
const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type));
if (isJSON) {
return await response.json();
} else {
@ -14,5 +15,5 @@ const getResponseBody = async (response: Response): Promise<any> => {
console.error(error);
}
}
return;
return undefined;
};

View File

@ -5,5 +5,5 @@ const getResponseHeader = (response: Response, responseHeader?: string): string
return content;
}
}
return;
return undefined;
};

View File

@ -1,8 +1,9 @@
{{>header}}
import { AbortController } from 'abort-controller';
import FormData from 'form-data';
import fetch, { BodyInit, Headers, RequestInit, Response } from 'node-fetch';
import fetch, { Headers } from 'node-fetch';
import type { RequestInit, Response } from 'node-fetch';
import type { AbortSignal } from 'node-fetch/externals';
import { ApiError } from './ApiError';
import type { ApiRequestOptions } from './ApiRequestOptions';

View File

@ -12,7 +12,7 @@ export const sendRequest = async (
headers,
method: options.method,
body: body ?? formData,
signal: controller.signal,
signal: controller.signal as AbortSignal,
};
onCancel(() => controller.abort());

View File

@ -1,5 +1,5 @@
const getRequestBody = (options: ApiRequestOptions): any => {
if (options.body) {
if (options.body !== undefined) {
if (options.mediaType?.includes('/json')) {
return JSON.stringify(options.body)
} else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) {
@ -8,6 +8,5 @@ const getRequestBody = (options: ApiRequestOptions): any => {
return JSON.stringify(options.body);
}
}
return;
return undefined;
};

View File

@ -3,7 +3,8 @@ const getResponseBody = (xhr: XMLHttpRequest): any => {
try {
const contentType = xhr.getResponseHeader('Content-Type');
if (contentType) {
const isJSON = contentType.toLowerCase().startsWith('application/json');
const jsonTypes = ['application/json', 'application/problem+json']
const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type));
if (isJSON) {
return JSON.parse(xhr.responseText);
} else {
@ -14,5 +15,5 @@ const getResponseBody = (xhr: XMLHttpRequest): any => {
console.error(error);
}
}
return;
return undefined;
};

View File

@ -5,5 +5,5 @@ const getResponseHeader = (xhr: XMLHttpRequest, responseHeader?: string): string
return content;
}
}
return;
return undefined;
};

View File

@ -32,7 +32,9 @@ import { request as __request } from '../core/request';
{{/if}}
{{#equals @root.httpClient 'angular'}}
@Injectable()
@Injectable({
providedIn: 'root',
})
{{/equals}}
export class {{{name}}}{{{@root.postfix}}} {
{{#if @root.exportClient}}

View File

@ -18,13 +18,13 @@ export type { OpenAPIConfig } from './core/OpenAPI';
{{#each models}}
{{#if @root.useUnionTypes}}
export type { {{{name}}} } from './models/{{{name}}}';
export type { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}';
{{else if enum}}
export { {{{name}}} } from './models/{{{name}}}';
export { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}';
{{else if enums}}
export { {{{name}}} } from './models/{{{name}}}';
export { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}';
{{else}}
export type { {{{name}}} } from './models/{{{name}}}';
export type { {{{name}}}{{#if @root.postfixModels}} as {{{name}}}{{{@root.postfixModels}}}{{/if}} } from './models/{{{name}}}';
{{/if}}
{{/each}}
{{/if}}
@ -41,7 +41,7 @@ export { ${{{name}}} } from './schemas/${{{name}}}';
{{#if services}}
{{#each services}}
export { {{{name}}}{{{@root.postfix}}} } from './services/{{{name}}}{{{@root.postfix}}}';
export { {{{name}}}{{{@root.postfixServices}}} } from './services/{{{name}}}{{{@root.postfixServices}}}';
{{/each}}
{{/if}}
{{/if}}

View File

@ -1,8 +1,13 @@
{{#if description}}
{{#ifdef description deprecated}}
/**
{{#if description}}
* {{{escapeComment description}}}
*/
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
export type {{{name}}} = {{>type parent=name}};
{{#if enums}}
{{#unless @root.useUnionTypes}}
@ -10,11 +15,16 @@ export type {{{name}}} = {{>type parent=name}};
export namespace {{{name}}} {
{{#each enums}}
{{#if description}}
{{#ifdef description deprecated}}
/**
{{#if description}}
* {{{escapeComment description}}}
*/
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
export enum {{{name}}} {
{{#each enum}}
{{{name}}} = {{{value}}},

View File

@ -1,8 +1,13 @@
{{#if description}}
{{#ifdef description deprecated}}
/**
{{#if description}}
* {{{escapeComment description}}}
*/
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
export enum {{{name}}} {
{{#each enum}}
{{#if description}}
@ -11,7 +16,7 @@ export enum {{{name}}} {
*/
{{/if}}
{{#containsSpaces name}}
"{{{name}}}" = {{{value}}},
'{{{name}}}' = {{{value}}},
{{else}}
{{{name}}} = {{{value}}},
{{/containsSpaces}}

View File

@ -1,15 +1,25 @@
{{#if description}}
{{#ifdef description deprecated}}
/**
{{#if description}}
* {{{escapeComment description}}}
*/
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
export type {{{name}}} = {
{{#each properties}}
{{#if description}}
{{#ifdef description deprecated}}
/**
{{#if description}}
* {{{escapeComment description}}}
*/
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
{{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type parent=../name}};
{{/each}}
};

View File

@ -1,6 +1,11 @@
{{#if description}}
{{#ifdef description deprecated}}
/**
{{#if description}}
* {{{escapeComment description}}}
*/
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
export type {{{name}}} = {{>type}};

View File

@ -6,9 +6,16 @@
{{/each}}
}: {
{{#each parameters}}
{{#ifdef description deprecated}}
/**
{{#if description}}
/** {{{escapeComment description}}} **/
* {{{escapeComment description}}}
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
{{{name}}}{{>isRequired}}: {{>type}},
{{/each}}
}

View File

@ -1,11 +1,16 @@
{{~#if properties~}}
{
{{#each properties}}
{{#if description}}
{{#ifdef description deprecated}}
/**
{{#if description}}
* {{{escapeComment description}}}
*/
{{/if}}
{{#if deprecated}}
* @deprecated
{{/if}}
*/
{{/ifdef}}
{{#if ../parent}}
{{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type parent=../parent}};
{{else}}

View File

@ -27,7 +27,7 @@ export const findOneOfParentDiscriminator = (openApi: OpenApi, parent?: Model):
}
}
}
return;
return undefined;
};
export const mapPropertyValue = (discriminator: OpenApiDiscriminator, parent: Model): string => {

View File

@ -1,50 +1,16 @@
import {
copyFile as __copyFile,
exists as __exists,
mkdir as __mkdir,
mkdirp as __mkdirp,
pathExists as __pathExists,
readFile as __readFile,
rm as __rm,
remove as __remove,
writeFile as __writeFile,
} from 'fs';
import { promisify } from 'util';
} from 'fs-extra';
// Wrapped file system calls
export const readFile = promisify(__readFile);
export const writeFile = promisify(__writeFile);
export const copyFile = promisify(__copyFile);
export const exists = promisify(__exists);
export const mkdir = (path: string): Promise<void> =>
new Promise((resolve, reject) => {
__mkdir(
path,
{
recursive: true,
},
error => {
if (error) {
reject(error);
} else {
resolve();
}
}
);
});
export const rmdir = (path: string): Promise<void> =>
new Promise((resolve, reject) => {
__rm(
path,
{
recursive: true,
force: true,
},
error => {
if (error) {
reject(error);
} else {
resolve();
}
}
);
});
// Export calls (needed for mocking)
export const readFile = __readFile;
export const writeFile = __writeFile;
export const copyFile = __copyFile;
export const exists = __pathExists;
export const mkdir = __mkdirp;
export const rmdir = __remove;

View File

@ -14,5 +14,7 @@ export const formatIndentation = (s: string, indent: Indent): string => {
return line; // Default output is tab formatted
}
});
return lines.join(EOL);
// Make sure we have a blank line at the end
const content = lines.join(EOL);
return `${content}${EOL}`;
};

View File

@ -10,5 +10,7 @@ describe('getPattern', () => {
expect(getPattern('\\')).toEqual('\\\\');
expect(getPattern('\\/')).toEqual('\\\\/');
expect(getPattern('\\/\\/')).toEqual('\\\\/\\\\/');
// eslint-disable-next-line prettier/prettier
expect(getPattern("'")).toEqual("\\'");
});
});

View File

@ -3,8 +3,12 @@
* However, to use it in HTML or inside new RegExp() we need to
* escape the pattern to become: '^\\d{3}-\\d{2}-\\d{4}$' in order
* to make it a valid regexp string.
*
* Also, escape single quote characters, because the output uses single quotes for strings
*
* @param pattern
*/
export const getPattern = (pattern?: string): string | undefined => {
return pattern?.replace(/\\/g, '\\\\');
// eslint-disable-next-line prettier/prettier
return pattern?.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
};

View File

@ -11,6 +11,7 @@ describe('registerHandlebarHelpers', () => {
useUnionTypes: false,
});
const helpers = Object.keys(Handlebars.helpers);
expect(helpers).toContain('ifdef');
expect(helpers).toContain('equals');
expect(helpers).toContain('notEquals');
expect(helpers).toContain('containsSpaces');

View File

@ -12,6 +12,14 @@ export const registerHandlebarHelpers = (root: {
useOptions: boolean;
useUnionTypes: boolean;
}): void => {
Handlebars.registerHelper('ifdef', function (this: any, ...args): string {
const options = args.pop();
if (!args.every(value => !value)) {
return options.fn(this);
}
return options.inverse(this);
});
Handlebars.registerHelper(
'equals',
function (this: any, a: string, b: string, options: Handlebars.HelperOptions): string {

View File

@ -28,7 +28,8 @@ import { writeClientServices } from './writeClientServices';
* @param exportSchemas Generate schemas
* @param exportSchemas Generate schemas
* @param indent Indentation options (4, 2 or tab)
* @param postfix Service name postfix
* @param postfixServices Service name postfix
* @param postfixModels Model name postfix
* @param clientName Custom client class name
* @param request Path to custom request file
*/
@ -44,7 +45,8 @@ export const writeClient = async (
exportModels: boolean,
exportSchemas: boolean,
indent: Indent,
postfix: string,
postfixServices: string,
postfixModels: string,
clientName?: string,
request?: string
): Promise<void> => {
@ -75,7 +77,7 @@ export const writeClient = async (
useUnionTypes,
useOptions,
indent,
postfix,
postfixServices,
clientName
);
}
@ -94,7 +96,7 @@ export const writeClient = async (
if (isDefined(clientName)) {
await mkdir(outputPath);
await writeClientClass(client, templates, outputPath, httpClient, clientName, indent, postfix);
await writeClientClass(client, templates, outputPath, httpClient, clientName, indent, postfixServices);
}
if (exportCore || exportServices || exportSchemas || exportModels) {
@ -108,7 +110,8 @@ export const writeClient = async (
exportServices,
exportModels,
exportSchemas,
postfix,
postfixServices,
postfixModels,
clientName
);
}

View File

@ -1,3 +1,5 @@
import { EOL } from 'os';
import type { Client } from '../client/interfaces/Client';
import { HttpClient } from '../HttpClient';
import { Indent } from '../Indent';
@ -38,11 +40,11 @@ describe('writeClientCore', () => {
await writeClientCore(client, templates, '/', HttpClient.FETCH, Indent.SPACE_4);
expect(writeFile).toBeCalledWith('/OpenAPI.ts', 'settings');
expect(writeFile).toBeCalledWith('/ApiError.ts', 'apiError');
expect(writeFile).toBeCalledWith('/ApiRequestOptions.ts', 'apiRequestOptions');
expect(writeFile).toBeCalledWith('/ApiResult.ts', 'apiResult');
expect(writeFile).toBeCalledWith('/CancelablePromise.ts', 'cancelablePromise');
expect(writeFile).toBeCalledWith('/request.ts', 'request');
expect(writeFile).toBeCalledWith('/OpenAPI.ts', `settings${EOL}`);
expect(writeFile).toBeCalledWith('/ApiError.ts', `apiError${EOL}`);
expect(writeFile).toBeCalledWith('/ApiRequestOptions.ts', `apiRequestOptions${EOL}`);
expect(writeFile).toBeCalledWith('/ApiResult.ts', `apiResult${EOL}`);
expect(writeFile).toBeCalledWith('/CancelablePromise.ts', `cancelablePromise${EOL}`);
expect(writeFile).toBeCalledWith('/request.ts', `request${EOL}`);
});
});

View File

@ -34,7 +34,7 @@ describe('writeClientIndex', () => {
},
};
await writeClientIndex(client, templates, '/', true, true, true, true, true, 'Service');
await writeClientIndex(client, templates, '/', true, true, true, true, true, 'Service', '');
expect(writeFile).toBeCalledWith('/index.ts', 'index');
});

View File

@ -19,7 +19,8 @@ import { sortServicesByName } from './sortServicesByName';
* @param exportServices Generate services
* @param exportModels Generate models
* @param exportSchemas Generate schemas
* @param postfix Service name postfix
* @param postfixServices Service name postfix
* @param postfixModels Model name postfix
* @param clientName Custom client class name
*/
export const writeClientIndex = async (
@ -31,7 +32,8 @@ export const writeClientIndex = async (
exportServices: boolean,
exportModels: boolean,
exportSchemas: boolean,
postfix: string,
postfixServices: string,
postfixModels: string,
clientName?: string
): Promise<void> => {
const templateResult = templates.index({
@ -40,7 +42,8 @@ export const writeClientIndex = async (
exportModels,
exportSchemas,
useUnionTypes,
postfix,
postfixServices,
postfixModels,
clientName,
server: client.server,
version: client.version,

View File

@ -1,3 +1,5 @@
import { EOL } from 'os';
import type { Model } from '../client/interfaces/Model';
import { HttpClient } from '../HttpClient';
import { Indent } from '../Indent';
@ -51,6 +53,6 @@ describe('writeClientModels', () => {
await writeClientModels(models, templates, '/', HttpClient.FETCH, false, Indent.SPACE_4);
expect(writeFile).toBeCalledWith('/User.ts', 'model');
expect(writeFile).toBeCalledWith('/User.ts', `model${EOL}`);
});
});

View File

@ -1,3 +1,5 @@
import { EOL } from 'os';
import type { Model } from '../client/interfaces/Model';
import { HttpClient } from '../HttpClient';
import { Indent } from '../Indent';
@ -51,6 +53,6 @@ describe('writeClientSchemas', () => {
await writeClientSchemas(models, templates, '/', HttpClient.FETCH, false, Indent.SPACE_4);
expect(writeFile).toBeCalledWith('/$User.ts', 'schema');
expect(writeFile).toBeCalledWith('/$User.ts', `schema${EOL}`);
});
});

View File

@ -1,3 +1,5 @@
import { EOL } from 'os';
import type { Service } from '../client/interfaces/Service';
import { HttpClient } from '../HttpClient';
import { Indent } from '../Indent';
@ -39,6 +41,6 @@ describe('writeClientServices', () => {
await writeClientServices(services, templates, '/', HttpClient.FETCH, false, false, Indent.SPACE_4, 'Service');
expect(writeFile).toBeCalledWith('/UserService.ts', 'service');
expect(writeFile).toBeCalledWith('/UserService.ts', `service${EOL}`);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,14 @@ const generate = async (input, output) => {
await OpenAPI.generate({
input,
output,
httpClient: OpenAPI.HttpClient.ANGULAR,
httpClient: OpenAPI.HttpClient.FETCH,
useOptions: true,
useUnionTypes: false,
exportCore: true,
exportSchemas: true,
exportModels: true,
exportServices: true,
clientName: 'Demo',
// clientName: 'Demo',
// indent: OpenAPI.Indent.SPACE_2,
// postfix: 'Service',
// request: './test/custom/request.ts',

View File

@ -15,6 +15,7 @@ describe('v2', () => {
exportSchemas: true,
exportModels: true,
exportServices: true,
postfixModels: 'Dto',
});
sync('./test/generated/v2/**/*.ts').forEach(file => {
@ -36,6 +37,7 @@ describe('v3', () => {
exportSchemas: true,
exportModels: true,
exportServices: true,
postfixModels: 'Dto',
});
sync('./test/generated/v3/**/*.ts').forEach(file => {

View File

@ -963,7 +963,9 @@
"enum": [
"Success",
"Warning",
"Error"
"Error",
"'Single Quote'",
"\"Double Quotes\""
]
},
"EnumWithNumbers": {
@ -1464,6 +1466,14 @@
}
]
},
"default": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"ModelWithPattern": {
"description": "This is a model that contains a some patterns",
"type": "object",
@ -1497,6 +1507,10 @@
"text": {
"type": "string",
"pattern": "^\\w+$"
},
"patternWithSingleQuotes": {
"type": "string",
"pattern": "^[a-zA-Z0-9']*$"
}
}
}

View File

@ -106,6 +106,28 @@
]
}
},
"/api/v{api-version}/parameters/deprecated": {
"post": {
"tags": [
"Deprecated"
],
"deprecated": true,
"operationId": "DeprecatedCall",
"parameters": [
{
"deprecated": true,
"description": "This parameter is deprecated",
"name": "parameter",
"in": "header",
"required": true,
"nullable": true,
"schema": {
"$ref": "#/components/schemas/DeprecatedModel"
}
}
]
}
},
"/api/v{api-version}/parameters/{parameterPath}": {
"post": {
"tags": [
@ -1537,7 +1559,9 @@
"enum": [
"Success",
"Warning",
"Error"
"Error",
"'Single Quote'",
"\"Double Quotes\""
]
},
"EnumWithNumbers": {
@ -1875,6 +1899,18 @@
}
}
},
"DeprecatedModel": {
"deprecated": true,
"description": "This is a deprecated model with a deprecated property",
"type": "object",
"properties": {
"prop": {
"deprecated": true,
"description": "This is a deprecated property",
"type": "string"
}
}
},
"ModelWithCircularReference": {
"description": "This is a model with one property containing a circular reference",
"type": "object",
@ -2064,6 +2100,76 @@
}
}
},
"CompositionWithOneOfAndSimpleDictionary": {
"description": "This is a model that contains a simple dictionary within composition",
"type": "object",
"properties": {
"propA": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": {
"type": "number"
}
}
]
}
}
},
"CompositionWithOneOfAndSimpleArrayDictionary": {
"description": "This is a model that contains a dictionary of simple arrays within composition",
"type": "object",
"properties": {
"propA": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "boolean"
}
}
}
]
}
}
},
"CompositionWithOneOfAndComplexArrayDictionary": {
"description": "This is a model that contains a dictionary of complex arrays (composited) within composition",
"type": "object",
"properties": {
"propA": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
]
}
}
}
]
}
}
},
"CompositionWithAllOfAndNullable": {
"description": "This is a model with one property with a 'all of' relationship",
"type": "object",
@ -2360,6 +2466,10 @@
"text": {
"type": "string",
"pattern": "^\\w+$"
},
"patternWithSingleQuotes": {
"type": "string",
"pattern": "^[a-zA-Z0-9']*$"
}
}
},
@ -2401,6 +2511,14 @@
}
}
},
"default": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"Pageable": {
"type": "object",
"properties": {
@ -2421,6 +2539,20 @@
}
}
}
},
"FreeFormObjectWithoutAdditionalProperties": {
"description": "This is a free-form object without additionalProperties.",
"type": "object"
},
"FreeFormObjectWithAdditionalPropertiesEqTrue": {
"description": "This is a free-form object with additionalProperties: true.",
"type": "object",
"additionalProperties": true
},
"FreeFormObjectWithAdditionalPropertiesEqEmptyObject": {
"description": "This is a free-form object with additionalProperties: {}.",
"type": "object",
"additionalProperties": {}
}
}
}

View File

@ -1,10 +1,10 @@
{
"compilerOptions": {
"outDir": "./dist",
"target": "es2020",
"target": "es2019",
"module": "commonjs",
"moduleResolution": "node",
"lib": ["es2020", "dom"],
"lib": ["es2019", "dom"],
"types": ["jest", "node"],
"declaration": false,
"declarationMap": false,

3
types/index.d.ts vendored
View File

@ -24,7 +24,8 @@ export type Options = {
exportModels?: boolean;
exportSchemas?: boolean;
indent?: Indent | '4' | '2' | 'tab';
postfix?: string;
postfixServices?: string;
postfixModels?: string;
request?: string;
write?: boolean;
};