feat: Add IfNeededConverter and PassthroughConverter.

This commit is contained in:
Ruben Verborgh
2021-01-16 22:44:37 +01:00
committed by Joachim Van Herwegen
parent 542901488f
commit 6763500466
6 changed files with 213 additions and 61 deletions

View File

@@ -2,10 +2,10 @@ import type { Representation } from '../ldp/representation/Representation';
import type { RepresentationPreferences } from '../ldp/representation/RepresentationPreferences';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import { InternalServerError } from '../util/errors/InternalServerError';
import type { Conditions } from './Conditions';
import { matchingMediaTypes } from './conversion/ConversionUtil';
import type { RepresentationConverter, RepresentationConverterArgs } from './conversion/RepresentationConverter';
import { IfNeededConverter } from './conversion/IfNeededConverter';
import { PassthroughConverter } from './conversion/PassthroughConverter';
import type { RepresentationConverter } from './conversion/RepresentationConverter';
import { PassthroughStore } from './PassthroughStore';
import type { ResourceStore } from './ResourceStore';
@@ -27,10 +27,9 @@ import type { ResourceStore } from './ResourceStore';
export class RepresentationConvertingStore<T extends ResourceStore = ResourceStore> extends PassthroughStore<T> {
protected readonly logger = getLoggerFor(this);
private readonly inConverter?: RepresentationConverter;
private readonly outConverter?: RepresentationConverter;
private readonly inType?: string;
private readonly inConverter: RepresentationConverter;
private readonly outConverter: RepresentationConverter;
private readonly inPreferences: RepresentationPreferences;
/**
* TODO: This should take RepresentationPreferences instead of a type string when supported by Components.js.
@@ -41,74 +40,29 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
inType?: string;
}) {
super(source);
this.inConverter = options.inConverter;
this.outConverter = options.outConverter;
this.inType = options.inType;
const { inConverter, outConverter, inType } = options;
this.inConverter = inConverter ? new IfNeededConverter(inConverter) : new PassthroughConverter();
this.outConverter = outConverter ? new IfNeededConverter(outConverter) : new PassthroughConverter();
this.inPreferences = !inType ? {} : { type: { [inType]: 1 }};
}
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
conditions?: Conditions): Promise<Representation> {
const representation = await super.getRepresentation(identifier, preferences, conditions);
return this.convertRepresentation({ identifier, representation, preferences }, this.outConverter);
return this.outConverter.handleSafe({ identifier, representation, preferences });
}
public async addResource(container: ResourceIdentifier, representation: Representation,
public async addResource(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
// We can potentially run into problems here if we convert a turtle document where the base IRI is required,
// since we don't know the resource IRI yet at this point.
representation = await this.convertInRepresentation(container, representation);
return this.source.addResource(container, representation, conditions);
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
return this.source.addResource(identifier, representation, conditions);
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<void> {
representation = await this.convertInRepresentation(identifier, representation);
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
return this.source.setRepresentation(identifier, representation, conditions);
}
/**
* Helper function that checks if the given representation matches the given preferences.
*/
private matchesPreferences(representation: Representation, preferences: RepresentationPreferences): boolean {
const { contentType } = representation.metadata;
if (!contentType) {
throw new InternalServerError('Content-Type is required for data conversion.');
}
// Check if there is a result if we try to map the preferences to the content-type
return matchingMediaTypes(preferences.type, { [contentType]: 1 }).length > 0;
}
/**
* Helper function that converts a Representation using the given args and converter,
* if the conversion is necessary and there is a converter.
*/
private async convertRepresentation(input: RepresentationConverterArgs, converter?: RepresentationConverter):
Promise<Representation> {
if (!converter || !input.preferences.type || this.matchesPreferences(input.representation, input.preferences)) {
return input.representation;
}
this.logger.debug(`Conversion needed for ${input.identifier
.path} from ${input.representation.metadata.contentType} to satisfy ${Object.entries(input.preferences.type)
.map(([ value, weight ]): string => `${value};q=${weight}`).join(', ')}`);
const converted = await converter.handleSafe(input);
this.logger.info(`Converted representation for ${input.identifier
.path} from ${input.representation.metadata.contentType} to ${converted.metadata.contentType}`);
return converted;
}
/**
* Helper function that converts an incoming representation if necessary.
*/
private async convertInRepresentation(identifier: ResourceIdentifier, representation: Representation):
Promise<Representation> {
if (!this.inType) {
return representation;
}
const preferences: RepresentationPreferences = { type: { [this.inType]: 1 }};
return this.convertRepresentation({ identifier, representation, preferences }, this.inConverter);
}
}