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

Commit

Permalink
Merge pull request #28 from contiamo/open-api-import
Browse files Browse the repository at this point in the history
Open api import
  • Loading branch information
Tejas Kumar authored Dec 7, 2018
2 parents 8a6ffd7 + a9dc566 commit 57c9b14
Show file tree
Hide file tree
Showing 19 changed files with 6,335 additions and 1,778 deletions.
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)

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",
"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",
"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")
.filter(file => file.endsWith(".js"))
.map(file => ({
input: `lib/bin/${file}`,
output: {
file: `lib/bin/${file}`,
format: "cjs",
banner: "#!/usr/bin/env node",
},
}));
Loading

0 comments on commit 57c9b14

Please sign in to comment.