feat: Add ContentTypeReplacer to conversion chain

This commit is contained in:
Joachim Van Herwegen 2021-10-26 16:55:34 +02:00
parent fa94c7d4bb
commit fdd42bb7b3
3 changed files with 25 additions and 10 deletions

View File

@ -21,12 +21,12 @@
"@type": "IfNeededConverter", "@type": "IfNeededConverter",
"comment": "Only continue converting if the requester cannot accept the available content type" "comment": "Only continue converting if the requester cannot accept the available content type"
}, },
{ "@id": "urn:solid-server:default:ContentTypeReplacer" },
{ {
"comment": "Automatically finds a path through a set of converters from one type to another.", "comment": "Automatically finds a path through a set of converters from one type to another.",
"@id": "urn:solid-server:default:ChainedConverter", "@id": "urn:solid-server:default:ChainedConverter",
"@type": "ChainedConverter", "@type": "ChainedConverter",
"converters": [ "converters": [
{ "@id": "urn:solid-server:default:ContentTypeReplacer" },
{ "@id": "urn:solid-server:default:RdfToQuadConverter" }, { "@id": "urn:solid-server:default:RdfToQuadConverter" },
{ "@id": "urn:solid-server:default:QuadToRdfConverter" }, { "@id": "urn:solid-server:default:QuadToRdfConverter" },
{ "@id": "urn:solid-server:default:ContainerToTemplateConverter" }, { "@id": "urn:solid-server:default:ContainerToTemplateConverter" },

View File

@ -4,7 +4,7 @@ import type { ValuePreferences } from '../../http/representation/RepresentationP
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { matchesMediaType, getConversionTarget } from './ConversionUtil'; import { matchesMediaType, getConversionTarget } from './ConversionUtil';
import type { RepresentationConverterArgs } from './RepresentationConverter'; import type { RepresentationConverterArgs } from './RepresentationConverter';
import { RepresentationConverter } from './RepresentationConverter'; import { TypedRepresentationConverter } from './TypedRepresentationConverter';
/** /**
* A {@link RepresentationConverter} that changes the content type * A {@link RepresentationConverter} that changes the content type
@ -13,7 +13,7 @@ import { RepresentationConverter } from './RepresentationConverter';
* Useful for when a content type is binary-compatible with another one; * Useful for when a content type is binary-compatible with another one;
* for instance, all JSON-LD files are valid JSON files. * for instance, all JSON-LD files are valid JSON files.
*/ */
export class ContentTypeReplacer extends RepresentationConverter { export class ContentTypeReplacer extends TypedRepresentationConverter {
private readonly contentTypeMap: Record<string, ValuePreferences> = {}; private readonly contentTypeMap: Record<string, ValuePreferences> = {};
/** /**
@ -40,15 +40,22 @@ export class ContentTypeReplacer extends RepresentationConverter {
} }
} }
public async getOutputTypes(contentType: string): Promise<ValuePreferences> {
const supported = Object.keys(this.contentTypeMap)
.filter((type): boolean => matchesMediaType(contentType, type))
.map((type): ValuePreferences => this.contentTypeMap[type]);
return Object.assign({} as ValuePreferences, ...supported);
}
public async canHandle({ representation, preferences }: RepresentationConverterArgs): Promise<void> { public async canHandle({ representation, preferences }: RepresentationConverterArgs): Promise<void> {
this.getReplacementType(representation.metadata.contentType, preferences.type); await this.getReplacementType(representation.metadata.contentType, preferences.type);
} }
/** /**
* Changes the content type on the representation. * Changes the content type on the representation.
*/ */
public async handle({ representation, preferences }: RepresentationConverterArgs): Promise<Representation> { public async handle({ representation, preferences }: RepresentationConverterArgs): Promise<Representation> {
const contentType = this.getReplacementType(representation.metadata.contentType, preferences.type); const contentType = await this.getReplacementType(representation.metadata.contentType, preferences.type);
const metadata = new RepresentationMetadata(representation.metadata, contentType); const metadata = new RepresentationMetadata(representation.metadata, contentType);
return { ...representation, metadata }; return { ...representation, metadata };
} }
@ -61,11 +68,9 @@ export class ContentTypeReplacer extends RepresentationConverter {
* Find a replacement content type that matches the preferences, * Find a replacement content type that matches the preferences,
* or throws an error if none was found. * or throws an error if none was found.
*/ */
private getReplacementType(contentType = 'unknown', preferred: ValuePreferences = {}): string { private async getReplacementType(contentType = 'unknown', preferred: ValuePreferences = {}): Promise<string> {
const supported = Object.keys(this.contentTypeMap) const supported = await this.getOutputTypes(contentType);
.filter((type): boolean => matchesMediaType(contentType, type)) const match = getConversionTarget(supported, preferred);
.map((type): ValuePreferences => this.contentTypeMap[type]);
const match = getConversionTarget(Object.assign({} as ValuePreferences, ...supported), preferred);
if (!match) { if (!match) {
throw new NotImplementedHttpError(`Cannot convert from ${contentType} to ${Object.keys(preferred)}`); throw new NotImplementedHttpError(`Cannot convert from ${contentType} to ${Object.keys(preferred)}`);
} }

View File

@ -97,4 +97,14 @@ describe('A ContentTypeReplacer', (): void => {
expect(result.data).toBe(data); expect(result.data).toBe(data);
expect(result.metadata.contentType).toBe('application/trig'); expect(result.metadata.contentType).toBe('application/trig');
}); });
it('returns all matching output types.', async(): Promise<void> => {
await expect(converter.getOutputTypes('application/n-triples')).resolves.toEqual({
'text/turtle': 1,
'application/trig': 1,
'application/n-quads': 1,
'application/octet-stream': 1,
'internal/anything': 1,
});
});
}); });