Skip to content

Commit efc5c22

Browse files
committed
Add enum parameterized routes to Strontium
1 parent 703de80 commit efc5c22

File tree

4 files changed

+233
-10
lines changed

4 files changed

+233
-10
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "strontium",
3-
"version": "2.7.11",
3+
"version": "2.8.0",
44
"description": "Strontium is a TypeScript toolkit for High Performance API servers built for Production not Projects.",
55
"main": "lib/src/index.js",
66
"types": "lib/src/index.d.ts",

src/http/abstract/RouterMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export type RouterMap = Array<{
55
method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE" | "OPTIONS"
66
route: string
77
endpointController: ConstructorOf<EndpointController>
8+
metadata?: { [key: string]: any }
89
}>

src/http/drivers/FastifyServer.ts

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,22 @@ export class FastifyServer implements Process {
2121
private port: number = 8080,
2222
private host: string = "127.0.0.1"
2323
) {
24-
for (let route of routes) {
24+
/*
25+
To handle limitations in Find My Way (Fastify's internal routing library)
26+
Strontium provides a preprocess format for routes to prevent certain conflicts.
27+
*/
28+
let processedRoutes = FastifyServer.preProcessRoutes(routes)
29+
30+
for (let route of processedRoutes) {
2531
switch (route.method) {
2632
case "GET":
2733
this.server.get(
2834
route.route,
2935
this.requestHandler(
3036
route.endpointController,
3137
route.route,
32-
"GET"
38+
"GET",
39+
route.metadata
3340
)
3441
)
3542
break
@@ -39,7 +46,8 @@ export class FastifyServer implements Process {
3946
this.requestHandler(
4047
route.endpointController,
4148
route.route,
42-
"POST"
49+
"POST",
50+
route.metadata
4351
)
4452
)
4553
break
@@ -49,7 +57,8 @@ export class FastifyServer implements Process {
4957
this.requestHandler(
5058
route.endpointController,
5159
route.route,
52-
"PATCH"
60+
"PATCH",
61+
route.metadata
5362
)
5463
)
5564
break
@@ -59,7 +68,8 @@ export class FastifyServer implements Process {
5968
this.requestHandler(
6069
route.endpointController,
6170
route.route,
62-
"PUT"
71+
"PUT",
72+
route.metadata
6373
)
6474
)
6575
break
@@ -69,7 +79,8 @@ export class FastifyServer implements Process {
6979
this.requestHandler(
7080
route.endpointController,
7181
route.route,
72-
"DELETE"
82+
"DELETE",
83+
route.metadata
7384
)
7485
)
7586
break
@@ -79,14 +90,100 @@ export class FastifyServer implements Process {
7990
this.requestHandler(
8091
route.endpointController,
8192
route.route,
82-
"OPTIONS"
93+
"OPTIONS",
94+
route.metadata
8395
)
8496
)
8597
break
8698
}
8799
}
88100
}
89101

102+
public static preProcessRoutes(routes: RouterMap): RouterMap {
103+
let processedRoutes: RouterMap = []
104+
for (let route of routes) {
105+
// Check if there are any enum param blocks
106+
let enumeratedBlocks = route.route.match(
107+
/{([a-zA-Z0-9_-]*)\|([a-zA-Z0-9_,-]*)}/g
108+
)
109+
110+
if (enumeratedBlocks === null) {
111+
processedRoutes.push(route)
112+
} else {
113+
const generatePermutations = (
114+
index: number
115+
): Array<Array<string>> => {
116+
// Typescript correctly identifies this function may run when enumeratedBlocks is null
117+
// however within this context that is not possible - so ! to overcome
118+
let currentBlock = enumeratedBlocks![index]
119+
120+
let childPermutations: Array<Array<string>> = []
121+
if (index < enumeratedBlocks!.length - 1) {
122+
childPermutations = generatePermutations(index + 1)
123+
}
124+
125+
let permutations: Array<Array<string>> = []
126+
127+
// Use a more direct method because the absence of "matchAll" in Node would make the RegEx method uglier
128+
let fieldContents = currentBlock
129+
.replace("{", "")
130+
.replace("}", "")
131+
132+
let [
133+
fieldName,
134+
serializedFieldValues,
135+
] = fieldContents.split("|")
136+
let fieldValues = serializedFieldValues.split(",")
137+
138+
for (let value of fieldValues) {
139+
if (childPermutations.length === 0) {
140+
permutations.push([value])
141+
} else {
142+
for (let childPermutation of childPermutations) {
143+
permutations.push([value, ...childPermutation])
144+
}
145+
}
146+
}
147+
148+
return permutations
149+
}
150+
151+
let routePermutations = generatePermutations(0)
152+
153+
for (let permutation of routePermutations) {
154+
let pathPermutation = route.route
155+
let parameters: { [key: string]: string } = {
156+
...(route.metadata || {}),
157+
}
158+
159+
for (let i = 0; i < enumeratedBlocks.length; i++) {
160+
let currentBlock = enumeratedBlocks[i]
161+
let fieldContents = currentBlock
162+
.replace("{", "")
163+
.replace("}", "")
164+
let [fieldName] = fieldContents.split("|")
165+
166+
parameters[fieldName] = permutation[i]
167+
168+
pathPermutation = pathPermutation.replace(
169+
currentBlock,
170+
permutation[i]
171+
)
172+
}
173+
174+
processedRoutes.push({
175+
endpointController: route.endpointController,
176+
method: route.method,
177+
route: pathPermutation,
178+
metadata: parameters,
179+
})
180+
}
181+
}
182+
}
183+
184+
return processedRoutes
185+
}
186+
90187
public isHealthy(): boolean {
91188
return this.isAlive
92189
}
@@ -147,7 +244,8 @@ export class FastifyServer implements Process {
147244
protected requestHandler(
148245
controller: ConstructorOf<EndpointController>,
149246
path: string,
150-
method: string
247+
method: string,
248+
routeMetadata: { [key: string]: any } = {}
151249
): (
152250
request: Fastify.FastifyRequest<any, any, any, any, any>,
153251
response: Fastify.FastifyReply<any>
@@ -197,7 +295,10 @@ export class FastifyServer implements Process {
197295
headers: request.headers,
198296
query: request.query,
199297
params: request.params,
200-
meta: this.getRequestMetadata(request),
298+
meta: {
299+
...routeMetadata,
300+
...this.getRequestMetadata(request),
301+
},
201302
})
202303

203304
rawResponse = await endpointController.handle(validatedInput)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { expect } from "chai"
2+
import { FastifyServer, RouterMap } from "../../../src/http"
3+
4+
describe("FastifyServer", () => {
5+
describe("Route Preprocessor", () => {
6+
it("should not affect normal route assignments", () => {
7+
let simpleRoutes: RouterMap = [
8+
{
9+
endpointController: {} as any, // Irrelevant to this test
10+
metadata: {
11+
hello: "world!",
12+
},
13+
route: "/v1/test",
14+
method: "GET",
15+
},
16+
]
17+
18+
let processedRoutes = FastifyServer.preProcessRoutes(simpleRoutes)
19+
20+
expect(processedRoutes).to.deep.eq(simpleRoutes)
21+
})
22+
23+
it("should rewrite enum parametrized routes to multiple fixed routes with metadata fields", () => {
24+
let complexRoutes: RouterMap = [
25+
{
26+
endpointController: {} as any, // Irrelevant to this test
27+
metadata: {
28+
hello: "world!",
29+
},
30+
route:
31+
"/v1/test/{enum_parameter|test,enum,values}/more-testing",
32+
method: "GET",
33+
},
34+
]
35+
36+
let processedRoutes = FastifyServer.preProcessRoutes(complexRoutes)
37+
38+
expect(processedRoutes).to.deep.eq([
39+
{
40+
endpointController: {} as any,
41+
metadata: {
42+
hello: "world!",
43+
enum_parameter: "test",
44+
},
45+
route: "/v1/test/test/more-testing",
46+
method: "GET",
47+
},
48+
{
49+
endpointController: {} as any,
50+
metadata: {
51+
hello: "world!",
52+
enum_parameter: "enum",
53+
},
54+
route: "/v1/test/enum/more-testing",
55+
method: "GET",
56+
},
57+
{
58+
endpointController: {} as any,
59+
metadata: {
60+
hello: "world!",
61+
enum_parameter: "values",
62+
},
63+
route: "/v1/test/values/more-testing",
64+
method: "GET",
65+
},
66+
])
67+
})
68+
69+
it("should handle rewriting nested enum parameters", () => {
70+
let complexRoutes: RouterMap = [
71+
{
72+
endpointController: {} as any, // Irrelevant to this test
73+
route:
74+
"/v1/test/{enum_parameter_1|a,b}/more-testing/:normal_param/{enum_parameter_2|x,y}",
75+
method: "GET",
76+
},
77+
]
78+
79+
let processedRoutes = FastifyServer.preProcessRoutes(complexRoutes)
80+
81+
expect(processedRoutes).to.deep.eq([
82+
{
83+
endpointController: {} as any,
84+
metadata: {
85+
enum_parameter_1: "a",
86+
enum_parameter_2: "x",
87+
},
88+
route: "/v1/test/a/more-testing/:normal_param/x",
89+
method: "GET",
90+
},
91+
{
92+
endpointController: {} as any,
93+
metadata: {
94+
enum_parameter_1: "a",
95+
enum_parameter_2: "y",
96+
},
97+
route: "/v1/test/a/more-testing/:normal_param/y",
98+
method: "GET",
99+
},
100+
{
101+
endpointController: {} as any,
102+
metadata: {
103+
enum_parameter_1: "b",
104+
enum_parameter_2: "x",
105+
},
106+
route: "/v1/test/b/more-testing/:normal_param/x",
107+
method: "GET",
108+
},
109+
{
110+
endpointController: {} as any,
111+
metadata: {
112+
enum_parameter_1: "b",
113+
enum_parameter_2: "y",
114+
},
115+
route: "/v1/test/b/more-testing/:normal_param/y",
116+
method: "GET",
117+
},
118+
])
119+
})
120+
})
121+
})

0 commit comments

Comments
 (0)