Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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