Skip to content

Commit dd47463

Browse files
committed
feat: rules-based automatic parameterisation from pathnames
There are multiple ways to automatically parameterise pathnames. This is one approach, and generally the fastest, a set of functions each accept the pathname of an incoming request and determine which parts are parameters. At present, only UUIDs are identified. Other approaches might include custom regex strings, or custom function logic. - Ability to automatically identify path parameters from a set of rules - Designed to be used in conjunction with more computationally expensive methods - Rules are functions. New functions can be added to identify path parameters from string patterns - The only current rule is that UUIDs in pathnames will be parameterised
1 parent 8609dbb commit dd47463

File tree

10 files changed

+88
-8
lines changed

10 files changed

+88
-8
lines changed

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.5.1",
4+
"version": "1.5.2",
55
"devtools_page": "index.html",
66
"permissions": [],
77
"icons": {

package-lock.json

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"test": "vitest",
99
"dev": "tsc && vite build && web-ext run -s dist",
1010
"build": "tsc && vite build && web-ext build --overwrite-dest -s dist",
11-
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix"
11+
"lint": "eslint --ext ts,tsx --fix ."
1212
},
1313
"dependencies": {
1414
"@chakra-ui/icons": "^2.1.1",
@@ -33,7 +33,8 @@
3333
"react-window": "^1.8.9",
3434
"redoc": "^2.1.3",
3535
"store2": "^2.14.2",
36-
"truncate-middle": "^1.0.6"
36+
"truncate-middle": "^1.0.6",
37+
"validator": "^13.11.0"
3738
},
3839
"devDependencies": {
3940
"@crxjs/vite-plugin": "^2.0.0-beta.19",
@@ -47,6 +48,7 @@
4748
"@types/react-dom": "^18.2.13",
4849
"@types/react-window": "^1.8.7",
4950
"@types/truncate-middle": "^1.0.3",
51+
"@types/validator": "^13.11.9",
5052
"@typescript-eslint/eslint-plugin": "^6.8.0",
5153
"@vitejs/plugin-react": "^4.1.0",
5254
"eslint": "^8.51.0",

src/lib/RequestStore.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,21 @@ it("parameterisation works after export and import", () => {
250250
// @ts-expect-error accessing private property
251251
expect(store.storeOptions).toEqual(expectedOptions);
252252
});
253+
254+
it("automatically parameterises pathnames containing known path parameters such as UUIDs", () => {
255+
const store = new RequestStore();
256+
const req = createSimpleRequest(`${base}/1/2/a`);
257+
const uuid = "7be85ff3-2785-45d4-81d0-b8b58f80dfdc";
258+
const host = "http://test.com";
259+
const pathname = `/v1/${uuid}/sites/${uuid}`;
260+
const url = `${host}${pathname}`;
261+
req.request.url = url;
262+
store.insert(req, contentIntTest);
263+
// The path parameters are the UUIDs in this example
264+
const splitUrl = pathname.split("/").slice(1);
265+
splitUrl[1] = ":param1";
266+
splitUrl[3] = ":param3";
267+
const expectUrl = `/${splitUrl.join("/")}`;
268+
// @ts-expect-error accessing private property
269+
expect(store.leafMap["test.com"]).toHaveProperty(expectUrl);
270+
});

src/lib/RequestStore.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
upsert,
66
persistOptions,
77
leafMapToRouterMap,
8+
fastPathParameterIndices,
89
} from "./store-helpers";
910
import { omit, unset } from "lodash";
1011
import leafMapToEndpoints from "./leafmap-to-endpoints";
@@ -103,12 +104,21 @@ export default class RequestStore {
103104
leaf: insertedLeaf,
104105
path: insertedPath,
105106
});
107+
let pathname = insertedPath;
108+
for (const idx of fastPathParameterIndices(pathname)) {
109+
const newPathname = this.parameterise(idx, pathname, insertedHost);
110+
if (newPathname) pathname = newPathname;
111+
}
106112
return true;
107113
}
108114

109-
public parameterise(index: number, path: string, host: string): void {
115+
public parameterise(
116+
index: number,
117+
path: string,
118+
host: string
119+
): string | null {
110120
const result = parameterise({ store: this.store, index, path, host });
111-
if (!result) return;
121+
if (!result) return null;
112122
const { removedPaths, insertedPath, insertedLeaf } = result;
113123
const unsetLeafMap = (path: string) => unset(this.leafMap[host], path);
114124
removedPaths.concat([path]).forEach(unsetLeafMap);
@@ -118,6 +128,7 @@ export default class RequestStore {
118128
leaf: insertedLeaf,
119129
path: insertedPath,
120130
});
131+
return insertedPath;
121132
}
122133

123134
public setDisabledHosts(disabledHosts: Set<string>): void {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { it, expect } from "vitest";
2+
import { fastPathParameterIndices } from "./automatic-parameterisation";
3+
4+
// [what is being tested, pathname, indices that should be parameterised]
5+
it.each([["UUIDs", "/path/7be85ff3-2785-45d4-81d0-b8b58f80dfdc", [1]]])(
6+
"returns indices of path parameters that are %s in pathnames",
7+
(_, path, expected) => {
8+
const result = fastPathParameterIndices(path);
9+
expect(result).toEqual(expected);
10+
}
11+
);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import validator from "validator";
2+
3+
const validationRules = [validator.isUUID];
4+
5+
/**
6+
* Fast and low-effort rules-based parameterisation of pathnames alone
7+
* Designed to accompany a more computationally intensive analysis
8+
* Returns an array of indices of path parameters in a given pathname that matches a validation rule
9+
*/
10+
export const fastPathParameterIndices = (pathname: string): Array<number> => {
11+
const indices = [];
12+
const parts = pathname.split("/").slice(1);
13+
for (let idx = 0; idx < parts.length; idx++) {
14+
const part = parts[idx];
15+
if (validationRules.some((rule) => rule(part))) {
16+
indices.push(idx);
17+
}
18+
}
19+
return indices;
20+
};

src/lib/store-helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { default as determineAuthFromHAR } from "./authentication";
99
export { default as remove } from "./remove";
1010
export * from "./merge";
1111
export * from "./helpers";
12+
export * from "./automatic-parameterisation";

src/ui/Main.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ function Main() {
102102
(index, path, host) => {
103103
requestStore.parameterise(index, path, host);
104104
setSpecEndpoints();
105+
return null;
105106
},
106107
[]
107108
);

src/ui/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ const defaultContextValue: ContextType = {
2525
setAllHosts: () => {},
2626
disabledHosts: new Set(),
2727
setDisabledHosts: () => {},
28-
parameterise: () => {},
28+
parameterise: () => null,
2929
import: () => false,
30-
export: () => '',
30+
export: () => "",
3131
options: () => defaultOptions,
3232
};
3333

0 commit comments

Comments
 (0)