mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Determine Typed Converter output based on input type
This commit is contained in:
parent
27306d6e3f
commit
fa94c7d4bb
@ -1,8 +1,8 @@
|
||||
import type { ValuePreferences } from '../../http/representation/RepresentationPreferences';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { getConversionTarget, getTypeWeight } from './ConversionUtil';
|
||||
import { RepresentationConverter } from './RepresentationConverter';
|
||||
import { getConversionTarget, getTypeWeight, preferencesToString } from './ConversionUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
type PromiseOrValue<T> = T | Promise<T>;
|
||||
type ValuePreferencesArg =
|
||||
@ -22,30 +22,37 @@ async function toValuePreferences(arg: ValuePreferencesArg): Promise<ValuePrefer
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link RepresentationConverter} that allows requesting the supported types.
|
||||
* A base {@link TypedRepresentationConverter} implementation for converters
|
||||
* that can convert from all its input types to all its output types.
|
||||
*
|
||||
* This base class handles the `canHandle` call by comparing the input content type to the stored input types
|
||||
* and the output preferences to the stored output types.
|
||||
*
|
||||
* Output weights are determined by multiplying all stored output weights with the weight of the input type.
|
||||
*/
|
||||
export abstract class BaseTypedRepresentationConverter extends RepresentationConverter {
|
||||
export abstract class BaseTypedRepresentationConverter extends TypedRepresentationConverter {
|
||||
protected inputTypes: Promise<ValuePreferences>;
|
||||
protected outputTypes: Promise<ValuePreferences>;
|
||||
|
||||
public constructor(inputTypes: ValuePreferencesArg = {}, outputTypes: ValuePreferencesArg = {}) {
|
||||
public constructor(inputTypes: ValuePreferencesArg, outputTypes: ValuePreferencesArg) {
|
||||
super();
|
||||
this.inputTypes = toValuePreferences(inputTypes);
|
||||
this.outputTypes = toValuePreferences(outputTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the supported input content types for this converter, mapped to a numerical priority.
|
||||
* Matches all inputs to all outputs.
|
||||
*/
|
||||
public async getInputTypes(): Promise<ValuePreferences> {
|
||||
return this.inputTypes;
|
||||
public async getOutputTypes(contentType: string): Promise<ValuePreferences> {
|
||||
const weight = getTypeWeight(contentType, await this.inputTypes);
|
||||
if (weight > 0) {
|
||||
const outputTypes = { ...await this.outputTypes };
|
||||
for (const [ key, value ] of Object.entries(outputTypes)) {
|
||||
outputTypes[key] = value * weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the supported output content types for this converter, mapped to a numerical quality.
|
||||
*/
|
||||
public async getOutputTypes(): Promise<ValuePreferences> {
|
||||
return this.outputTypes;
|
||||
return outputTypes;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,19 +64,18 @@ export abstract class BaseTypedRepresentationConverter extends RepresentationCon
|
||||
* Throws an error with details if conversion is not possible.
|
||||
*/
|
||||
public async canHandle(args: RepresentationConverterArgs): Promise<void> {
|
||||
const types = [ this.getInputTypes(), this.getOutputTypes() ];
|
||||
const { contentType } = args.representation.metadata;
|
||||
|
||||
if (!contentType) {
|
||||
throw new NotImplementedHttpError('Can not convert data without a Content-Type.');
|
||||
}
|
||||
|
||||
const [ inputTypes, outputTypes ] = await Promise.all(types);
|
||||
const outputTypes = await this.getOutputTypes(contentType);
|
||||
const outputPreferences = args.preferences.type ?? {};
|
||||
if (getTypeWeight(contentType, inputTypes) === 0 || !getConversionTarget(outputTypes, outputPreferences)) {
|
||||
if (!getConversionTarget(outputTypes, outputPreferences)) {
|
||||
throw new NotImplementedHttpError(
|
||||
`Cannot convert from ${contentType} to ${Object.keys(outputPreferences)
|
||||
}, only from ${Object.keys(inputTypes)} to ${Object.keys(outputTypes)}.`,
|
||||
`Cannot convert from ${contentType} to ${preferencesToString(outputPreferences)
|
||||
}, only to ${preferencesToString(outputTypes)}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,16 @@ import type { ValuePreference, ValuePreferences } from '../../http/representatio
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { cleanPreferences, getBestPreference, getTypeWeight } from './ConversionUtil';
|
||||
import { cleanPreferences, getBestPreference, getTypeWeight, preferencesToString } from './ConversionUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { RepresentationConverter } from './RepresentationConverter';
|
||||
import type { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
type ConverterPreference = ValuePreference & { converter: TypedRepresentationConverter };
|
||||
type ConverterPreference = {
|
||||
converter: TypedRepresentationConverter;
|
||||
inType: string;
|
||||
outTypes: ValuePreferences;
|
||||
};
|
||||
|
||||
/**
|
||||
* A chain of converters that can go from `inTypes` to `outTypes`.
|
||||
@ -17,16 +21,15 @@ type ConverterPreference = ValuePreference & { converter: TypedRepresentationCon
|
||||
type ConversionPath = {
|
||||
converters: TypedRepresentationConverter[];
|
||||
intermediateTypes: string[];
|
||||
inTypes: ValuePreferences;
|
||||
inType: string;
|
||||
outTypes: ValuePreferences;
|
||||
};
|
||||
|
||||
/**
|
||||
* The result of applying a `ConversionPath` to a specific input.
|
||||
* The result of choosing a specific output for a `ConversionPath`.
|
||||
*/
|
||||
type MatchedPath = {
|
||||
path: ConversionPath;
|
||||
inType: string;
|
||||
outType: string;
|
||||
weight: number;
|
||||
};
|
||||
@ -78,8 +81,8 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
return input.representation;
|
||||
}
|
||||
|
||||
const { path, inType, outType } = match;
|
||||
this.logger.debug(`Converting ${inType} -> ${[ ...path.intermediateTypes, outType ].join(' -> ')}.`);
|
||||
const { path, outType } = match;
|
||||
this.logger.debug(`Converting ${path.inType} -> ${[ ...path.intermediateTypes, outType ].join(' -> ')}.`);
|
||||
|
||||
const args = { ...input };
|
||||
for (let i = 0; i < path.converters.length - 1; ++i) {
|
||||
@ -105,7 +108,7 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
|
||||
const weight = getTypeWeight(type, preferences);
|
||||
if (weight > 0) {
|
||||
this.logger.debug(`No conversion required: ${type} already matches ${Object.keys(preferences)}`);
|
||||
this.logger.debug(`No conversion required: ${type} already matches ${preferencesToString(preferences)}`);
|
||||
return { value: type, weight };
|
||||
}
|
||||
|
||||
@ -122,13 +125,13 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
// Generate paths from all converters that match the input type
|
||||
let paths = await this.converters.reduce(async(matches: Promise<ConversionPath[]>, converter):
|
||||
Promise<ConversionPath[]> => {
|
||||
const inTypes = await converter.getInputTypes();
|
||||
if (getTypeWeight(inType, inTypes) > 0) {
|
||||
const outTypes = await converter.getOutputTypes(inType);
|
||||
if (Object.keys(outTypes).length > 0) {
|
||||
(await matches).push({
|
||||
converters: [ converter ],
|
||||
intermediateTypes: [],
|
||||
inTypes,
|
||||
outTypes: await converter.getOutputTypes(),
|
||||
inType,
|
||||
outTypes,
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
@ -137,18 +140,18 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
// It's impossible for a path to have a higher weight than this value
|
||||
const maxWeight = Math.max(...Object.values(outPreferences));
|
||||
|
||||
let bestPath = this.findBest(inType, outPreferences, paths);
|
||||
paths = this.removeBadPaths(paths, maxWeight, inType, bestPath);
|
||||
let bestPath = this.findBest(outPreferences, paths);
|
||||
paths = this.removeBadPaths(paths, maxWeight, bestPath);
|
||||
// This will always stop at some point since paths can't have the same converter twice
|
||||
while (paths.length > 0) {
|
||||
// For every path, find all the paths that can be made by adding 1 more converter
|
||||
const promises = paths.map(async(path): Promise<ConversionPath[]> => this.takeStep(path));
|
||||
paths = (await Promise.all(promises)).flat();
|
||||
const newBest = this.findBest(inType, outPreferences, paths);
|
||||
const newBest = this.findBest(outPreferences, paths);
|
||||
if (newBest && (!bestPath || newBest.weight > bestPath.weight)) {
|
||||
bestPath = newBest;
|
||||
}
|
||||
paths = this.removeBadPaths(paths, maxWeight, inType, bestPath);
|
||||
paths = this.removeBadPaths(paths, maxWeight, bestPath);
|
||||
}
|
||||
|
||||
if (!bestPath) {
|
||||
@ -161,18 +164,17 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path from the given list that can convert the given type to the given preferences.
|
||||
* Finds the path from the given list that can convert to the given preferences.
|
||||
* If there are multiple matches the one with the highest result weight gets chosen.
|
||||
* Will return undefined if there are no matches.
|
||||
*/
|
||||
private findBest(type: string, preferences: ValuePreferences, paths: ConversionPath[]): MatchedPath | undefined {
|
||||
private findBest(preferences: ValuePreferences, paths: ConversionPath[]): MatchedPath | undefined {
|
||||
// Need to use null instead of undefined so `reduce` doesn't take the first element of the array as `best`
|
||||
return paths.reduce((best: MatchedPath | null, path): MatchedPath | null => {
|
||||
const outMatch = getBestPreference(path.outTypes, preferences);
|
||||
if (outMatch && !(best && best.weight >= outMatch.weight)) {
|
||||
// Create new MatchedPath, using the output match above
|
||||
const inWeight = getTypeWeight(type, path.inTypes);
|
||||
return { path, inType: type, outType: outMatch.value, weight: inWeight * outMatch.weight };
|
||||
return { path, outType: outMatch.value, weight: outMatch.weight };
|
||||
}
|
||||
return best;
|
||||
}, null) ?? undefined;
|
||||
@ -184,11 +186,9 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
*
|
||||
* @param paths - Paths to filter.
|
||||
* @param maxWeight - The maximum weight in the output preferences.
|
||||
* @param inType - The input type.
|
||||
* @param bestMatch - The current best path.
|
||||
*/
|
||||
private removeBadPaths(paths: ConversionPath[], maxWeight: number, inType: string, bestMatch?: MatchedPath):
|
||||
ConversionPath[] {
|
||||
private removeBadPaths(paths: ConversionPath[], maxWeight: number, bestMatch?: MatchedPath): ConversionPath[] {
|
||||
// All paths are still good if there is no best match yet
|
||||
if (!bestMatch) {
|
||||
return paths;
|
||||
@ -200,9 +200,7 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
|
||||
// Only return paths that can potentially improve upon bestPath
|
||||
return paths.filter((path): boolean => {
|
||||
const optimisticWeight = getTypeWeight(inType, path.inTypes) *
|
||||
Math.max(...Object.values(path.outTypes)) *
|
||||
maxWeight;
|
||||
const optimisticWeight = Math.max(...Object.values(path.outTypes)) * maxWeight;
|
||||
return optimisticWeight > bestMatch.weight;
|
||||
});
|
||||
}
|
||||
@ -218,9 +216,9 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
// Create a new path for every converter that can be appended
|
||||
return Promise.all(nextConverters.map(async(pref): Promise<ConversionPath> => ({
|
||||
converters: [ ...path.converters, pref.converter ],
|
||||
intermediateTypes: [ ...path.intermediateTypes, pref.value ],
|
||||
inTypes: path.inTypes,
|
||||
outTypes: this.modifyTypeWeights(pref.weight, await pref.converter.getOutputTypes()),
|
||||
intermediateTypes: [ ...path.intermediateTypes, pref.inType ],
|
||||
inType: path.inType,
|
||||
outTypes: pref.outTypes,
|
||||
})));
|
||||
}
|
||||
|
||||
@ -237,13 +235,15 @@ export class ChainedConverter extends RepresentationConverter {
|
||||
*/
|
||||
private async supportedConverters(types: ValuePreferences, converters: TypedRepresentationConverter[]):
|
||||
Promise<ConverterPreference[]> {
|
||||
const promises = converters.map(async(converter): Promise<ConverterPreference | undefined> => {
|
||||
const inputTypes = await converter.getInputTypes();
|
||||
const match = getBestPreference(types, inputTypes);
|
||||
if (match) {
|
||||
return { ...match, converter };
|
||||
const typeEntries = Object.entries(types);
|
||||
const results: ConverterPreference[] = [];
|
||||
for (const converter of converters) {
|
||||
for (const [ inType, weight ] of typeEntries) {
|
||||
let outTypes = await converter.getOutputTypes(inType);
|
||||
outTypes = this.modifyTypeWeights(weight, outTypes);
|
||||
results.push({ converter, inType, outTypes });
|
||||
}
|
||||
});
|
||||
return (await Promise.all(promises)).filter(Boolean) as ConverterPreference[];
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
@ -164,3 +164,13 @@ export function matchesMediaType(mediaA: string, mediaB: string): boolean {
|
||||
export function isInternalContentType(contentType?: string): boolean {
|
||||
return typeof contentType !== 'undefined' && matchesMediaType(contentType, INTERNAL_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a preferences object to a string for display purposes.
|
||||
* @param preferences - Preferences to serialize
|
||||
*/
|
||||
export function preferencesToString(preferences: ValuePreferences): string {
|
||||
return Object.entries(preferences)
|
||||
.map(([ type, weight ]): string => `${type}:${weight}`)
|
||||
.join(',');
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export class ErrorToTemplateConverter extends BaseTypedRepresentationConverter {
|
||||
private readonly contentType: string;
|
||||
|
||||
public constructor(templateEngine: TemplateEngine, templateOptions?: TemplateOptions) {
|
||||
super(INTERNAL_ERROR, templateOptions?.contentType ?? DEFAULT_TEMPLATE_OPTIONS.contentType);
|
||||
super(INTERNAL_ERROR, templateOptions?.contentType ?? DEFAULT_TEMPLATE_OPTIONS.contentType!);
|
||||
// Workaround for https://github.com/LinkedSoftwareDependencies/Components.js/issues/20
|
||||
if (!templateOptions || Object.keys(templateOptions).length === 0) {
|
||||
templateOptions = DEFAULT_TEMPLATE_OPTIONS;
|
||||
|
@ -27,7 +27,7 @@ export class QuadToRdfConverter extends BaseTypedRepresentationConverter {
|
||||
public async handle({ identifier, representation: quads, preferences }: RepresentationConverterArgs):
|
||||
Promise<Representation> {
|
||||
// Can not be undefined if the `canHandle` call passed
|
||||
const contentType = getConversionTarget(await this.getOutputTypes(), preferences.type)!;
|
||||
const contentType = getConversionTarget(await this.getOutputTypes(INTERNAL_QUADS), preferences.type)!;
|
||||
let data: Readable;
|
||||
|
||||
// Use prefixes if possible (see https://github.com/rubensworks/rdf-serialize.js/issues/1)
|
||||
|
@ -6,12 +6,7 @@ import { RepresentationConverter } from './RepresentationConverter';
|
||||
*/
|
||||
export abstract class TypedRepresentationConverter extends RepresentationConverter {
|
||||
/**
|
||||
* Gets the supported input content types for this converter, mapped to a numerical priority.
|
||||
* Gets the output content types this converter can convert the input type to, mapped to a numerical priority.
|
||||
*/
|
||||
public abstract getInputTypes(): Promise<ValuePreferences>;
|
||||
|
||||
/**
|
||||
* Gets the supported output content types for this converter, mapped to a numerical quality.
|
||||
*/
|
||||
public abstract getOutputTypes(): Promise<ValuePreferences>;
|
||||
public abstract getOutputTypes(contentType: string): Promise<ValuePreferences>;
|
||||
}
|
||||
|
@ -22,15 +22,7 @@ const logger: jest.Mocked<Logger> = getLoggerFor('GuardedStream') as any;
|
||||
|
||||
class DummyConverter extends BaseTypedRepresentationConverter {
|
||||
public constructor() {
|
||||
super('*/*', 'custom/type');
|
||||
}
|
||||
|
||||
public async getInputTypes(): Promise<Record<string, number>> {
|
||||
return { [INTERNAL_QUADS]: 1 };
|
||||
}
|
||||
|
||||
public async getOutputTypes(): Promise<Record<string, number>> {
|
||||
return { 'x/x': 1 };
|
||||
super(INTERNAL_QUADS, 'x/x');
|
||||
}
|
||||
|
||||
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||
|
@ -7,56 +7,37 @@ class CustomTypedRepresentationConverter extends BaseTypedRepresentationConverte
|
||||
}
|
||||
|
||||
describe('A BaseTypedRepresentationConverter', (): void => {
|
||||
it('defaults to allowing everything.', async(): Promise<void> => {
|
||||
const converter = new CustomTypedRepresentationConverter();
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({
|
||||
});
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts strings.', async(): Promise<void> => {
|
||||
const converter = new CustomTypedRepresentationConverter('a/b', 'c/d');
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({
|
||||
'a/b': 1,
|
||||
});
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({
|
||||
await expect(converter.getOutputTypes('a/b')).resolves.toEqual({
|
||||
'c/d': 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts string arrays.', async(): Promise<void> => {
|
||||
const converter = new CustomTypedRepresentationConverter([ 'a/b', 'c/d' ], [ 'e/f', 'g/h' ]);
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({
|
||||
'a/b': 1,
|
||||
'c/d': 1,
|
||||
});
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({
|
||||
'e/f': 1,
|
||||
'g/h': 1,
|
||||
});
|
||||
const output = { 'e/f': 1, 'g/h': 1 };
|
||||
await expect(converter.getOutputTypes('a/b')).resolves.toEqual(output);
|
||||
await expect(converter.getOutputTypes('c/d')).resolves.toEqual(output);
|
||||
});
|
||||
|
||||
it('accepts records.', async(): Promise<void> => {
|
||||
const converter = new CustomTypedRepresentationConverter({ 'a/b': 0.5 }, { 'c/d': 0.5 });
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({
|
||||
'a/b': 0.5,
|
||||
});
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({
|
||||
'c/d': 0.5,
|
||||
await expect(converter.getOutputTypes('a/b')).resolves.toEqual({
|
||||
'c/d': 0.5 * 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
it('can not handle input without a Content-Type.', async(): Promise<void> => {
|
||||
const args: RepresentationConverterArgs = { representation: { metadata: { }}, preferences: {}} as any;
|
||||
const converter = new CustomTypedRepresentationConverter('*/*');
|
||||
const converter = new CustomTypedRepresentationConverter('*/*', 'b/b');
|
||||
await expect(converter.canHandle(args)).rejects.toThrow(NotImplementedHttpError);
|
||||
});
|
||||
|
||||
it('can not handle a type that does not match the input types.', async(): Promise<void> => {
|
||||
const args: RepresentationConverterArgs =
|
||||
{ representation: { metadata: { contentType: 'b/b' }}, preferences: {}} as any;
|
||||
const converter = new CustomTypedRepresentationConverter('a/a');
|
||||
const converter = new CustomTypedRepresentationConverter('a/a', 'b/b');
|
||||
await expect(converter.canHandle(args)).rejects.toThrow(NotImplementedHttpError);
|
||||
});
|
||||
|
||||
|
@ -15,19 +15,11 @@ class DummyConverter extends BaseTypedRepresentationConverter {
|
||||
private readonly outTypes: ValuePreferences;
|
||||
|
||||
public constructor(inTypes: ValuePreferences, outTypes: ValuePreferences) {
|
||||
super();
|
||||
super(inTypes, outTypes);
|
||||
this.inTypes = inTypes;
|
||||
this.outTypes = outTypes;
|
||||
}
|
||||
|
||||
public async getInputTypes(): Promise<ValuePreferences> {
|
||||
return this.inTypes;
|
||||
}
|
||||
|
||||
public async getOutputTypes(): Promise<ValuePreferences> {
|
||||
return this.outTypes;
|
||||
}
|
||||
|
||||
public async handle(input: RepresentationConverterArgs): Promise<Representation> {
|
||||
// Make sure the input type is supported
|
||||
const inType = input.representation.metadata.contentType!;
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
getTypeWeight,
|
||||
getWeightedPreferences, isInternalContentType,
|
||||
matchesMediaPreferences,
|
||||
matchesMediaType,
|
||||
matchesMediaType, preferencesToString,
|
||||
} from '../../../../src/storage/conversion/ConversionUtil';
|
||||
import { InternalServerError } from '../../../../src/util/errors/InternalServerError';
|
||||
|
||||
@ -153,4 +153,11 @@ describe('ConversionUtil', (): void => {
|
||||
expect(isInternalContentType('text/turtle')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#preferencesToString', (): void => {
|
||||
it('returns a string serialization.', async(): Promise<void> => {
|
||||
const preferences: ValuePreferences = { 'a/*': 1, 'b/b': 0.8, 'c/c': 0 };
|
||||
expect(preferencesToString(preferences)).toEqual('a/*:1,b/b:0.8,c/c:0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,8 +9,7 @@ describe('An ErrorToJsonConverter', (): void => {
|
||||
const preferences = {};
|
||||
|
||||
it('supports going from errors to json.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({ 'internal/error': 1 });
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ 'application/json': 1 });
|
||||
await expect(converter.getOutputTypes('internal/error')).resolves.toEqual({ 'application/json': 1 });
|
||||
});
|
||||
|
||||
it('adds all HttpError fields.', async(): Promise<void> => {
|
||||
|
@ -13,8 +13,7 @@ describe('An ErrorToQuadConverter', (): void => {
|
||||
const preferences = {};
|
||||
|
||||
it('supports going from errors to quads.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({ 'internal/error': 1 });
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ 'internal/quads': 1 });
|
||||
await expect(converter.getOutputTypes('internal/error')).resolves.toEqual({ 'internal/quads': 1 });
|
||||
});
|
||||
|
||||
it('adds triples for all error fields.', async(): Promise<void> => {
|
||||
|
@ -24,8 +24,7 @@ describe('An ErrorToTemplateConverter', (): void => {
|
||||
});
|
||||
|
||||
it('supports going from errors to the given content type.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({ 'internal/error': 1 });
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ 'text/html': 1 });
|
||||
await expect(converter.getOutputTypes('internal/error')).resolves.toEqual({ 'text/html': 1 });
|
||||
});
|
||||
|
||||
it('works with non-HTTP errors.', async(): Promise<void> => {
|
||||
|
@ -8,8 +8,8 @@ describe('A FormToJsonConverter', (): void => {
|
||||
const converter = new FormToJsonConverter();
|
||||
|
||||
it('supports going from form data to json.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({ 'application/x-www-form-urlencoded': 1 });
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ 'application/json': 1 });
|
||||
await expect(converter.getOutputTypes('application/x-www-form-urlencoded'))
|
||||
.resolves.toEqual({ 'application/json': 1 });
|
||||
});
|
||||
|
||||
it('converts form data to JSON.', async(): Promise<void> => {
|
||||
|
@ -17,8 +17,7 @@ describe('A MarkdownToHtmlConverter', (): void => {
|
||||
});
|
||||
|
||||
it('supports going from markdown to html.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({ 'text/markdown': 1 });
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ 'text/html': 1 });
|
||||
await expect(converter.getOutputTypes('text/markdown')).resolves.toEqual({ 'text/html': 1 });
|
||||
});
|
||||
|
||||
it('converts markdown and inserts it in the template.', async(): Promise<void> => {
|
||||
|
@ -19,19 +19,14 @@ describe('A QuadToRdfConverter', (): void => {
|
||||
metadata = new RepresentationMetadata(identifier, INTERNAL_QUADS);
|
||||
});
|
||||
|
||||
it('supports parsing quads.', async(): Promise<void> => {
|
||||
await expect(new QuadToRdfConverter().getInputTypes())
|
||||
.resolves.toEqual({ [INTERNAL_QUADS]: 1 });
|
||||
});
|
||||
|
||||
it('defaults to rdfSerializer preferences when given no output preferences.', async(): Promise<void> => {
|
||||
await expect(new QuadToRdfConverter().getOutputTypes())
|
||||
await expect(new QuadToRdfConverter().getOutputTypes(INTERNAL_QUADS))
|
||||
.resolves.toEqual(await rdfSerializer.getContentTypesPrioritized());
|
||||
});
|
||||
|
||||
it('supports overriding output preferences.', async(): Promise<void> => {
|
||||
const outputPreferences = { 'text/turtle': 1 };
|
||||
await expect(new QuadToRdfConverter({ outputPreferences }).getOutputTypes())
|
||||
await expect(new QuadToRdfConverter({ outputPreferences }).getOutputTypes(INTERNAL_QUADS))
|
||||
.resolves.toEqual(outputPreferences);
|
||||
});
|
||||
|
||||
|
@ -16,12 +16,11 @@ describe('A RdfToQuadConverter', (): void => {
|
||||
const converter = new RdfToQuadConverter();
|
||||
const identifier: ResourceIdentifier = { path: 'path' };
|
||||
|
||||
it('supports parsing the same types as rdfParser.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual(await rdfParser.getContentTypesPrioritized());
|
||||
});
|
||||
|
||||
it('supports serializing as quads.', async(): Promise<void> => {
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ [INTERNAL_QUADS]: 1 });
|
||||
const types = Object.entries(await rdfParser.getContentTypesPrioritized());
|
||||
for (const [ type, weight ] of types) {
|
||||
await expect(converter.getOutputTypes(type)).resolves.toEqual({ [INTERNAL_QUADS]: weight });
|
||||
}
|
||||
});
|
||||
|
||||
it('can handle turtle to quad conversions.', async(): Promise<void> => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user