diff --git a/config/presets/ldp/metadata-handler.json b/config/presets/ldp/metadata-handler.json index 19692c9b4..1cecf833f 100644 --- a/config/presets/ldp/metadata-handler.json +++ b/config/presets/ldp/metadata-handler.json @@ -2,9 +2,9 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@graph": [ { - "@id": "urn:solid-server:default:MetadataExtractor", - "@type": "BasicMetadataExtractor", - "parsers": [ + "@id": "urn:solid-server:default:MetadataParser", + "@type": "ParallelHandler", + "handlers": [ { "@type": "ContentTypeParser" }, diff --git a/config/presets/ldp/request-parser.json b/config/presets/ldp/request-parser.json index 6129013f5..8af28c8d3 100644 --- a/config/presets/ldp/request-parser.json +++ b/config/presets/ldp/request-parser.json @@ -10,8 +10,8 @@ "args_preferenceParser": { "@type": "AcceptPreferenceParser" }, - "args_metadataExtractor": { - "@id": "urn:solid-server:default:MetadataExtractor" + "args_metadataParser": { + "@id": "urn:solid-server:default:MetadataParser" }, "args_bodyParser": { "@type": "WaterfallHandler", diff --git a/src/index.ts b/src/index.ts index 35b448c90..40a759d1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -85,13 +85,11 @@ export * from './ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy'; export * from './ldp/auxiliary/Validator'; // LDP/HTTP/Metadata -export * from './ldp/http/metadata/BasicMetadataExtractor'; export * from './ldp/http/metadata/ConstantMetadataWriter'; export * from './ldp/http/metadata/ContentTypeParser'; export * from './ldp/http/metadata/LinkRelMetadataWriter'; export * from './ldp/http/metadata/LinkTypeParser'; export * from './ldp/http/metadata/MappedMetadataWriter'; -export * from './ldp/http/metadata/MetadataExtractor'; export * from './ldp/http/metadata/MetadataParser'; export * from './ldp/http/metadata/MetadataWriter'; export * from './ldp/http/metadata/SlugParser'; diff --git a/src/ldp/http/BasicRequestParser.ts b/src/ldp/http/BasicRequestParser.ts index 58ada209d..8965f8890 100644 --- a/src/ldp/http/BasicRequestParser.ts +++ b/src/ldp/http/BasicRequestParser.ts @@ -1,8 +1,9 @@ import type { HttpRequest } from '../../server/HttpRequest'; import { InternalServerError } from '../../util/errors/InternalServerError'; import type { Operation } from '../operations/Operation'; +import { RepresentationMetadata } from '../representation/RepresentationMetadata'; import type { BodyParser } from './BodyParser'; -import type { MetadataExtractor } from './metadata/MetadataExtractor'; +import type { MetadataParser } from './metadata/MetadataParser'; import type { PreferenceParser } from './PreferenceParser'; import { RequestParser } from './RequestParser'; import type { TargetExtractor } from './TargetExtractor'; @@ -13,18 +14,18 @@ import type { TargetExtractor } from './TargetExtractor'; export interface BasicRequestParserArgs { targetExtractor: TargetExtractor; preferenceParser: PreferenceParser; - metadataExtractor: MetadataExtractor; + metadataParser: MetadataParser; bodyParser: BodyParser; } /** * 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 { private readonly targetExtractor!: TargetExtractor; private readonly preferenceParser!: PreferenceParser; - private readonly metadataExtractor!: MetadataExtractor; + private readonly metadataParser!: MetadataParser; private readonly bodyParser!: BodyParser; public constructor(args: BasicRequestParserArgs) { @@ -39,7 +40,8 @@ export class BasicRequestParser extends RequestParser { } const target = await this.targetExtractor.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 }); return { method, target, preferences, body }; diff --git a/src/ldp/http/metadata/BasicMetadataExtractor.ts b/src/ldp/http/metadata/BasicMetadataExtractor.ts deleted file mode 100644 index 57f848ec0..000000000 --- a/src/ldp/http/metadata/BasicMetadataExtractor.ts +++ /dev/null @@ -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 { - const metadata = new RepresentationMetadata(target); - for (const parser of this.parsers) { - await parser.parse(request, metadata); - } - return metadata; - } -} diff --git a/src/ldp/http/metadata/ContentTypeParser.ts b/src/ldp/http/metadata/ContentTypeParser.ts index 01c06bbfc..77c8ccfe8 100644 --- a/src/ldp/http/metadata/ContentTypeParser.ts +++ b/src/ldp/http/metadata/ContentTypeParser.ts @@ -1,17 +1,17 @@ import type { HttpRequest } from '../../../server/HttpRequest'; import type { RepresentationMetadata } from '../../representation/RepresentationMetadata'; -import type { MetadataParser } from './MetadataParser'; +import { MetadataParser } from './MetadataParser'; /** * Parser for the `content-type` header. * Currently only stores the media type and ignores other parameters such as charset. */ -export class ContentTypeParser implements MetadataParser { - public async parse(request: HttpRequest, metadata: RepresentationMetadata): Promise { - const contentType = request.headers['content-type']; +export class ContentTypeParser extends MetadataParser { + public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise { + const contentType = input.request.headers['content-type']; if (contentType) { // 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(); } } } diff --git a/src/ldp/http/metadata/LinkTypeParser.ts b/src/ldp/http/metadata/LinkTypeParser.ts index 16925a464..cc71c7920 100644 --- a/src/ldp/http/metadata/LinkTypeParser.ts +++ b/src/ldp/http/metadata/LinkTypeParser.ts @@ -4,19 +4,19 @@ import type { HttpRequest } from '../../../server/HttpRequest'; import { parseParameters, splitAndClean, transformQuotedStrings } from '../../../util/HeaderUtil'; import { RDF } from '../../../util/Vocabularies'; 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. */ -export class LinkTypeParser implements MetadataParser { +export class LinkTypeParser extends MetadataParser { protected readonly logger = getLoggerFor(this); - public async parse(request: HttpRequest, metadata: RepresentationMetadata): Promise { - const link = request.headers.link ?? []; + public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise { + const link = input.request.headers.link ?? []; const entries: string[] = Array.isArray(link) ? link : [ link ]; for (const entry of entries) { - this.parseLink(entry, metadata); + this.parseLink(entry, input.metadata); } } diff --git a/src/ldp/http/metadata/MetadataExtractor.ts b/src/ldp/http/metadata/MetadataExtractor.ts deleted file mode 100644 index 0c5f94f33..000000000 --- a/src/ldp/http/metadata/MetadataExtractor.ts +++ /dev/null @@ -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> {} diff --git a/src/ldp/http/metadata/MetadataParser.ts b/src/ldp/http/metadata/MetadataParser.ts index 3bf7bd4df..563e0fcbd 100644 --- a/src/ldp/http/metadata/MetadataParser.ts +++ b/src/ldp/http/metadata/MetadataParser.ts @@ -1,15 +1,9 @@ import type { HttpRequest } from '../../../server/HttpRequest'; +import { AsyncHandler } from '../../../util/handlers/AsyncHandler'; 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. */ -export interface MetadataParser { - /** - * 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; -} +export abstract class MetadataParser extends AsyncHandler<{ request: HttpRequest; metadata: RepresentationMetadata }> {} diff --git a/src/ldp/http/metadata/SlugParser.ts b/src/ldp/http/metadata/SlugParser.ts index b49524bf1..6324981a8 100644 --- a/src/ldp/http/metadata/SlugParser.ts +++ b/src/ldp/http/metadata/SlugParser.ts @@ -3,23 +3,23 @@ import type { HttpRequest } from '../../../server/HttpRequest'; import { BadRequestHttpError } from '../../../util/errors/BadRequestHttpError'; import { HTTP } from '../../../util/Vocabularies'; import type { RepresentationMetadata } from '../../representation/RepresentationMetadata'; -import type { MetadataParser } from './MetadataParser'; +import { MetadataParser } from './MetadataParser'; /** * Converts the contents of the slug header to metadata. */ -export class SlugParser implements MetadataParser { +export class SlugParser extends MetadataParser { protected readonly logger = getLoggerFor(this); - public async parse(request: HttpRequest, metadata: RepresentationMetadata): Promise { - const { slug } = request.headers; + public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise { + const { slug } = input.request.headers; if (slug) { if (Array.isArray(slug)) { this.logger.warn(`Expected 0 or 1 Slug headers but received ${slug.length}`); throw new BadRequestHttpError('Request has multiple Slug headers'); } this.logger.debug(`Request Slug is '${slug}'.`); - metadata.set(HTTP.slug, slug); + input.metadata.set(HTTP.slug, slug); } } } diff --git a/test/integration/RequestParser.test.ts b/test/integration/RequestParser.test.ts index d8db689be..af3a31d89 100644 --- a/test/integration/RequestParser.test.ts +++ b/test/integration/RequestParser.test.ts @@ -3,7 +3,6 @@ import arrayifyStream from 'arrayify-stream'; import streamifyArray from 'streamify-array'; import { AcceptPreferenceParser } from '../../src/ldp/http/AcceptPreferenceParser'; import { BasicRequestParser } from '../../src/ldp/http/BasicRequestParser'; -import { BasicMetadataExtractor } from '../../src/ldp/http/metadata/BasicMetadataExtractor'; import { ContentTypeParser } from '../../src/ldp/http/metadata/ContentTypeParser'; import { OriginalUrlExtractor } from '../../src/ldp/http/OriginalUrlExtractor'; 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 => { const targetExtractor = new OriginalUrlExtractor(); const preferenceParser = new AcceptPreferenceParser(); - const metadataExtractor = new BasicMetadataExtractor([ new ContentTypeParser() ]); + const metadataParser = new ContentTypeParser(); 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 => { const request = streamifyArray([ ' .' ]) as HttpRequest; diff --git a/test/unit/ldp/http/BasicRequestParser.test.ts b/test/unit/ldp/http/BasicRequestParser.test.ts index f6648d0d0..6aa020afd 100644 --- a/test/unit/ldp/http/BasicRequestParser.test.ts +++ b/test/unit/ldp/http/BasicRequestParser.test.ts @@ -1,23 +1,24 @@ import { BasicRequestParser } from '../../../../src/ldp/http/BasicRequestParser'; 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 { TargetExtractor } from '../../../../src/ldp/http/TargetExtractor'; +import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata'; import { StaticAsyncHandler } from '../../../util/StaticAsyncHandler'; describe('A BasicRequestParser', (): void => { let targetExtractor: TargetExtractor; let preferenceParser: PreferenceParser; - let metadataExtractor: MetadataExtractor; + let metadataParser: MetadataParser; let bodyParser: BodyParser; let requestParser: BasicRequestParser; beforeEach(async(): Promise => { targetExtractor = new StaticAsyncHandler(true, 'target' 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); - requestParser = new BasicRequestParser({ targetExtractor, preferenceParser, metadataExtractor, bodyParser }); + requestParser = new BasicRequestParser({ targetExtractor, preferenceParser, metadataParser, bodyParser }); }); it('can handle any input.', async(): Promise => { @@ -35,7 +36,7 @@ describe('A BasicRequestParser', (): void => { method: 'GET', target: 'target', preferences: 'preference', - body: { data: 'body', metadata: 'metadata' }, + body: { data: 'body', metadata: new RepresentationMetadata('target') }, }); }); }); diff --git a/test/unit/ldp/http/metadata/BasicMetadataExtractor.test.ts b/test/unit/ldp/http/metadata/BasicMetadataExtractor.test.ts deleted file mode 100644 index 4e244addf..000000000 --- a/test/unit/ldp/http/metadata/BasicMetadataExtractor.test.ts +++ /dev/null @@ -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 { - 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 => { - await expect(handler.canHandle({} as any)).resolves.toBeUndefined(); - }); - - it('will add metadata from the parsers.', async(): Promise => { - 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' ]); - }); -}); diff --git a/test/unit/ldp/http/metadata/ContentTypeParser.test.ts b/test/unit/ldp/http/metadata/ContentTypeParser.test.ts index b9731a0ec..119772eb9 100644 --- a/test/unit/ldp/http/metadata/ContentTypeParser.test.ts +++ b/test/unit/ldp/http/metadata/ContentTypeParser.test.ts @@ -13,13 +13,13 @@ describe('A ContentTypeParser', (): void => { }); it('does nothing if there is no content-type header.', async(): Promise => { - await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); + await expect(parser.handle({ request, metadata })).resolves.toBeUndefined(); expect(metadata.quads()).toHaveLength(0); }); it('sets the given content-type as metadata.', async(): Promise => { 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.contentType).toBe('text/plain'); }); diff --git a/test/unit/ldp/http/metadata/LinkTypeParser.test.ts b/test/unit/ldp/http/metadata/LinkTypeParser.test.ts index b36854c74..0001aa42b 100644 --- a/test/unit/ldp/http/metadata/LinkTypeParser.test.ts +++ b/test/unit/ldp/http/metadata/LinkTypeParser.test.ts @@ -14,20 +14,20 @@ describe('A LinkTypeParser', (): void => { }); it('does nothing if there are no type headers.', async(): Promise => { - await parser.parse(request, metadata); + await parser.handle({ request, metadata }); expect(metadata.quads()).toHaveLength(0); }); it('stores link headers with rel = type as metadata.', async(): Promise => { request.headers.link = ';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.get(RDF.type)?.value).toBe('http://test.com/type'); }); it('supports multiple link headers.', async(): Promise => { request.headers.link = [ ';rel="type"', ';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.getAll(RDF.type).map((term): any => term.value)) .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 => { request.headers.link = ';rel="type" , ;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.getAll(RDF.type).map((term): any => term.value)) .toEqual([ 'http://test.com/typeA', 'http://test.com/typeB' ]); @@ -43,13 +43,13 @@ describe('A LinkTypeParser', (): void => { it('ignores invalid link headers.', async(): Promise => { request.headers.link = 'http://test.com/type;rel="type"'; - await parser.parse(request, metadata); + await parser.handle({ request, metadata }); expect(metadata.quads()).toHaveLength(0); }); it('ignores non-type link headers.', async(): Promise => { request.headers.link = ';rel="notype" , '; - await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); + await expect(parser.handle({ request, metadata })).resolves.toBeUndefined(); expect(metadata.quads()).toHaveLength(0); }); }); diff --git a/test/unit/ldp/http/metadata/SlugParser.test.ts b/test/unit/ldp/http/metadata/SlugParser.test.ts index d91fd0c66..05c1a588a 100644 --- a/test/unit/ldp/http/metadata/SlugParser.test.ts +++ b/test/unit/ldp/http/metadata/SlugParser.test.ts @@ -15,20 +15,20 @@ describe('A SlugParser', (): void => { }); it('does nothing if there is no slug header.', async(): Promise => { - await expect(parser.parse(request, metadata)).resolves.toBeUndefined(); + await expect(parser.handle({ request, metadata })).resolves.toBeUndefined(); expect(metadata.quads()).toHaveLength(0); }); it('errors if there are multiple slug headers.', async(): Promise => { 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('Request has multiple Slug headers'); }); it('stores the slug metadata.', async(): Promise => { 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.get(HTTP.slug)?.value).toBe('slugA'); });