Skip to content

Commit

Permalink
Merge pull request #54 from bitgopatmcl/consistent-path-parameters
Browse files Browse the repository at this point in the history
fix: use consistent format for path parameters between server and client
  • Loading branch information
ericcrosson-bitgo authored Apr 26, 2022
2 parents 5467fb4 + 35e9ed8 commit 4d21e38
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 1 deletion.
5 changes: 4 additions & 1 deletion packages/express-wrapper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
ResponseType,
} from '@api-ts/io-ts-http';

import { apiTsPathToExpress } from './path';

export type Function<R extends HttpRoute> = (
input: RequestType<R>,
) => ResponseType<R> | Promise<ResponseType<R>>;
Expand Down Expand Up @@ -141,7 +143,8 @@ export function createServer<Spec extends ApiSpec>(
);
const handlers = [...stack.slice(0, stack.length - 1), handler];

router[method](httpRoute.path, handlers);
const expressPath = apiTsPathToExpress(httpRoute.path);
router[method](expressPath, handlers);
}
}

Expand Down
8 changes: 8 additions & 0 deletions packages/express-wrapper/src/path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Converts an io-ts-http path to an express one
// assumes that only simple path parameters are present and the wildcard features in express
// arent used.

const PATH_PARAM = /{(\w+)}/g;

export const apiTsPathToExpress = (inputPath: string) =>
inputPath.replace(PATH_PARAM, ':$1');
21 changes: 21 additions & 0 deletions packages/express-wrapper/test/path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import test from 'ava';

import { apiTsPathToExpress } from '../src/path';

test('should pass through paths with no parameters', (t) => {
const input = '/foo/bar';
const output = apiTsPathToExpress(input);
t.deepEqual(output, input);
});

test('should translate a path segment that specifies a parameter', (t) => {
const input = '/foo/{bar}';
const output = apiTsPathToExpress(input);
t.deepEqual(output, '/foo/:bar');
});

test('should translate multiple path segments', (t) => {
const input = '/foo/{bar}/baz/{id}';
const output = apiTsPathToExpress(input);
t.deepEqual(output, '/foo/:bar/baz/:id');
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,25 @@ const PutHello = httpRoute({
});
type PutHello = typeof PutHello;

const GetHello = httpRoute({
path: '/hello/{id}',
method: 'GET',
request: httpRequest({
params: {
id: t.string,
},
}),
response: {
ok: t.type({
id: t.string,
}),
},
});

const ApiSpec = apiSpec({
'hello.world': {
put: PutHello,
get: GetHello,
},
});

Expand Down Expand Up @@ -76,6 +92,8 @@ const CreateHelloWorld = async (parameters: {
});
};

const GetHelloWorld = async (params: { id: string }) => Response.ok(params);

test('should offer a delightful developer experience', async (t) => {
const app = createServer(ApiSpec, (app: express.Application) => {
// Configure app-level middleware
Expand All @@ -84,6 +102,7 @@ test('should offer a delightful developer experience', async (t) => {
return {
'hello.world': {
put: [routeMiddleware, CreateHelloWorld],
get: [GetHelloWorld],
},
};
});
Expand All @@ -105,6 +124,29 @@ test('should offer a delightful developer experience', async (t) => {
t.like(response, { message: "Who's there?" });
});

test('should handle io-ts-http formatted path parameters', async (t) => {
const app = createServer(ApiSpec, (app: express.Application) => {
app.use(express.json());
app.use(appMiddleware);
return {
'hello.world': {
put: [routeMiddleware, CreateHelloWorld],
get: [GetHelloWorld],
},
};
});

const server = supertest(app);
const apiClient = buildApiClient(supertestRequestFactory(server), ApiSpec);

const response = await apiClient['hello.world']
.get({ id: '1337' })
.decodeExpecting(200)
.then((res) => res.body);

t.like(response, { id: '1337' });
});

test('should invoke app-level middleware', async (t) => {
const app = createServer(ApiSpec, (app: express.Application) => {
// Configure app-level middleware
Expand All @@ -113,6 +155,7 @@ test('should invoke app-level middleware', async (t) => {
return {
'hello.world': {
put: [CreateHelloWorld],
get: [GetHelloWorld],
},
};
});
Expand All @@ -135,6 +178,7 @@ test('should invoke route-level middleware', async (t) => {
return {
'hello.world': {
put: [routeMiddleware, CreateHelloWorld],
get: [GetHelloWorld],
},
};
});
Expand All @@ -157,6 +201,7 @@ test('should infer status code from response type', async (t) => {
return {
'hello.world': {
put: [CreateHelloWorld],
get: [GetHelloWorld],
},
};
});
Expand All @@ -179,6 +224,7 @@ test('should return a 400 when request fails to decode', async (t) => {
return {
'hello.world': {
put: [CreateHelloWorld],
get: [GetHelloWorld],
},
};
});
Expand Down

0 comments on commit 4d21e38

Please sign in to comment.