Skip to content

Commit 5bc9d44

Browse files
authored
feat(transport-commons): add context.http.response (#2524)
1 parent 3c66997 commit 5bc9d44

File tree

9 files changed

+127
-51
lines changed

9 files changed

+127
-51
lines changed

package-lock.json

Lines changed: 20 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/express/src/rest.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@ const serviceMiddleware = (): RequestHandler => {
3636
const contextBase = createContext(service, method, { http: {} });
3737
res.hook = contextBase;
3838

39-
const context = await (service as any)[method](...args, contextBase);
39+
const context = await (service as any)[method](...args, contextBase);
4040
res.hook = context;
4141

42-
const result = http.getData(context);
43-
const statusCode = http.getStatusCode(context, result);
44-
45-
res.data = result;
46-
res.statusCode = statusCode;
42+
const response = http.getResponse(context);
43+
res.statusCode = response.status;
44+
res.set(response.headers);
45+
res.data = response.body;
4746

4847
return next();
4948
});

packages/express/test/rest.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ describe('@feathersjs/express/rest provider', () => {
196196

197197
app.service('hook-status').hooks({
198198
after (hook: HookContext) {
199-
hook.http.statusCode = 206;
199+
hook.http.status = 206;
200200
}
201201
});
202202

@@ -205,6 +205,25 @@ describe('@feathersjs/express/rest provider', () => {
205205
assert.strictEqual(res.status, 206);
206206
});
207207

208+
it('allows to set response headers in a hook', async () => {
209+
app.use('/hook-headers', {
210+
async get () {
211+
return {};
212+
}
213+
});
214+
215+
app.service('hook-headers').hooks({
216+
after (hook: HookContext) {
217+
hook.http.headers = { foo: 'first', bar: ['second', 'third'] };
218+
}
219+
});
220+
221+
const res = await axios.get<any>('http://localhost:4777/hook-headers/dishes');
222+
223+
assert.strictEqual(res.headers.foo, 'first');
224+
assert.strictEqual(res.headers.bar, 'second, third');
225+
});
226+
208227
it('sets the hook object in res.hook on error', async () => {
209228
const params = {
210229
route: {},

packages/feathers/src/declarations.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,17 @@ export interface Params {
256256

257257
export interface Http {
258258
/**
259-
* A writeable, optional property that allows to override the standard HTTP status
260-
* code that should be returned.
259+
* A writeable, optional property with status code override.
261260
*/
262-
statusCode?: number;
261+
status?: number;
262+
/**
263+
* A writeable, optional property with headers.
264+
*/
265+
headers?: { [key: string]: string | string[] };
266+
/**
267+
* A writeable, optional property with `Location` header's value.
268+
*/
269+
location?: string;
263270
}
264271

265272
export interface HookContext<A = Application, S = any> extends BaseHookContext<ServiceGenericType<S>> {
@@ -333,11 +340,11 @@ export interface HookContext<A = Application, S = any> extends BaseHookContext<S
333340
* A writeable, optional property that allows to override the standard HTTP status
334341
* code that should be returned.
335342
*
336-
* @deprecated Use `http.statusCode` instead.
343+
* @deprecated Use `http.status` instead.
337344
*/
338345
statusCode?: number;
339346
/**
340-
* A writeable, optional property that contains options specific to HTTP transports.
347+
* A writeable, optional property with options specific to HTTP transports.
341348
*/
342349
http?: Http;
343350
/**

packages/feathers/src/hooks/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ export function hookMixin<A> (
8282
event: null,
8383
type: null,
8484
get statusCode () {
85-
return this.http?.statusCode;
85+
return this.http?.status;
8686
},
8787
set statusCode (value: number) {
88-
(this.http ||= {}).statusCode = value;
88+
(this.http ||= {}).status = value;
8989
}
9090
});
9191

packages/koa/src/rest.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,13 @@ const serviceMiddleware = (): Middleware => {
3232
const contextBase = createContext(service, method, { http: {} });
3333
ctx.hook = contextBase;
3434

35-
const context = await (service as any)[method](...args, contextBase);
35+
const context = await (service as any)[method](...args, contextBase);
3636
ctx.hook = context;
3737

38-
const result = http.getData(context);
39-
const statusCode = http.getStatusCode(context, result);
40-
41-
ctx.body = result;
42-
ctx.status = statusCode;
38+
const response = http.getResponse(context);
39+
ctx.status = response.status;
40+
ctx.set(response.headers);
41+
ctx.body = response.body;
4342

4443
return next();
4544
};

packages/transport-commons/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,14 @@
5353
"*.js"
5454
],
5555
"dependencies": {
56-
"@feathersjs/commons": "^5.0.0-pre.17",
57-
"@feathersjs/errors": "^5.0.0-pre.17",
58-
"@feathersjs/feathers": "^5.0.0-pre.17",
56+
"@feathersjs/commons": "^5.0.0-pre.16",
57+
"@feathersjs/errors": "^5.0.0-pre.16",
58+
"@feathersjs/feathers": "^5.0.0-pre.16",
59+
"encodeurl": "^1.0.2",
5960
"lodash": "^4.17.21"
6061
},
6162
"devDependencies": {
63+
"@types/encodeurl": "^1.0.0",
6264
"@types/lodash": "^4.14.181",
6365
"@types/mocha": "^9.1.0",
6466
"@types/node": "^17.0.23",

packages/transport-commons/src/http.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { MethodNotAllowed } from '@feathersjs/errors/lib';
22
import { HookContext, NullableId, Params } from '@feathersjs/feathers';
3+
import encodeUrl from 'encodeurl';
34

45
export const METHOD_HEADER = 'x-service-method';
56

@@ -13,7 +14,8 @@ export const statusCodes = {
1314
created: 201,
1415
noContent: 204,
1516
methodNotAllowed: 405,
16-
success: 200
17+
success: 200,
18+
seeOther: 303
1719
};
1820

1921
export const knownMethods: { [key: string]: string } = {
@@ -25,7 +27,7 @@ export const knownMethods: { [key: string]: string } = {
2527

2628
export function getServiceMethod (_httpMethod: string, id: unknown, headerOverride?: string) {
2729
const httpMethod = _httpMethod.toLowerCase();
28-
30+
2931
if (httpMethod === 'post' && headerOverride) {
3032
return headerOverride;
3133
}
@@ -53,24 +55,41 @@ export const argumentsFor = {
5355
default: ({ data, params }: ServiceParams) => [ data, params ]
5456
}
5557

56-
export function getData (context: HookContext) {
57-
return context.dispatch !== undefined
58-
? context.dispatch
59-
: context.result;
60-
}
58+
export function getStatusCode (context: HookContext, body: any, location: string|string[]) {
59+
const { http = {} } = context;
6160

62-
export function getStatusCode (context: HookContext, data?: any) {
63-
if (context.http?.statusCode) {
64-
return context.http.statusCode;
61+
if (http.status) {
62+
return http.status;
6563
}
6664

6765
if (context.method === 'create') {
6866
return statusCodes.created;
6967
}
7068

71-
if (!data) {
69+
if (location !== undefined) {
70+
return statusCodes.seeOther;
71+
}
72+
73+
if (!body) {
7274
return statusCodes.noContent;
7375
}
7476

7577
return statusCodes.success;
7678
}
79+
80+
export function getResponse (context: HookContext) {
81+
const { http = {} } = context;
82+
const body = context.dispatch !== undefined ? context.dispatch : context.result;
83+
84+
let headers = http.headers || {};
85+
let location = headers.Location;
86+
87+
if (http.location !== undefined) {
88+
location = encodeUrl(http.location);
89+
headers = { ...headers, Location: location };
90+
}
91+
92+
const status = getStatusCode(context, body, location);
93+
94+
return { status, headers, body };
95+
}

packages/transport-commons/test/http.test.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { HookContext } from '@feathersjs/feathers';
33
import { http } from '../src';
44

55
describe('@feathersjs/transport-commons HTTP helpers', () => {
6-
it('getData', () => {
6+
it('getResponse body', () => {
77
const plainData = { message: 'hi' };
88
const dispatch = { message: 'from dispatch' };
99
const resultContext = {
@@ -13,22 +13,41 @@ describe('@feathersjs/transport-commons HTTP helpers', () => {
1313
dispatch
1414
};
1515

16-
assert.deepStrictEqual(http.getData(resultContext as HookContext), plainData);
17-
assert.deepStrictEqual(http.getData(dispatchContext as HookContext), dispatch);
16+
assert.strictEqual(http.getResponse(resultContext as HookContext).body, plainData);
17+
assert.strictEqual(http.getResponse(dispatchContext as HookContext).body, dispatch);
1818
});
1919

20-
it('getStatusCode', async () => {
20+
it('getResponse status', () => {
2121
const statusContext = {
22-
http: { statusCode: 202 }
22+
http: { status: 202 }
2323
};
2424
const createContext = {
2525
method: 'create'
2626
};
27+
const redirectContext = {
28+
http: { location: '/' }
29+
};
30+
31+
assert.strictEqual(http.getResponse(statusContext as HookContext).status, 202);
32+
assert.strictEqual(http.getResponse(createContext as HookContext).status, http.statusCodes.created);
33+
assert.strictEqual(http.getResponse(redirectContext as HookContext).status, http.statusCodes.seeOther);
34+
assert.strictEqual(http.getResponse({} as HookContext).status, http.statusCodes.noContent);
35+
assert.strictEqual(http.getResponse({result: true} as HookContext).status, http.statusCodes.success);
36+
});
37+
38+
it('getResponse headers', () => {
39+
const headers = { key: 'value' } as any;
40+
const headersContext = {
41+
http: { headers }
42+
};
43+
const locationContext = {
44+
http: { location: '/' }
45+
};
2746

28-
assert.strictEqual(http.getStatusCode(statusContext as HookContext, {}), 202);
29-
assert.strictEqual(http.getStatusCode(createContext as HookContext, {}), http.statusCodes.created);
30-
assert.strictEqual(http.getStatusCode({} as HookContext), http.statusCodes.noContent);
31-
assert.strictEqual(http.getStatusCode({} as HookContext, {}), http.statusCodes.success);
47+
assert.deepStrictEqual(http.getResponse({} as HookContext).headers, {});
48+
assert.deepStrictEqual(http.getResponse({http: {}} as HookContext).headers, {});
49+
assert.strictEqual(http.getResponse(headersContext as HookContext).headers, headers);
50+
assert.deepStrictEqual(http.getResponse(locationContext as HookContext).headers, { Location: '/' });
3251
});
3352

3453
it('getServiceMethod', () => {

0 commit comments

Comments
 (0)