refactor: Make request related handle calls consistent

This commit is contained in:
Joachim Van Herwegen 2021-01-11 11:32:15 +01:00
parent 995a2dc74d
commit f17054c647
12 changed files with 45 additions and 38 deletions

View File

@ -34,7 +34,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor {
if (!dpop) {
throw new BadRequestHttpError('No DPoP header specified.');
}
const resource = await this.targetExtractor.handleSafe(request);
const resource = await this.targetExtractor.handleSafe({ request });
try {
const { webid: webId } = await this.verify(

View File

@ -27,7 +27,7 @@ const parsers: {
* Supports Accept, Accept-Charset, Accept-Encoding, Accept-Language and Accept-DateTime.
*/
export class AcceptPreferenceParser extends PreferenceParser {
public async handle({ headers }: HttpRequest): Promise<RepresentationPreferences> {
public async handle({ request: { headers }}: { request: HttpRequest }): Promise<RepresentationPreferences> {
const preferences: RepresentationPreferences = {};
for (const { name, header, parse } of parsers) {
const value = headers[header];

View File

@ -36,9 +36,9 @@ export class BasicRequestParser extends RequestParser {
if (!method) {
throw new Error('No method specified on the HTTP request');
}
const target = await this.targetExtractor.handleSafe(request);
const preferences = await this.preferenceParser.handleSafe(request);
const metadata = await this.metadataExtractor.handleSafe(request);
const target = await this.targetExtractor.handleSafe({ request });
const preferences = await this.preferenceParser.handleSafe({ request });
const metadata = await this.metadataExtractor.handleSafe({ request });
const body = await this.bodyParser.handleSafe({ request, metadata });
return { method, target, preferences, body };

View File

@ -11,7 +11,7 @@ import { TargetExtractor } from './TargetExtractor';
* TODO: input requires more extensive cleaning/parsing based on headers (see #22).
*/
export class BasicTargetExtractor extends TargetExtractor {
public async handle({ url, connection, headers }: HttpRequest): Promise<ResourceIdentifier> {
public async handle({ request: { url, connection, headers }}: { request: HttpRequest }): Promise<ResourceIdentifier> {
if (!url) {
throw new Error('Missing URL');
}

View File

@ -5,4 +5,4 @@ import type { RepresentationPreferences } from '../representation/Representation
/**
* Creates {@link RepresentationPreferences} based on the incoming HTTP headers in a {@link HttpRequest}.
*/
export abstract class PreferenceParser extends AsyncHandler<HttpRequest, RepresentationPreferences> {}
export abstract class PreferenceParser extends AsyncHandler<{ request: HttpRequest }, RepresentationPreferences> {}

View File

@ -5,4 +5,4 @@ import type { ResourceIdentifier } from '../representation/ResourceIdentifier';
/**
* Extracts a {@link ResourceIdentifier} from an incoming {@link HttpRequest}.
*/
export abstract class TargetExtractor extends AsyncHandler<HttpRequest, ResourceIdentifier> {}
export abstract class TargetExtractor extends AsyncHandler<{ request: HttpRequest }, ResourceIdentifier> {}

View File

@ -14,7 +14,7 @@ export class BasicMetadataExtractor extends MetadataExtractor {
this.parsers = parsers;
}
public async handle(request: HttpRequest):
public async handle({ request }: { request: HttpRequest }):
Promise<RepresentationMetadata> {
const metadata = new RepresentationMetadata();
for (const parser of this.parsers) {

View File

@ -6,4 +6,4 @@ import type { RepresentationMetadata } from '../../representation/Representation
* Parses the metadata of a {@link HttpRequest} into a {@link RepresentationMetadata}.
*/
export abstract class MetadataExtractor extends
AsyncHandler<HttpRequest, RepresentationMetadata> {}
AsyncHandler<{ request: HttpRequest }, RepresentationMetadata> {}

View File

@ -74,7 +74,7 @@ describe('A DPoPWebIdExtractor', (): void => {
it('calls the target extractor with the correct parameters.', async(): Promise<void> => {
await webIdExtractor.handleSafe(request);
expect(targetExtractor.handle).toHaveBeenCalledTimes(1);
expect(targetExtractor.handle).toHaveBeenCalledWith(request);
expect(targetExtractor.handle).toHaveBeenCalledWith({ request });
});
it('calls the DPoP verifier with the correct parameters.', async(): Promise<void> => {

View File

@ -3,41 +3,47 @@ import type { HttpRequest } from '../../../../src/server/HttpRequest';
describe('An AcceptPreferenceParser', (): void => {
const preferenceParser = new AcceptPreferenceParser();
let request: HttpRequest;
beforeEach(async(): Promise<void> => {
request = { headers: {}} as HttpRequest;
});
it('can handle all input.', async(): Promise<void> => {
await expect(preferenceParser.canHandle({} as HttpRequest)).resolves.toBeUndefined();
await expect(preferenceParser.canHandle({ request })).resolves.toBeUndefined();
});
it('returns an empty result if there is no relevant input.', async(): Promise<void> => {
await expect(preferenceParser.handle({ headers: {}} as HttpRequest)).resolves.toEqual({});
await expect(preferenceParser.handle({ request })).resolves.toEqual({});
});
it('parses accept headers.', async(): Promise<void> => {
await expect(preferenceParser.handle({ headers: { accept: 'audio/*; q=0.2, audio/basic' }} as HttpRequest))
request.headers = { accept: 'audio/*; q=0.2, audio/basic' };
await expect(preferenceParser.handle({ request }))
.resolves.toEqual({ type: { 'audio/basic': 1, 'audio/*': 0.2 }});
});
it('parses accept-charset headers.', async(): Promise<void> => {
await expect(preferenceParser.handle(
{ headers: { 'accept-charset': 'iso-8859-5, unicode-1-1;q=0.8' }} as unknown as HttpRequest,
)).resolves.toEqual({ charset: { 'iso-8859-5': 1, 'unicode-1-1': 0.8 }});
request.headers = { 'accept-charset': 'iso-8859-5, unicode-1-1;q=0.8' };
await expect(preferenceParser.handle({ request }))
.resolves.toEqual({ charset: { 'iso-8859-5': 1, 'unicode-1-1': 0.8 }});
});
it('parses accept-datetime headers.', async(): Promise<void> => {
await expect(preferenceParser.handle(
{ headers: { 'accept-datetime': 'Tue, 20 Mar 2001 20:35:00 GMT' }} as unknown as HttpRequest,
request.headers = { 'accept-datetime': 'Tue, 20 Mar 2001 20:35:00 GMT' };
await expect(preferenceParser.handle({ request }))
// eslint-disable-next-line @typescript-eslint/naming-convention
)).resolves.toEqual({ datetime: { 'Tue, 20 Mar 2001 20:35:00 GMT': 1 }});
.resolves.toEqual({ datetime: { 'Tue, 20 Mar 2001 20:35:00 GMT': 1 }});
});
it('parses accept-encoding headers.', async(): Promise<void> => {
await expect(preferenceParser.handle(
{ headers: { 'accept-encoding': 'gzip;q=1.0, identity; q=0.5, *;q=0' }} as unknown as HttpRequest,
)).resolves.toEqual({ encoding: { gzip: 1, identity: 0.5, '*': 0 }});
request.headers = { 'accept-encoding': 'gzip;q=1.0, identity; q=0.5, *;q=0' };
await expect(preferenceParser.handle({ request }))
.resolves.toEqual({ encoding: { gzip: 1, identity: 0.5, '*': 0 }});
});
it('parses accept-language headers.', async(): Promise<void> => {
await expect(preferenceParser.handle({ headers: { 'accept-language': 'da, en-gb;q=0.8, en;q=0.7' }} as HttpRequest))
request.headers = { 'accept-language': 'da, en-gb;q=0.8, en;q=0.7' };
await expect(preferenceParser.handle({ request }))
.resolves.toEqual({ language: { da: 1, 'en-gb': 0.8, en: 0.7 }});
});
});

View File

@ -8,47 +8,48 @@ describe('A BasicTargetExtractor', (): void => {
});
it('errors if there is no URL.', async(): Promise<void> => {
await expect(extractor.handle({ headers: { host: 'test.com' }} as any)).rejects.toThrow('Missing URL');
await expect(extractor.handle({ request: { headers: { host: 'test.com' }} as any })).rejects.toThrow('Missing URL');
});
it('errors if there is no host.', async(): Promise<void> => {
await expect(extractor.handle({ url: 'url', headers: {}} as any)).rejects.toThrow('Missing Host header');
await expect(extractor.handle({ request: { url: 'url', headers: {}} as any }))
.rejects.toThrow('Missing Host header');
});
it('errors if the host is invalid.', async(): Promise<void> => {
await expect(extractor.handle({ url: 'url', headers: { host: 'test.com/forbidden' }} as any))
await expect(extractor.handle({ request: { url: 'url', headers: { host: 'test.com/forbidden' }} as any }))
.rejects.toThrow('The request has an invalid Host header: test.com/forbidden');
});
it('returns the input URL.', async(): Promise<void> => {
await expect(extractor.handle({ url: 'url', headers: { host: 'test.com' }} as any))
await expect(extractor.handle({ request: { url: 'url', headers: { host: 'test.com' }} as any }))
.resolves.toEqual({ path: 'http://test.com/url' });
});
it('supports host:port combinations.', async(): Promise<void> => {
await expect(extractor.handle({ url: 'url', headers: { host: 'localhost:3000' }} as any))
await expect(extractor.handle({ request: { url: 'url', headers: { host: 'localhost:3000' }} as any }))
.resolves.toEqual({ path: 'http://localhost:3000/url' });
});
it('uses https protocol if the connection is secure.', async(): Promise<void> => {
await expect(extractor.handle(
{ url: 'url', headers: { host: 'test.com' }, connection: { encrypted: true } as any } as any,
{ request: { url: 'url', headers: { host: 'test.com' }, connection: { encrypted: true } as any } as any },
)).resolves.toEqual({ path: 'https://test.com/url' });
});
it('encodes paths.', async(): Promise<void> => {
await expect(extractor.handle({ url: '/a%20path%26/name', headers: { host: 'test.com' }} as any))
await expect(extractor.handle({ request: { url: '/a%20path%26/name', headers: { host: 'test.com' }} as any }))
.resolves.toEqual({ path: 'http://test.com/a%20path%26/name' });
await expect(extractor.handle({ url: '/a path%26/name', headers: { host: 'test.com' }} as any))
await expect(extractor.handle({ request: { url: '/a path%26/name', headers: { host: 'test.com' }} as any }))
.resolves.toEqual({ path: 'http://test.com/a%20path%26/name' });
await expect(extractor.handle({ url: '/path&%26/name', headers: { host: 'test.com' }} as any))
await expect(extractor.handle({ request: { url: '/path&%26/name', headers: { host: 'test.com' }} as any }))
.resolves.toEqual({ path: 'http://test.com/path%26%26/name' });
});
it('encodes hosts.', async(): Promise<void> => {
await expect(extractor.handle({ url: '/', headers: { host: '點看' }} as any))
await expect(extractor.handle({ request: { url: '/', headers: { host: '點看' }} as any }))
.resolves.toEqual({ path: 'http://xn--c1yn36f/' });
});
@ -57,7 +58,7 @@ describe('A BasicTargetExtractor', (): void => {
host: 'test.com',
forwarded: 'by=203.0.113.60',
};
await expect(extractor.handle({ url: '/foo/bar', headers } as any))
await expect(extractor.handle({ request: { url: '/foo/bar', headers } as any }))
.resolves.toEqual({ path: 'http://test.com/foo/bar' });
});
@ -66,7 +67,7 @@ describe('A BasicTargetExtractor', (): void => {
host: 'test.com',
forwarded: 'proto=https;host=pod.example',
};
await expect(extractor.handle({ url: '/foo/bar', headers } as any))
await expect(extractor.handle({ request: { url: '/foo/bar', headers } as any }))
.resolves.toEqual({ path: 'https://pod.example/foo/bar' });
});
});

View File

@ -32,7 +32,7 @@ describe('A BasicMetadataExtractor', (): void => {
});
it('will add metadata from the parsers.', async(): Promise<void> => {
const metadata = await handler.handle({ headers: { aa: 'valA', bb: 'valB' } as any } as HttpRequest);
const metadata = await handler.handle({ request: { headers: { aa: 'valA', bb: 'valB' } as any } as HttpRequest });
expect(metadata.getAll(RDF.type).map((term): any => term.value)).toEqual([ 'valA', 'valB' ]);
});
});