feat: Incorporate server-side representation quality.

Closes https://github.com/solid/community-server/issues/467
This commit is contained in:
Ruben Verborgh
2021-01-05 00:12:26 +01:00
parent 09ae959333
commit 8cd3f7d2e5
8 changed files with 70 additions and 73 deletions

View File

@@ -42,7 +42,7 @@ describe('A RepresentationConvertingStore', (): void => {
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(source.getRepresentation).toHaveBeenLastCalledWith(
{ path: 'path' },
{ type: { 'application/*': 0, 'text/turtle': 1, 'internal/*': 0 }},
{ type: { 'application/*': 0, 'text/turtle': 1 }},
undefined,
);
expect(outConverter.handleSafe).toHaveBeenCalledTimes(0);
@@ -70,7 +70,7 @@ describe('A RepresentationConvertingStore', (): void => {
expect(outConverter.handleSafe).toHaveBeenLastCalledWith({
identifier: { path: 'path' },
representation: { data: 'data', metadata },
preferences: { type: { 'text/plain': 1, 'text/turtle': 0, 'internal/*': 0 }},
preferences: { type: { 'text/plain': 1, 'text/turtle': 0 }},
});
});

View File

@@ -5,7 +5,6 @@ import type {
RepresentationPreferences,
} from '../../../../src/ldp/representation/RepresentationPreferences';
import { ChainedConverter } from '../../../../src/storage/conversion/ChainedConverter';
import { supportsConversion } from '../../../../src/storage/conversion/ConversionUtil';
import type { RepresentationConverterArgs } from '../../../../src/storage/conversion/RepresentationConverter';
import { TypedRepresentationConverter } from '../../../../src/storage/conversion/TypedRepresentationConverter';
import { CONTENT_TYPE } from '../../../../src/util/Vocabularies';
@@ -28,10 +27,6 @@ class DummyConverter extends TypedRepresentationConverter {
return this.outTypes;
}
public async canHandle(input: RepresentationConverterArgs): Promise<void> {
supportsConversion(input, Object.keys(this.inTypes), Object.keys(this.outTypes));
}
public async handle(input: RepresentationConverterArgs): Promise<Representation> {
const metadata = new RepresentationMetadata(input.representation.metadata,
{ [CONTENT_TYPE]: Object.keys(input.preferences.type!)[0] });
@@ -85,7 +80,7 @@ describe('A ChainedConverter', (): void => {
});
it('errors if the end of the chain does not support the preferences.', async(): Promise<void> => {
delete preferences.type;
preferences.type = { 'abc/def': 1 };
await expect(converter.canHandle(args)).rejects.toThrow();
});

View File

@@ -1,6 +1,9 @@
import type { Representation } from '../../../../src/ldp/representation/Representation';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import type { RepresentationPreferences } from '../../../../src/ldp/representation/RepresentationPreferences';
import type {
ValuePreferences,
RepresentationPreferences,
} from '../../../../src/ldp/representation/RepresentationPreferences';
import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier';
import {
matchesMediaType,
@@ -22,15 +25,19 @@ describe('ConversionUtil', (): void => {
describe('#supportsConversion', (): void => {
it('requires an input type.', async(): Promise<void> => {
const preferences: RepresentationPreferences = {};
expect((): any => supportsConversion({ identifier, representation, preferences }, [ 'a/x' ], [ 'a/x' ]))
.toThrow('Input type required for conversion.');
expect((): any => supportsConversion({ identifier, representation, preferences },
{ 'a/x': 1 },
{ 'a/x': 1 }))
.toThrow('No content type indicated on request.');
});
it('requires a matching input type.', async(): Promise<void> => {
metadata.contentType = 'a/x';
const preferences: RepresentationPreferences =
{ type: { 'b/x': 1 }};
expect((): any => supportsConversion({ identifier, representation, preferences }, [ 'c/x' ], [ 'a/x' ]))
expect((): any => supportsConversion({ identifier, representation, preferences },
{ 'c/x': 1 },
{ 'a/x': 1 }))
.toThrow('Can only convert from c/x to a/x.');
});
@@ -38,7 +45,9 @@ describe('ConversionUtil', (): void => {
metadata.contentType = 'a/x';
const preferences: RepresentationPreferences =
{ type: { 'b/x': 1 }};
expect((): any => supportsConversion({ identifier, representation, preferences }, [ 'a/x' ], [ 'c/x' ]))
expect((): any => supportsConversion({ identifier, representation, preferences },
{ 'a/x': 1 },
{ 'c/x': 1 }))
.toThrow('Can only convert from a/x to c/x.');
});
@@ -46,58 +55,58 @@ describe('ConversionUtil', (): void => {
metadata.contentType = 'a/x';
const preferences: RepresentationPreferences =
{ type: { 'b/x': 1 }};
expect(supportsConversion({ identifier, representation, preferences }, [ 'a/x' ], [ 'b/x' ]))
expect(supportsConversion({ identifier, representation, preferences },
{ 'a/x': 1 },
{ 'b/x': 1 }))
.toBeUndefined();
});
});
describe('#matchingMediaTypes', (): void => {
it('requires type preferences.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{};
expect((): any => matchingMediaTypes(preferences, [ 'a/b' ]))
.toThrow('Output type required for conversion.');
it('returns the empty array if no preferences specified.', async(): Promise<void> => {
expect(matchingMediaTypes())
.toEqual([]);
});
it('returns matching types if weight > 0.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: { 'a/x': 1, 'b/x': 0.5, 'c/x': 0 }};
expect(matchingMediaTypes(preferences, [ 'b/x', 'c/x' ]))
const preferences: ValuePreferences = { 'a/x': 1, 'b/x': 0.5, 'c/x': 0 };
expect(matchingMediaTypes(preferences, { 'b/x': 1, 'c/x': 1 }))
.toEqual([ 'b/x' ]);
});
it('sorts by descending weight.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: { 'a/x': 1, 'b/x': 0.5, 'c/x': 0.8 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'b/x', 'c/x' ]))
const preferences: ValuePreferences = { 'a/x': 1, 'b/x': 0.5, 'c/x': 0.8 };
expect(matchingMediaTypes(preferences, { 'a/x': 1, 'b/x': 1, 'c/x': 1 }))
.toEqual([ 'a/x', 'c/x', 'b/x' ]);
});
it('incorporates representation qualities when calculating weight.', async(): Promise<void> => {
const preferences: ValuePreferences = { 'a/x': 1, 'b/x': 0.5, 'c/x': 0.8 };
expect(matchingMediaTypes(preferences, { 'a/x': 0.1, 'b/x': 1, 'c/x': 0.6 }))
.toEqual([ 'b/x', 'c/x', 'a/x' ]);
});
it('errors if there invalid types.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: { 'b/x': 1 }};
expect((): any => matchingMediaTypes(preferences, [ 'noType' ]))
const preferences: ValuePreferences = { 'b/x': 1 };
expect((): any => matchingMediaTypes(preferences, { noType: 1 }))
.toThrow(new InternalServerError(`Unexpected type preference: noType`));
});
it('filters out internal types.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: { '*/*': 1 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'internal/quads' ]))
const preferences: ValuePreferences = { '*/*': 1 };
expect(matchingMediaTypes(preferences, { 'a/x': 1, 'internal/quads': 1 }))
.toEqual([ 'a/x' ]);
});
it('keeps internal types that are specifically requested.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: { '*/*': 1, 'internal/*': 0.5 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'internal/quads' ]))
const preferences: ValuePreferences = { '*/*': 1, 'internal/*': 0.5 };
expect(matchingMediaTypes(preferences, { 'a/x': 1, 'internal/quads': 1 }))
.toEqual([ 'a/x', 'internal/quads' ]);
});
it('takes the most relevant weight for a type.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: { '*/*': 1, 'internal/quads': 0.5 }};
expect(matchingMediaTypes(preferences, [ 'a/x', 'internal/quads' ]))
const preferences: ValuePreferences = { '*/*': 1, 'internal/quads': 0.5 };
expect(matchingMediaTypes(preferences, { 'a/x': 1, 'internal/quads': 1 }))
.toEqual([ 'a/x', 'internal/quads' ]);
});
});