mirror of
https://github.com/ferdikoomen/openapi-typescript-codegen.git
synced 2025-12-08 20:16:21 +00:00
Merge branch 'master' into feature/cancelable
# Conflicts: # README.md # package.json # src/utils/registerHandlebarTemplates.ts # test/__snapshots__/index.spec.js.snap # test/custom/request.ts # test/e2e/v3.fetch.spec.js # test/e2e/v3.node.spec.js # yarn.lock
This commit is contained in:
commit
d7c153ff55
@ -1,31 +1,31 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: circleci/node:latest-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "package.json" }}
|
||||
- v1-dependencies-
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: yarn install
|
||||
- save_cache:
|
||||
key: v1-dependencies-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: Build library
|
||||
command: yarn run release
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: yarn run test:coverage
|
||||
- run:
|
||||
name: Run e2e tests
|
||||
command: yarn run test:e2e
|
||||
- run:
|
||||
name: Submit to Codecov
|
||||
command: yarn run codecov
|
||||
build:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: circleci/node:latest-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "package.json" }}
|
||||
- v1-dependencies-
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: yarn install
|
||||
- save_cache:
|
||||
key: v1-dependencies-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: Build library
|
||||
command: yarn run release
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: yarn run test:coverage
|
||||
- run:
|
||||
name: Run e2e tests
|
||||
command: yarn run test:e2e
|
||||
- run:
|
||||
name: Submit to Codecov
|
||||
command: yarn run codecov
|
||||
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "04:00"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: "@types/node-fetch"
|
||||
- dependency-name: "node-fetch"
|
||||
@ -1,7 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- node
|
||||
script:
|
||||
- npm run release
|
||||
- npm run test:coverage
|
||||
- npm run codecov
|
||||
@ -27,5 +27,5 @@ https://help.github.com/articles/using-pull-requests
|
||||
5. Ensure the code is formatted by running: `yarn run eslint:fix`
|
||||
6. Commit your changes using a descriptive commit message
|
||||
|
||||
After your Pull Request is created, it will automatically be build using Circle CI
|
||||
and Travis. When the build is successful then the Pull Request is ready for review.
|
||||
After your Pull Request is created, it will automatically be build using Circle CI.
|
||||
When the build is successful then the Pull Request is ready for review.
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Ferdi Koomen
|
||||
Copyright (c) 2021 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
|
||||
|
||||
36
README.md
36
README.md
@ -2,12 +2,11 @@
|
||||
|
||||
[![NPM][npm-image]][npm-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Dependency Status][deps-image]][deps-url]
|
||||
[![Coverage][coverage-image]][coverage-url]
|
||||
[![Quality][quality-image]][quality-url]
|
||||
[![Code Climate][climate-image]][climate-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
[![Build][build-image]][build-url]
|
||||
|
||||
> Node.js library that generates Typescript clients based on the OpenAPI specification.
|
||||
|
||||
@ -15,7 +14,7 @@
|
||||
- Frontend ❤️ OpenAPI, but we do not want to use JAVA codegen in our builds
|
||||
- Quick, lightweight, robust and framework agnostic 🚀
|
||||
- Supports generation of TypeScript clients
|
||||
- Supports generations of fetch and XHR http clients
|
||||
- Supports generations of fetch, XHR and Axios 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
|
||||
@ -41,13 +40,15 @@ $ openapi --help
|
||||
-V, --version output the version number
|
||||
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
|
||||
-o, --output <value> Output directory (required)
|
||||
-c, --client <value> HTTP client to generate [fetch, xhr, node] (default: "fetch")
|
||||
-c, --client <value> HTTP client to generate [fetch, xhr, axios, node] (default: "fetch")
|
||||
--useOptions Use options instead of arguments
|
||||
--useUnionTypes Use union types instead of enums
|
||||
--exportCore <value> Write core files to disk (default: true)
|
||||
--exportServices <value> Write services to disk (default: true)
|
||||
--exportModels <value> Write models to disk (default: true)
|
||||
--exportSchemas <value> Write schemas to disk (default: false)
|
||||
--request <value> Path to custom request file
|
||||
-h, --help display help for command
|
||||
|
||||
Examples
|
||||
$ openapi --input ./spec.json
|
||||
@ -404,12 +405,12 @@ as part of your types to ensure everything is able to be TypeScript generated.
|
||||
|
||||
External references may be:
|
||||
* *relative references* - references to other files at the same location e.g.
|
||||
`{ $ref: 'schemas/customer.yml' }`
|
||||
`{ $ref: 'schemas/customer.yml' }`
|
||||
* *remote references* - fully qualified references to another remote location
|
||||
e.g. `{ $ref: 'https://myexampledomain.com/schemas/customer_schema.yml' }`
|
||||
e.g. `{ $ref: 'https://myexampledomain.com/schemas/customer_schema.yml' }`
|
||||
|
||||
For remote references, both files (when the file is on the current filesystem)
|
||||
and http(s) URLs are supported.
|
||||
For remote references, both files (when the file is on the current filesystem)
|
||||
and http(s) URLs are supported.
|
||||
|
||||
External references may also contain internal paths in the external schema (e.g.
|
||||
`schemas/collection.yml#/definitions/schemas/Customer`) and back-references to
|
||||
@ -420,14 +421,6 @@ At start-up, an OpenAPI or Swagger file with external references will be "bundle
|
||||
so that all external references and back-references will be resolved (but local
|
||||
references preserved).
|
||||
|
||||
### Compare to other generators
|
||||
Depending on which swagger generator you use, you will see different output.
|
||||
For instance: Different ways of generating models, services, level of quality,
|
||||
HTTP client, etc. I've compiled a list with the results per area and how they
|
||||
compare against the openapi-typescript-codegen.
|
||||
|
||||
[Click here to see the comparison](https://htmlpreview.github.io/?https://github.com/ferdikoomen/openapi-typescript-codegen/blob/master/samples/index.html)
|
||||
|
||||
|
||||
FAQ
|
||||
===
|
||||
@ -453,6 +446,9 @@ module.exports = {
|
||||
|
||||
|
||||
### Node.js support
|
||||
> Since version 3.x [`node-fetch`](https://www.npmjs.com/package/node-fetch) switched to ESM only, breaking many
|
||||
> CommonJS based toolchains (like Jest). Right now we do not support this new version!
|
||||
|
||||
By default, this library will generate a client that is compatible with the (browser based) [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API),
|
||||
however this client will not work inside the Node.js environment. If you want to generate a Node.js compatible client then
|
||||
you can specify `--client node` in the openapi call:
|
||||
@ -460,7 +456,7 @@ you can specify `--client node` in the openapi call:
|
||||
`openapi --input ./spec.json --output ./dist --client node`
|
||||
|
||||
This will generate a client that uses [`node-fetch`](https://www.npmjs.com/package/node-fetch) internally. However,
|
||||
in order to compile and run this client, you will need to install the `node-fetch` dependencies:
|
||||
in order to compile and run this client, you might need to install the `node-fetch@2.x` dependencies:
|
||||
|
||||
```
|
||||
npm install @types/node-fetch --save-dev
|
||||
@ -483,10 +479,6 @@ in your `tsconfig.json` file.
|
||||
[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
|
||||
[travis-url]: https://travis-ci.org/ferdikoomen/openapi-typescript-codegen
|
||||
[travis-image]: https://img.shields.io/travis/ferdikoomen/openapi-typescript-codegen.svg
|
||||
[deps-url]: https://david-dm.org/ferdikoomen/openapi-typescript-codegen
|
||||
[deps-image]: https://img.shields.io/david/ferdikoomen/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
|
||||
[quality-url]: https://lgtm.com/projects/g/ferdikoomen/openapi-typescript-codegen
|
||||
@ -495,3 +487,5 @@ in your `tsconfig.json` file.
|
||||
[climate-image]: https://img.shields.io/codeclimate/maintainability/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
|
||||
|
||||
@ -12,7 +12,7 @@ const params = program
|
||||
.version(pkg.version)
|
||||
.requiredOption('-i, --input <value>', 'OpenAPI specification, can be a path, url or string content (required)')
|
||||
.requiredOption('-o, --output <value>', 'Output directory (required)')
|
||||
.option('-c, --client <value>', 'HTTP client to generate [fetch, xhr, node]', 'fetch')
|
||||
.option('-c, --client <value>', 'HTTP client to generate [fetch, xhr, node, axios]', 'fetch')
|
||||
.option('--useOptions', 'Use options instead of arguments')
|
||||
.option('--useUnionTypes', 'Use union types instead of enums')
|
||||
.option('--exportCore <value>', 'Write core files to disk', true)
|
||||
|
||||
@ -20,10 +20,12 @@ module.exports = {
|
||||
testMatch: [
|
||||
'<rootDir>/test/e2e/v2.fetch.spec.js',
|
||||
'<rootDir>/test/e2e/v2.xhr.spec.js',
|
||||
'<rootDir>/test/e2e/v2.axios.spec.js',
|
||||
'<rootDir>/test/e2e/v2.node.spec.js',
|
||||
'<rootDir>/test/e2e/v2.babel.spec.js',
|
||||
'<rootDir>/test/e2e/v3.fetch.spec.js',
|
||||
'<rootDir>/test/e2e/v3.xhr.spec.js',
|
||||
'<rootDir>/test/e2e/v3.axios.spec.js',
|
||||
'<rootDir>/test/e2e/v3.node.spec.js',
|
||||
'<rootDir>/test/e2e/v3.babel.spec.js',
|
||||
],
|
||||
|
||||
66
package.json
66
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openapi-typescript-codegen",
|
||||
"version": "0.10.0",
|
||||
"version": "0.11.0",
|
||||
"description": "Library that generates Typescript clients based on the OpenAPI specification.",
|
||||
"author": "Ferdi Koomen",
|
||||
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",
|
||||
@ -32,7 +32,6 @@
|
||||
}
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"bin": {
|
||||
"openapi": "bin/index.js"
|
||||
@ -61,47 +60,48 @@
|
||||
"codecov": "codecov --token=66c30c23-8954-4892-bef9-fbaed0a2e42b"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"abort-controller": "^3.0.0",
|
||||
"axios": "^0.23.0",
|
||||
"camelcase": "^6.2.0",
|
||||
"commander": "^7.0.0",
|
||||
"commander": "^8.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
"handlebars": "^4.7.6",
|
||||
"js-yaml": "^4.0.0",
|
||||
"json-schema-ref-parser": "^9.0.7",
|
||||
"mkdirp": "^1.0.4",
|
||||
"node-fetch": "^2.6.5",
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.13.10",
|
||||
"@babel/core": "7.13.10",
|
||||
"@babel/preset-env": "7.13.10",
|
||||
"@babel/preset-typescript": "7.13.0",
|
||||
"@rollup/plugin-commonjs": "17.1.0",
|
||||
"@rollup/plugin-node-resolve": "11.2.0",
|
||||
"@types/express": "4.17.11",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/js-yaml": "4.0.0",
|
||||
"@types/node": "14.14.32",
|
||||
"@types/node-fetch": "2.5.8",
|
||||
"@types/qs": "6.9.6",
|
||||
"@typescript-eslint/eslint-plugin": "4.17.0",
|
||||
"@typescript-eslint/parser": "4.17.0",
|
||||
"abort-controller": "3.0.0",
|
||||
"codecov": "3.8.1",
|
||||
"eslint": "7.21.0",
|
||||
"eslint-config-prettier": "8.1.0",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"@babel/cli": "7.15.7",
|
||||
"@babel/core": "7.15.8",
|
||||
"@babel/preset-env": "7.15.8",
|
||||
"@babel/preset-typescript": "7.15.0",
|
||||
"@rollup/plugin-commonjs": "21.0.0",
|
||||
"@rollup/plugin-node-resolve": "13.0.5",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/glob": "7.1.4",
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/node": "16.11.1",
|
||||
"@types/qs": "6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "5.1.0",
|
||||
"@typescript-eslint/parser": "5.1.0",
|
||||
"codecov": "3.8.3",
|
||||
"eslint": "8.0.1",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"express": "4.17.1",
|
||||
"form-data": "4.0.0",
|
||||
"glob": "7.1.6",
|
||||
"jest": "26.6.3",
|
||||
"jest-cli": "26.6.3",
|
||||
"node-fetch": "2.6.1",
|
||||
"prettier": "2.2.1",
|
||||
"puppeteer": "8.0.0",
|
||||
"qs": "6.9.6",
|
||||
"rollup": "2.41.0",
|
||||
"glob": "7.2.0",
|
||||
"jest": "27.3.0",
|
||||
"jest-cli": "27.3.0",
|
||||
"prettier": "2.4.1",
|
||||
"puppeteer": "10.4.0",
|
||||
"qs": "6.10.1",
|
||||
"rollup": "2.58.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-typescript2": "0.30.0",
|
||||
"typescript": "4.2.3"
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ const getPlugins = () => {
|
||||
return plugins;
|
||||
}
|
||||
return [...plugins, terser()];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
input: './src/index.ts',
|
||||
|
||||
@ -1,274 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="google" value="notranslate">
|
||||
<meta http-equiv="Content-Language" content="en_EN">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" crossorigin="anonymous">
|
||||
<title>Compared to other generators</title>
|
||||
<style>
|
||||
details summary {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<section class="page-header">
|
||||
<h1>Compared to other generators</h1>
|
||||
<p class="lead">
|
||||
Depending on which swagger generator you use, you will see different output. For instance:
|
||||
Different ways of generating models, services, level of quality, HTTP client, etc.
|
||||
I've compiled a list below with the results per area and how they compare
|
||||
against the <strong>openapi-typescript-codegen.</strong>
|
||||
</p>
|
||||
</section>
|
||||
<h5>I've used the standard petshop examples from OpenAPI:</h5>
|
||||
<ul>
|
||||
<li><a href="https://petstore3.swagger.io/api/v3/openapi.json" target="_blank">https://petstore3.swagger.io/api/v3/openapi.json</a></li>
|
||||
<li><a href="https://petstore.swagger.io/v2/swagger.json" target="_blank">https://petstore.swagger.io/v2/swagger.json</a></li>
|
||||
</ul>
|
||||
<hr/>
|
||||
<h5>And used the following generators with their default options:</h5>
|
||||
<ul>
|
||||
<li>typescript-aurelia</li>
|
||||
<li>typescript-angular</li>
|
||||
<li>typescript-inversify</li>
|
||||
<li>typescript-angular</li>
|
||||
<li>typescript-fetch</li>
|
||||
<li>typescript-jquery</li>
|
||||
<li>typescript-node</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%"></th>
|
||||
<th width="10%">openapi-typescript-codegen</th>
|
||||
<th width="10%">aurelia</th>
|
||||
<th width="10%">inversify</th>
|
||||
<th width="10%">angular</th>
|
||||
<th width="10%">fetch</th>
|
||||
<th width="10%">jquery</th>
|
||||
<th width="10%">node</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Supports OpenApi v2 specification</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="success" data-type="aurelia">✅</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="success" data-type="fetch">✅</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="success" data-type="node">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Supports OpenApi v3 specification</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="danger" data-type="aurelia">❌</td>
|
||||
<td class="danger" data-type="inversify">❌</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="success" data-type="fetch">✅</td>
|
||||
<td class="danger" data-type="jquery">❌</td>
|
||||
<td class="danger" data-type="node">❌</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Supports authentication</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="danger" data-type="aurelia">❌</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="success" data-type="fetch">✅</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="success" data-type="node">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Strongly typed models</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="success" data-type="aurelia">✅</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="success" data-type="fetch">✅</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="success" data-type="node">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Strongly typed enums</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="success" data-type="aurelia">✅</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="success" data-type="fetch">✅</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="success" data-type="node">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Models and services exported as individual files</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="danger" data-type="aurelia">❌</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="danger" data-type="fetch">❌</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="danger" data-type="node">❌</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Index file that exports all services and models</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="success" data-type="aurelia">✅</td>
|
||||
<td class="danger" data-type="inversify">❌</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="success" data-type="fetch">✅</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="danger" data-type="node">❌</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Service returns typed result</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="success" data-type="aurelia">✅</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="danger" data-type="fetch">❌</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="success" data-type="node">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Service supports sending and receiving binary content</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="success" data-type="aurelia">✅</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="warning" data-type="angular">
|
||||
<span>⚠️</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>V3 version sends data as <code>application/octet-stream</code> instead of <code>application/x-www-form-urlencoded</code></p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="warning" data-type="fetch">
|
||||
<span>⚠️</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>V3 version sends data as <code>application/octet-stream</code> instead of <code>application/x-www-form-urlencoded</code></p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="success" data-type="node">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Models and services contain inline documentation</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="success" data-type="aurelia">✅</td>
|
||||
<td class="success" data-type="inversify">✅</td>
|
||||
<td class="success" data-type="angular">✅</td>
|
||||
<td class="success" data-type="fetch">✅</td>
|
||||
<td class="success" data-type="jquery">✅</td>
|
||||
<td class="success" data-type="node">✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Framework agnostic</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="danger" data-type="aurelia">❌</td>
|
||||
<td class="danger" data-type="inversify">❌</td>
|
||||
<td class="danger" data-type="angular">❌</td>
|
||||
<td class="warning" data-type="node">
|
||||
<span>⚠️</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Requires portable-fetch</p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="danger" data-type="jquery">❌</td>
|
||||
<td class="warning" data-type="node">
|
||||
<span>⚠️</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Requires bluebird</p>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Compiles in strict mode without issues</th>
|
||||
<td class="success" data-type="openapi-typescript-codegen">✅</td>
|
||||
<td class="danger" data-type="aurelia">
|
||||
<span>❌</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Errors when compiling: <code>PetApi.ts:147:30 - error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'</code></p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="danger" data-type="inversify">
|
||||
<span>❌</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Errors when compiling: <code>pet.service.ts:312:159 - error TS2304: Cannot find name 'body'</code></p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="danger" data-type="angular">
|
||||
<span>❌</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Errors when compiling: <code>pet.service.ts:528:26 - error TS1345: An expression of type 'void' cannot be tested for truthiness</code></p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="danger" data-type="fetch">
|
||||
<span>❌</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Errors when compiling: <code>api.ts:2276:67 - error TS2300: Duplicate identifier 'username'</code></p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="danger" data-type="jquery">
|
||||
<span>❌</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Errors when compiling: <code>PetApi.ts:25:12 - error TS2322: Type 'null' is not assignable to type 'JQueryAjaxSettings | undefined'</code></p>
|
||||
</details>
|
||||
</td>
|
||||
<td class="danger" data-type="node">
|
||||
<span>❌</span>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p>Errors when compiling: <code>api.ts:1631:45 - error TS2694: Namespace '"http"' has no exported member 'ClientResponse'</code></p>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Generated size (typescript)</th>
|
||||
<td data-type="openapi-typescript-codegen"><span class="badge">30KB</span></td>
|
||||
<td data-type="aurelia"><span class="badge">29KB</span></td>
|
||||
<td data-type="inversify"><span class="badge">37KB</span></td>
|
||||
<td data-type="angular"><span class="badge">63KB</span></td>
|
||||
<td data-type="fetch"><span class="badge">85KB</span></td>
|
||||
<td data-type="jquery"><span class="badge">57KB</span></td>
|
||||
<td data-type="node"><span class="badge">65KB</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Build size (javascript, not minimized)</th>
|
||||
<td data-type="openapi-typescript-codegen"><span class="badge">17KB</span></td>
|
||||
<td data-type="aurelia"><span class="badge">16KB</span></td>
|
||||
<td data-type="inversify"><span class="badge">22KB</span></td>
|
||||
<td data-type="angular"><span class="badge">36KB</span></td>
|
||||
<td data-type="fetch"><span class="badge">48KB</span></td>
|
||||
<td data-type="jquery"><span class="badge">37KB</span></td>
|
||||
<td data-type="node"><span class="badge">53KB</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Generation time</th>
|
||||
<td data-type="openapi-typescript-codegen"><span class="badge">0.2s</span></td>
|
||||
<td data-type="aurelia"><span class="badge">0.7s</span></td>
|
||||
<td data-type="inversify"><span class="badge">0.7s</span></td>
|
||||
<td data-type="angular"><span class="badge">1.4s</span></td>
|
||||
<td data-type="fetch"><span class="badge">1.1s</span></td>
|
||||
<td data-type="jquery"><span class="badge">0.7s</span></td>
|
||||
<td data-type="node"><span class="badge">0.7s</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -2,4 +2,5 @@ export enum HttpClient {
|
||||
FETCH = 'fetch',
|
||||
XHR = 'xhr',
|
||||
NODE = 'node',
|
||||
AXIOS = 'axios',
|
||||
}
|
||||
|
||||
@ -3,4 +3,5 @@ import type { Model } from './Model';
|
||||
export interface OperationParameter extends Model {
|
||||
in: 'path' | 'query' | 'header' | 'formData' | 'body' | 'cookie';
|
||||
prop: string;
|
||||
mediaType: string | null;
|
||||
}
|
||||
|
||||
1
src/client/interfaces/Type.d.ts
vendored
1
src/client/interfaces/Type.d.ts
vendored
@ -3,4 +3,5 @@ export interface Type {
|
||||
base: string;
|
||||
template: string | null;
|
||||
imports: string[];
|
||||
isNullable: boolean;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export type Options = {
|
||||
* service layer, etc.
|
||||
* @param input The relative location of the OpenAPI spec
|
||||
* @param output The relative location of the output directory
|
||||
* @param httpClient The selected httpClient (fetch or XHR)
|
||||
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
|
||||
* @param useOptions Use options or arguments functions
|
||||
* @param useUnionTypes Use union types instead of enums
|
||||
* @param exportCore: Generate core client classes
|
||||
|
||||
@ -3,7 +3,7 @@ import type { Enum } from '../../../client/interfaces/Enum';
|
||||
export function 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)) {
|
||||
if (/^(\w+=[0-9]+)/g.test(description)) {
|
||||
const matches = description.match(/(\w+=[0-9]+,?)/g);
|
||||
if (matches) {
|
||||
// Grab the values from the description
|
||||
|
||||
@ -22,7 +22,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
|
||||
isDefinition,
|
||||
isReadOnly: definition.readOnly === true,
|
||||
isNullable: definition['x-nullable'] === true,
|
||||
isRequired: definition.default !== undefined,
|
||||
isRequired: false,
|
||||
format: definition.format,
|
||||
maximum: definition.maximum,
|
||||
exclusiveMaximum: definition.exclusiveMaximum,
|
||||
|
||||
@ -31,6 +31,11 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema,
|
||||
});
|
||||
|
||||
if (definition.properties) {
|
||||
const properties = getModelProperties(openApi, definition, getModel);
|
||||
properties.forEach(property => {
|
||||
composition.imports.push(...property.imports);
|
||||
composition.enums.push(...property.enums);
|
||||
});
|
||||
composition.properties.push({
|
||||
name: 'properties',
|
||||
export: 'interface',
|
||||
@ -46,7 +51,7 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [...getModelProperties(openApi, definition, getModel)],
|
||||
properties,
|
||||
});
|
||||
}
|
||||
return composition;
|
||||
|
||||
@ -15,7 +15,7 @@ export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema,
|
||||
for (const propertyName in definition.properties) {
|
||||
if (definition.properties.hasOwnProperty(propertyName)) {
|
||||
const property = definition.properties[propertyName];
|
||||
const propertyRequired = definition.required?.includes(propertyName) || property.default !== undefined;
|
||||
const propertyRequired = !!definition.required?.includes(propertyName);
|
||||
if (property.$ref) {
|
||||
const model = getType(property.$ref);
|
||||
models.push({
|
||||
|
||||
@ -7,6 +7,7 @@ describe('getModelTemplate', () => {
|
||||
base: 'Link',
|
||||
template: 'Model',
|
||||
imports: ['Model'],
|
||||
isNullable: false,
|
||||
});
|
||||
expect(template).toEqual('<T>');
|
||||
});
|
||||
@ -17,6 +18,7 @@ describe('getModelTemplate', () => {
|
||||
base: 'string',
|
||||
template: null,
|
||||
imports: [],
|
||||
isNullable: false,
|
||||
});
|
||||
expect(template).toEqual('');
|
||||
});
|
||||
|
||||
@ -42,6 +42,7 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
mediaType: null,
|
||||
};
|
||||
|
||||
if (parameter.$ref) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import camelCase from 'camelcase';
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Replaces any invalid characters from a parameter name.
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import type { OpenApi } from '../interfaces/OpenApi';
|
||||
import type { OpenApiReference } from '../interfaces/OpenApiReference';
|
||||
|
||||
const ESCAPED_REF_SLASH = /~1/g;
|
||||
const ESCAPED_REF_TILDE = /~0/g;
|
||||
|
||||
export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
|
||||
if (item.$ref) {
|
||||
// Fetch the paths to the definitions, this converts:
|
||||
@ -13,9 +16,10 @@ export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
|
||||
// Try to find the reference by walking down the path,
|
||||
// if we cannot find it, then we throw an error.
|
||||
let result: any = openApi;
|
||||
paths.forEach((path: string): void => {
|
||||
if (result.hasOwnProperty(path)) {
|
||||
result = result[path];
|
||||
paths.forEach(path => {
|
||||
const decodedPath = decodeURIComponent(path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~'));
|
||||
if (result.hasOwnProperty(decodedPath)) {
|
||||
result = result[decodedPath];
|
||||
} else {
|
||||
throw new Error(`Could not find reference: "${item.$ref}"`);
|
||||
}
|
||||
|
||||
@ -17,9 +17,10 @@ export function getType(value?: string, template?: string): Type {
|
||||
base: 'any',
|
||||
template: null,
|
||||
imports: [],
|
||||
isNullable: false,
|
||||
};
|
||||
|
||||
const valueClean = stripNamespace(value || '');
|
||||
const valueClean = decodeURIComponent(stripNamespace(value || ''));
|
||||
|
||||
if (/\[.*\]$/g.test(valueClean)) {
|
||||
const matches = valueClean.match(/(.*?)\[(.*)\]$/);
|
||||
|
||||
@ -4,6 +4,6 @@ export function sortByRequired(a: OperationParameter, b: OperationParameter): nu
|
||||
const aNeedsValue = a.isRequired && a.default === undefined;
|
||||
const bNeedsValue = b.isRequired && b.default === undefined;
|
||||
if (aNeedsValue && !bNeedsValue) return -1;
|
||||
if (!aNeedsValue && bNeedsValue) return 1;
|
||||
if (bNeedsValue && !aNeedsValue) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
2
src/openApi/v3/interfaces/OpenApiSchema.d.ts
vendored
2
src/openApi/v3/interfaces/OpenApiSchema.d.ts
vendored
@ -25,7 +25,7 @@ export interface OpenApiSchema extends OpenApiReference, WithEnumExtension {
|
||||
minProperties?: number;
|
||||
required?: string[];
|
||||
enum?: (string | number)[];
|
||||
type?: string;
|
||||
type?: string | string[];
|
||||
allOf?: OpenApiSchema[];
|
||||
oneOf?: OpenApiSchema[];
|
||||
anyOf?: OpenApiSchema[];
|
||||
|
||||
@ -1,30 +1,24 @@
|
||||
import { isDefined } from '../../../utils/isDefined';
|
||||
import type { Dictionary } from '../../../utils/types';
|
||||
import type { OpenApi } from '../interfaces/OpenApi';
|
||||
import type { OpenApiMediaType } from '../interfaces/OpenApiMediaType';
|
||||
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
|
||||
|
||||
export function getContent(openApi: OpenApi, content: Dictionary<OpenApiMediaType>): OpenApiSchema | null {
|
||||
/* prettier-ignore */
|
||||
return (
|
||||
content['application/json-patch+json'] &&
|
||||
content['application/json-patch+json'].schema
|
||||
) || (
|
||||
content['application/json'] &&
|
||||
content['application/json'].schema
|
||||
) || (
|
||||
content['text/json'] &&
|
||||
content['text/json'].schema
|
||||
) || (
|
||||
content['text/plain'] &&
|
||||
content['text/plain'].schema
|
||||
) || (
|
||||
content['multipart/mixed'] &&
|
||||
content['multipart/mixed'].schema
|
||||
) || (
|
||||
content['multipart/related'] &&
|
||||
content['multipart/related'].schema
|
||||
) || (
|
||||
content['multipart/batch'] &&
|
||||
content['multipart/batch'].schema
|
||||
) || null;
|
||||
const basicMediaTypeSchema =
|
||||
content['application/json-patch+json']?.schema ||
|
||||
content['application/json']?.schema ||
|
||||
content['text/json']?.schema ||
|
||||
content['text/plain']?.schema ||
|
||||
content['multipart/mixed']?.schema ||
|
||||
content['multipart/related']?.schema ||
|
||||
content['multipart/batch']?.schema;
|
||||
|
||||
if (basicMediaTypeSchema) {
|
||||
return basicMediaTypeSchema;
|
||||
}
|
||||
|
||||
const mediaTypes = Object.values(content);
|
||||
const mediaType = mediaTypes.find(mediaType => isDefined(mediaType.schema));
|
||||
return mediaType?.schema || null;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import type { Enum } from '../../../client/interfaces/Enum';
|
||||
export function 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)) {
|
||||
if (/^(\w+=[0-9]+)/g.test(description)) {
|
||||
const matches = description.match(/(\w+=[0-9]+,?)/g);
|
||||
if (matches) {
|
||||
// Grab the values from the description
|
||||
|
||||
10
src/openApi/v3/parser/getMediaType.ts
Normal file
10
src/openApi/v3/parser/getMediaType.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { Dictionary } from '../../../utils/types';
|
||||
import type { OpenApi } from '../interfaces/OpenApi';
|
||||
import type { OpenApiMediaType } from '../interfaces/OpenApiMediaType';
|
||||
|
||||
export function getMediaType(openApi: OpenApi, content: Dictionary<OpenApiMediaType>): string | null {
|
||||
return (
|
||||
Object.keys(content).find(key => ['application/json-patch+json', 'application/json', 'text/json', 'text/plain', 'multipart/mixed', 'multipart/related', 'multipart/batch'].includes(key)) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
@ -23,7 +23,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
|
||||
isDefinition,
|
||||
isReadOnly: definition.readOnly === true,
|
||||
isNullable: definition.nullable === true,
|
||||
isRequired: definition.default !== undefined,
|
||||
isRequired: false,
|
||||
format: definition.format,
|
||||
maximum: definition.maximum,
|
||||
exclusiveMaximum: definition.exclusiveMaximum,
|
||||
@ -180,6 +180,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
|
||||
model.type = definitionType.type;
|
||||
model.base = definitionType.base;
|
||||
model.template = definitionType.template;
|
||||
model.isNullable = definitionType.isNullable;
|
||||
model.imports.push(...definitionType.imports);
|
||||
model.default = getModelDefault(definition, model);
|
||||
return model;
|
||||
|
||||
@ -31,6 +31,11 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema,
|
||||
});
|
||||
|
||||
if (definition.properties) {
|
||||
const properties = getModelProperties(openApi, definition, getModel);
|
||||
properties.forEach(property => {
|
||||
composition.imports.push(...property.imports);
|
||||
composition.enums.push(...property.enums);
|
||||
});
|
||||
composition.properties.push({
|
||||
name: 'properties',
|
||||
export: 'interface',
|
||||
@ -46,7 +51,7 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [...getModelProperties(openApi, definition, getModel)],
|
||||
properties,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema,
|
||||
for (const propertyName in definition.properties) {
|
||||
if (definition.properties.hasOwnProperty(propertyName)) {
|
||||
const property = definition.properties[propertyName];
|
||||
const propertyRequired = definition.required?.includes(propertyName) || property.default !== undefined;
|
||||
const propertyRequired = !!definition.required?.includes(propertyName);
|
||||
if (property.$ref) {
|
||||
const model = getType(property.$ref);
|
||||
models.push({
|
||||
|
||||
@ -7,6 +7,7 @@ describe('getModelTemplate', () => {
|
||||
base: 'Link',
|
||||
template: 'Model',
|
||||
imports: ['Model'],
|
||||
isNullable: false,
|
||||
});
|
||||
expect(template).toEqual('<T>');
|
||||
});
|
||||
@ -17,6 +18,7 @@ describe('getModelTemplate', () => {
|
||||
base: 'string',
|
||||
template: null,
|
||||
imports: [],
|
||||
isNullable: false,
|
||||
});
|
||||
expect(template).toEqual('');
|
||||
});
|
||||
|
||||
@ -27,6 +27,7 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
mediaType: null,
|
||||
};
|
||||
|
||||
if (parameter.$ref) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import camelCase from 'camelcase';
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Replaces any invalid characters from a parameter name.
|
||||
|
||||
@ -4,6 +4,7 @@ import type { OpenApi } from '../interfaces/OpenApi';
|
||||
import type { OpenApiRequestBody } from '../interfaces/OpenApiRequestBody';
|
||||
import { getComment } from './getComment';
|
||||
import { getContent } from './getContent';
|
||||
import { getMediaType } from './getMediaType';
|
||||
import { getModel } from './getModel';
|
||||
import { getType } from './getType';
|
||||
|
||||
@ -27,11 +28,13 @@ export function getOperationRequestBody(openApi: OpenApi, parameter: OpenApiRequ
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
mediaType: null,
|
||||
};
|
||||
|
||||
if (parameter.content) {
|
||||
const schema = getContent(openApi, parameter.content);
|
||||
if (schema) {
|
||||
requestBody.mediaType = getMediaType(openApi, parameter.content);
|
||||
if (schema?.$ref) {
|
||||
const model = getType(schema.$ref);
|
||||
requestBody.export = 'reference';
|
||||
|
||||
@ -34,4 +34,28 @@ describe('getRef', () => {
|
||||
type: 'integer',
|
||||
});
|
||||
});
|
||||
|
||||
it('should produce correct result for encoded ref path', () => {
|
||||
expect(
|
||||
getRef(
|
||||
{
|
||||
openapi: '3.0',
|
||||
info: {
|
||||
title: 'dummy',
|
||||
version: '1.0',
|
||||
},
|
||||
paths: {
|
||||
'/api/user/{id}': {
|
||||
description: 'This is an Example path',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$ref: '#/paths/~1api~1user~1%7Bid%7D',
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
description: 'This is an Example path',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import type { OpenApi } from '../interfaces/OpenApi';
|
||||
import type { OpenApiReference } from '../interfaces/OpenApiReference';
|
||||
|
||||
const ESCAPED_REF_SLASH = /~1/g;
|
||||
const ESCAPED_REF_TILDE = /~0/g;
|
||||
|
||||
export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
|
||||
if (item.$ref) {
|
||||
// Fetch the paths to the definitions, this converts:
|
||||
@ -13,9 +16,10 @@ export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
|
||||
// Try to find the reference by walking down the path,
|
||||
// if we cannot find it, then we throw an error.
|
||||
let result: any = openApi;
|
||||
paths.forEach((path: string): void => {
|
||||
if (result.hasOwnProperty(path)) {
|
||||
result = result[path];
|
||||
paths.forEach(path => {
|
||||
const decodedPath = decodeURIComponent(path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~'));
|
||||
if (result.hasOwnProperty(decodedPath)) {
|
||||
result = result[decodedPath];
|
||||
} else {
|
||||
throw new Error(`Could not find reference: "${item.$ref}"`);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('number');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should convert string', () => {
|
||||
@ -15,6 +16,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('string');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should convert string array', () => {
|
||||
@ -23,6 +25,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('string');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should convert template with primary', () => {
|
||||
@ -31,6 +34,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('Link');
|
||||
expect(type.template).toEqual('string');
|
||||
expect(type.imports).toEqual(['Link']);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should convert template with model', () => {
|
||||
@ -39,6 +43,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('Link');
|
||||
expect(type.template).toEqual('Model');
|
||||
expect(type.imports).toEqual(['Link', 'Model']);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should have double imports', () => {
|
||||
@ -47,6 +52,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('Link');
|
||||
expect(type.template).toEqual('Link');
|
||||
expect(type.imports).toEqual(['Link', 'Link']);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should convert generic', () => {
|
||||
@ -55,6 +61,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('T');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support dot', () => {
|
||||
@ -63,6 +70,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('model_000');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual(['model_000']);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support dashes', () => {
|
||||
@ -71,6 +79,7 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('some_special_schema');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual(['some_special_schema']);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support dollar sign', () => {
|
||||
@ -79,5 +88,24 @@ describe('getType', () => {
|
||||
expect(type.base).toEqual('$some_special_schema');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual(['$some_special_schema']);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support multiple base types', () => {
|
||||
const type = getType(['string', 'int']);
|
||||
expect(type.type).toEqual('string | number');
|
||||
expect(type.base).toEqual('string | number');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
expect(type.isNullable).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support multiple nullable types', () => {
|
||||
const type = getType(['string', 'null']);
|
||||
expect(type.type).toEqual('string');
|
||||
expect(type.base).toEqual('string');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
expect(type.isNullable).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -5,21 +5,35 @@ import { stripNamespace } from './stripNamespace';
|
||||
function encode(value: string): string {
|
||||
return value.replace(/^[^a-zA-Z_$]+/g, '').replace(/[^\w$]+/g, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse any string value into a type object.
|
||||
* @param value String value like "integer" or "Link[Model]".
|
||||
* @param values String or String[] value like "integer", "Link[Model]" or ["string", "null"]
|
||||
* @param template Optional template class from parent (needed to process generics)
|
||||
*/
|
||||
export function getType(value?: string, template?: string): Type {
|
||||
export function getType(values?: string | string[], template?: string): Type {
|
||||
const result: Type = {
|
||||
type: 'any',
|
||||
base: 'any',
|
||||
template: null,
|
||||
imports: [],
|
||||
isNullable: false,
|
||||
};
|
||||
|
||||
const valueClean = stripNamespace(value || '');
|
||||
// Special case for JSON Schema spec (december 2020, page 17),
|
||||
// that allows type to be an array of primitive types...
|
||||
if (Array.isArray(values)) {
|
||||
const type = values
|
||||
.filter(value => value !== 'null')
|
||||
.filter(value => hasMappedType(value))
|
||||
.map(value => getMappedType(value))
|
||||
.join(' | ');
|
||||
result.type = type;
|
||||
result.base = type;
|
||||
result.isNullable = values.includes('null');
|
||||
return result;
|
||||
}
|
||||
|
||||
const valueClean = decodeURIComponent(stripNamespace(values || ''));
|
||||
|
||||
if (/\[.*\]$/g.test(valueClean)) {
|
||||
const matches = valueClean.match(/(.*?)\[(.*)\]$/);
|
||||
|
||||
@ -4,6 +4,6 @@ export function sortByRequired(a: OperationParameter, b: OperationParameter): nu
|
||||
const aNeedsValue = a.isRequired && a.default === undefined;
|
||||
const bNeedsValue = b.isRequired && b.default === undefined;
|
||||
if (aNeedsValue && !bNeedsValue) return -1;
|
||||
if (!aNeedsValue && bNeedsValue) return 1;
|
||||
if (bNeedsValue && !aNeedsValue) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ export type ApiRequestOptions = {
|
||||
readonly query?: Record<string, any>;
|
||||
readonly formData?: Record<string, any>;
|
||||
readonly body?: any;
|
||||
readonly mediaType?: string;
|
||||
readonly responseHeader?: string;
|
||||
readonly errors?: Record<number, string>;
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ type Config = {
|
||||
USERNAME?: string | Resolver<string>;
|
||||
PASSWORD?: string | Resolver<string>;
|
||||
HEADERS?: Headers | Resolver<Headers>;
|
||||
ENCODE_PATH?: (path: string) => string;
|
||||
}
|
||||
|
||||
export const OpenAPI: Config = {
|
||||
@ -23,4 +24,5 @@ export const OpenAPI: Config = {
|
||||
USERNAME: undefined,
|
||||
PASSWORD: undefined,
|
||||
HEADERS: undefined,
|
||||
ENCODE_PATH: undefined,
|
||||
};
|
||||
|
||||
30
src/templates/core/axios/getHeaders.hbs
Normal file
30
src/templates/core/axios/getHeaders.hbs
Normal file
@ -0,0 +1,30 @@
|
||||
async function getHeaders(options: ApiRequestOptions, formData?: FormData): Promise<Record<string, string>> {
|
||||
const token = await resolve(options, OpenAPI.TOKEN);
|
||||
const username = await resolve(options, OpenAPI.USERNAME);
|
||||
const password = await resolve(options, OpenAPI.PASSWORD);
|
||||
const additionalHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {}
|
||||
|
||||
const headers = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
...formHeaders,
|
||||
})
|
||||
.filter(([key, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: value,
|
||||
}), {} as Record<string, string>);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers['Authorization'] = `Basic ${credentials}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
6
src/templates/core/axios/getResponseBody.hbs
Normal file
6
src/templates/core/axios/getResponseBody.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
function getResponseBody(response: AxiosResponse<any>): any {
|
||||
if (response.status !== 204) {
|
||||
return response.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
9
src/templates/core/axios/getResponseHeader.hbs
Normal file
9
src/templates/core/axios/getResponseHeader.hbs
Normal file
@ -0,0 +1,9 @@
|
||||
function getResponseHeader(response: AxiosResponse<any>, responseHeader?: string): string | null {
|
||||
if (responseHeader) {
|
||||
const content = response.headers[responseHeader];
|
||||
if (isString(content)) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
85
src/templates/core/axios/request.hbs
Normal file
85
src/templates/core/axios/request.hbs
Normal file
@ -0,0 +1,85 @@
|
||||
{{>header}}
|
||||
|
||||
import { AbortController, AbortSignal } from 'abort-controller';
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import FormData from 'form-data';
|
||||
|
||||
import { ApiError } from './ApiError';
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import type { ApiResult } from './ApiResult';
|
||||
import { CancelablePromise } from './CancelablePromise';
|
||||
import { OpenAPI } from './OpenAPI';
|
||||
|
||||
{{>functions/isDefined}}
|
||||
|
||||
|
||||
{{>functions/isString}}
|
||||
|
||||
|
||||
{{>functions/isStringWithValue}}
|
||||
|
||||
|
||||
{{>functions/isSuccess}}
|
||||
|
||||
|
||||
{{>functions/base64}}
|
||||
|
||||
|
||||
{{>functions/getQueryString}}
|
||||
|
||||
|
||||
{{>functions/getUrl}}
|
||||
|
||||
|
||||
{{>functions/getFormData}}
|
||||
|
||||
|
||||
{{>functions/resolve}}
|
||||
|
||||
|
||||
{{>axios/getHeaders}}
|
||||
|
||||
|
||||
{{>axios/sendRequest}}
|
||||
|
||||
|
||||
{{>axios/getResponseHeader}}
|
||||
|
||||
|
||||
{{>axios/getResponseBody}}
|
||||
|
||||
|
||||
{{>functions/catchErrors}}
|
||||
|
||||
|
||||
/**
|
||||
* Request using axios client
|
||||
* @param options The request options from the the service
|
||||
* @returns CancelablePromise<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
export function request<T>(options: ApiRequestOptions): CancelablePromise<T> {
|
||||
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
||||
try {
|
||||
const url = getUrl(options);
|
||||
const response = await sendRequest(options, url, onCancel);
|
||||
const responseBody = getResponseBody(response);
|
||||
const responseHeader = getResponseHeader(response, options.responseHeader);
|
||||
|
||||
const result: ApiResult = {
|
||||
url,
|
||||
ok: isSuccess(response.status),
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: responseHeader || responseBody,
|
||||
};
|
||||
|
||||
catchErrors(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
16
src/templates/core/axios/sendRequest.hbs
Normal file
16
src/templates/core/axios/sendRequest.hbs
Normal file
@ -0,0 +1,16 @@
|
||||
async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (cancelHandler: () => void) => void): Promise<AxiosResponse<any>> {
|
||||
const controller = new AbortController();
|
||||
const formData = options.formData && getFormData(options.formData);
|
||||
const data = formData || options.body;
|
||||
const config: AxiosRequestConfig = {
|
||||
url,
|
||||
data,
|
||||
method: options.method,
|
||||
headers: await getHeaders(options, formData),
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
onCancel(() => controller.abort());
|
||||
|
||||
return await axios.request(config);
|
||||
}
|
||||
@ -2,25 +2,34 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
|
||||
const token = await resolve(options, OpenAPI.TOKEN);
|
||||
const username = await resolve(options, OpenAPI.USERNAME);
|
||||
const password = await resolve(options, OpenAPI.PASSWORD);
|
||||
const defaultHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
const additionalHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
|
||||
const headers = new Headers({
|
||||
const defaultHeaders = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...defaultHeaders,
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
});
|
||||
})
|
||||
.filter(([key, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: value,
|
||||
}), {});
|
||||
|
||||
const headers = new Headers(defaultHeaders);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers.append('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = btoa(`${username}:${password}`);
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers.append('Authorization', `Basic ${credentials}`);
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isBlob(options.body)) {
|
||||
if (options.mediaType) {
|
||||
headers.append('Content-Type', options.mediaType);
|
||||
} else if (isBlob(options.body)) {
|
||||
headers.append('Content-Type', options.body.type || 'application/octet-stream');
|
||||
} else if (isString(options.body)) {
|
||||
headers.append('Content-Type', 'text/plain');
|
||||
|
||||
@ -4,7 +4,9 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isString(options.body) || isBlob(options.body)) {
|
||||
if (options.mediaType?.includes('/json')) {
|
||||
return JSON.stringify(options.body)
|
||||
} else if (isString(options.body) || isBlob(options.body)) {
|
||||
return options.body;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
async function getResponseBody(response: Response): Promise<any> {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
if (response.status !== 204) {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -18,6 +18,9 @@ import { OpenAPI } from './OpenAPI';
|
||||
{{>functions/isBlob}}
|
||||
|
||||
|
||||
{{>functions/base64}}
|
||||
|
||||
|
||||
{{>functions/getQueryString}}
|
||||
|
||||
|
||||
|
||||
7
src/templates/core/functions/base64.hbs
Normal file
7
src/templates/core/functions/base64.hbs
Normal file
@ -0,0 +1,7 @@
|
||||
function base64(str: string): string {
|
||||
try {
|
||||
return btoa(str);
|
||||
} catch (err) {
|
||||
return Buffer.from(str).toString('base64');
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
function getUrl(options: ApiRequestOptions): string {
|
||||
const path = options.path.replace(/[:]/g, '_');
|
||||
const path = OpenAPI.ENCODE_PATH ? OpenAPI.ENCODE_PATH(options.path) : options.path;
|
||||
const url = `${OpenAPI.BASE}${path}`;
|
||||
|
||||
if (options.query) {
|
||||
return `${url}${getQueryString(options.query)}`;
|
||||
}
|
||||
|
||||
@ -2,25 +2,34 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
|
||||
const token = await resolve(options, OpenAPI.TOKEN);
|
||||
const username = await resolve(options, OpenAPI.USERNAME);
|
||||
const password = await resolve(options, OpenAPI.PASSWORD);
|
||||
const defaultHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
const additionalHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
|
||||
const headers = new Headers({
|
||||
const defaultHeaders = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...defaultHeaders,
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
});
|
||||
})
|
||||
.filter(([key, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: value,
|
||||
}), {});
|
||||
|
||||
const headers = new Headers(defaultHeaders);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers.append('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers.append('Authorization', `Basic ${credentials}`);
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isBinary(options.body)) {
|
||||
if (options.mediaType) {
|
||||
headers.append('Content-Type', options.mediaType);
|
||||
} else if (isBinary(options.body)) {
|
||||
headers.append('Content-Type', 'application/octet-stream');
|
||||
} else if (isString(options.body)) {
|
||||
headers.append('Content-Type', 'text/plain');
|
||||
|
||||
@ -4,8 +4,10 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isString(options.body) || isBinary(options.body)) {
|
||||
return options.body;
|
||||
if (options.mediaType?.includes('/json')) {
|
||||
return JSON.stringify(options.body)
|
||||
} else if (isString(options.body) || isBlob(options.body) || isBinary(options.body)) {
|
||||
return options.body as any;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
async function getResponseBody(response: Response): Promise<any> {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
if (response.status !== 204) {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -20,9 +20,15 @@ import { OpenAPI } from './OpenAPI';
|
||||
{{>functions/isStringWithValue}}
|
||||
|
||||
|
||||
{{>functions/isBlob}}
|
||||
|
||||
|
||||
{{>functions/isBinary}}
|
||||
|
||||
|
||||
{{>functions/base64}}
|
||||
|
||||
|
||||
{{>functions/getQueryString}}
|
||||
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
{{~#equals @root.httpClient 'fetch'}}{{>fetch/request}}{{/equals~}}
|
||||
{{~#equals @root.httpClient 'xhr'}}{{>xhr/request}}{{/equals~}}
|
||||
{{~#equals @root.httpClient 'axios'}}{{>axios/request}}{{/equals~}}
|
||||
{{~#equals @root.httpClient 'node'}}{{>node/request}}{{/equals~}}
|
||||
|
||||
@ -2,25 +2,34 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
|
||||
const token = await resolve(options, OpenAPI.TOKEN);
|
||||
const username = await resolve(options, OpenAPI.USERNAME);
|
||||
const password = await resolve(options, OpenAPI.PASSWORD);
|
||||
const defaultHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
const additionalHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
|
||||
const headers = new Headers({
|
||||
const defaultHeaders = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...defaultHeaders,
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
});
|
||||
})
|
||||
.filter(([key, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: value,
|
||||
}), {});
|
||||
|
||||
const headers = new Headers(defaultHeaders);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers.append('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = btoa(`${username}:${password}`);
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers.append('Authorization', `Basic ${credentials}`);
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isBlob(options.body)) {
|
||||
if (options.mediaType) {
|
||||
headers.append('Content-Type', options.mediaType);
|
||||
} else if (isBlob(options.body)) {
|
||||
headers.append('Content-Type', options.body.type || 'application/octet-stream');
|
||||
} else if (isString(options.body)) {
|
||||
headers.append('Content-Type', 'text/plain');
|
||||
|
||||
@ -4,7 +4,9 @@ function getRequestBody(options: ApiRequestOptions): any {
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isString(options.body) || isBlob(options.body)) {
|
||||
if (options.mediaType?.includes('/json')) {
|
||||
return JSON.stringify(options.body)
|
||||
} else if (isString(options.body) || isBlob(options.body)) {
|
||||
return options.body;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
function getResponseBody(xhr: XMLHttpRequest): any {
|
||||
try {
|
||||
const contentType = xhr.getResponseHeader('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return JSON.parse(xhr.responseText);
|
||||
} else {
|
||||
return xhr.responseText;
|
||||
if (xhr.status !== 204) {
|
||||
try {
|
||||
const contentType = xhr.getResponseHeader('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return JSON.parse(xhr.responseText);
|
||||
} else {
|
||||
return xhr.responseText;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -21,6 +21,9 @@ import { OpenAPI } from './OpenAPI';
|
||||
{{>functions/isSuccess}}
|
||||
|
||||
|
||||
{{>functions/base64}}
|
||||
|
||||
|
||||
{{>functions/getQueryString}}
|
||||
|
||||
|
||||
|
||||
@ -16,7 +16,11 @@ import type { {{{this}}} } from './{{{this}}}';
|
||||
{{else equals export 'all-of'}}
|
||||
{{>exportComposition}}
|
||||
{{else equals export 'enum'}}
|
||||
{{#if @root.useUnionTypes}}
|
||||
{{>exportType}}
|
||||
{{else}}
|
||||
{{>exportEnum}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{>exportType}}
|
||||
{{/equals}}
|
||||
|
||||
@ -70,6 +70,9 @@ export class {{{name}}} {
|
||||
{{/if}}
|
||||
{{#if parametersBody}}
|
||||
body: {{{parametersBody.name}}},
|
||||
{{#if parametersBody.mediaType}}
|
||||
mediaType: '{{{parametersBody.mediaType}}}',
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if responseHeader}}
|
||||
responseHeader: '{{{responseHeader}}}',
|
||||
|
||||
@ -9,10 +9,10 @@ export { OpenAPI } from './core/OpenAPI';
|
||||
{{#if models}}
|
||||
|
||||
{{#each models}}
|
||||
{{#if enum}}
|
||||
export { {{{name}}} } from './models/{{{name}}}';
|
||||
{{else if @root.useUnionTypes}}
|
||||
{{#if @root.useUnionTypes}}
|
||||
export type { {{{name}}} } from './models/{{{name}}}';
|
||||
{{else if enum}}
|
||||
export { {{{name}}} } from './models/{{{name}}}';
|
||||
{{else if enums}}
|
||||
export { {{{name}}} } from './models/{{{name}}}';
|
||||
{{else}}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{{~#equals base 'File'~}}
|
||||
{{~#equals @root.httpClient 'fetch'}}Blob{{/equals~}}
|
||||
{{~#equals @root.httpClient 'xhr'}}Blob{{/equals~}}
|
||||
{{~#equals @root.httpClient 'axios'}}Blob{{/equals~}}
|
||||
{{~#equals @root.httpClient 'node'}}Buffer | ArrayBuffer | ArrayBufferView{{/equals~}}
|
||||
{{~else~}}
|
||||
{{{base}}}
|
||||
|
||||
@ -1 +1 @@
|
||||
({{#intersection properties parent}}{{this}}{{/intersection}}){{>isNullable}}
|
||||
{{#intersection properties parent}}{{this}}{{/intersection}}{{>isNullable}}
|
||||
|
||||
@ -1 +1 @@
|
||||
({{#union properties parent}}{{this}}{{/union}}){{>isNullable}}
|
||||
{{#union properties parent}}{{this}}{{/union}}{{>isNullable}}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { exists, readFile } from './fileSystem';
|
||||
import { getOpenApiSpec } from './getOpenApiSpec';
|
||||
|
||||
jest.mock('./fileSystem');
|
||||
|
||||
const existsMocked = exists as jest.MockedFunction<typeof exists>;
|
||||
const readFileMocked = readFile as jest.MockedFunction<typeof readFile>;
|
||||
|
||||
function mockPromise<T>(value: T): Promise<T> {
|
||||
return new Promise<T>(resolve => resolve(value));
|
||||
}
|
||||
|
||||
describe('getOpenApiSpec', () => {
|
||||
it('should read the json file', async () => {
|
||||
existsMocked.mockReturnValue(mockPromise(true));
|
||||
readFileMocked.mockReturnValue(mockPromise('{"message": "Hello World!"}'));
|
||||
const spec = await getOpenApiSpec('spec.json');
|
||||
expect(spec.message).toEqual('Hello World!');
|
||||
});
|
||||
|
||||
it('should read the yaml file', async () => {
|
||||
existsMocked.mockReturnValue(mockPromise(true));
|
||||
readFileMocked.mockReturnValue(mockPromise('message: "Hello World!"'));
|
||||
const spec = await getOpenApiSpec('spec.yaml');
|
||||
expect(spec.message).toEqual('Hello World!');
|
||||
});
|
||||
});
|
||||
@ -1,8 +1,4 @@
|
||||
import { load } from 'js-yaml';
|
||||
import RefParser from 'json-schema-ref-parser';
|
||||
import { extname } from 'path';
|
||||
|
||||
import { readSpec } from './readSpec';
|
||||
|
||||
/**
|
||||
* Load and parse te open api spec. If the file extension is ".yml" or ".yaml"
|
||||
@ -11,26 +7,5 @@ import { readSpec } from './readSpec';
|
||||
* @param input
|
||||
*/
|
||||
export async function getOpenApiSpec(input: string): Promise<any> {
|
||||
const extension = extname(input).toLowerCase();
|
||||
const content = await readSpec(input);
|
||||
let rootObject: any;
|
||||
switch (extension) {
|
||||
case '.yml':
|
||||
case '.yaml':
|
||||
try {
|
||||
rootObject = load(content);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not parse OpenApi YAML: "${input}"`);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
try {
|
||||
rootObject = JSON.parse(content);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not parse OpenApi JSON: "${input}"`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return await RefParser.bundle(rootObject);
|
||||
return await RefParser.bundle(input);
|
||||
}
|
||||
|
||||
@ -21,13 +21,23 @@ export function registerHandlebarHelpers(root: { httpClient: HttpClient; useOpti
|
||||
Handlebars.registerHelper('union', function (this: any, properties: Model[], parent: string | undefined, options: Handlebars.HelperOptions) {
|
||||
const type = Handlebars.partials['type'];
|
||||
const types = properties.map(property => type({ ...root, ...property, parent }));
|
||||
return options.fn(types.filter(unique).join(' | '));
|
||||
const uniqueTypes = types.filter(unique);
|
||||
let uniqueTypesString = uniqueTypes.join(' | ');
|
||||
if (uniqueTypes.length > 1) {
|
||||
uniqueTypesString = `(${uniqueTypesString})`;
|
||||
}
|
||||
return options.fn(uniqueTypesString);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('intersection', function (this: any, properties: Model[], parent: string | undefined, options: Handlebars.HelperOptions) {
|
||||
const type = Handlebars.partials['type'];
|
||||
const types = properties.map(property => type({ ...root, ...property, parent }));
|
||||
return options.fn(types.filter(unique).join(' & '));
|
||||
const uniqueTypes = types.filter(unique);
|
||||
let uniqueTypesString = uniqueTypes.join(' & ');
|
||||
if (uniqueTypes.length > 1) {
|
||||
uniqueTypesString = `(${uniqueTypesString})`;
|
||||
}
|
||||
return options.fn(uniqueTypesString);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('enumerator', function (this: any, enumerators: Enum[], parent: string | undefined, name: string | undefined, options: Handlebars.HelperOptions) {
|
||||
|
||||
@ -4,6 +4,11 @@ import { HttpClient } from '../HttpClient';
|
||||
import templateCoreApiError from '../templates/core/ApiError.hbs';
|
||||
import templateCoreApiRequestOptions from '../templates/core/ApiRequestOptions.hbs';
|
||||
import templateCoreApiResult from '../templates/core/ApiResult.hbs';
|
||||
import axiosGetHeaders from '../templates/core/axios/getHeaders.hbs';
|
||||
import axiosGetResponseBody from '../templates/core/axios/getResponseBody.hbs';
|
||||
import axiosGetResponseHeader from '../templates/core/axios/getResponseHeader.hbs';
|
||||
import axiosRequest from '../templates/core/axios/request.hbs';
|
||||
import axiosSendRequest from '../templates/core/axios/sendRequest.hbs';
|
||||
import templateCancelablePromise from '../templates/core/CancelablePromise.hbs';
|
||||
import fetchGetHeaders from '../templates/core/fetch/getHeaders.hbs';
|
||||
import fetchGetRequestBody from '../templates/core/fetch/getRequestBody.hbs';
|
||||
@ -11,6 +16,7 @@ import fetchGetResponseBody from '../templates/core/fetch/getResponseBody.hbs';
|
||||
import fetchGetResponseHeader from '../templates/core/fetch/getResponseHeader.hbs';
|
||||
import fetchRequest from '../templates/core/fetch/request.hbs';
|
||||
import fetchSendRequest from '../templates/core/fetch/sendRequest.hbs';
|
||||
import functionBase64 from '../templates/core/functions/base64.hbs';
|
||||
import functionCatchErrors from '../templates/core/functions/catchErrors.hbs';
|
||||
import functionGetFormData from '../templates/core/functions/getFormData.hbs';
|
||||
import functionGetQueryString from '../templates/core/functions/getQueryString.hbs';
|
||||
@ -151,6 +157,7 @@ export function registerHandlebarTemplates(root: { httpClient: HttpClient; useOp
|
||||
Handlebars.registerPartial('functions/isString', Handlebars.template(functionIsString));
|
||||
Handlebars.registerPartial('functions/isStringWithValue', Handlebars.template(functionIsStringWithValue));
|
||||
Handlebars.registerPartial('functions/isSuccess', Handlebars.template(functionIsSuccess));
|
||||
Handlebars.registerPartial('functions/base64', Handlebars.template(functionBase64));
|
||||
Handlebars.registerPartial('functions/resolve', Handlebars.template(functionResolve));
|
||||
|
||||
// Specific files for the fetch client implementation
|
||||
@ -177,5 +184,12 @@ export function registerHandlebarTemplates(root: { httpClient: HttpClient; useOp
|
||||
Handlebars.registerPartial('node/sendRequest', Handlebars.template(nodeSendRequest));
|
||||
Handlebars.registerPartial('node/request', Handlebars.template(nodeRequest));
|
||||
|
||||
// Specific files for the axios client implementation
|
||||
Handlebars.registerPartial('axios/getHeaders', Handlebars.template(axiosGetHeaders));
|
||||
Handlebars.registerPartial('axios/getResponseBody', Handlebars.template(axiosGetResponseBody));
|
||||
Handlebars.registerPartial('axios/getResponseHeader', Handlebars.template(axiosGetResponseHeader));
|
||||
Handlebars.registerPartial('axios/sendRequest', Handlebars.template(axiosSendRequest));
|
||||
Handlebars.registerPartial('axios/request', Handlebars.template(axiosRequest));
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ import { writeClientServices } from './writeClientServices';
|
||||
* @param client Client object with all the models, services, etc.
|
||||
* @param templates Templates wrapper with all loaded Handlebars templates
|
||||
* @param output The relative location of the output directory
|
||||
* @param httpClient The selected httpClient (fetch, xhr or node)
|
||||
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
|
||||
* @param useOptions Use options or arguments functions
|
||||
* @param useUnionTypes Use union types instead of enums
|
||||
* @param exportCore: Generate core client classes
|
||||
|
||||
@ -10,7 +10,7 @@ import { Templates } from './registerHandlebarTemplates';
|
||||
* @param client Client object, containing, models, schemas and services
|
||||
* @param templates The loaded handlebar templates
|
||||
* @param outputPath Directory to write the generated files to
|
||||
* @param httpClient The selected httpClient (fetch, xhr or node)
|
||||
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
|
||||
* @param request: Path to custom request file
|
||||
*/
|
||||
export async function writeClientCore(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient, request?: string): Promise<void> {
|
||||
|
||||
@ -11,7 +11,7 @@ import { Templates } from './registerHandlebarTemplates';
|
||||
* @param models Array of Models to write
|
||||
* @param templates The loaded handlebar templates
|
||||
* @param outputPath Directory to write the generated files to
|
||||
* @param httpClient The selected httpClient (fetch, xhr or node)
|
||||
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
|
||||
* @param useUnionTypes Use union types instead of enums
|
||||
*/
|
||||
export async function writeClientModels(models: Model[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean): Promise<void> {
|
||||
|
||||
@ -11,7 +11,7 @@ import { Templates } from './registerHandlebarTemplates';
|
||||
* @param models Array of Models to write
|
||||
* @param templates The loaded handlebar templates
|
||||
* @param outputPath Directory to write the generated files to
|
||||
* @param httpClient The selected httpClient (fetch, xhr or node)
|
||||
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
|
||||
* @param useUnionTypes Use union types instead of enums
|
||||
*/
|
||||
export async function writeClientSchemas(models: Model[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean): Promise<void> {
|
||||
|
||||
@ -13,7 +13,7 @@ const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION';
|
||||
* @param services Array of Services to write
|
||||
* @param templates The loaded handlebar templates
|
||||
* @param outputPath Directory to write the generated files to
|
||||
* @param httpClient The selected httpClient (fetch, xhr or node)
|
||||
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
|
||||
* @param useUnionTypes Use union types instead of enums
|
||||
* @param useOptions Use options or arguments functions
|
||||
*/
|
||||
|
||||
@ -35,6 +35,7 @@ export type ApiRequestOptions = {
|
||||
readonly query?: Record<string, any>;
|
||||
readonly formData?: Record<string, any>;
|
||||
readonly body?: any;
|
||||
readonly mediaType?: string;
|
||||
readonly responseHeader?: string;
|
||||
readonly errors?: Record<number, string>;
|
||||
}"
|
||||
@ -146,6 +147,7 @@ type Config = {
|
||||
USERNAME?: string | Resolver<string>;
|
||||
PASSWORD?: string | Resolver<string>;
|
||||
HEADERS?: Headers | Resolver<Headers>;
|
||||
ENCODE_PATH?: (path: string) => string;
|
||||
}
|
||||
|
||||
export const OpenAPI: Config = {
|
||||
@ -156,6 +158,7 @@ export const OpenAPI: Config = {
|
||||
USERNAME: undefined,
|
||||
PASSWORD: undefined,
|
||||
HEADERS: undefined,
|
||||
ENCODE_PATH: undefined,
|
||||
};"
|
||||
`;
|
||||
|
||||
@ -185,6 +188,14 @@ function isBlob(value: any): value is Blob {
|
||||
return value instanceof Blob;
|
||||
}
|
||||
|
||||
function base64(str: string): string {
|
||||
try {
|
||||
return btoa(str);
|
||||
} catch (err) {
|
||||
return Buffer.from(str).toString('base64');
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryString(params: Record<string, any>): string {
|
||||
const qs: string[] = [];
|
||||
|
||||
@ -209,9 +220,8 @@ function getQueryString(params: Record<string, any>): string {
|
||||
}
|
||||
|
||||
function getUrl(options: ApiRequestOptions): string {
|
||||
const path = options.path.replace(/[:]/g, '_');
|
||||
const path = OpenAPI.ENCODE_PATH ? OpenAPI.ENCODE_PATH(options.path) : options.path;
|
||||
const url = \`\${OpenAPI.BASE}\${path}\`;
|
||||
|
||||
if (options.query) {
|
||||
return \`\${url}\${getQueryString(options.query)}\`;
|
||||
}
|
||||
@ -245,25 +255,34 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
|
||||
const token = await resolve(options, OpenAPI.TOKEN);
|
||||
const username = await resolve(options, OpenAPI.USERNAME);
|
||||
const password = await resolve(options, OpenAPI.PASSWORD);
|
||||
const defaultHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
const additionalHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
|
||||
const headers = new Headers({
|
||||
const defaultHeaders = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...defaultHeaders,
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
});
|
||||
})
|
||||
.filter(([key, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: value,
|
||||
}), {});
|
||||
|
||||
const headers = new Headers(defaultHeaders);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers.append('Authorization', \`Bearer \${token}\`);
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = btoa(\`\${username}:\${password}\`);
|
||||
const credentials = base64(\`\${username}:\${password}\`);
|
||||
headers.append('Authorization', \`Basic \${credentials}\`);
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isBlob(options.body)) {
|
||||
if (options.mediaType) {
|
||||
headers.append('Content-Type', options.mediaType);
|
||||
} else if (isBlob(options.body)) {
|
||||
headers.append('Content-Type', options.body.type || 'application/octet-stream');
|
||||
} else if (isString(options.body)) {
|
||||
headers.append('Content-Type', 'text/plain');
|
||||
@ -280,7 +299,9 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isString(options.body) || isBlob(options.body)) {
|
||||
if (options.mediaType?.includes('/json')) {
|
||||
return JSON.stringify(options.body)
|
||||
} else if (isString(options.body) || isBlob(options.body)) {
|
||||
return options.body;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
@ -321,18 +342,20 @@ function getResponseHeader(response: Response, responseHeader?: string): string
|
||||
}
|
||||
|
||||
async function getResponseBody(response: Response): Promise<any> {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
if (response.status !== 204) {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -696,12 +719,18 @@ export enum EnumWithNumbers {
|
||||
'_1' = 1,
|
||||
'_2' = 2,
|
||||
'_3' = 3,
|
||||
'_1.1' = 1.1,
|
||||
'_1.2' = 1.2,
|
||||
'_1.3' = 1.3,
|
||||
'_100' = 100,
|
||||
'_200' = 200,
|
||||
'_300' = 300,
|
||||
'_-100' = -100,
|
||||
'_-200' = -200,
|
||||
'_-300' = -300,
|
||||
'_-1.1' = -1.1,
|
||||
'_-1.2' = -1.2,
|
||||
'_-1.3' = -1.3,
|
||||
}"
|
||||
`;
|
||||
|
||||
@ -2172,7 +2201,7 @@ export class ParametersService {
|
||||
parameterPath: string,
|
||||
): CancelablePromise<void> {
|
||||
return __request({
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
path: \`/api/v\${OpenAPI.VERSION}/parameters/\${parameterPath}\`,
|
||||
headers: {
|
||||
'parameterHeader': parameterHeader,
|
||||
@ -2209,7 +2238,7 @@ export class ParametersService {
|
||||
_default?: string,
|
||||
): CancelablePromise<void> {
|
||||
return __request({
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
path: \`/api/v\${OpenAPI.VERSION}/parameters/\${parameterPath1}/\${parameterPath2}/\${parameterPath3}\`,
|
||||
headers: {
|
||||
'parameter.header': parameterHeader,
|
||||
@ -2465,6 +2494,7 @@ export type ApiRequestOptions = {
|
||||
readonly query?: Record<string, any>;
|
||||
readonly formData?: Record<string, any>;
|
||||
readonly body?: any;
|
||||
readonly mediaType?: string;
|
||||
readonly responseHeader?: string;
|
||||
readonly errors?: Record<number, string>;
|
||||
}"
|
||||
@ -2576,6 +2606,7 @@ type Config = {
|
||||
USERNAME?: string | Resolver<string>;
|
||||
PASSWORD?: string | Resolver<string>;
|
||||
HEADERS?: Headers | Resolver<Headers>;
|
||||
ENCODE_PATH?: (path: string) => string;
|
||||
}
|
||||
|
||||
export const OpenAPI: Config = {
|
||||
@ -2586,6 +2617,7 @@ export const OpenAPI: Config = {
|
||||
USERNAME: undefined,
|
||||
PASSWORD: undefined,
|
||||
HEADERS: undefined,
|
||||
ENCODE_PATH: undefined,
|
||||
};"
|
||||
`;
|
||||
|
||||
@ -2615,6 +2647,14 @@ function isBlob(value: any): value is Blob {
|
||||
return value instanceof Blob;
|
||||
}
|
||||
|
||||
function base64(str: string): string {
|
||||
try {
|
||||
return btoa(str);
|
||||
} catch (err) {
|
||||
return Buffer.from(str).toString('base64');
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryString(params: Record<string, any>): string {
|
||||
const qs: string[] = [];
|
||||
|
||||
@ -2639,9 +2679,8 @@ function getQueryString(params: Record<string, any>): string {
|
||||
}
|
||||
|
||||
function getUrl(options: ApiRequestOptions): string {
|
||||
const path = options.path.replace(/[:]/g, '_');
|
||||
const path = OpenAPI.ENCODE_PATH ? OpenAPI.ENCODE_PATH(options.path) : options.path;
|
||||
const url = \`\${OpenAPI.BASE}\${path}\`;
|
||||
|
||||
if (options.query) {
|
||||
return \`\${url}\${getQueryString(options.query)}\`;
|
||||
}
|
||||
@ -2675,25 +2714,34 @@ async function getHeaders(options: ApiRequestOptions): Promise<Headers> {
|
||||
const token = await resolve(options, OpenAPI.TOKEN);
|
||||
const username = await resolve(options, OpenAPI.USERNAME);
|
||||
const password = await resolve(options, OpenAPI.PASSWORD);
|
||||
const defaultHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
const additionalHeaders = await resolve(options, OpenAPI.HEADERS);
|
||||
|
||||
const headers = new Headers({
|
||||
const defaultHeaders = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...defaultHeaders,
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
});
|
||||
})
|
||||
.filter(([key, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: value,
|
||||
}), {});
|
||||
|
||||
const headers = new Headers(defaultHeaders);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers.append('Authorization', \`Bearer \${token}\`);
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = btoa(\`\${username}:\${password}\`);
|
||||
const credentials = base64(\`\${username}:\${password}\`);
|
||||
headers.append('Authorization', \`Basic \${credentials}\`);
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isBlob(options.body)) {
|
||||
if (options.mediaType) {
|
||||
headers.append('Content-Type', options.mediaType);
|
||||
} else if (isBlob(options.body)) {
|
||||
headers.append('Content-Type', options.body.type || 'application/octet-stream');
|
||||
} else if (isString(options.body)) {
|
||||
headers.append('Content-Type', 'text/plain');
|
||||
@ -2710,7 +2758,9 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined {
|
||||
}
|
||||
|
||||
if (options.body) {
|
||||
if (isString(options.body) || isBlob(options.body)) {
|
||||
if (options.mediaType?.includes('/json')) {
|
||||
return JSON.stringify(options.body)
|
||||
} else if (isString(options.body) || isBlob(options.body)) {
|
||||
return options.body;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
@ -2751,18 +2801,20 @@ function getResponseHeader(response: Response, responseHeader?: string): string
|
||||
}
|
||||
|
||||
async function getResponseBody(response: Response): Promise<any> {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
if (response.status !== 204) {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const isJSON = contentType.toLowerCase().startsWith('application/json');
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -3258,12 +3310,18 @@ export enum EnumWithNumbers {
|
||||
'_1' = 1,
|
||||
'_2' = 2,
|
||||
'_3' = 3,
|
||||
'_1.1' = 1.1,
|
||||
'_1.2' = 1.2,
|
||||
'_1.3' = 1.3,
|
||||
'_100' = 100,
|
||||
'_200' = 200,
|
||||
'_300' = 300,
|
||||
'_-100' = -100,
|
||||
'_-200' = -200,
|
||||
'_-300' = -300,
|
||||
'_-1.1' = -1.1,
|
||||
'_-1.2' = -1.2,
|
||||
'_-1.3' = -1.3,
|
||||
}"
|
||||
`;
|
||||
|
||||
@ -3722,7 +3780,7 @@ exports[`v3 should generate: ./test/generated/v3/models/SimpleStringWithPattern.
|
||||
/**
|
||||
* This is a simple string
|
||||
*/
|
||||
export type SimpleStringWithPattern = string | null;"
|
||||
export type SimpleStringWithPattern = string;"
|
||||
`;
|
||||
|
||||
exports[`v3 should generate: ./test/generated/v3/schemas/$ArrayWithArray.ts 1`] = `
|
||||
@ -4542,7 +4600,6 @@ exports[`v3 should generate: ./test/generated/v3/schemas/$SimpleStringWithPatter
|
||||
/* eslint-disable */
|
||||
export const $SimpleStringWithPattern = {
|
||||
type: 'string',
|
||||
isNullable: true,
|
||||
maxLength: 64,
|
||||
pattern: '^[a-zA-Z0-9_]*$',
|
||||
};"
|
||||
@ -4644,7 +4701,7 @@ export class ComplexService {
|
||||
requestBody?: {
|
||||
readonly key: string | null,
|
||||
name: string | null,
|
||||
enabled: boolean,
|
||||
enabled?: boolean,
|
||||
readonly type: 'Monkey' | 'Horse' | 'Bird',
|
||||
listOfModels?: Array<ModelWithString> | null,
|
||||
listOfStrings?: Array<string> | null,
|
||||
@ -4659,6 +4716,7 @@ export class ComplexService {
|
||||
method: 'PUT',
|
||||
path: \`/api/v\${OpenAPI.VERSION}/complex/\${id}\`,
|
||||
body: requestBody,
|
||||
mediaType: 'application/json-patch+json',
|
||||
});
|
||||
}
|
||||
|
||||
@ -4935,7 +4993,7 @@ export class ParametersService {
|
||||
requestBody: ModelWithString | null,
|
||||
): CancelablePromise<void> {
|
||||
return __request({
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
path: \`/api/v\${OpenAPI.VERSION}/parameters/\${parameterPath}\`,
|
||||
cookies: {
|
||||
'parameterCookie': parameterCookie,
|
||||
@ -4950,6 +5008,7 @@ export class ParametersService {
|
||||
'parameterForm': parameterForm,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
});
|
||||
}
|
||||
|
||||
@ -4977,7 +5036,7 @@ export class ParametersService {
|
||||
_default?: string,
|
||||
): CancelablePromise<void> {
|
||||
return __request({
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
path: \`/api/v\${OpenAPI.VERSION}/parameters/\${parameterPath1}/\${parameterPath2}/\${parameterPath3}\`,
|
||||
cookies: {
|
||||
'PARAMETER-COOKIE': parameterCookie,
|
||||
@ -4993,6 +5052,7 @@ export class ParametersService {
|
||||
'parameter_form': parameterForm,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
});
|
||||
}
|
||||
|
||||
@ -5012,6 +5072,7 @@ export class ParametersService {
|
||||
'parameter': parameter,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
});
|
||||
}
|
||||
|
||||
@ -5031,6 +5092,7 @@ export class ParametersService {
|
||||
'parameter': parameter,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
});
|
||||
}
|
||||
|
||||
@ -5059,6 +5121,7 @@ export class RequestBodyService {
|
||||
method: 'POST',
|
||||
path: \`/api/v\${OpenAPI.VERSION}/requestBody/\`,
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,15 @@ export function request<T>(options: ApiRequestOptions): CancelablePromise<T> {
|
||||
try {
|
||||
// Do your request...
|
||||
const timeout = setTimeout(() => {
|
||||
resolve({ ...options });
|
||||
resolve({
|
||||
url,
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'dummy',
|
||||
body: {
|
||||
...options,
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
|
||||
// Cancel your request...
|
||||
|
||||
@ -10,10 +10,7 @@ async function start() {
|
||||
// and load the localhost page, this page will load the
|
||||
// javascript modules (see server.js for more info)
|
||||
browser = await puppeteer.launch({
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
]
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
});
|
||||
page = await browser.newPage();
|
||||
page.on('console', msg => console.log(msg.text()));
|
||||
|
||||
@ -11,15 +11,21 @@ function compileWithBabel(dir) {
|
||||
const result = babel.transformSync(content, {
|
||||
filename: file,
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
modules: false,
|
||||
targets: {
|
||||
node: true,
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
targets: {
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
}],
|
||||
['@babel/preset-typescript', {
|
||||
onlyRemoveTypeImports: true,
|
||||
}],
|
||||
],
|
||||
[
|
||||
'@babel/preset-typescript',
|
||||
{
|
||||
onlyRemoveTypeImports: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
const out = file.replace(/\.ts$/, '.js');
|
||||
|
||||
@ -22,7 +22,7 @@ function compileWithTypescript(dir) {
|
||||
strictNullChecks: true,
|
||||
strictFunctionTypes: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
skipLibCheck: true
|
||||
skipLibCheck: true,
|
||||
},
|
||||
include: ['./index.ts'],
|
||||
};
|
||||
|
||||
@ -3,10 +3,7 @@
|
||||
const fs = require('fs');
|
||||
|
||||
function copy(dir) {
|
||||
fs.copyFileSync(
|
||||
'./test/e2e/assets/script.js',
|
||||
`./test/e2e/generated/${dir}/script.js`,
|
||||
);
|
||||
fs.copyFileSync('./test/e2e/assets/script.js', `./test/e2e/generated/${dir}/script.js`);
|
||||
}
|
||||
|
||||
module.exports = copy;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
const express = require('express');
|
||||
|
||||
let app;
|
||||
let server
|
||||
let server;
|
||||
|
||||
async function start(dir) {
|
||||
return new Promise(resolve => {
|
||||
@ -12,10 +12,13 @@ async function start(dir) {
|
||||
// Serve the JavaScript files from the specific folder, since we are using browser
|
||||
// based ES6 modules, this also means that we can just request the js/index.js file
|
||||
// and all other relative paths are resolved from that file.
|
||||
app.use('/js', express.static(`./test/e2e/generated/${dir}/`, {
|
||||
extensions: ['', 'js'],
|
||||
index: 'index.js',
|
||||
}));
|
||||
app.use(
|
||||
'/js',
|
||||
express.static(`./test/e2e/generated/${dir}/`, {
|
||||
extensions: ['', 'js'],
|
||||
index: 'index.js',
|
||||
})
|
||||
);
|
||||
|
||||
// When we request the index then we can just return the script loader.
|
||||
// This file is copied from test/e2e/assets/script.js to the output directory
|
||||
|
||||
51
test/e2e/v2.axios.spec.js
Normal file
51
test/e2e/v2.axios.spec.js
Normal file
@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
const generate = require('./scripts/generate');
|
||||
const compileWithTypescript = require('./scripts/compileWithTypescript');
|
||||
const server = require('./scripts/server');
|
||||
|
||||
describe('v2.node', () => {
|
||||
beforeAll(async () => {
|
||||
await generate('v2/axios', 'v2', 'axios');
|
||||
compileWithTypescript('v2/axios');
|
||||
await server.start('v2/axios');
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
const { OpenAPI, SimpleService } = require('./generated/v2/axios/index.js');
|
||||
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN');
|
||||
OpenAPI.TOKEN = tokenRequest;
|
||||
const result = await SimpleService.getCallWithoutParametersAndResponse();
|
||||
expect(tokenRequest.mock.calls.length).toBe(1);
|
||||
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
|
||||
});
|
||||
|
||||
it('complexService', async () => {
|
||||
const { ComplexService } = require('./generated/v2/axios/index.js');
|
||||
const result = await ComplexService.complexTypes({
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
try {
|
||||
const { SimpleService } = require('./generated/v2/axios/index.js');
|
||||
const promise = SimpleService.getCallWithoutParametersAndResponse();
|
||||
setTimeout(() => {
|
||||
promise.cancel();
|
||||
}, 10);
|
||||
await promise;
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('canceled');
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -7,7 +7,6 @@ const server = require('./scripts/server');
|
||||
const browser = require('./scripts/browser');
|
||||
|
||||
describe('v2.fetch', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v2/babel', 'v2', 'fetch', true, true);
|
||||
await copy('v2/babel');
|
||||
@ -17,8 +16,8 @@ describe('v2.fetch', () => {
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
|
||||
@ -7,7 +7,6 @@ const server = require('./scripts/server');
|
||||
const browser = require('./scripts/browser');
|
||||
|
||||
describe('v2.fetch', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v2/fetch', 'v2', 'fetch');
|
||||
await copy('v2/fetch');
|
||||
@ -17,8 +16,8 @@ describe('v2.fetch', () => {
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
|
||||
@ -5,7 +5,6 @@ const compileWithTypescript = require('./scripts/compileWithTypescript');
|
||||
const server = require('./scripts/server');
|
||||
|
||||
describe('v2.node', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v2/node', 'v2', 'node');
|
||||
compileWithTypescript('v2/node');
|
||||
@ -18,7 +17,7 @@ describe('v2.node', () => {
|
||||
|
||||
it('requests token', async () => {
|
||||
const { OpenAPI, SimpleService } = require('./generated/v2/node/index.js');
|
||||
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN')
|
||||
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN');
|
||||
OpenAPI.TOKEN = tokenRequest;
|
||||
const result = await SimpleService.getCallWithoutParametersAndResponse();
|
||||
expect(tokenRequest.mock.calls.length).toBe(1);
|
||||
|
||||
@ -7,7 +7,6 @@ const server = require('./scripts/server');
|
||||
const browser = require('./scripts/browser');
|
||||
|
||||
describe('v2.xhr', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v2/xhr', 'v2', 'xhr');
|
||||
await copy('v2/xhr');
|
||||
@ -17,8 +16,8 @@ describe('v2.xhr', () => {
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
|
||||
70
test/e2e/v3.axios.spec.js
Normal file
70
test/e2e/v3.axios.spec.js
Normal file
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const generate = require('./scripts/generate');
|
||||
const compileWithTypescript = require('./scripts/compileWithTypescript');
|
||||
const server = require('./scripts/server');
|
||||
|
||||
describe('v3.node', () => {
|
||||
beforeAll(async () => {
|
||||
await generate('v3/axios', 'v3', 'axios');
|
||||
compileWithTypescript('v3/axios');
|
||||
await server.start('v3/axios');
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
const { OpenAPI, SimpleService } = require('./generated/v3/axios/index.js');
|
||||
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN');
|
||||
OpenAPI.TOKEN = tokenRequest;
|
||||
OpenAPI.USERNAME = undefined;
|
||||
OpenAPI.PASSWORD = undefined;
|
||||
const result = await SimpleService.getCallWithoutParametersAndResponse();
|
||||
expect(tokenRequest.mock.calls.length).toBe(1);
|
||||
expect(result.headers.authorization).toBe('Bearer MY_TOKEN');
|
||||
});
|
||||
|
||||
it('uses credentials', async () => {
|
||||
const { OpenAPI, SimpleService } = require('./generated/v3/axios/index.js');
|
||||
OpenAPI.TOKEN = undefined;
|
||||
OpenAPI.USERNAME = 'username';
|
||||
OpenAPI.PASSWORD = 'password';
|
||||
const result = await SimpleService.getCallWithoutParametersAndResponse();
|
||||
expect(result.headers.authorization).toBe('Basic dXNlcm5hbWU6cGFzc3dvcmQ=');
|
||||
});
|
||||
|
||||
it('complexService', async () => {
|
||||
const { ComplexService } = require('./generated/v3/axios/index.js');
|
||||
const result = await ComplexService.complexTypes({
|
||||
first: {
|
||||
second: {
|
||||
third: 'Hello World!',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('formData', async () => {
|
||||
const { ParametersService } = require('./generated/v3/axios/index.js');
|
||||
const result = await ParametersService.callWithParameters('valueHeader', 'valueQuery', 'valueForm', 'valueCookie', 'valuePath', {
|
||||
prop: 'valueBody',
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
try {
|
||||
const { SimpleService } = require('./generated/v3/axios/index.js');
|
||||
const promise = SimpleService.getCallWithoutParametersAndResponse();
|
||||
setTimeout(() => {
|
||||
promise.cancel();
|
||||
}, 10);
|
||||
await promise;
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('canceled');
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -7,7 +7,6 @@ const server = require('./scripts/server');
|
||||
const browser = require('./scripts/browser');
|
||||
|
||||
describe('v3.fetch', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v3/babel', 'v3', 'fetch', true, true);
|
||||
await copy('v3/babel');
|
||||
@ -17,8 +16,8 @@ describe('v3.fetch', () => {
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
|
||||
@ -7,7 +7,6 @@ const server = require('./scripts/server');
|
||||
const browser = require('./scripts/browser');
|
||||
|
||||
describe('v3.fetch', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v3/fetch', 'v3', 'fetch');
|
||||
await copy('v3/fetch');
|
||||
@ -17,8 +16,8 @@ describe('v3.fetch', () => {
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
@ -58,6 +57,16 @@ describe('v3.fetch', () => {
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('formData', async () => {
|
||||
const result = await browser.evaluate(async () => {
|
||||
const { ParametersService } = window.api;
|
||||
return await ParametersService.callWithParameters('valueHeader', 'valueQuery', 'valueForm', 'valueCookie', 'valuePath', {
|
||||
prop: 'valueBody',
|
||||
});
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
try {
|
||||
await browser.evaluate(async () => {
|
||||
|
||||
@ -5,7 +5,6 @@ const compileWithTypescript = require('./scripts/compileWithTypescript');
|
||||
const server = require('./scripts/server');
|
||||
|
||||
describe('v3.node', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v3/node', 'v3', 'node');
|
||||
compileWithTypescript('v3/node');
|
||||
@ -18,7 +17,7 @@ describe('v3.node', () => {
|
||||
|
||||
it('requests token', async () => {
|
||||
const { OpenAPI, SimpleService } = require('./generated/v3/node/index.js');
|
||||
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN')
|
||||
const tokenRequest = jest.fn().mockResolvedValue('MY_TOKEN');
|
||||
OpenAPI.TOKEN = tokenRequest;
|
||||
OpenAPI.USERNAME = undefined;
|
||||
OpenAPI.PASSWORD = undefined;
|
||||
@ -48,6 +47,14 @@ describe('v3.node', () => {
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('formData', async () => {
|
||||
const { ParametersService } = require('./generated/v3/node/index.js');
|
||||
const result = await ParametersService.callWithParameters('valueHeader', 'valueQuery', 'valueForm', 'valueCookie', 'valuePath', {
|
||||
prop: 'valueBody',
|
||||
});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('can abort the request', async () => {
|
||||
try {
|
||||
const { SimpleService } = require('./generated/v3/node/index.js');
|
||||
|
||||
@ -7,7 +7,6 @@ const server = require('./scripts/server');
|
||||
const browser = require('./scripts/browser');
|
||||
|
||||
describe('v3.xhr', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await generate('v3/xhr', 'v3', 'xhr');
|
||||
await copy('v3/xhr');
|
||||
@ -17,8 +16,8 @@ describe('v3.xhr', () => {
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
await browser.stop();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('requests token', async () => {
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
}
|
||||
},
|
||||
"/api/v{api-version}/parameters/{parameterPath}": {
|
||||
"get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Parameters"
|
||||
],
|
||||
@ -106,7 +106,7 @@
|
||||
}
|
||||
},
|
||||
"/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}": {
|
||||
"get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Parameters"
|
||||
],
|
||||
@ -816,12 +816,18 @@
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
1.1,
|
||||
1.20,
|
||||
1.300,
|
||||
100,
|
||||
200,
|
||||
300,
|
||||
-100,
|
||||
-200,
|
||||
-300
|
||||
-300,
|
||||
-1.1,
|
||||
-1.20,
|
||||
-1.300
|
||||
]
|
||||
},
|
||||
"EnumFromDescription": {
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
}
|
||||
},
|
||||
"/api/v{api-version}/parameters/{parameterPath}": {
|
||||
"get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Parameters"
|
||||
],
|
||||
@ -137,7 +137,7 @@
|
||||
}
|
||||
},
|
||||
"/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}": {
|
||||
"get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Parameters"
|
||||
],
|
||||
@ -1315,12 +1315,18 @@
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
1.1,
|
||||
1.20,
|
||||
1.300,
|
||||
100,
|
||||
200,
|
||||
300,
|
||||
-100,
|
||||
-200,
|
||||
-300
|
||||
-300,
|
||||
-1.1,
|
||||
-1.20,
|
||||
-1.300
|
||||
]
|
||||
},
|
||||
"EnumFromDescription": {
|
||||
|
||||
1
types/index.d.ts
vendored
1
types/index.d.ts
vendored
@ -2,6 +2,7 @@ export declare enum HttpClient {
|
||||
FETCH = 'fetch',
|
||||
XHR = 'xhr',
|
||||
NODE = 'node',
|
||||
AXIOS = 'axios',
|
||||
}
|
||||
|
||||
export type Options = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user