Skip to content

Commit ec171e6

Browse files
committed
v1.0.1
- Fix: cannot parameterise /1/2<- when /1/2/... exists - Removed some stray comments - Path parameters are always required - Readme additions - Setup releases
1 parent 9873fb0 commit ec171e6

10 files changed

Lines changed: 51 additions & 29 deletions

File tree

README.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<a name="readme-top"></a>
22

33
[![MIT License][license-shield]][license-url]
4+
[![Download in the Chrome Web Store][chrome-shield]][chrome-url]
45

56
<!-- PROJECT LOGO -->
67
<br />
@@ -10,7 +11,7 @@
1011
</a>
1112

1213

13-
<p align="center">
14+
<p align="center" style="max-width: 600px;">
1415
Effortlessly discover API behaviour with a Chrome extension that automatically generates OpenAPI specifications in real time for any app or website.
1516
<br />
1617
<br />
@@ -42,11 +43,14 @@ OpenAPI DevTools is a Chrome extension that generates OpenAPI specifications in
4243
<img width="80%" src="resources/demo-img.png">
4344
</p>
4445

45-
- [Download and extract the zip](https://github.com/AndrewWalsh/openapi-devtools/raw/main/resources/dist.zip)
46-
- In Chrome, navigate to `chrome://extensions`
47-
- In the top right enable the `Developer mode` toggle
48-
- In the top left click `Load unpacked` and select the extracted `dist` directory
49-
- Open a new tab and then select `OpenAPI` in the developer tools (open with `cmd+i` or `ctrl+i`)
46+
[Download the extension in the Chrome Web Store](https://chrome.google.com/webstore/detail/openapi-devtools/jelghndoknklgabjgaeppjhommkkmdii).
47+
48+
Otherwise, to install manually:
49+
- [Download and extract the dist.zip file in the latest release](https://github.com/AndrewWalsh/openapi-devtools/releases/latest/download/dist.zip)
50+
- In Chrome, navigate to `chrome://extensions`
51+
- In the top right enable the `Developer mode` toggle
52+
- In the top left click `Load unpacked` and select the extracted `dist` directory
53+
- Open a new tab and then select `OpenAPI` in the developer tools (open with `cmd+i` or `ctrl+i`)
5054

5155
<p align="right">(<a href="#readme-top">back to top</a>)</p>
5256

@@ -58,6 +62,12 @@ When the same endpoint responds with different data, such as a value that is som
5862

5963
<p align="right">(<a href="#readme-top">back to top</a>)</p>
6064

65+
## What is OpenAPI?
66+
67+
An [OpenAPI](https://www.openapis.org/) specification is a description of what an API expects to receive and what it will respond with. It is governed by the OpenAPI Initiative and the Linux Foundation. OpenAPI specifications are the modern standard for RESTful APIs, and systems that have them are far easier to work with.
68+
69+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
70+
6171
## Contributing
6272

6373
To develop the project:
@@ -66,12 +76,14 @@ To develop the project:
6676
- Navigate to `chrome://extensions`
6777
- In the top right enable the `Developer mode` toggle
6878
- In the top left click `Load unpacked` and select the extracted `dist` directory
69-
- You should now see the tool in Chrome DevTools. You can interact it with like a regular page, including inspection of the React app.
79+
- You should now see the tool in Chrome DevTools. You can interact it with like a regular page, including inspection of the React app
7080
- [Extensions Reloader](https://chrome.google.com/webstore/detail/extensions-reloader/fimgfedafeadlieiabdeeaodndnlbhid) is suggested to update the tool after running `npm run build` and updating the `dist` directory
7181

7282
<p align="right">(<a href="#readme-top">back to top</a>)</p>
7383

7484
<!-- MARKDOWN LINKS & IMAGES -->
7585
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
7686
[license-url]: https://github.com/AndrewWalsh/openapi-devtools/blob/main/LICENSE.txt
77-
[license-shield]: https://img.shields.io/github/license/othneildrew/Best-README-Template.svg?style=for-the-badge
87+
[license-shield]: https://img.shields.io/github/license/AndrewWalsh/openapi-devtools.svg?style=for-the-badge
88+
[chrome-url]: https://chrome.google.com/webstore/detail/openapi-devtools/jelghndoknklgabjgaeppjhommkkmdii
89+
[chrome-shield]: https://img.shields.io/badge/Google%20Chrome-4285F4?style=for-the-badge&logo=GoogleChrome&logoColor=white

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "openapi-devtools",
4-
"version": "1.0.0",
4+
"version": "1.0.1",
55
"devtools_page": "index.html",
66
"permissions": [],
77
"icons": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "openapi-devtools",
33
"description": "Effortlessly discover API behaviour with a Chrome extension that automatically generates OpenAPI specifications in real time for any app or website.",
44
"private": true,
5-
"version": "0.1.0",
5+
"version": "1.0.0",
66
"type": "module",
77
"scripts": {
88
"test": "vitest",

resources/dist.zip

-209 Bytes
Binary file not shown.

src/lib/RequestStore.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,22 @@ it("collapses into a single route when paramaterised", () => {
120120
expect(store.leafMap).toEqual(expected);
121121
expect(getResBodyTypes(store, host, "/1/2/3/ANY/ANY")).toEqual(["null", "integer", "string"]);
122122
});
123+
124+
it("can parameterise paths that are subsets of another path", () => {
125+
const store = new RequestStore();
126+
const req1 = createSimpleRequest(`${base}/1/2/a`);
127+
const req2 = createSimpleRequest(`${base}/1/2`);
128+
store.insert(req1, { foo: "bar" });
129+
store.insert(req2, { foo: 1 });
130+
store.parameterise(1, "/1/2", host);
131+
const expected = {
132+
[host]: {
133+
'/1/2/a': expect.any(Object),
134+
'/1/:param1': expect.any(Object),
135+
}
136+
};
137+
// @ts-expect-error accessing private property
138+
expect(store.leafMap).toEqual(expected);
139+
expect(getResBodyTypes(store, host, "/1/2/a")).toBe("string");
140+
expect(getResBodyTypes(store, host, "/1/ANY")).toBe("integer");
141+
});

src/lib/RequestStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ export default class RequestStore {
6565
const result = parameterise({ store: this.store, index, path, host });
6666
if (!result) return;
6767
const { removedPaths, insertedPath, insertedLeaf } = result;
68-
const unsetPath = (path: string) => unset(this.leafMap[host], path);
69-
removedPaths.concat([path]).forEach(unsetPath);
68+
const unsetLeafMap = (path: string) => unset(this.leafMap[host], path);
69+
removedPaths.concat([path]).forEach(unsetLeafMap);
7070
insertLeafMap({
7171
leafMap: this.leafMap,
7272
host,

src/lib/endpoints-to-oai31.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const createPathParameterTypes = (
9494
const parameters: ParameterObject[] = dynamicParts.map(({ part: name }) => ({
9595
name,
9696
in: "path",
97-
required: false,
97+
required: true,
9898
schema: {
9999
type: "string",
100100
},
@@ -178,9 +178,6 @@ const endpointsToOAI31 = (endpoints: Array<Endpoint>): OpenApiBuilder => {
178178
}
179179
}
180180

181-
for (const host in uniqueHosts) {
182-
builder.addServer({ url: host });
183-
}
184181
return builder;
185182
};
186183

src/lib/store-helpers/find-pathnames-in-router.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const formatPathname = (parts: Array<string>) => `/${parts.join("/").slice(1)}`;
1111

1212
/**
1313
* Walk the tree. Return string[][], each element of which is path
14-
* E.g. [["api", "v1", "user"], ["api", "v1", "user"]]
14+
* E.g. [["api", "v1", "users"], ["api", "v1", "posts"]]
1515
*/
1616
const recurse = (
1717
node: RadixNode<RouteData>,
@@ -29,14 +29,12 @@ const recurse = (
2929
// If it's also the last part, include leaf children
3030
// Otherwise, recurse on all children
3131
if (isLast) {
32-
for (const [part, child] of node.children.entries()) {
33-
if (!child.children.size) {
34-
const value = {
35-
pathname: formatPathname([...walked, part]),
36-
leaf: child.data!.data as Leaf,
37-
};
38-
pathnames.push(value);
39-
}
32+
for (const [lastPart, child] of node.children.entries()) {
33+
const value = {
34+
pathname: formatPathname([...walked, lastPart]),
35+
leaf: child.data!.data as Leaf,
36+
};
37+
pathnames.push(value);
4038
}
4139
} else {
4240
for (const [lastPart, child] of node.children.entries()) {

src/lib/store-helpers/helpers.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ export const getParameterisedPath = (
1616
) => {
1717
if (!matchedRoute?.params) return path;
1818
const nextPath = pathToArray(path);
19-
// Rely on the face that params are named "something{position}" e.g. param0, param1, ...
2019
const paramNames = Object.keys(matchedRoute.params);
2120
for (const paramName of paramNames) {
22-
// Must align with constants.DEFAULT_PARAM_NAME
2321
const position = Number(paramName.replace(paramRe, "$1"));
2422
nextPath[position] = `:${paramName}`;
2523
}

src/lib/store-helpers/prune-router.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ const pruneRouter = (
1414
const isLast = parts.length === 1;
1515
const part = parts[0];
1616
const matchAny = isParameter(part);
17-
// Not a wildcard, and no matching children
1817
if (!matchAny && !node.children.has(part)) return;
19-
// Recurse first
2018
if (!isLast) {
2119
if (matchAny) {
2220
for (const child of node.children.values()) {

0 commit comments

Comments
 (0)