Skip to content
This repository has been archived by the owner on Nov 11, 2023. It is now read-only.

Open api import #28

Merged
merged 59 commits into from
Dec 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
372f35e
Add open-api scripts dependencies
fabien0102 Aug 8, 2018
07cf8e9
Add import open-api script
fabien0102 Aug 14, 2018
4b143d3
Add import-open-api bin and build process
fabien0102 Aug 14, 2018
c0e7e62
Fix commander output options
fabien0102 Aug 14, 2018
150eb5d
Add some unit tests and refactor a bit
fabien0102 Sep 28, 2018
4ddb4ee
Add TBody generic argument to Mutate
fabien0102 Oct 9, 2018
6362070
Deal with object with additional properties
fabien0102 Oct 9, 2018
85c28b7
Deal with object properties and refs
fabien0102 Oct 9, 2018
6008c8f
Deal with object.allOf
fabien0102 Oct 9, 2018
40b83ff
Use a ternary instead of if/else
fabien0102 Oct 9, 2018
f6e3883
Remove the first empty line
fabien0102 Oct 9, 2018
bb10870
Test and fix generateSchemaDefinition
fabien0102 Oct 9, 2018
51cc4f2
Deal with array as additionalProperties
fabien0102 Oct 9, 2018
2a0d48a
Add missing data types
fabien0102 Oct 9, 2018
4b3123e
Remove duplicate types in response
fabien0102 Oct 9, 2018
bd4ca19
Deal with params in path
fabien0102 Oct 9, 2018
121f38b
Add @types/commander
fabien0102 Oct 9, 2018
6f2f155
Move everything to `src`
fabien0102 Oct 9, 2018
454c5bf
Fix the confusion between in: query and in: path
fabien0102 Oct 9, 2018
948c0a7
Import qs
fabien0102 Oct 9, 2018
be99514
Deal with path param
fabien0102 Oct 9, 2018
f79cbd9
Generate Mutate component FTW \o/
fabien0102 Oct 9, 2018
646daa6
Deal with empty object
fabien0102 Nov 19, 2018
fecad05
Add a operationId duplication check
fabien0102 Nov 19, 2018
d8c51e2
Add a section about openAPI
fabien0102 Nov 19, 2018
3dd597a
Add rollup to build correctly the bin part
fabien0102 Nov 19, 2018
fbba9ca
Update operational-scripts
fabien0102 Nov 19, 2018
71cea08
Deal with `verb` prop in Mutate generator
fabien0102 Nov 19, 2018
7b41941
Remove workaround
fabien0102 Nov 19, 2018
26c12b8
Be unsensitive the extension file case
fabien0102 Nov 19, 2018
c60065a
Deal with swagger 2.0 specs
fabien0102 Nov 22, 2018
66ee93a
Implement responses types
fabien0102 Nov 22, 2018
76d2a7b
Deal with parameters
fabien0102 Nov 23, 2018
53d04ba
Better fallback if error is not defined
fabien0102 Nov 23, 2018
7bdf8d5
[Breaking] Reorder generics type of Mutate to be more consistant
fabien0102 Nov 23, 2018
ab7b854
Add a little \n
fabien0102 Nov 23, 2018
7fd5eb9
Don't include the baseUrl in the generated components
fabien0102 Nov 23, 2018
8beee73
Add import from github flow :tada:
fabien0102 Nov 23, 2018
1a38d44
Export better types for custom responses
fabien0102 Nov 26, 2018
69117aa
Fix how delete components works (id is injected after)
fabien0102 Nov 27, 2018
a1095b2
Fix npm build
fabien0102 Nov 27, 2018
4ed5034
Fix npm build
fabien0102 Nov 27, 2018
b62ddc6
Fix delete edge case
fabien0102 Nov 27, 2018
bf76759
Deal with long polling
fabien0102 Nov 27, 2018
f65cd44
Fix polling types
fabien0102 Nov 27, 2018
d76a54e
Deal with no response case
fabien0102 Nov 27, 2018
d3a51af
Introduce query params
fabien0102 Nov 29, 2018
c6756dd
Fix typos
fabien0102 Nov 29, 2018
9a710a7
Rename TBody to TRequestBody
fabien0102 Nov 29, 2018
07ce83f
Refactor overkill discrimination
fabien0102 Nov 29, 2018
6e5027e
Generate query params
fabien0102 Nov 29, 2018
ef52906
Fix swagger2openapi definition
fabien0102 Dec 3, 2018
c9d3a86
Improve code style
fabien0102 Dec 3, 2018
e4dc683
Add a transformer param for advanced cases
fabien0102 Dec 6, 2018
c16fdd2
Add a tslint disable on empty object
fabien0102 Dec 7, 2018
bb0564e
Update documentation about codegen
Dec 7, 2018
3a8aba1
Deal with unused imports
fabien0102 Dec 7, 2018
782349a
Document QueryParams and transformer
Dec 7, 2018
a9dc566
Fix README snippet
Dec 7, 2018
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,13 @@ typings/
public
lib

# End of https://www.gitignore.io/api/node,macos,vscode

# Cache for parcel serverl in example
.cache

package-lock.json

# Files managed by operational-scripts
tslint.json
tsconfig.json
Expand Down
159 changes: 140 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# RESTful React
[![Greenkeeper badge](https://badges.greenkeeper.io/contiamo/restful-react.svg)](https://greenkeeper.io/)

[![Build Status](https://travis-ci.org/contiamo/restful-react.svg?branch=master)](https://travis-ci.org/contiamo/restful-react)

TejasQ marked this conversation as resolved.
Show resolved Hide resolved
Building React apps that interact with a backend API presents a set of questions, challenges and potential gotchas. This project aims to remove such pitfalls, and provide a pleasant developer experience when crafting such applications. It can be considered a thin wrapper around the [fetch API](https://developer.mozilla.org/en/docs/Web/API/Fetch_API) in the form of a React component.

Expand All @@ -10,24 +7,28 @@ As an abstraction, this tool allows for greater consistency and maintainability
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Overview](#overview)
- [Getting Started](#getting-started)
- [Features](#features)
- [Global Configuration](#global-configuration)
- [`RestfulProvider` API](#restfulprovider-api)
- [Composability](#composability)
- [`Get` Component API](#get-component-api)
- [Full `Get` Component API](#full-get-component-api)
- [Loading and Error States](#loading-and-error-states)
- [Lazy Fetching](#lazy-fetching)
- [Response Resolution](#response-resolution)
- [Debouncing Requests](#debouncing-requests)
- [TypeScript Integration](#typescript-integration)
- [Query Parameters](#query-parameters)
- [Mutations with `Mutate`](#mutations-with-mutate)
- [`Mutate` Component API](#mutate-component-api)
- [Full `Mutate` Component API](#full-mutate-component-api)
- [Polling with `Poll`](#polling-with-poll)
- [Long Polling](#long-polling)
- [`Poll` Component API](#poll-component-api)
- [Full `Poll` Component API](#full-poll-component-api)
- [Code Generation](#code-generation)
- [Usage](#usage)
- [Import from GitHub](#import-from-github)
- [Transforming an Original Spec](#transforming-an-original-spec)
- [Caching](#caching)
- [Contributing](#contributing)
- [Code](#code)
Expand Down Expand Up @@ -61,6 +62,8 @@ To install and use this library, simply `yarn add restful-react`, or `npm i rest

## Features

Restful React ships with the following features that we think might be useful.

### Global Configuration

API endpoints usually sit alongside a base, global URL. As a convenience, the `RestfulProvider` allows top-level configuration of your requests, that are then passed down the React tree to `Get` components.
Expand Down Expand Up @@ -91,6 +94,7 @@ import React from "react";
import Get from "restful-react";

const MyComponent = () => (
/* Make a request to https://dog.ceo/api/breeds/image/random" */
<Get path="/breeds/image/random">
{randomDogImage => <img alt="Here's a good boye!" src={randomDogImage.message} />}
</Get>
Expand Down Expand Up @@ -134,29 +138,36 @@ Here's some docs about the [RequestInit](https://developer.mozilla.org/en-US/doc

### Composability

`Get` components can be composed together and request URLs at an accumulation of their collective path props. Consider,
`Get` components can be composed together and request URLs are an accumulation of their collective path props. Consider,

[![Edit Restful React demos](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/30n66z45mq)

```jsx
// Assuming we're using a RestfulProvider with base={HOST} somewhere,
// Assuming we're using a RestfulProvider with base="https://my.web/api somewhere,
import React from "react";
import Get from "restful-react";

export default () => (
{/* Use the lazy prop to not send a request */}
<Get path="/breeds" lazy>
{/* Send a request to "https://my.web/api/breeds */}
<Get path="/breeds">
{data => {
return (
<div>
<h1>Random Image</h1>
{/* Composes path with parent: sends request to /breeds/image/random */}
<Get path="/image/random">
{/*
Composes path with parent: sends request to https://my.web/api/breeds/image/random.
This happens because there is no preceding / to the path.
*/}
<Get path="image/random">
{image => <img alt="Random Image" src={image && image.message} />}
</Get>

<h1>All Breeds</h1>
{/* Composes path with parent: sends request to /breeds/list */}

{/*
Composes path with parent: sends request to https://my.web/api/list
The preceding slash (/) ALWAYS queries the ROOT of the RestfulProvider's base.
*/}
<Get path="/list">
{list => (
<ul>{list && list.message.map(dogName => <li>{dogName}</li>)}</ul>
Expand All @@ -169,11 +180,11 @@ export default () => (
);
```

From the above example, _not only_ does the path accumulate based on the nesting of each `Get`, but each `Get` _can_ override its parent with other props as well: including having _specific_ `requestOptions` if there was a valid use case.
From the above example, _not only_ does the path compose based on the nesting of each `Get`, but each `Get` _can_ override its parent with other props as well: including having _specific_ `requestOptions` if there was a valid use case.

To opt-out of this behavior `Get` components can use an alternative URL as their `base` prop.

#### [`Get` Component API](src/Get.tsx#L50-L87)
#### [Full `Get` Component API](src/Get.tsx#L50-L87)

### Loading and Error States

Expand Down Expand Up @@ -339,6 +350,14 @@ One of the most poweful features of RESTful React, each component exported is st

![Using RESTful React in VS Code](assets/labs.gif)

### Query Parameters

All components in this library support query params (`https://my.site/?query=param`) via a `queryParams` prop. Each `Get`, `Mutate` and `Poll` component is _generic_, having a type signature of `Get<TData, TError, TQueryParams>`. If described, the `queryParams` prop is _fully_ type-safe in usage and provides autocomplete functionality.

![Autocompletion on QueryParams](assets/idp.gif)

Please note that the above example was built using our [OpenAPI generator](#code-generation) in order to infer the type of component from the specification and automatically generate the entire type-safe component in a _very_ quick and easy way.

### Mutations with `Mutate`

Restful React exposes an additional component called `Mutate`. These components allow sending requests with other HTTP verbs in order to mutate backend resources.
Expand Down Expand Up @@ -375,7 +394,7 @@ const Movies = ({ dispatch }) => (

Each mutation returns a promise, that can then be used to update local component state, or dispatch an action, or do something else depending on your use case.

#### [`Mutate` Component API](src/Mutate.tsx#L31-L47)
#### [Full `Mutate` Component API](src/Mutate.tsx#L31-L47)

### Polling with `Poll`

Expand Down Expand Up @@ -443,7 +462,7 @@ Note from the previous example, `Poll` also exposes more states: `finished`, and

#### Long Polling

At Contiamo, we have a [powerful Long Polling specification](docs/contiamo-long-poll.md) in place that allows us to build real-time apps over HTTP, as opposed to WebSockets. At a glance the specification can be distilled into:
At Contiamo, we have a [powerful Long Polling specification](docs/contiamo-long-poll.md) in place that allows us to build real-time apps over HTTPS, as opposed to WebSockets. At a glance the specification can be distilled into:

- Web UI sends a request with a `Prefer` header that contains:
- a time, in seconds, to keep requests open (`60s`), and
Expand All @@ -461,7 +480,109 @@ Visually, this is represented as below.

To get this functionality in Restful React, it is as simple as specifying a `wait` prop on your `Poll` component, provided your server implements the specification as well.

#### [`Poll` Component API](src/Poll.tsx#L53-L101)
#### [Full `Poll` Component API](src/Poll.tsx#L53-L101)

### Code Generation

Restful React is able to generate _type-safe_ React components from any valid OpenAPI v3 or Swagger v2 specification, either in `yaml` or `json` formats.

#### Usage

Type-safe React data fetchers can be generated from an OpenAPI specification using the following command:

- `restful-react import --file MY_OPENAPI_SPEC.yaml --output my-awesome-generated-types.d.tsx`

This command can be invoked by _either_:

- Installing `restful-react` globally and running it in the terminal: `npm i -g restful-react`, or
- Adding a `script` to your `package.json` like so:

```diff
"scripts": {
"start": "webpack-dev-server",
"build": "webpack -p",
+ "generate-fetcher": "restful-react import --file MY_SWAGGER_DOCS.json --output FETCHERS.tsx"
}
```

Your components can then be generated by running `npm run generate-fetcher`. Optionally, we recommend linting/prettifying the output for readability like so:

```diff
"scripts": {
"start": "webpack-dev-server",
"build": "webpack -p",
"generate-fetcher": "restful-react import --file MY_SWAGGER_DOCS.json --output FETCHERS.tsx",
+ "postgenerate-fetcher": "prettier FETCHERS.d.tsx --write"
}
```

#### Import from GitHub

Adding the `--github` flag to `restful-react import` instead of a `--file` allows us to **create React components from an OpenAPI spec _remotely hosted on GitHub._** <sup>_(how is this real life_ 🔥 _)_</sup>

To generate components from remote specifications, you'll need to follow the following steps:

1. Visit [your GitHub settings](https://github.com/settings/tokens).
1. Click **Generate New Token** and choose the following:

Token Description: (enter anything, usually your computer name)
Scopes:
[X] repo
[X] repo:status
[X] repo_deployment
[X] public_repo
[X] repo:invite

1. Click **Generate token**.
1. Copy the generated string.
1. Open Terminal and run `restful-react import --github username:repo:branch:path/to/openapi.yaml --output MY_FETCHERS.tsx`.
1. You will be prompted for a token.
1. Paste your token.
1. You will be asked if you'd like to save it for later. This is _entirely_ up to you and completely safe: it is saved in your `node_modules` folder and _not_ committed to version control or sent to us or anything: the source code of this whole thing is public so you're safe.

**Caveat:** _Since_ your token is stored in `node_modules`, your token will be removed on each `npm install` of `restful-react`.

1. You're done! 🎉

#### Transforming an Original Spec

In some cases, you might need to augment an existing OpenAPI specification on the fly, for code-generation purposes. Our CLI makes this quite easy:

```bash
restful-react import --file myspec.yaml --output mybettercomponents.tsx --transformer path/to/my-transformer.js
```

The function specified in `--transformer` is pure: it imports your `--file`, transforms it, and passes the augmented OpenAPI specification to Restful React's generator. Here's how it can be used:

```ts
// /path/to/my-transformer.js

/**
* Transformer function for restful-react.
*
* @param {OpenAPIObject} schema
* @return {OpenAPIObject}
*/
module.exports = inputSchema => ({
...inputSchema,
// Place your augmentations here
paths: Object.entries(schema.paths).reduce(
(mem, [path, pathItem]) => ({
...mem,
[path]: Object.entries(pathItem).reduce(
(pathItemMem, [verb, operation]) => ({
...pathItemMem,
[verb]: {
...fixOperationId(path, verb, operation),
},
}),
{},
),
}),
{},
),
});
```

### Caching

Expand Down
Binary file added assets/idp.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 31 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"files": [
"lib"
],
"bin": {
"restful-react": "lib/bin/restful-react.js"
},
"keywords": [
"rest",
"restful",
Expand All @@ -24,7 +27,13 @@
"email": "tejas@tejas.qa",
"url": "https://twitter.com/tejaskumar_"
},
"contributors": [],
"contributors": [
{
"name": "Fabien Bernard",
"email": "fabien@contiamo.com",
"url": "https://fabien0102.com/en"
}
],
"repository": {
"type": "git",
"url": "https://github.com/contiamo/restful-react"
Expand All @@ -33,19 +42,25 @@
"start": "operational-scripts start",
"example": "parcel example/index.html",
"test": "operational-scripts test",
"build": "operational-scripts build --for npm",
"build": "operational-scripts build --for npm && rollup -c rollup.config.js",
"preversion": "npm run build",
"version": "auto-changelog -p && git add CHANGELOG.md",
"lint": "tslint src/**/*{ts,tsx} --project .",
"prepublishOnly": "operational-scripts prepare",
"prepublishOnly": "operational-scripts prepare && rollup -c rollup.config.js",
"ci": "[ ! -z $DANGER_GITHUB_API_TOKEN ] && yarn danger ci || echo \"Skipping Danger for External Contributor\""
},
"devDependencies": {
"@operational/scripts": "^1.1.2",
"@operational/scripts": "^1.2.0",
"@types/commander": "^2.12.2",
"@types/inquirer": "0.0.43",
"@types/jest": "^23.3.1",
"@types/lodash": "^4.14.116",
"@types/nock": "^9.3.0",
"@types/node": "^10.5.7",
"@types/qs": "^6.5.1",
"@types/react": "^16.4.1",
"@types/request": "^2.48.1",
"@types/yamljs": "^0.2.30",
"auto-changelog": "^1.8.0",
"danger": "^4.0.1",
"doctoc": "^1.3.1",
Expand All @@ -59,17 +74,27 @@
"prettier": "^1.13.5",
"react-dom": "^16.4.2",
"react-testing-library": "^5.0.0",
"request": "^2.88.0",
TejasQ marked this conversation as resolved.
Show resolved Hide resolved
"rollup": "^0.67.3",
"rollup-plugin-typescript2": "^0.16.1",
"ts-jest": "^23.1.4",
"tslint": "^5.10.0",
"tslint-config-prettier": "^1.13.0",
"tslint-plugin-blank-line": "^0.0.9",
"typescript": "^3.0.3"
},
"dependencies": {
"lodash": "^4.17.11",
"case": "^1.5.5",
"commander": "^2.17.1",
"inquirer": "^6.2.0",
"lodash": "^4.17.10",
"openapi3-ts": "^0.12.0",
TejasQ marked this conversation as resolved.
Show resolved Hide resolved
"qs": "^6.6.0",
"react": "^16.4.1",
"react-fast-compare": "^2.0.1",
"url": "^0.11.0"
"swagger2openapi": "^3.2.14",
"url": "^0.11.0",
"yamljs": "^0.3.0"
},
"peerDependencies": {
"react": "^16.4.1"
Expand Down
15 changes: 15 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { readdirSync } = require("fs");

/**
* Rollup configuration to build correctly our scripts (nodejs scripts need to be cjs)
*/
module.exports = readdirSync("lib/bin")
TejasQ marked this conversation as resolved.
Show resolved Hide resolved
.filter(file => file.endsWith(".js"))
.map(file => ({
input: `lib/bin/${file}`,
output: {
file: `lib/bin/${file}`,
format: "cjs",
banner: "#!/usr/bin/env node",
},
}));
Loading