You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Add OpenAPI documentation
Changes:
- README.md - Update mention that java is needed both the Datastore emulator (existing dependency) and now openapi-generator-cli
- developer-documentation.md - Add section about adding a new API with example
- openapi-documentation.md - Add documentation about openapi. Maintenance expectations. Explanation of each additional property.
* address feedback from @jrobbins
* remove format field
* update docs with modular openapi files
* simplify paths file ref
* Update developer-documentation.md
* Remove redundant components
Copy file name to clipboardexpand all lines: README.md
+1-1
Original file line number
Diff line number
Diff line change
@@ -14,7 +14,7 @@ For a one-click setup that leverages devcontainers, check out the devcontainer
14
14
15
15
### Installation
16
16
1. Install gcloud and needed components:
17
-
1. Before you begin, make sure that you have a java JRE (version 8 or greater) installed. JRE is required to use the DataStore Emulator.
17
+
1. Before you begin, make sure that you have a java JRE (version 8 or greater) installed. JRE is required to use the DataStore Emulator and [openapi-generator-cli](https://github.com/OpenAPITools/openapi-generator-cli).
18
18
1.[Google App Engine SDK for Python](https://cloud.google.com/appengine/docs/standard/python3/setting-up-environment). Make sure to select Python 3.
Copy file name to clipboardexpand all lines: developer-documentation.md
+261
Original file line number
Diff line number
Diff line change
@@ -79,3 +79,264 @@ address of any Google account that you own, such as an `@gmail.com` account.
79
79
80
80
- When run locally, Datastore Emulator is used for storing all the entries. To reset local database, remove the local directory for storing data/config for the emulator. The default directory is `<USER_CONFIG_DIR>/emulators/datastore`. The value of `<USER_CONFIG_DIR>` can be found by running: `$ gcloud info --format='get(config.paths.global_config_dir)'` in the terminal. To learn more about using the Datastore Emulator CLI, execute `$ gcloud beta emulators datastore --help`.
81
81
- Executing `npm start` or `npm test` automatically starts the Datastore Emulator and shuts it down afterwards.
82
+
83
+
## Adding a new API
84
+
85
+
This section outlines the steps to consider when adding a new API.
86
+
87
+
Note: For all new APIs, please consider using [OpenAPI](https://www.openapis.org/).
88
+
With OpenAPI, developers can write a specification for their API and have code
89
+
generated for them on both the frontend and backend. This helps remove the
90
+
burden of manually writing data models and data encoding and decoding for both sides.
91
+
There is a tool installed as a devDependency called
*Before completing this step, read the [Paths and Operations](https://swagger.io/docs/specification/paths-and-operations/) and [Describing Parameters](https://swagger.io/docs/specification/describing-parameters/) OpenAPI docs*
107
+
108
+
#### Step 1a: Create a `.paths.yaml` file
109
+
110
+
- In the openapi directory, create a new file with all characters being lowercase. The prefix of the file should follow the same format as: `<noun _1>_<parameter name_1>`...`<noun_n>_<parameter_name_n>`. The suffix should be `.paths.yaml`.
111
+
- Example 1: /componentsusers -> File `componentusers.paths.yaml`
112
+
- Example 2: /components/{componentId} -> File `components_componentid.paths.yaml`
113
+
- Example 3: /components/{componentId}/users/{userId} -> File `components_componentid_users_userid.yaml`
114
+
115
+
116
+
#### Step 1b: Add Operations
117
+
118
+
Operations = HTTP verbs. (e.g. GET, POST, PUT, etc)
119
+
120
+
- Add the operation(s) under the path.
121
+
- Ensure each operation has a `summary`, `description` and `operationId`
122
+
- If your path has path parameters, describe the parameters now too.
123
+
- Mark required parameters with `required: true`.
124
+
125
+
<details>
126
+
<summary>Example (click to expand)</summary>
127
+
128
+
#### openapi/features_featureid.paths.yaml
129
+
```yaml
130
+
get:
131
+
summary: Get a feature by ID.
132
+
description: |
133
+
Get a feature by ID. More details about this here.
134
+
Also, can do more comments
135
+
operationId: getFeatureById
136
+
parameters:
137
+
- name: feature_id
138
+
in: path
139
+
description: Feature ID
140
+
required: true
141
+
schema:
142
+
type: integer
143
+
post:
144
+
summary: Update a feature by ID.
145
+
description: |
146
+
Update a feature with the given ID.
147
+
More details about this here.
148
+
operationId: updateFeatureById
149
+
parameters:
150
+
- name: feature_id
151
+
in: path
152
+
description: Feature ID
153
+
required: true
154
+
schema:
155
+
type: integer
156
+
```
157
+
</details>
158
+
159
+
160
+
161
+
### Step 2: Describe the request body
162
+
163
+
*Before completing this step, read the [Describing Request Body](https://swagger.io/docs/specification/describing-request-body/) OpenAPI doc*
164
+
165
+
*Skip this step if there is no request body*
166
+
167
+
#### Step 2a: Create a `.schemas.yaml` file
168
+
169
+
- In the openapi directory, create a new file with all characters being lowercase. The prefix of the file should follow the same format as: `<noun _1>_<parameter name_1>`...`<noun_n>_<parameter_name_n>`. The prefix should be the same as the prefix described in Step 1. The suffix should be `.schemas.yaml`
170
+
- Create a top level object and name it the appropriately.
171
+
- Describe the schema for of the object.
172
+
173
+
174
+
<details>
175
+
<summary>Example (click to expand)</summary>
176
+
177
+
#### openapi/features_featureid.schemas.yaml
178
+
```yaml
179
+
Feature:
180
+
description: A feature
181
+
type: object
182
+
properties:
183
+
id:
184
+
type: integer
185
+
name:
186
+
type: string
187
+
live:
188
+
type: boolean
189
+
description: Some optional field
190
+
required:
191
+
- id
192
+
- name
193
+
```
194
+
</details>
195
+
196
+
#### Step 2b: Use schema object in `.paths.yaml` file
197
+
198
+
- Add $ref under {HTTP verb}.requestBody.application/json.schema
199
+
- The value of the $ref should equal `{schemas file name}#/{Object name}`.
200
+
201
+
<details>
202
+
<summary>Example (click to expand)</summary>
203
+
204
+
#### openapi/features_featureid.paths.yaml
205
+
```yaml
206
+
...
207
+
post:
208
+
summary: Update a feature by ID.
209
+
description: |
210
+
Update a feature with the given ID.
211
+
More details about this here.
212
+
operationId: updateFeatureById
213
+
parameters:
214
+
- name: feature_id
215
+
in: path
216
+
description: Feature ID
217
+
required: true
218
+
schema:
219
+
type: integer
220
+
requestBody:
221
+
content:
222
+
application/json:
223
+
schema:
224
+
$ref: 'features_featureid.schemas.yaml#/Feature'
225
+
```
226
+
</details>
227
+
228
+
*For this example, only needed to describe a request body for the `post` operation.*
229
+
230
+
### Step 3: Describe the Responses
231
+
232
+
*Before completing this step, read the [Describing Responses](https://swagger.io/docs/specification/describing-request-body/) OpenAPI doc*
233
+
234
+
*Skip this step if there is no response body*
235
+
236
+
#### Step 3a: Create a `.schemas.yaml` file
237
+
238
+
- If Step 2a was skipped, go back and complete it.
239
+
- Describe the response object
240
+
241
+
#### Step 3b: Use schema object in `.paths.yaml` file
242
+
243
+
- Add the appropriate response code(s)
244
+
- Don't worry about describing global errors like unauthorized calls right now.
245
+
- For each response code, reference the schemas file. The value of the $ref should equal `{schemas file name}#/{Object name}`.
246
+
247
+
<details>
248
+
<summary>Example (click to expand)</summary>
249
+
250
+
#### openapi/features_featureid.paths.yaml
251
+
```yaml
252
+
...
253
+
post:
254
+
summary: Update a feature by ID.
255
+
description: |
256
+
Update a feature with the given ID.
257
+
More details about this here.
258
+
operationId: updateFeatureById
259
+
parameters:
260
+
- name: feature_id
261
+
in: path
262
+
description: Feature ID
263
+
required: true
264
+
schema:
265
+
type: integer
266
+
requestBody:
267
+
content:
268
+
application/json:
269
+
schema:
270
+
$ref: 'features_featureid.schemas.yaml#/Feature'
271
+
responses:
272
+
'200':
273
+
description: An updated feature
274
+
content:
275
+
application/json:
276
+
schema:
277
+
$ref: 'features_featureid.schemas.yaml#/Feature'
278
+
```
279
+
</details>
280
+
281
+
### Step 4: Add the path to the high level openapi/api.yaml
282
+
283
+
- Under paths, add the path. Under the path, add a $ref to the `.paths.yaml` file. The value of the $ref should equal `{paths file name}`.
284
+
285
+
<details>
286
+
<summary>Example (click to expand)</summary>
287
+
288
+
#### openapi/api.yaml
289
+
```yaml
290
+
paths:
291
+
/features/{feature_id}:
292
+
$ref: 'features_featureid.paths.yaml'
293
+
```
294
+
</details>
295
+
296
+
297
+
### Step 5: Generate the Code
298
+
299
+
Validate that the linked schema objects are valid. There should be zero errors and zero warnings:
300
+
- `npm run openapi-validate`
301
+
302
+
Generate the code:
303
+
- `npm run openapi`
304
+
305
+
306
+
### Step 5: Incorporate Into Backend
307
+
308
+
Currently, the repository is configured to use the generated Python data models for the backend. *Once all routes are generated by OpenAPI, it would be wise to revisit using the controllers as well*
309
+
310
+
- Open `main.py`
311
+
- Locate the `api_routes` variable.
312
+
- Add a route.
313
+
- In this example, it would be `Route(f'{API_BASE}/features/<int:feature_id>', features_api.FeaturesAPI)`.
314
+
- In the handler, the generated model classes can be imported from `chromestatus_openapi.models`.
315
+
- Since we do not use the controllers, you will need to return a dictionary of the model class. Then, Flask can convert it appropriately to json. Each generated class has a `to_dict()` method to accomplish this.
316
+
317
+
### Step 6: Incorporate Into Frontend
318
+
319
+
The frontend use @lit-labs/context to pass the client around. The benefits of it can be seen [here](https://lit.dev/docs/data/context/) and the advertised use cases [here](https://lit.dev/docs/data/context/#example-use-cases).
320
+
321
+
Your element needs to use a context consumer to retrieve the client that is provided by `chromedash-app`. Once you have the client, you can make an API call like normal.
322
+
323
+
```js
324
+
import {ContextConsumer} from '@lit-labs/context';
325
+
import {chromestatusOpenApiContext} from '../contexts/openapi-context';
326
+
export class SomeElement extends LitElement {
327
+
// Nice to have type hinting so that the IDE can auto complete the client and its functions.
This tool is a Node wrapper around the Java JAR [openapi-generator](https://github.com/OpenAPITools/openapi-generator). It will download the correct version of the pinned JAR automatically and acts a proxy between the JAR and the user's commands when invoking this tool.
10
+
11
+
### Maintenance
12
+
13
+
#### Node Wrapper
14
+
15
+
The Node wrapper is pinned in package.json under the devDependencies. For the latest version, visit the [npmjs website for it](https://www.npmjs.com/package/@openapitools/openapi-generator-cli).
16
+
17
+
NPM and Dependabot will likely keep this up to date.
18
+
19
+
#### The JAR
20
+
21
+
The JAR version is maintained by openapitools.json. For the latest version, check the [GitHub Releases](https://github.com/OpenAPITools/openapi-generator) for the JAR.
22
+
23
+
:warning: Unlike, the node wrapper, there is no process to keep it updated. Routinely, maintainers will need to update this pin.
24
+
25
+
## Generated Packages
26
+
27
+
All the generated packages are stored in the `gen/` folder. Nothing should manually change the code after generation.
28
+
29
+
Developers should run `npm run openapi` to generate both the frontend and backend.
30
+
31
+
### Backend
32
+
33
+
`npm run openapi-backend` command does the following:
34
+
- Cleans the folder
35
+
- Uses the [python-flask](https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/python-flask.md) generator. Current additional properties are:
36
+
-`packageName=chromestatus_openapi` - We generate a complete python package and this is the specified name.
37
+
- Run `pip install` again. We treat this as an independent package and it is specified in the `requirements.txt` file.
38
+
39
+
Other notes: The generator allows you to specify generating the `models` only. But for the `python-flask` generator, it will generate an incomplete setup.py in that mode. If the setup.py is incomplete, pip install will fail. As a result, we generate everything. **A future optimization would be to exclude the controller file changes via .gitignore.** Once all routes are generated by OpenAPI, it would be wise to revisit using the controllers as well.
40
+
41
+
### Frontend
42
+
43
+
`npm run openapi-frontend` command does the following:
44
+
- Cleans the folder
45
+
- Uses the [typescript-fetch](https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/typescript-fetch.md) generator. Current additional properties are:
46
+
-`npmName=chromestatus-openapi` - We generate a complete NPM package and this is the specified name. (That package is written in typescript and if we wanted to not use it as an independenct package, we would need typescript ourselves)
47
+
-`withInterfaces=true` - Generate interfaces. Useful for when we migrate to typescript.
48
+
-`supportsES6=true` - By default, it generates CommonJS ES5. That client interferes with the test framework. As a result, generate the newer es6 version.
49
+
- Run `npm install` again. We treat this as an independent package and it is specified in the `package.json` file.
0 commit comments