refactor: Use record for representation preference.

This commit is contained in:
Ruben Verborgh
2021-01-04 23:06:52 +01:00
parent 15d1ae179f
commit 4828912593
21 changed files with 107 additions and 128 deletions

View File

@@ -122,7 +122,7 @@ export class WebAclAuthorizer extends Authorizer {
try {
const acl = await this.aclManager.getAclDocument(id);
this.logger.debug(`Trying to read the ACL document ${acl.path}`);
const data = await this.resourceStore.getRepresentation(acl, { type: [{ value: INTERNAL_QUADS, weight: 1 }]});
const data = await this.resourceStore.getRepresentation(acl, { type: { [INTERNAL_QUADS]: 1 }});
this.logger.info(`Reading ACL statements from ${acl.path}`);
const resourceId = await this.aclManager.getAclConstrainedResource(id);

View File

@@ -6,7 +6,6 @@ import {
parseAcceptEncoding,
parseAcceptLanguage,
} from '../../util/HeaderUtil';
import type { RepresentationPreference } from '../representation/RepresentationPreference';
import type { RepresentationPreferences } from '../representation/RepresentationPreferences';
import { PreferenceParser } from './PreferenceParser';
@@ -31,13 +30,13 @@ export class AcceptPreferenceParser extends PreferenceParser {
(Object.keys(headers) as (keyof RepresentationPreferences)[]).forEach((key): void => {
const preferences = this.parseHeader(headers[key]!.func, headers[key]!.val);
if (preferences.length > 0) {
result[key] = preferences;
result[key] = Object.fromEntries(preferences);
}
});
// Accept-DateTime is currently specified to simply have a datetime as value
if (input.headers['accept-datetime']) {
result.datetime = [{ value: input.headers['accept-datetime'] as string, weight: 1 }];
result.datetime = { [input.headers['accept-datetime'] as string]: 1 };
}
return result;
@@ -48,13 +47,10 @@ export class AcceptPreferenceParser extends PreferenceParser {
* @param input - Input header string.
* @param parseFunction - Function that converts header string to {@link AcceptHeader}.
*
* @returns A list of {@link RepresentationPreference}. Returns an empty list if input was not defined.
* @returns A list of preferences. Returns an empty list if input was not defined.
*/
private parseHeader(parseFunction: (input: string) => AcceptHeader[], input?: string): RepresentationPreference[] {
if (!input) {
return [];
}
return parseFunction(input).map((accept): RepresentationPreference =>
({ value: accept.range, weight: accept.weight }));
private parseHeader(parseFunction: (input: string) => AcceptHeader[], input?: string): [string, number][] {
return (input ? parseFunction(input) : [])
.map(({ range, weight }): [string, number] => [ range, weight ]);
}
}

View File

@@ -1,18 +1,10 @@
/**
* Represents a single preference in a request.
*/
export interface RepresentationPreference {
/**
* The actual preference value.
*/
value: string;
/**
* How preferred this value is in a number going from 0 to 1.
* Follows the quality values rule from RFC 7231:
*
* "The weight is normalized to a real number in the range 0 through 1,
* where 0.001 is the least preferred and 1 is the most preferred; a
* value of 0 means "not acceptable"."
*/
weight: number;
}
* Represents preferred values along a single content negotiation dimension.
*
* The number represents how preferred this value is from 0 to 1.
* Follows the quality values rule from RFC 7231:
* "The weight is normalized to a real number in the range 0 through 1,
* where 0.001 is the least preferred and 1 is the most preferred; a
* value of 0 means "not acceptable"."
*/
export type RepresentationPreference = Record<string, number>;

View File

@@ -4,9 +4,9 @@ import type { RepresentationPreference } from './RepresentationPreference';
* Contains the preferences of which kind of representation is requested.
*/
export interface RepresentationPreferences {
type?: RepresentationPreference[];
charset?: RepresentationPreference[];
datetime?: RepresentationPreference[];
encoding?: RepresentationPreference[];
language?: RepresentationPreference[];
type?: RepresentationPreference;
charset?: RepresentationPreference;
datetime?: RepresentationPreference;
encoding?: RepresentationPreference;
language?: RepresentationPreference;
}

View File

@@ -90,8 +90,8 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
return input.representation;
}
this.logger.debug(`Conversion needed for ${input.identifier
.path} from ${input.representation.metadata.contentType} to satisfy ${input.preferences.type
.map((pref): string => `${pref.value};q=${pref.weight}`).join(', ')}`);
.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
@@ -107,7 +107,7 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
if (!this.inType) {
return representation;
}
const preferences: RepresentationPreferences = { type: [{ value: this.inType, weight: 1 }]};
const preferences: RepresentationPreferences = { type: { [this.inType]: 1 }};
return this.convertRepresentation({ identifier, representation, preferences }, this.inConverter);
}

View File

@@ -58,7 +58,7 @@ export class ChainedConverter extends TypedRepresentationConverter {
const args = { ...input };
for (let i = 0; i < this.converters.length - 1; ++i) {
const value = await this.getMatchingType(this.converters[i], this.converters[i + 1]);
args.preferences = { type: [{ value, weight: 1 }]};
args.preferences = { type: { [value]: 1 }};
args.representation = await this.converters[i].handle(args);
}
args.preferences = input.preferences;

View File

@@ -1,4 +1,3 @@
import type { RepresentationPreference } from '../../ldp/representation/RepresentationPreference';
import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences';
import { INTERNAL_ALL } from '../../util/ContentTypes';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
@@ -21,43 +20,42 @@ import type { RepresentationConverterArgs } from './RepresentationConverter';
*
* @returns The weighted and filtered list of matching types.
*/
export const matchingMediaTypes = (preferences: RepresentationPreferences, types: string[]):
RepresentationPreference[] => {
if (!Array.isArray(preferences.type)) {
export const matchingMediaTypes = (preferences: RepresentationPreferences, available: string[]):
string[] => {
const preferredTypes = preferences.type;
if (!preferredTypes || Object.keys(preferredTypes).length === 0) {
throw new BadRequestHttpError('Output type required for conversion.');
}
const prefMap = preferences.type.reduce((map: Record<string, number>, pref): Record<string, number> => {
if (map[pref.value]) {
throw new BadRequestHttpError(`Duplicate type preference found: ${pref.value}`);
}
map[pref.value] = pref.weight;
return map;
}, {});
// Prevent accidental use of internal types
if (!prefMap[INTERNAL_ALL]) {
prefMap[INTERNAL_ALL] = 0;
if (!preferredTypes[INTERNAL_ALL]) {
preferredTypes[INTERNAL_ALL] = 0;
}
// RFC 7231
// Media ranges can be overridden by more specific media ranges or
// specific media types. If more than one media range applies to a
// given type, the most specific reference has precedence.
const weightedSupported = types.map((type): RepresentationPreference => {
const weightedSupported = available.map((type): [string, number] => {
const match = /^([^/]+)\/([^\s;]+)/u.exec(type);
if (!match) {
throw new InternalServerError(`Unexpected type preference: ${type}`);
}
const [ , main, sub ] = match;
const weight = prefMap[type] ?? prefMap[`${main}/${sub}`] ?? prefMap[`${main}/*`] ?? prefMap['*/*'] ?? 0;
return { value: type, weight };
const weight =
preferredTypes[type] ??
preferredTypes[`${main}/${sub}`] ??
preferredTypes[`${main}/*`] ??
preferredTypes['*/*'] ??
0;
return [ type, weight ];
});
// Return all non-zero preferences in descending order of weight
return weightedSupported
.filter((pref): boolean => pref.weight !== 0)
.sort((prefA, prefB): number => prefB.weight - prefA.weight);
.filter(([ , weight ]): boolean => weight !== 0)
.sort(([ , weightA ], [ , weightB ]): number => weightB - weightA)
.map(([ type ]): string => type);
};
/**

View File

@@ -2,6 +2,7 @@ import type { Readable } from 'stream';
import rdfSerializer from 'rdf-serialize';
import type { Representation } from '../../ldp/representation/Representation';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import type { RepresentationPreference } from '../../ldp/representation/RepresentationPreference';
import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { guardStream } from '../../util/GuardedStream';
@@ -14,11 +15,11 @@ import { TypedRepresentationConverter } from './TypedRepresentationConverter';
* Converts `internal/quads` to most major RDF serializations.
*/
export class QuadToRdfConverter extends TypedRepresentationConverter {
public async getInputTypes(): Promise<Record<string, number>> {
public async getInputTypes(): Promise<RepresentationPreference> {
return { [INTERNAL_QUADS]: 1 };
}
public async getOutputTypes(): Promise<Record<string, number>> {
public async getOutputTypes(): Promise<RepresentationPreference> {
return rdfSerializer.getContentTypesPrioritized();
}
@@ -27,7 +28,7 @@ export class QuadToRdfConverter extends TypedRepresentationConverter {
}
private async quadsToRdf(quads: Representation, preferences: RepresentationPreferences): Promise<Representation> {
const contentType = matchingMediaTypes(preferences, await rdfSerializer.getContentTypes())[0].value;
const contentType = matchingMediaTypes(preferences, await rdfSerializer.getContentTypes())[0];
const metadata = new RepresentationMetadata(quads.metadata, { [CONTENT_TYPE]: contentType });
return {
binary: true,

View File

@@ -1,3 +1,4 @@
import type { RepresentationPreference } from '../../ldp/representation/RepresentationPreference';
import { supportsConversion } from './ConversionUtil';
import { RepresentationConverter } from './RepresentationConverter';
import type { RepresentationConverterArgs } from './RepresentationConverter';
@@ -7,18 +8,14 @@ import type { RepresentationConverterArgs } from './RepresentationConverter';
*/
export abstract class TypedRepresentationConverter extends RepresentationConverter {
/**
* Get a hash of all supported input content types for this converter, mapped to a numerical priority.
* The priority weight goes from 0 up to 1.
* @returns A promise resolving to a hash mapping content type to a priority number.
* Gets the supported input content types for this converter, mapped to a numerical priority.
*/
public abstract getInputTypes(): Promise<Record<string, number>>;
public abstract getInputTypes(): Promise<RepresentationPreference>;
/**
* Get a hash of all supported output content types for this converter, mapped to a numerical priority.
* The priority weight goes from 0 up to 1.
* @returns A promise resolving to a hash mapping content type to a priority number.
* Gets the supported output content types for this converter, mapped to a numerical quality.
*/
public abstract getOutputTypes(): Promise<Record<string, number>>;
public abstract getOutputTypes(): Promise<RepresentationPreference>;
/**
* Verifies whether this converter supports the input.

View File

@@ -86,7 +86,8 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
const store = new Store<BaseQuad>();
try {
// Read the quads of the current representation
const quads = await this.source.getRepresentation(identifier, { type: [{ value: INTERNAL_QUADS, weight: 1 }]});
const quads = await this.source.getRepresentation(identifier,
{ type: { [INTERNAL_QUADS]: 1 }});
const importEmitter = store.import(quads.data);
await new Promise((resolve, reject): void => {
importEmitter.on('end', resolve);

View File

@@ -15,7 +15,7 @@ export class PreferenceSupport {
private readonly converter: RepresentationConverter;
public constructor(type: string, converter: RepresentationConverter) {
this.preferences = { type: [{ value: type, weight: 1 }]};
this.preferences = { type: { [type]: 1 }};
this.converter = converter;
}

View File

@@ -25,7 +25,7 @@ describe('A ChainedConverter', (): void => {
const result = await converter.handleSafe({
representation,
preferences: { type: [{ value: 'text/turtle', weight: 1 }]},
preferences: { type: { 'text/turtle': 1 }},
identifier: { path: 'path' },
});
@@ -43,7 +43,7 @@ describe('A ChainedConverter', (): void => {
const result = await converter.handleSafe({
representation,
preferences: { type: [{ value: 'application/ld+json', weight: 1 }]},
preferences: { type: { 'application/ld+json': 1 }},
identifier: { path: 'path' },
});

View File

@@ -34,8 +34,8 @@ describe('A BasicRequestParser with simple input parsers', (): void => {
method: 'POST',
target: { path: 'http://test.com/' },
preferences: {
type: [{ value: 'text/turtle', weight: 0.8 }],
language: [{ value: 'en-gb', weight: 1 }, { value: 'en', weight: 0.5 }],
type: { 'text/turtle': 0.8 },
language: { 'en-gb': 1, en: 0.5 },
},
body: {
data: expect.any(Readable),

View File

@@ -14,33 +14,30 @@ describe('An AcceptPreferenceParser', (): void => {
it('parses accept headers.', async(): Promise<void> => {
await expect(preferenceParser.handle({ headers: { accept: 'audio/*; q=0.2, audio/basic' }} as HttpRequest))
.resolves.toEqual({ type: [{ value: 'audio/basic', weight: 1 }, { value: 'audio/*', weight: 0.2 }]});
.resolves.toEqual({ type: { 'audio/basic': 1, 'audio/*': 0.2 }});
});
it('parses accept-charset headers.', async(): Promise<void> => {
await expect(preferenceParser.handle(
{ headers: { 'accept-charset': 'iso-8859-5, unicode-1-1;q=0.8' }} as unknown as HttpRequest,
)).resolves.toEqual({ charset: [{ value: 'iso-8859-5', weight: 1 }, { value: 'unicode-1-1', weight: 0.8 }]});
)).resolves.toEqual({ charset: { 'iso-8859-5': 1, 'unicode-1-1': 0.8 }});
});
it('parses accept-datetime headers.', async(): Promise<void> => {
await expect(preferenceParser.handle(
{ headers: { 'accept-datetime': 'Tue, 20 Mar 2001 20:35:00 GMT' }} as unknown as HttpRequest,
)).resolves.toEqual({ datetime: [{ value: 'Tue, 20 Mar 2001 20:35:00 GMT', weight: 1 }]});
// eslint-disable-next-line @typescript-eslint/naming-convention
)).resolves.toEqual({ datetime: { 'Tue, 20 Mar 2001 20:35:00 GMT': 1 }});
});
it('parses accept-encoding headers.', async(): Promise<void> => {
await expect(preferenceParser.handle(
{ headers: { 'accept-encoding': 'gzip;q=1.0, identity; q=0.5, *;q=0' }} as unknown as HttpRequest,
)).resolves.toEqual(
{ encoding: [{ value: 'gzip', weight: 1 }, { value: 'identity', weight: 0.5 }, { value: '*', weight: 0 }]},
);
)).resolves.toEqual({ encoding: { gzip: 1, identity: 0.5, '*': 0 }});
});
it('parses accept-language headers.', async(): Promise<void> => {
await expect(preferenceParser.handle({ headers: { 'accept-language': 'da, en-gb;q=0.8, en;q=0.7' }} as HttpRequest))
.resolves.toEqual(
{ language: [{ value: 'da', weight: 1 }, { value: 'en-gb', weight: 0.8 }, { value: 'en', weight: 0.7 }]},
);
.resolves.toEqual({ language: { da: 1, 'en-gb': 0.8, en: 0.7 }});
});
});

View File

@@ -32,9 +32,8 @@ describe('A RepresentationConvertingStore', (): void => {
});
it('returns the Representation from the source if no changes are required.', async(): Promise<void> => {
const result = await store.getRepresentation({ path: 'path' }, { type: [
{ value: 'application/*', weight: 0 }, { value: 'text/turtle', weight: 1 },
]});
const result = await store.getRepresentation({ path: 'path' },
{ type: { 'application/*': 0, 'text/turtle': 1 }});
expect(result).toEqual({
data: 'data',
metadata: expect.any(RepresentationMetadata),
@@ -43,7 +42,7 @@ describe('A RepresentationConvertingStore', (): void => {
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(source.getRepresentation).toHaveBeenLastCalledWith(
{ path: 'path' },
{ type: [{ value: 'application/*', weight: 0 }, { value: 'text/turtle', weight: 1 }]},
{ type: { 'application/*': 0, 'text/turtle': 1, 'internal/*': 0 }},
undefined,
);
expect(outConverter.handleSafe).toHaveBeenCalledTimes(0);
@@ -64,15 +63,14 @@ describe('A RepresentationConvertingStore', (): void => {
});
it('calls the converter if another output is preferred.', async(): Promise<void> => {
await expect(store.getRepresentation({ path: 'path' }, { type: [
{ value: 'text/plain', weight: 1 }, { value: 'text/turtle', weight: 0 },
]})).resolves.toEqual(convertedOut);
await expect(store.getRepresentation({ path: 'path' },
{ type: { 'text/plain': 1, 'text/turtle': 0 }})).resolves.toEqual(convertedOut);
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(outConverter.handleSafe).toHaveBeenCalledTimes(1);
expect(outConverter.handleSafe).toHaveBeenLastCalledWith({
identifier: { path: 'path' },
representation: { data: 'data', metadata },
preferences: { type: [{ value: 'text/plain', weight: 1 }, { value: 'text/turtle', weight: 0 }]},
preferences: { type: { 'text/plain': 1, 'text/turtle': 0, 'internal/*': 0 }},
});
});

View File

@@ -31,7 +31,7 @@ class DummyConverter extends TypedRepresentationConverter {
public async handle(input: RepresentationConverterArgs): Promise<Representation> {
const metadata = new RepresentationMetadata(input.representation.metadata,
{ [CONTENT_TYPE]: input.preferences.type![0].value });
{ [CONTENT_TYPE]: Object.keys(input.preferences.type!)[0] });
return { ...input.representation, metadata };
}
}
@@ -53,7 +53,7 @@ describe('A ChainedConverter', (): void => {
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
representation = { metadata } as Representation;
preferences = { type: [{ value: 'internal/quads', weight: 1 }]};
preferences = { type: { 'internal/quads': 1 }};
args = { representation, preferences, identifier: { path: 'path' }};
});

View File

@@ -7,7 +7,6 @@ import {
matchingMediaTypes,
supportsConversion,
} from '../../../../src/storage/conversion/ConversionUtil';
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
import { InternalServerError } from '../../../../src/util/errors/InternalServerError';
describe('ConversionUtil', (): void => {
@@ -29,21 +28,24 @@ describe('ConversionUtil', (): void => {
it('requires a matching input type.', async(): Promise<void> => {
metadata.contentType = 'a/x';
const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]};
const preferences: RepresentationPreferences =
{ type: { 'b/x': 1 }};
expect((): any => supportsConversion({ identifier, representation, preferences }, [ 'c/x' ], [ 'a/x' ]))
.toThrow('Can only convert from c/x to a/x.');
});
it('requires a matching output type.', async(): Promise<void> => {
metadata.contentType = 'a/x';
const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]};
const preferences: RepresentationPreferences =
{ type: { 'b/x': 1 }};
expect((): any => supportsConversion({ identifier, representation, preferences }, [ 'a/x' ], [ 'c/x' ]))
.toThrow('Can only convert from a/x to c/x.');
});
it('succeeds with a valid input and output type.', async(): Promise<void> => {
metadata.contentType = 'a/x';
const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]};
const preferences: RepresentationPreferences =
{ type: { 'b/x': 1 }};
expect(supportsConversion({ identifier, representation, preferences }, [ 'a/x' ], [ 'b/x' ]))
.toBeUndefined();
});
@@ -51,55 +53,52 @@ describe('ConversionUtil', (): void => {
describe('#matchingMediaTypes', (): void => {
it('requires type preferences.', async(): Promise<void> => {
const preferences: RepresentationPreferences = {};
const preferences: RepresentationPreferences =
{};
expect((): any => matchingMediaTypes(preferences, [ 'a/b' ]))
.toThrow('Output type required for conversion.');
});
it('returns matching types if weight > 0.', async(): Promise<void> => {
const preferences: RepresentationPreferences = { type:
[{ value: 'a/x', weight: 1 }, { value: 'b/x', weight: 0.5 }, { value: 'c/x', weight: 0 }]};
expect(matchingMediaTypes(preferences, [ 'b/x', 'c/x' ])).toEqual([{ value: 'b/x', weight: 0.5 }]);
const preferences: RepresentationPreferences =
{ type: { 'a/x': 1, 'b/x': 0.5, 'c/x': 0 }};
expect(matchingMediaTypes(preferences, [ 'b/x', 'c/x' ]))
.toEqual([ 'b/x' ]);
});
it('sorts by descending weight.', async(): Promise<void> => {
const preferences: RepresentationPreferences = { type:
[{ value: 'a/x', weight: 1 }, { value: 'b/x', weight: 0.5 }, { value: 'c/x', weight: 0.8 }]};
expect(matchingMediaTypes(preferences, [ 'a/x', 'b/x', 'c/x' ]))
.toEqual([{ value: 'a/x', weight: 1 }, { value: 'c/x', weight: 0.8 }, { value: 'b/x', weight: 0.5 }]);
});
it('errors if there are duplicate preferences.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: [{ value: 'b/x', weight: 1 }, { value: 'b/x', weight: 0 }]};
expect((): any => matchingMediaTypes(preferences, [ 'b/x' ]))
.toThrow(new BadRequestHttpError(`Duplicate type preference found: b/x`));
{ type: { 'a/x': 1, 'b/x': 0.5, 'c/x': 0.8 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'b/x', 'c/x' ]))
.toEqual([ 'a/x', 'c/x', 'b/x' ]);
});
it('errors if there invalid types.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: [{ value: 'b/x', weight: 1 }]};
{ type: { 'b/x': 1 }};
expect((): any => matchingMediaTypes(preferences, [ 'noType' ]))
.toThrow(new InternalServerError(`Unexpected type preference: noType`));
});
it('filters out internal types.', async(): Promise<void> => {
const preferences: RepresentationPreferences = { type: [{ value: '*/*', weight: 1 }]};
expect(matchingMediaTypes(preferences, [ 'a/x', 'internal/quads' ])).toEqual([{ value: 'a/x', weight: 1 }]);
const preferences: RepresentationPreferences =
{ type: { '*/*': 1 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'internal/quads' ]))
.toEqual([ 'a/x' ]);
});
it('keeps internal types that are specifically requested.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: [{ value: '*/*', weight: 1 }, { value: 'internal/*', weight: 0.5 }]};
{ type: { '*/*': 1, 'internal/*': 0.5 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'internal/quads' ]))
.toEqual([{ value: 'a/x', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]);
.toEqual([ 'a/x', 'internal/quads' ]);
});
it('takes the most relevant weight for a type.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: [{ value: '*/*', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]};
{ type: { '*/*': 1, 'internal/quads': 0.5 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'internal/quads' ]))
.toEqual([{ value: 'a/x', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]);
.toEqual([ 'a/x', 'internal/quads' ]);
});
});

View File

@@ -25,13 +25,13 @@ describe('A QuadToRdfConverter', (): void => {
it('can handle quad to turtle conversions.', async(): Promise<void> => {
const representation = { metadata } as Representation;
const preferences: RepresentationPreferences = { type: [{ value: 'text/turtle', weight: 1 }]};
const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }};
await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined();
});
it('can handle quad to JSON-LD conversions.', async(): Promise<void> => {
const representation = { metadata } as Representation;
const preferences: RepresentationPreferences = { type: [{ value: 'application/ld+json', weight: 1 }]};
const preferences: RepresentationPreferences = { type: { 'application/ld+json': 1 }};
await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined();
});
@@ -44,7 +44,7 @@ describe('A QuadToRdfConverter', (): void => {
) ]),
metadata,
} as Representation;
const preferences: RepresentationPreferences = { type: [{ value: 'text/turtle', weight: 1 }]};
const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }};
const result = await converter.handle({ identifier, representation, preferences });
expect(result).toMatchObject({
binary: true,
@@ -67,7 +67,7 @@ describe('A QuadToRdfConverter', (): void => {
) ]),
metadata,
} as Representation;
const preferences: RepresentationPreferences = { type: [{ value: 'application/ld+json', weight: 1 }]};
const preferences: RepresentationPreferences = { type: { 'application/ld+json': 1 }};
const result = await converter.handle({ identifier, representation, preferences });
expect(result).toMatchObject({
binary: true,

View File

@@ -28,14 +28,14 @@ describe('A RdfToQuadConverter.test.ts', (): void => {
it('can handle turtle to quad conversions.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
const representation = { metadata } as Representation;
const preferences: RepresentationPreferences = { type: [{ value: INTERNAL_QUADS, weight: 1 }]};
const preferences: RepresentationPreferences = { type: { [INTERNAL_QUADS]: 1 }};
await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined();
});
it('can handle JSON-LD to quad conversions.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/ld+json' });
const representation = { metadata } as Representation;
const preferences: RepresentationPreferences = { type: [{ value: INTERNAL_QUADS, weight: 1 }]};
const preferences: RepresentationPreferences = { type: { [INTERNAL_QUADS]: 1 }};
await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined();
});
@@ -45,7 +45,7 @@ describe('A RdfToQuadConverter.test.ts', (): void => {
data: streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]),
metadata,
} as Representation;
const preferences: RepresentationPreferences = { type: [{ value: INTERNAL_QUADS, weight: 1 }]};
const preferences: RepresentationPreferences = { type: { [INTERNAL_QUADS]: 1 }};
const result = await converter.handle({ identifier, representation, preferences });
expect(result).toEqual({
binary: false,
@@ -66,7 +66,7 @@ describe('A RdfToQuadConverter.test.ts', (): void => {
data: streamifyArray([ '{"@id": "http://test.com/s", "http://test.com/p": { "@id": "http://test.com/o" }}' ]),
metadata,
} as Representation;
const preferences: RepresentationPreferences = { type: [{ value: INTERNAL_QUADS, weight: 1 }]};
const preferences: RepresentationPreferences = { type: { [INTERNAL_QUADS]: 1 }};
const result = await converter.handle({ identifier, representation, preferences });
expect(result).toEqual({
binary: false,
@@ -87,7 +87,7 @@ describe('A RdfToQuadConverter.test.ts', (): void => {
data: streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.co' ]),
metadata,
} as Representation;
const preferences: RepresentationPreferences = { type: [{ value: INTERNAL_QUADS, weight: 1 }]};
const preferences: RepresentationPreferences = { type: { [INTERNAL_QUADS]: 1 }};
const result = await converter.handle({ identifier, representation, preferences });
expect(result).toEqual({
binary: false,

View File

@@ -68,7 +68,7 @@ describe('A SparqlUpdatePatchHandler', (): void => {
const basicChecks = async(quads: Quad[]): Promise<boolean> => {
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(source.getRepresentation).toHaveBeenLastCalledWith(
{ path: 'path' }, { type: [{ value: INTERNAL_QUADS, weight: 1 }]},
{ path: 'path' }, { type: { [INTERNAL_QUADS]: 1 }},
);
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
expect(order).toEqual([ 'acquire', 'getRepresentation', 'setRepresentation', 'release' ]);

View File

@@ -7,7 +7,7 @@ import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpE
describe('A PreferenceSupport', (): void => {
const type = 'internal/quads';
const preferences: RepresentationPreferences = { type: [{ value: type, weight: 1 }]};
const preferences: RepresentationPreferences = { type: { [type]: 1 }};
let converter: RepresentationConverter;
let support: PreferenceSupport;
const identifier: ResourceIdentifier = 'identifier' as any;