Skip to content

Commit 4d21e38

Browse files
Merge pull request #54 from bitgopatmcl/consistent-path-parameters
fix: use consistent format for path parameters between server and client
2 parents 5467fb4 + 35e9ed8 commit 4d21e38

File tree

4 files changed

+79
-1
lines changed

4 files changed

+79
-1
lines changed

packages/express-wrapper/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
ResponseType,
1515
} from '@api-ts/io-ts-http';
1616

17+
import { apiTsPathToExpress } from './path';
18+
1719
export type Function<R extends HttpRoute> = (
1820
input: RequestType<R>,
1921
) => ResponseType<R> | Promise<ResponseType<R>>;
@@ -141,7 +143,8 @@ export function createServer<Spec extends ApiSpec>(
141143
);
142144
const handlers = [...stack.slice(0, stack.length - 1), handler];
143145

144-
router[method](httpRoute.path, handlers);
146+
const expressPath = apiTsPathToExpress(httpRoute.path);
147+
router[method](expressPath, handlers);
145148
}
146149
}
147150

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Converts an io-ts-http path to an express one
2+
// assumes that only simple path parameters are present and the wildcard features in express
3+
// arent used.
4+
5+
const PATH_PARAM = /{(\w+)}/g;
6+
7+
export const apiTsPathToExpress = (inputPath: string) =>
8+
inputPath.replace(PATH_PARAM, ':$1');
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import test from 'ava';
2+
3+
import { apiTsPathToExpress } from '../src/path';
4+
5+
test('should pass through paths with no parameters', (t) => {
6+
const input = '/foo/bar';
7+
const output = apiTsPathToExpress(input);
8+
t.deepEqual(output, input);
9+
});
10+
11+
test('should translate a path segment that specifies a parameter', (t) => {
12+
const input = '/foo/{bar}';
13+
const output = apiTsPathToExpress(input);
14+
t.deepEqual(output, '/foo/:bar');
15+
});
16+
17+
test('should translate multiple path segments', (t) => {
18+
const input = '/foo/{bar}/baz/{id}';
19+
const output = apiTsPathToExpress(input);
20+
t.deepEqual(output, '/foo/:bar/baz/:id');
21+
});

packages/express-wrapper/test/test-server.ts renamed to packages/express-wrapper/test/server.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,25 @@ const PutHello = httpRoute({
3939
});
4040
type PutHello = typeof PutHello;
4141

42+
const GetHello = httpRoute({
43+
path: '/hello/{id}',
44+
method: 'GET',
45+
request: httpRequest({
46+
params: {
47+
id: t.string,
48+
},
49+
}),
50+
response: {
51+
ok: t.type({
52+
id: t.string,
53+
}),
54+
},
55+
});
56+
4257
const ApiSpec = apiSpec({
4358
'hello.world': {
4459
put: PutHello,
60+
get: GetHello,
4561
},
4662
});
4763

@@ -76,6 +92,8 @@ const CreateHelloWorld = async (parameters: {
7692
});
7793
};
7894

95+
const GetHelloWorld = async (params: { id: string }) => Response.ok(params);
96+
7997
test('should offer a delightful developer experience', async (t) => {
8098
const app = createServer(ApiSpec, (app: express.Application) => {
8199
// Configure app-level middleware
@@ -84,6 +102,7 @@ test('should offer a delightful developer experience', async (t) => {
84102
return {
85103
'hello.world': {
86104
put: [routeMiddleware, CreateHelloWorld],
105+
get: [GetHelloWorld],
87106
},
88107
};
89108
});
@@ -105,6 +124,29 @@ test('should offer a delightful developer experience', async (t) => {
105124
t.like(response, { message: "Who's there?" });
106125
});
107126

127+
test('should handle io-ts-http formatted path parameters', async (t) => {
128+
const app = createServer(ApiSpec, (app: express.Application) => {
129+
app.use(express.json());
130+
app.use(appMiddleware);
131+
return {
132+
'hello.world': {
133+
put: [routeMiddleware, CreateHelloWorld],
134+
get: [GetHelloWorld],
135+
},
136+
};
137+
});
138+
139+
const server = supertest(app);
140+
const apiClient = buildApiClient(supertestRequestFactory(server), ApiSpec);
141+
142+
const response = await apiClient['hello.world']
143+
.get({ id: '1337' })
144+
.decodeExpecting(200)
145+
.then((res) => res.body);
146+
147+
t.like(response, { id: '1337' });
148+
});
149+
108150
test('should invoke app-level middleware', async (t) => {
109151
const app = createServer(ApiSpec, (app: express.Application) => {
110152
// Configure app-level middleware
@@ -113,6 +155,7 @@ test('should invoke app-level middleware', async (t) => {
113155
return {
114156
'hello.world': {
115157
put: [CreateHelloWorld],
158+
get: [GetHelloWorld],
116159
},
117160
};
118161
});
@@ -135,6 +178,7 @@ test('should invoke route-level middleware', async (t) => {
135178
return {
136179
'hello.world': {
137180
put: [routeMiddleware, CreateHelloWorld],
181+
get: [GetHelloWorld],
138182
},
139183
};
140184
});
@@ -157,6 +201,7 @@ test('should infer status code from response type', async (t) => {
157201
return {
158202
'hello.world': {
159203
put: [CreateHelloWorld],
204+
get: [GetHelloWorld],
160205
},
161206
};
162207
});
@@ -179,6 +224,7 @@ test('should return a 400 when request fails to decode', async (t) => {
179224
return {
180225
'hello.world': {
181226
put: [CreateHelloWorld],
227+
get: [GetHelloWorld],
182228
},
183229
};
184230
});

0 commit comments

Comments
 (0)