feat: Update MetadataParsers to be AsyncHandlers

This way the MetadataExtractor class is not needed anymore
after a small change in the request parser.
This commit is contained in:
Joachim Van Herwegen 2021-05-07 15:42:06 +02:00
parent 8491300f42
commit 9666f6dd6a
16 changed files with 49 additions and 133 deletions

View File

@ -2,9 +2,9 @@
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"@graph": [ "@graph": [
{ {
"@id": "urn:solid-server:default:MetadataExtractor", "@id": "urn:solid-server:default:MetadataParser",
"@type": "BasicMetadataExtractor", "@type": "ParallelHandler",
"parsers": [ "handlers": [
{ {
"@type": "ContentTypeParser" "@type": "ContentTypeParser"
}, },

View File

@ -10,8 +10,8 @@
"args_preferenceParser": { "args_preferenceParser": {
"@type": "AcceptPreferenceParser" "@type": "AcceptPreferenceParser"
}, },
"args_metadataExtractor": { "args_metadataParser": {
"@id": "urn:solid-server:default:MetadataExtractor" "@id": "urn:solid-server:default:MetadataParser"
}, },
"args_bodyParser": { "args_bodyParser": {
"@type": "WaterfallHandler", "@type": "WaterfallHandler",

View File

@ -85,13 +85,11 @@ export * from './ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy';
export * from './ldp/auxiliary/Validator'; export * from './ldp/auxiliary/Validator';
// LDP/HTTP/Metadata // LDP/HTTP/Metadata
export * from './ldp/http/metadata/BasicMetadataExtractor';
export * from './ldp/http/metadata/ConstantMetadataWriter'; export * from './ldp/http/metadata/ConstantMetadataWriter';
export * from './ldp/http/metadata/ContentTypeParser'; export * from './ldp/http/metadata/ContentTypeParser';
export * from './ldp/http/metadata/LinkRelMetadataWriter'; export * from './ldp/http/metadata/LinkRelMetadataWriter';
export * from './ldp/http/metadata/LinkTypeParser'; export * from './ldp/http/metadata/LinkTypeParser';
export * from './ldp/http/metadata/MappedMetadataWriter'; export * from './ldp/http/metadata/MappedMetadataWriter';
export * from './ldp/http/metadata/MetadataExtractor';
export * from './ldp/http/metadata/MetadataParser'; export * from './ldp/http/metadata/MetadataParser';
export * from './ldp/http/metadata/MetadataWriter'; export * from './ldp/http/metadata/MetadataWriter';
export * from './ldp/http/metadata/SlugParser'; export * from './ldp/http/metadata/SlugParser';

View File

@ -1,8 +1,9 @@
import type { HttpRequest } from '../../server/HttpRequest'; import type { HttpRequest } from '../../server/HttpRequest';
import { InternalServerError } from '../../util/errors/InternalServerError'; import { InternalServerError } from '../../util/errors/InternalServerError';
import type { Operation } from '../operations/Operation'; import type { Operation } from '../operations/Operation';
import { RepresentationMetadata } from '../representation/RepresentationMetadata';
import type { BodyParser } from './BodyParser'; import type { BodyParser } from './BodyParser';
import type { MetadataExtractor } from './metadata/MetadataExtractor'; import type { MetadataParser } from './metadata/MetadataParser';
import type { PreferenceParser } from './PreferenceParser'; import type { PreferenceParser } from './PreferenceParser';
import { RequestParser } from './RequestParser'; import { RequestParser } from './RequestParser';
import type { TargetExtractor } from './TargetExtractor'; import type { TargetExtractor } from './TargetExtractor';
@ -13,18 +14,18 @@ import type { TargetExtractor } from './TargetExtractor';
export interface BasicRequestParserArgs { export interface BasicRequestParserArgs {
targetExtractor: TargetExtractor; targetExtractor: TargetExtractor;
preferenceParser: PreferenceParser; preferenceParser: PreferenceParser;
metadataExtractor: MetadataExtractor; metadataParser: MetadataParser;
bodyParser: BodyParser; bodyParser: BodyParser;
} }
/** /**
* Creates an {@link Operation} from an incoming {@link HttpRequest} by aggregating the results * Creates an {@link Operation} from an incoming {@link HttpRequest} by aggregating the results
* of a {@link TargetExtractor}, {@link PreferenceParser}, {@link MetadataExtractor}, and {@link BodyParser}. * of a {@link TargetExtractor}, {@link PreferenceParser}, {@link MetadataParser}, and {@link BodyParser}.
*/ */
export class BasicRequestParser extends RequestParser { export class BasicRequestParser extends RequestParser {
private readonly targetExtractor!: TargetExtractor; private readonly targetExtractor!: TargetExtractor;
private readonly preferenceParser!: PreferenceParser; private readonly preferenceParser!: PreferenceParser;
private readonly metadataExtractor!: MetadataExtractor; private readonly metadataParser!: MetadataParser;
private readonly bodyParser!: BodyParser; private readonly bodyParser!: BodyParser;
public constructor(args: BasicRequestParserArgs) { public constructor(args: BasicRequestParserArgs) {
@ -39,7 +40,8 @@ export class BasicRequestParser extends RequestParser {
} }
const target = await this.targetExtractor.handleSafe({ request }); const target = await this.targetExtractor.handleSafe({ request });
const preferences = await this.preferenceParser.handleSafe({ request }); const preferences = await this.preferenceParser.handleSafe({ request });
const metadata = await this.metadataExtractor.handleSafe({ request, target }); const metadata = new RepresentationMetadata(target);
await this.metadataParser.handleSafe({ request, metadata });
const body = await this.bodyParser.handleSafe({ request, metadata }); const body = await this.bodyParser.handleSafe({ request, metadata });
return { method, target, preferences, body }; return { method, target, preferences, body };

View File

@ -1,26 +0,0 @@
import type { HttpRequest } from '../../../server/HttpRequest';
import { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../representation/ResourceIdentifier';
import { MetadataExtractor } from './MetadataExtractor';
import type { MetadataParser } from './MetadataParser';
/**
* MetadataExtractor that lets each of its MetadataParsers add metadata based on the HttpRequest.
*/
export class BasicMetadataExtractor extends MetadataExtractor {
private readonly parsers: MetadataParser[];
public constructor(parsers: MetadataParser[]) {
super();
this.parsers = parsers;
}
public async handle({ request, target }: { request: HttpRequest; target: ResourceIdentifier }):
Promise<RepresentationMetadata> {
const metadata = new RepresentationMetadata(target);
for (const parser of this.parsers) {
await parser.parse(request, metadata);
}
return metadata;
}
}

View File

@ -1,17 +1,17 @@
import type { HttpRequest } from '../../../server/HttpRequest'; import type { HttpRequest } from '../../../server/HttpRequest';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata'; import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { MetadataParser } from './MetadataParser'; import { MetadataParser } from './MetadataParser';
/** /**
* Parser for the `content-type` header. * Parser for the `content-type` header.
* Currently only stores the media type and ignores other parameters such as charset. * Currently only stores the media type and ignores other parameters such as charset.
*/ */
export class ContentTypeParser implements MetadataParser { export class ContentTypeParser extends MetadataParser {
public async parse(request: HttpRequest, metadata: RepresentationMetadata): Promise<void> { public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
const contentType = request.headers['content-type']; const contentType = input.request.headers['content-type'];
if (contentType) { if (contentType) {
// Will need to use HeaderUtil once parameters need to be parsed // Will need to use HeaderUtil once parameters need to be parsed
metadata.contentType = /^[^;]*/u.exec(contentType)![0].trim(); input.metadata.contentType = /^[^;]*/u.exec(contentType)![0].trim();
} }
} }
} }

View File

@ -4,19 +4,19 @@ import type { HttpRequest } from '../../../server/HttpRequest';
import { parseParameters, splitAndClean, transformQuotedStrings } from '../../../util/HeaderUtil'; import { parseParameters, splitAndClean, transformQuotedStrings } from '../../../util/HeaderUtil';
import { RDF } from '../../../util/Vocabularies'; import { RDF } from '../../../util/Vocabularies';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata'; import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { MetadataParser } from './MetadataParser'; import { MetadataParser } from './MetadataParser';
/** /**
* Parses Link headers with "rel=type" parameters and adds them as RDF.type metadata. * Parses Link headers with "rel=type" parameters and adds them as RDF.type metadata.
*/ */
export class LinkTypeParser implements MetadataParser { export class LinkTypeParser extends MetadataParser {
protected readonly logger = getLoggerFor(this); protected readonly logger = getLoggerFor(this);
public async parse(request: HttpRequest, metadata: RepresentationMetadata): Promise<void> { public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
const link = request.headers.link ?? []; const link = input.request.headers.link ?? [];
const entries: string[] = Array.isArray(link) ? link : [ link ]; const entries: string[] = Array.isArray(link) ? link : [ link ];
for (const entry of entries) { for (const entry of entries) {
this.parseLink(entry, metadata); this.parseLink(entry, input.metadata);
} }
} }

View File

@ -1,10 +0,0 @@
import type { HttpRequest } from '../../../server/HttpRequest';
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../representation/ResourceIdentifier';
/**
* Parses the metadata of a {@link HttpRequest} into a {@link RepresentationMetadata}.
*/
export abstract class MetadataExtractor extends
AsyncHandler<{ request: HttpRequest; target: ResourceIdentifier }, RepresentationMetadata> {}

View File

@ -1,15 +1,9 @@
import type { HttpRequest } from '../../../server/HttpRequest'; import type { HttpRequest } from '../../../server/HttpRequest';
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata'; import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
/** /**
* A parser that takes a specific part of an HttpRequest and converts it to medata, * A parser that takes a specific part of an HttpRequest and converts it into metadata,
* such as the value of a header entry. * such as the value of a header entry.
*/ */
export interface MetadataParser { export abstract class MetadataParser extends AsyncHandler<{ request: HttpRequest; metadata: RepresentationMetadata }> {}
/**
* Potentially adds metadata to the RepresentationMetadata based on the HttpRequest contents.
* @param request - Request with potential metadata.
* @param metadata - Metadata objects that should be updated.
*/
parse: (request: HttpRequest, metadata: RepresentationMetadata) => Promise<void>;
}

View File

@ -3,23 +3,23 @@ import type { HttpRequest } from '../../../server/HttpRequest';
import { BadRequestHttpError } from '../../../util/errors/BadRequestHttpError'; import { BadRequestHttpError } from '../../../util/errors/BadRequestHttpError';
import { HTTP } from '../../../util/Vocabularies'; import { HTTP } from '../../../util/Vocabularies';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata'; import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { MetadataParser } from './MetadataParser'; import { MetadataParser } from './MetadataParser';
/** /**
* Converts the contents of the slug header to metadata. * Converts the contents of the slug header to metadata.
*/ */
export class SlugParser implements MetadataParser { export class SlugParser extends MetadataParser {
protected readonly logger = getLoggerFor(this); protected readonly logger = getLoggerFor(this);
public async parse(request: HttpRequest, metadata: RepresentationMetadata): Promise<void> { public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
const { slug } = request.headers; const { slug } = input.request.headers;
if (slug) { if (slug) {
if (Array.isArray(slug)) { if (Array.isArray(slug)) {
this.logger.warn(`Expected 0 or 1 Slug headers but received ${slug.length}`); this.logger.warn(`Expected 0 or 1 Slug headers but received ${slug.length}`);
throw new BadRequestHttpError('Request has multiple Slug headers'); throw new BadRequestHttpError('Request has multiple Slug headers');
} }
this.logger.debug(`Request Slug is '${slug}'.`); this.logger.debug(`Request Slug is '${slug}'.`);
metadata.set(HTTP.slug, slug); input.metadata.set(HTTP.slug, slug);
} }
} }
} }

View File

@ -3,7 +3,6 @@ import arrayifyStream from 'arrayify-stream';
import streamifyArray from 'streamify-array'; import streamifyArray from 'streamify-array';
import { AcceptPreferenceParser } from '../../src/ldp/http/AcceptPreferenceParser'; import { AcceptPreferenceParser } from '../../src/ldp/http/AcceptPreferenceParser';
import { BasicRequestParser } from '../../src/ldp/http/BasicRequestParser'; import { BasicRequestParser } from '../../src/ldp/http/BasicRequestParser';
import { BasicMetadataExtractor } from '../../src/ldp/http/metadata/BasicMetadataExtractor';
import { ContentTypeParser } from '../../src/ldp/http/metadata/ContentTypeParser'; import { ContentTypeParser } from '../../src/ldp/http/metadata/ContentTypeParser';
import { OriginalUrlExtractor } from '../../src/ldp/http/OriginalUrlExtractor'; import { OriginalUrlExtractor } from '../../src/ldp/http/OriginalUrlExtractor';
import { RawBodyParser } from '../../src/ldp/http/RawBodyParser'; import { RawBodyParser } from '../../src/ldp/http/RawBodyParser';
@ -13,9 +12,9 @@ import type { HttpRequest } from '../../src/server/HttpRequest';
describe('A BasicRequestParser with simple input parsers', (): void => { describe('A BasicRequestParser with simple input parsers', (): void => {
const targetExtractor = new OriginalUrlExtractor(); const targetExtractor = new OriginalUrlExtractor();
const preferenceParser = new AcceptPreferenceParser(); const preferenceParser = new AcceptPreferenceParser();
const metadataExtractor = new BasicMetadataExtractor([ new ContentTypeParser() ]); const metadataParser = new ContentTypeParser();
const bodyParser = new RawBodyParser(); const bodyParser = new RawBodyParser();
const requestParser = new BasicRequestParser({ targetExtractor, preferenceParser, metadataExtractor, bodyParser }); const requestParser = new BasicRequestParser({ targetExtractor, preferenceParser, metadataParser, bodyParser });
it('can parse an incoming request.', async(): Promise<void> => { it('can parse an incoming request.', async(): Promise<void> => {
const request = streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]) as HttpRequest; const request = streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]) as HttpRequest;

View File

@ -1,23 +1,24 @@
import { BasicRequestParser } from '../../../../src/ldp/http/BasicRequestParser'; import { BasicRequestParser } from '../../../../src/ldp/http/BasicRequestParser';
import type { BodyParser } from '../../../../src/ldp/http/BodyParser'; import type { BodyParser } from '../../../../src/ldp/http/BodyParser';
import type { MetadataExtractor } from '../../../../src/ldp/http/metadata/MetadataExtractor'; import type { MetadataParser } from '../../../../src/ldp/http/metadata/MetadataParser';
import type { PreferenceParser } from '../../../../src/ldp/http/PreferenceParser'; import type { PreferenceParser } from '../../../../src/ldp/http/PreferenceParser';
import type { TargetExtractor } from '../../../../src/ldp/http/TargetExtractor'; import type { TargetExtractor } from '../../../../src/ldp/http/TargetExtractor';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import { StaticAsyncHandler } from '../../../util/StaticAsyncHandler'; import { StaticAsyncHandler } from '../../../util/StaticAsyncHandler';
describe('A BasicRequestParser', (): void => { describe('A BasicRequestParser', (): void => {
let targetExtractor: TargetExtractor; let targetExtractor: TargetExtractor;
let preferenceParser: PreferenceParser; let preferenceParser: PreferenceParser;
let metadataExtractor: MetadataExtractor; let metadataParser: MetadataParser;
let bodyParser: BodyParser; let bodyParser: BodyParser;
let requestParser: BasicRequestParser; let requestParser: BasicRequestParser;
beforeEach(async(): Promise<void> => { beforeEach(async(): Promise<void> => {
targetExtractor = new StaticAsyncHandler(true, 'target' as any); targetExtractor = new StaticAsyncHandler(true, 'target' as any);
preferenceParser = new StaticAsyncHandler(true, 'preference' as any); preferenceParser = new StaticAsyncHandler(true, 'preference' as any);
metadataExtractor = new StaticAsyncHandler(true, 'metadata' as any); metadataParser = new StaticAsyncHandler(true, undefined);
bodyParser = new StaticAsyncHandler(true, 'body' as any); bodyParser = new StaticAsyncHandler(true, 'body' as any);
requestParser = new BasicRequestParser({ targetExtractor, preferenceParser, metadataExtractor, bodyParser }); requestParser = new BasicRequestParser({ targetExtractor, preferenceParser, metadataParser, bodyParser });
}); });
it('can handle any input.', async(): Promise<void> => { it('can handle any input.', async(): Promise<void> => {
@ -35,7 +36,7 @@ describe('A BasicRequestParser', (): void => {
method: 'GET', method: 'GET',
target: 'target', target: 'target',
preferences: 'preference', preferences: 'preference',
body: { data: 'body', metadata: 'metadata' }, body: { data: 'body', metadata: new RepresentationMetadata('target') },
}); });
}); });
}); });

View File

@ -1,42 +0,0 @@
import { BasicMetadataExtractor } from '../../../../../src/ldp/http/metadata/BasicMetadataExtractor';
import type { MetadataParser } from '../../../../../src/ldp/http/metadata/MetadataParser';
import type { RepresentationMetadata } from '../../../../../src/ldp/representation/RepresentationMetadata';
import type { HttpRequest } from '../../../../../src/server/HttpRequest';
import { RDF } from '../../../../../src/util/Vocabularies';
class BasicParser implements MetadataParser {
private readonly header: string;
public constructor(header: string) {
this.header = header;
}
public async parse(input: HttpRequest, metadata: RepresentationMetadata): Promise<void> {
const header = input.headers[this.header];
if (header) {
if (typeof header === 'string') {
metadata.add(RDF.type, header);
}
}
}
}
describe('A BasicMetadataExtractor', (): void => {
const handler = new BasicMetadataExtractor([
new BasicParser('aa'),
new BasicParser('bb'),
]);
it('can handle all requests.', async(): Promise<void> => {
await expect(handler.canHandle({} as any)).resolves.toBeUndefined();
});
it('will add metadata from the parsers.', async(): Promise<void> => {
const target = { path: 'http://test.com/id' };
const metadata = await handler.handle(
{ target, request: { headers: { aa: 'valA', bb: 'valB' } as any } as HttpRequest },
);
expect(metadata.identifier.value).toBe(target.path);
expect(metadata.getAll(RDF.type).map((term): any => term.value)).toEqual([ 'valA', 'valB' ]);
});
});

View File

@ -13,13 +13,13 @@ describe('A ContentTypeParser', (): void => {
}); });
it('does nothing if there is no content-type header.', async(): Promise<void> => { it('does nothing if there is no content-type header.', async(): Promise<void> => {
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(0); expect(metadata.quads()).toHaveLength(0);
}); });
it('sets the given content-type as metadata.', async(): Promise<void> => { it('sets the given content-type as metadata.', async(): Promise<void> => {
request.headers['content-type'] = 'text/plain;charset=UTF-8'; request.headers['content-type'] = 'text/plain;charset=UTF-8';
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(1); expect(metadata.quads()).toHaveLength(1);
expect(metadata.contentType).toBe('text/plain'); expect(metadata.contentType).toBe('text/plain');
}); });

View File

@ -14,20 +14,20 @@ describe('A LinkTypeParser', (): void => {
}); });
it('does nothing if there are no type headers.', async(): Promise<void> => { it('does nothing if there are no type headers.', async(): Promise<void> => {
await parser.parse(request, metadata); await parser.handle({ request, metadata });
expect(metadata.quads()).toHaveLength(0); expect(metadata.quads()).toHaveLength(0);
}); });
it('stores link headers with rel = type as metadata.', async(): Promise<void> => { it('stores link headers with rel = type as metadata.', async(): Promise<void> => {
request.headers.link = '<http://test.com/type>;rel="type"'; request.headers.link = '<http://test.com/type>;rel="type"';
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(1); expect(metadata.quads()).toHaveLength(1);
expect(metadata.get(RDF.type)?.value).toBe('http://test.com/type'); expect(metadata.get(RDF.type)?.value).toBe('http://test.com/type');
}); });
it('supports multiple link headers.', async(): Promise<void> => { it('supports multiple link headers.', async(): Promise<void> => {
request.headers.link = [ '<http://test.com/typeA>;rel="type"', '<http://test.com/typeB>;rel=type' ]; request.headers.link = [ '<http://test.com/typeA>;rel="type"', '<http://test.com/typeB>;rel=type' ];
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(2); expect(metadata.quads()).toHaveLength(2);
expect(metadata.getAll(RDF.type).map((term): any => term.value)) expect(metadata.getAll(RDF.type).map((term): any => term.value))
.toEqual([ 'http://test.com/typeA', 'http://test.com/typeB' ]); .toEqual([ 'http://test.com/typeA', 'http://test.com/typeB' ]);
@ -35,7 +35,7 @@ describe('A LinkTypeParser', (): void => {
it('supports multiple link header values in the same entry.', async(): Promise<void> => { it('supports multiple link header values in the same entry.', async(): Promise<void> => {
request.headers.link = '<http://test.com/typeA>;rel="type" , <http://test.com/typeB>;rel=type'; request.headers.link = '<http://test.com/typeA>;rel="type" , <http://test.com/typeB>;rel=type';
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(2); expect(metadata.quads()).toHaveLength(2);
expect(metadata.getAll(RDF.type).map((term): any => term.value)) expect(metadata.getAll(RDF.type).map((term): any => term.value))
.toEqual([ 'http://test.com/typeA', 'http://test.com/typeB' ]); .toEqual([ 'http://test.com/typeA', 'http://test.com/typeB' ]);
@ -43,13 +43,13 @@ describe('A LinkTypeParser', (): void => {
it('ignores invalid link headers.', async(): Promise<void> => { it('ignores invalid link headers.', async(): Promise<void> => {
request.headers.link = 'http://test.com/type;rel="type"'; request.headers.link = 'http://test.com/type;rel="type"';
await parser.parse(request, metadata); await parser.handle({ request, metadata });
expect(metadata.quads()).toHaveLength(0); expect(metadata.quads()).toHaveLength(0);
}); });
it('ignores non-type link headers.', async(): Promise<void> => { it('ignores non-type link headers.', async(): Promise<void> => {
request.headers.link = '<http://test.com/typeA>;rel="notype" , <http://test.com/typeB>'; request.headers.link = '<http://test.com/typeA>;rel="notype" , <http://test.com/typeB>';
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(0); expect(metadata.quads()).toHaveLength(0);
}); });
}); });

View File

@ -15,20 +15,20 @@ describe('A SlugParser', (): void => {
}); });
it('does nothing if there is no slug header.', async(): Promise<void> => { it('does nothing if there is no slug header.', async(): Promise<void> => {
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(0); expect(metadata.quads()).toHaveLength(0);
}); });
it('errors if there are multiple slug headers.', async(): Promise<void> => { it('errors if there are multiple slug headers.', async(): Promise<void> => {
request.headers.slug = [ 'slugA', 'slugB' ]; request.headers.slug = [ 'slugA', 'slugB' ];
const result = parser.parse(request, metadata); const result = parser.handle({ request, metadata });
await expect(result).rejects.toThrow(BadRequestHttpError); await expect(result).rejects.toThrow(BadRequestHttpError);
await expect(result).rejects.toThrow('Request has multiple Slug headers'); await expect(result).rejects.toThrow('Request has multiple Slug headers');
}); });
it('stores the slug metadata.', async(): Promise<void> => { it('stores the slug metadata.', async(): Promise<void> => {
request.headers.slug = 'slugA'; request.headers.slug = 'slugA';
await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(1); expect(metadata.quads()).toHaveLength(1);
expect(metadata.get(HTTP.slug)?.value).toBe('slugA'); expect(metadata.get(HTTP.slug)?.value).toBe('slugA');
}); });