Skip to content

Commit 173cb12

Browse files
committed
docs: readme, code examples
1 parent 977f534 commit 173cb12

16 files changed

+727
-11
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/.idea
2-
/node_modules
2+
node_modules
33
/.vscode
44
/test/out
55
/specs

README.md

+63-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,66 @@
11
[![Build Status](https://travis-ci.org/devexperts/swagger-codegen-ts.svg?branch=master)](https://travis-ci.org/devexperts/swagger-codegen-ts)
2-
3-
### FAQ
4-
1. **Why don't spec codecs reuse common parts?**
5-
6-
That's because different versions of specs refer to different versions of [JSON Schema](http://json-schema.org) and they are generally not the same. We would like to avoid maintaining JSON Schema composition in this project. (for now)
72

8-
### Contributions
9-
- use https://www.conventionalcommits.org/en/v1.0.0-beta.2/
3+
# Typesafe OpenAPI generator for TypeScript
104

11-
### Publish
12-
`npm version major|minor|patch`
5+
## Features
6+
* Generates client code from **OpenAPI 3.0, 2.0** (aka Swagger) and **AsyncAPI** specs
7+
* **Pluggable HTTP clients:** can use `fetch`, `Axios` or any other library
8+
* **Flexible response types:** works with Promises and reactive streams like RxJS
9+
* **Runtime type checks:** validates server responses against the spec
10+
* Written in **pure TypeScript** using [`fp-ts`](https://github.com/gcanti/fp-ts) and [`io-ts`](https://github.com/gcanti/io-ts) libraries
11+
12+
## Demo code
13+
14+
> The examples below refer to the [Pet Store OpenAPI 3.0 schema](https://petstore3.swagger.io/).
15+
16+
After running the codegen, interacting with a REST API may be as simple as this:
17+
18+
```typescript
19+
import { petController as createPetController } from "./src/generated/petstore.json/paths/PetController";
20+
import { Pet } from "./src/generated/petstore.json/components/schemas/Pet";
21+
22+
// Creating a controller, see the "HTTP Clients" wiki page for more details
23+
const petController = createPetController({ httpClient: fetchHttpClient });
24+
25+
// The returned object is guaranteed to be a valid `Pet`
26+
const createdPet: Promise<Pet> = petController.addPet({
27+
body: {
28+
// The parameters are statically typed, IntelliSense works, too
29+
name: "Spotty",
30+
photoUrls: [],
31+
},
32+
});
33+
```
34+
35+
More usage scenarios are supported - check the [usage page](./docs/usage/generated-code.md) for more detail.
36+
37+
## Installation
38+
39+
1. Make sure the peer dependencies are installed, then install the codegen itself:
40+
```
41+
yarn add typescript fp-ts io-ts io-ts-types
42+
yarn add -D @devexperts/swagger-codegen-ts
43+
```
44+
45+
2. Create a console script that would invoke the `generate` function, passing the options such as path to the schema file and the output directory.
46+
See the [Generators](docs/usage/api.md) page for the API reference, and [examples/generate](examples/generate) for sample scripts.
47+
48+
3. In most cases, you might want to include the code generation step into the build and local launch scripts. Example:
49+
```diff
50+
/* package.json */
51+
52+
"scripts": {
53+
+ "generate:api": "ts-node scripts/generate-api.ts",
54+
- "start": "react-scripts start",
55+
+ "start": "yarn generate:api && react-scripts start",
56+
- "build": "react-scripts build"
57+
+ "build": "yarn generate:api && react-scripts build"
58+
}
59+
```
60+
61+
## Contributing
62+
63+
* Feel free to file bugs and feature requests in [GitHub issues](https://github.com/devexperts/swagger-codegen-ts/issues/new).
64+
* Pull requests are welcome - please use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.2/).
65+
66+
Please read the [Contributors Guide](./docs/development/contributors-guide.md) for more information.

docs/development/clients.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Creating HTTP Clients
2+
3+
The code generated by OpenAPI and AsyncAPI generators requires the user to provide an `HTTPClient` or `WebSocketClient` instance respectively.
4+
5+
The original intent is to create such instances in the consumer's application; however, some predefined clients may be added in the future. This guide describes basic steps to create one.
6+
7+
### Defining a return type
8+
9+
In order to support the desired return type, the generated code must know how to perform some basic operations over that type, such as creating a "successful" or "failed" response object, or applying a given transform function to the response. These operations are abstracted via the [`MonadThrow`](https://gcanti.github.io/fp-ts/modules/MonadThrow.ts.html) interface from the `fp-ts` library.
10+
11+
Some common types already have corresponding `Monad` implementations, while for others you might have to implement one from scratch. For an example of how this can be done and what functions are required, check how the `Monad` is implemented for RxJS's `Observable` in the [`fp-ts-rxjs`](https://github.com/gcanti/fp-ts-rxjs/blob/master/src/Observable.ts) package.
12+
13+
### Implementing the actual HTTP requests logic
14+
15+
Once the `Monad` implementation is available, just two methods need to be added to form a working `HTTPClient`: `throwError` and `request`. As can be seen from their signatures, they create a "failed" response and make actual HTTP calls, respectively.
16+
17+
Considering the above, an `HTTPClient` for RxJS could be defined as follows:
18+
19+
```typescript
20+
import { Monad } from "fp-ts-rxjs/lib/Observable";
21+
import { throwError } from "rxjs";
22+
23+
const rxjsHttpClient: HTTPClient1<"Observable"> = {
24+
...Monad,
25+
request: (req) => {
26+
return ajax({
27+
url: req.url,
28+
method: req.method,
29+
// add the logic to handle `req.body` and other parameters
30+
}).pipe();
31+
},
32+
throwError,
33+
};
34+
```
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Contributors Guide
2+
3+
Thanks for your attention and efforts to improve the codegen!
4+
5+
Here are some resources to help you getting familiar with the code base.
6+
7+
* [Development: Overview](./overview.md)
8+
9+
# Testing
10+
11+
# Submitting changes
12+
13+
# Code style and conventions

docs/development/overview.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Development: Overview
2+
3+
TODO: describe the architecture and data flows
4+
5+
### FAQ
6+
1. **Why don't spec codecs reuse common parts?**
7+
8+
That's because different versions of specs refer to different versions of [JSON Schema](http://json-schema.org) and they are generally not the same. We would like to avoid maintaining JSON Schema composition in this project. (for now)
9+
10+
### Publish
11+
`npm version major|minor|patch`

docs/usage/api.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# API
2+
3+
The single entry point to the generator is the `generate` function:
4+
5+
```typescript
6+
import { generate } from "@devexperts/swagger-codegen-ts";
7+
import { OpenapiObjectCodec } from "@devexperts/swagger-codegen-ts/dist/schema/3.0/openapi-object";
8+
import { serialize } from "@devexperts/swagger-codegen-ts/dist/language/typescript/3.0";
9+
10+
generate({
11+
spec: path.resolve(__dirname, "petstore.json"),
12+
out: path.resolve(__dirname, "src/generated"),
13+
language: serialize,
14+
decoder: OpenapiObjectCodec,
15+
});
16+
```
17+
18+
See the typedocs for more info about the options.
19+
20+
# CLI
21+
22+
Not implemented yet - PRs are highly welcome!

docs/usage/clients.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Clients Reference
2+
3+
TODO: provide pre-made HTTP client factories
4+
5+
For developing a custom `HTTPClient`, please refer to [Developers Guide - HTTP clients](../development/clients.md).

docs/usage/generated-code.md

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Using the generated code
2+
3+
> The examples below refer to the [Pet Store OpenAPI 3.0 schema](https://petstore3.swagger.io/).
4+
5+
## Basic example: using a single controller
6+
7+
In the most basic scenario, you may need just a single **controller** from the generated code. In this case, the code is as simple as this:
8+
9+
```typescript
10+
import { petController as createPetController } from "./src/generated/petstore.json/paths/PetController";
11+
import { Pet } from "./src/generated/petstore.json/components/schemas/Pet";
12+
13+
// Creating a controller, see the "HTTP Clients" wiki page for more details
14+
const petController = createPetController({ httpClient: fetchHttpClient });
15+
16+
// The returned object is guaranteed to be a valid `Pet`
17+
const createdPet: Promise<Pet> = petController.addPet({
18+
body: {
19+
// The parameters are statically typed, IntelliSense works, too
20+
name: "Spotty",
21+
photoUrls: [],
22+
},
23+
});
24+
```
25+
26+
## The `controllers` object
27+
28+
In most projects, the generated code includes more than one controller. Sometimes it's handy to have a single entry points to all of them - for this purpose, the `controllers` object is created by the generator:
29+
30+
```typescript
31+
import { controllers } from "./src/generated/petstore.json/paths/paths";
32+
33+
const api = controllers({ httpClient: fetchHttpClient });
34+
const pets = api.petController.findPetsByStatus({
35+
query: { status: some("available") },
36+
});
37+
// api.userController and api.storeController are also available
38+
```
39+
40+
## Plugging different HTTP clients
41+
42+
Generated functions may be instructed to return any generic type with one or two type arguments, for example `Promise<Response>` or `Observable<Either<Error, Response>>`. The return type is specified by providing a corresponding **client**. In the example below, providing an `rxjsHttpClient` makes the `petController` return RxJS's `Observable`:
43+
44+
```typescript
45+
import { Observable } from "rxjs";
46+
47+
// Now create another controller returning an RxJS stream
48+
const petRxjsController = createPetController({ httpClient: rxjsHttpClient });
49+
const createdPet$: Observable<Pet> = petRxjsController.addPet({
50+
body: {
51+
name: "Spotty",
52+
photoUrls: [],
53+
},
54+
});
55+
```
56+
57+
The list of bundled clients and more information can be found in the [Clients](./clients.md) page.
58+
59+
## Using [`RemoteData`](https://github.com/devexperts/remote-data-ts)
60+
61+
The codegen provides first class support for the `RemoteData<Error, Response>` type, making it easier to build complex logic on top of the generated controllers.
62+
63+
```typescript
64+
const petRDController = createPetController({ httpClient: liveDataHttpClient });
65+
/**
66+
* `LiveData<E, A> = Observable<RemoteData<E, A>>`
67+
*
68+
* Emits `pending` when the request is started,
69+
* then `success(Pet)` or `failure(Error)` upon completion.
70+
*/
71+
const createdPet$: LiveData<Error, Pet> = petRDController.addPet({
72+
body: {
73+
name: "Spotty",
74+
photoUrls: [],
75+
},
76+
});
77+
```
78+
79+
## Validation utils
80+
81+
Each schema defined in the spec produces a TS type and an `io-ts` codec, which can be used for runtime type checking in the application code:
82+
83+
```typescript
84+
import { either } from "fp-ts";
85+
import { pipe } from "fp-ts/function";
86+
import { User, UserIO } from "./src/generated/petstore.json/components/schemas/User";
87+
88+
pipe(
89+
UserIO.decode(JSON.parse(localStorage.getItem('PetStore.user'))),
90+
either.fold(
91+
error => {
92+
console.log('The user record is not valid');
93+
},
94+
(user: User) => {
95+
console.log(`Was previously logged in as: ${user.email}`);
96+
}
97+
)
98+
);
99+
```
100+
101+
Learn more on the `Either` type: [Getting started with fp-ts: Either vs Validation](https://dev.to/gcanti/getting-started-with-fp-ts-either-vs-validation-5eja)
102+

examples/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/out/

examples/generate/01-single-spec.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { generate } from '@devexperts/swagger-codegen-ts';
2+
import * as path from 'path';
3+
import { serialize as serializeOpenAPI3 } from '@devexperts/swagger-codegen-ts/dist/language/typescript/3.0';
4+
import { OpenapiObjectCodec } from '@devexperts/swagger-codegen-ts/dist/schema/3.0/openapi-object';
5+
import { either } from 'fp-ts';
6+
7+
const cwd = path.resolve(__dirname, '../../test/specs/3.0');
8+
9+
// Note that the `generate` function does not generate immediately but returns a `TaskEither`
10+
// see: https://dev.to/ryanleecode/practical-guide-to-fp-ts-p3-task-either-taskeither-2hpl
11+
const codegenTask = generate({
12+
cwd,
13+
spec: path.resolve(cwd, 'petstore.yaml'),
14+
out: path.resolve(__dirname, '../out'),
15+
language: serializeOpenAPI3,
16+
decoder: OpenapiObjectCodec,
17+
});
18+
19+
// The result of a `TaskEither` invocation is a promise that is always resolved to an `Either`
20+
// Make sure that Either's left side is handled, otherwise the errors would be silently ignored.
21+
codegenTask().then(
22+
either.fold(
23+
error => {
24+
console.error('Code generation failed', error);
25+
process.exit(1);
26+
},
27+
() => {
28+
console.log('Generated successfully');
29+
},
30+
),
31+
);
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { generate } from '@devexperts/swagger-codegen-ts';
2+
import * as path from 'path';
3+
import { serialize as serializeOpenAPI3 } from '@devexperts/swagger-codegen-ts/dist/language/typescript/3.0';
4+
import { OpenapiObjectCodec } from '@devexperts/swagger-codegen-ts/dist/schema/3.0/openapi-object';
5+
import { array, either, taskEither } from 'fp-ts';
6+
import { pipe } from 'fp-ts/function';
7+
8+
const cwd = path.resolve(__dirname, '../../test/specs/3.0');
9+
const specs = ['nested/link-example.yaml', 'demo.yml'];
10+
11+
// Create a task for each input spec file
12+
const tasks = pipe(
13+
specs,
14+
array.map(spec =>
15+
generate({
16+
cwd,
17+
spec: path.resolve(cwd, spec),
18+
out: path.resolve(__dirname, '../out'),
19+
language: serializeOpenAPI3,
20+
decoder: OpenapiObjectCodec,
21+
}),
22+
),
23+
// Notice how the sequence operation is used to run multiple tasks in parallel
24+
taskEither.sequenceArray,
25+
);
26+
27+
tasks().then(
28+
either.fold(
29+
error => {
30+
console.error(error);
31+
process.exit(1);
32+
},
33+
() => {
34+
console.log('Generated successfully');
35+
},
36+
),
37+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { generate } from '@devexperts/swagger-codegen-ts';
2+
import * as path from 'path';
3+
import { serialize as serializeOpenAPI3 } from '@devexperts/swagger-codegen-ts/dist/language/typescript/3.0';
4+
import { OpenapiObjectCodec } from '@devexperts/swagger-codegen-ts/dist/schema/3.0/openapi-object';
5+
import { taskEither } from 'fp-ts';
6+
import { pipe } from 'fp-ts/function';
7+
import { remove } from 'fs-extra';
8+
import { identity } from 'io-ts';
9+
10+
const cwd = path.resolve(__dirname, '../../test/specs/3.0');
11+
const out = path.resolve(__dirname, '../out');
12+
13+
const cleanTask = taskEither.tryCatch(() => remove(out), identity);
14+
15+
const generateTask = generate({
16+
cwd,
17+
spec: 'file-and-text.yml',
18+
out,
19+
language: serializeOpenAPI3,
20+
decoder: OpenapiObjectCodec,
21+
});
22+
23+
pipe(
24+
[cleanTask, generateTask],
25+
taskEither.sequenceSeqArray,
26+
taskEither.match(
27+
error => {
28+
console.error(error);
29+
process.exit(1);
30+
},
31+
() => {
32+
console.log('Generated successfully');
33+
},
34+
),
35+
)();

0 commit comments

Comments
 (0)