Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OpenAPI 3 (WIP) #31

Merged
merged 26 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ba102be
wip: add OpenApi 3 support
yhnavein Jun 20, 2024
f8751f8
chore: upgrade sample .net projects
yhnavein Jun 26, 2024
39c3ff1
feat: workout the parameters handling in OA3
yhnavein Jun 26, 2024
01c3da2
feat: implement most of type generation part for OpenAPI 3
yhnavein Jul 1, 2024
72a129a
feat: support oneOf / anyOf for type generation
yhnavein Jul 2, 2024
c3aa213
feat: basic functionality for generating operations is there
yhnavein Jul 2, 2024
035019e
impr: better support for array types
yhnavein Jul 3, 2024
bf6641c
feat: properly support return types
yhnavein Jul 3, 2024
6704687
impr: use undici for handling http request
yhnavein Jul 3, 2024
9303b3f
tests: re-enable snapshot tests + regen
yhnavein Jul 3, 2024
697400b
chore: reorganize code
yhnavein Jul 4, 2024
1990727
chore: more code cleanup
yhnavein Jul 4, 2024
e9f538b
feat: support form-data and urlencoded content types
yhnavein Jul 4, 2024
9c64e39
impr: better handle response types for fetch
yhnavein Jul 5, 2024
ac5221f
impr: better code in templates with less warnings
yhnavein Jul 7, 2024
7ba815f
impr: simplify processing path params
yhnavein Jul 7, 2024
bb5c57a
docs: improve docs
yhnavein Jul 7, 2024
d86a2ab
chore: experiment with aspnet core samples
yhnavein Jul 8, 2024
cfddb7c
fix: properly serialize query params (using ASPNET way for now)
yhnavein Jul 8, 2024
0e545fe
docs: add Kiota to comparison
yhnavein Jul 8, 2024
73a4769
impr: more robust query parameter serialization
yhnavein Jul 10, 2024
d7c1256
feat: allow query param serialization config
yhnavein Jul 10, 2024
51ec643
fix: default value for template was too aggressive
yhnavein Jul 10, 2024
33cf740
fix: support correctly Swashbuckle's UseOneOfForPolymorphism
yhnavein Jul 10, 2024
f04e071
fix: last few issues with default CLI values
yhnavein Jul 10, 2024
fc2f8ce
fix: adjust Content Type header in urlencoded case
yhnavein Jul 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [18.x, 20.x, 21.x, 22.x]

steps:
- uses: actions/checkout@v4
Expand All @@ -27,3 +27,8 @@ jobs:
- run: yarn build
- run: yarn test
- run: node dist/cli.js -c test/ci-test.config.json
- run: yarn coverage

- name: Update Coverage Badge
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
uses: we-cli/coverage-badge-action@main
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ dist/
coverage/
test/api*.json
.tmp/
.nyc_output/
15 changes: 12 additions & 3 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"extension": ["js", "ts"],
"require": ["sucrase/register"],
"spec": ["src/**/*.spec.ts", "test/**/*.spec.ts"]
"extension": [
"js",
"ts"
],
"require": [
"sucrase/register",
"./test/test-setup.ts"
],
"spec": [
"src/**/*.spec.ts",
"test/*.spec.ts"
]
}
3 changes: 1 addition & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"name": "Swaggie",
"program": "${workspaceFolder}/src/cli.ts",
"cwd": "${workspaceFolder}",
"runtimeArgs": ["-r", "sucrase/register"],
"protocol": "inspector",
"runtimeArgs": ["-r", "@swc/register"],
"args": ["-c", "./test/sample-config.json"]
}
]
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@

"files.associations": {
"templates/**/*.ejs": "plaintext"
},
"[html]": {
"editor.formatOnSave": false
}
}
191 changes: 138 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

![npm latest version](https://img.shields.io/npm/v/swaggie)
![NodeCI](https://github.com/yhnavein/swaggie/workflows/NodeCI/badge.svg)
![Snyk Vulnerabilities for GitHub Repo](https://img.shields.io/snyk/vulnerabilities/github/yhnavein/swaggie.svg)
![Test Coverage](https://img.shields.io/badge/test_coverage-98%25-brightgreen)
![npm downloads](https://img.shields.io/npm/dw/swaggie.svg)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/swaggie.svg)
![npm install size](https://packagephobia.now.sh/badge?p=swaggie)

<!-- ![Dependencies](https://img.shields.io/david/yhnavein/swaggie.svg) -->

Generate ES6 or Typescript code from an OpenAPI 2.0 spec, so that accessing REST API resources from the client code is less error-prone, static-typed and just easier to use long-term.
Generate ES6 or Typescript code from an OpenAPI 3.0 spec, so that accessing REST API resources from the client code is less error-prone, static-typed and just easier to use long-term.

You can take a look at the [Examples section](#example) down below.

Expand All @@ -29,29 +29,43 @@ Or globally to run CLI from anywhere

npm install swaggie -g

## OpenAPI versions

Swaggie from version 1.0 supports OpenAPI 3.0 (and some features of 3.1). Swagger or OpenAPI v2 documents are not supported anymore, but you have few options how to deal with it:

- **(preferred)** From your backend server generate OpenAPI 3.0 spec instead of version 2 (samples are updated to use OpenAPI 3.0)
- Convert your OpenAPI 2.0 spec to 3.0 using [swagger2openapi](https://www.npmjs.com/package/swagger2openapi) tool (or something similar)
- If you can't do that for any reason, you can stick to `Swaggie v0.x`. But upgrade is suggested

Please note that OpenAPI 3.0 is a major spec upgrade and it's possible that there will be some breaking changes in the generated code.
I have tried my best to minimize the impact, but it was not possible to avoid it completely.

More info about breaking changes can be found in the [Releases](https://github.com/yhnavein/swaggie/releases).

### CLI

```
Usage: swaggie [options]

Options:

-h, --help output usage information
-V, --version output the version number
-c, --config <path> The path to the configuration JSON file. You can do all the set up there instead of parameters in the CLI
-s, --src <url|path> The url or path to the Open API spec file
-t, --template <string> Template used forgenerating API client. Default: "axios"
-o, --out <path> The path to the file where the API would be generated
-b, --baseUrl <string> Base URL that will be used as a default value in the clients. Default: ""
--preferAny Use "any" type instead of "unknown". Default: false
--servicePrefix <string> Prefix for service names. Useful when you have multiple APIs and you want to avoid name collisions. Default: ''
--queryModels <bool> Generate models for query string instead list of parameters. Default: false
-V, --version output the version number
-c, --config <path> The path to the configuration JSON file. You can do all the set up there instead of parameters in the CLI
-s, --src <url|path> The url or path to the Open API spec file
-o, --out <filePath> The path to the file where the API would be generated. Use stdout if left empty
-b, --baseUrl <string> Base URL that will be used as a default value in the clients (default: "")
-t, --template <string> Template used forgenerating API client. Default: "axios"
--preferAny Use "any" type instead of "unknown" (default: false)
--servicePrefix <string> Prefix for service names. Useful when you have multiple APIs and you want to avoid name collisions (default: "")
--allowDots <bool> Determines if dots should be used for serialization object properties
--arrayFormat <format> Determines how arrays should be serialized (choices: "indices", "repeat", "brackets")
-h, --help display help for command
```

Sample CLI usage using Swagger's Pet Store:

```bash
swaggie -s https://petstore.swagger.io/v2/swagger.json -o ./client/petstore/
swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore/
```

`swaggie` outputs TypeScript that is somehow formatted, but it's far from perfect. You can adjust the generated code by prettifying output using your preferred beautify tool using your repo's styling guidelines. For example involving `prettier` looks like this:
Expand All @@ -72,13 +86,16 @@ Sample configuration looks like this:
{
"$schema": "https://raw.githubusercontent.com/yhnavein/swaggie/master/schema.json",
"out": "./src/client/petstore.ts",
"src": "https://petstore.swagger.io/v2/swagger.json",
"src": "https://petstore3.swagger.io/api/v3/openapi.json",
"template": "axios",
"baseUrl": "/api",
"preferAny": true,
"servicePrefix": "",
"queryModels": true,
"dateFormat": "Date" // "string" | "Date"
"dateFormat": "Date", // "string" | "Date"
"queryParamsSerialization": {
"arrayFormat": "repeat", // "repeat" | "brackets" | "indices"
"allowDots": true
}
}
```

Expand All @@ -98,39 +115,47 @@ ng2 Template for Angular 2+ (uses HttpClient, InjectionTokens, etc)
If you want to use your own template, you can use the path to your template for the `-t` parameter:

```
swaggie -s https://petstore.swagger.io/v2/swagger.json -o ./client/petstore --template ./my-swaggie-template/
swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore --template ./my-swaggie-template/
```

### Code
## Usage – Integrating into your project

```javascript
const swaggie = require('swaggie');
swaggie
.genCode({
src: 'http://petstore.swagger.io/v2/swagger.json',
out: './api/petstore.ts',
})
.then(complete, error);
Let's assume that you have a [PetStore API](http://petstore.swagger.io/) as your REST API and you are developing a client app written in TypeScript that will consume this API.

function complete(spec) {
console.info('Service generation complete');
}
Instead of writing any code by hand for fetching particular resources, we will let Swaggie do it for us.

function error(e) {
console.error(e.toString());
}
```
### Query Parameters Serialization

## Usage – Integrating into your project
When it comes to use of query parameters then you might need to adjust the way these parameters will be serialized, as backend server you are using expects them to be in a specific format. Thankfully in Swaggie you can specify how they should be handled. If you won't provide any configuration, then Swaggie will use the defaults values expected in the ASP.NET Core world.

Let's assume that you have a [PetStore API](http://petstore.swagger.io/) as your REST API and you are developing a client app written in TypeScript that will consume this API.
For your convenience there are few config examples to achieve different serialization formats for an object `{ "a": { "b": 1 }, "c": [2, 3] }`:

Instead of writing any code by hand for fetching particular resources, we will let Swaggie do it for us.
| Expected Format | allowDots | arrayFormat |
| ----------------------- | --------- | ----------- |
| `?a.b=1&c=2&c=3` | `true` | `repeat` |
| `?a.b=1&c[]=2&c[]=3` | `true` | `brackets` |
| `?a.b=1&c[0]=2&c[1]=3` | `true` | `indices` |
| `?a[b]=1&c=2&c=3` | `false` | `repeat` |
| `?a[b]=1&c[]=2&c[]=3` | `false` | `brackets` |
| `?a[b]=1&c[0]=2&c[1]=3` | `false` | `indices` |

Once you know what your backend expects, you can adjust the configuration file accordingly: (below are default values)

```json
{
"queryParamsSerialization": {
"arrayFormat": "repeat",
"allowDots": true
}
}
```

### Code Quality

> Please note that it's **recommended** to pipe Swaggie command to some prettifier like `prettier`, `biome` or `dprint` to make the generated code look not only nice, but also persistent.
> Because Swaggie relies on a templating engine, whitespaces are generally a mess, so they may change between versions.

### Suggested prettiers
**Suggested prettiers**

[prettier](https://prettier.io/) - the most popular one

Expand All @@ -151,7 +176,7 @@ You are not limited to any of these, but in our examples we will use Prettier. P
Let's run `swaggie` against PetStore API and see what will happen:

```bash
swaggie -s https://petstore.swagger.io/v2/swagger.json -o ./api/petstore.ts && prettier ./api/petstore.ts --write
swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./api/petstore.ts && prettier ./api/petstore.ts --write
```

```typescript
Expand All @@ -160,19 +185,21 @@ swaggie -s https://petstore.swagger.io/v2/swagger.json -o ./api/petstore.ts && p
import Axios, { AxiosPromise } from 'axios';
const axios = Axios.create({
baseURL: '/api',
paramsSerializer: (params) =>
encodeParams(params, null, {
allowDots: true,
arrayFormat: 'repeat',
}),
});

/** [...] **/

export const petClient = {
/**
* @param petId
* @return Success
*/
getPetById(petId: number): AxiosPromise<Pet> {
let url = '/pet/{petId}';

url = url.replace('{petId}', encodeURIComponent('' + petId));
let url = `/pet/${encodeURIComponent(`${petId}`)}`;

return axios.request<Pet>({
url: url,
Expand Down Expand Up @@ -208,17 +235,75 @@ You might wonder how to set up server to fully utilize Swaggie's features. For t

Server is not necessary to use Swaggie. Swaggie cares only about the JSON/yaml file with the Open API spec, but for your development purpose you might want to have a server that can serve this file automatically from the actual endpoints.

## Notes
## Competitors

If you are familiar with the client-code generators for the Swagger / OpenAPI standards then you might wonder why `swaggie` is better than existing tools. I compiled a quick comparison with other tools below:

### Swaggie

- Fast and small ![swaggie size](https://packagephobia.now.sh/badge?p=swaggie)
- Lightweight and easy to start
- Easy to contribute to, custom templates
- Flexible, suits well in the existing apps
- Generates REST clients and all models
- Supports different templates (like `axios`, `fetch`, `xior`, `swr-axios`, `ng1`, `ng2`)
- Written in TypeScript
- Generates only one file with everything you need inside

### [NSwag](https://github.com/RicoSuter/NSwag)

- Slow and big ![nswag size](https://packagephobia.now.sh/badge?p=nswag)
- Complicated templates, not easy to contribute to
- Enforces usage of other tools and architecture
- Generates more boilerplate code
- Written in .NET, require .NET to execute, although published to npm as well
- Many more features (but mostly for .NET apps), client generation is just a part of it

### [Hey API](https://heyapi.vercel.app)

- Fast and small ![nswag size](https://packagephobia.now.sh/badge?p=%40hey-api%2Fopenapi-ts)
- No flexibility, other clients are discouraged from use
- Generates a lot of code and multiple files
- Written in TypeScript

### [Kiota](https://learn.microsoft.com/en-us/openapi/kiota/)

- A lot of boilerplate code and many files
- Written in .NET, requires .NET to execute, published to NuGet
- Not flexible at all - you need to use their architecture in your code
- Looks like an enterprise solution with many configuration options

## Using Swaggie programmatically

```javascript
const swaggie = require('swaggie');
swaggie
.genCode({
src: 'https://petstore3.swagger.io/api/v3/openapi.json',
out: './api/petstore.ts',
})
.then(complete, error);

If you are familiar with the client-code generators for the Swagger / OpenAPI standards then you might wonder why `swaggie` is better than existing tools. Currently the most popular alternative is an open-source `NSwag`.
function complete(spec) {
console.info('Service generation complete');
}

Quick comparison table:
function error(e) {
console.error(e.toString());
}
```

## Notes

| swaggie | NSwag |
| --------------------------------------------------------------- | ----------------------------------------------------------- |
| - Written in node.js + TypeScript | - Written in .NET |
| - Fast | - Slow |
| - ![swaggie size](https://packagephobia.now.sh/badge?p=swaggie) | - ![nswag size](https://packagephobia.now.sh/badge?p=nswag) |
| - Easy to contribute to | - Contributing hard |
| - Lightweight | - Complicated templates |
| - Only features generating API clients for TS/JS | - Many more features (but mostly for .NET apps) |
| Supported | Not supported |
| ------------------------------------------------------------------------------ | ------------------------------------------ |
| OpenAPI 3 | Swagger 2 |
| `allOf`, `oneOf`, `anyOf`, `$ref` to schemas | `not` |
| Spec formats: `JSON`, `YAML` | Very complex query params |
| Extensions: `x-position`, `x-name`, `x-enumNames`, `x-enum-varnames` | Multiple response types (one will be used) |
| Content types: `JSON`, `text`, `multipart/form-data` | Multiple request types (one will be used) |
| Content types: `application/x-www-form-urlencoded`, `application/octet-stream` | References to other spec files |
| Different types of enum definitions (+ OpenAPI 3.1 support for enums) | |
| Paths inheritance, comments (descriptions) | |
| Getting documents from remote locations or as path reference (local file) | |
| Grouping endpoints by tags + handle gracefully duplicate operation ids | |
15 changes: 9 additions & 6 deletions api.config.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/yhnavein/swaggie/master/schema.json",
"out": "./.tmp/swagger-test/petstore.ts",
"src": "https://petstore.swagger.io/v2/swagger.json",
"template": "ng2",
"$schema": "./schema.json",
"out": "./.tmp/fetch-petstore.ts",
"src": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json",
"template": "fetch",
"preferAny": true,
"queryModels": true,
"dateFormat": "string"
"dateFormat": "string",
"queryParamsSerialization": {
"arrayFormat": "indices",
"allowDots": false
}
}
Loading