mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Incorporate server-side representation quality.
Closes https://github.com/solid/community-server/issues/467
This commit is contained in:
@@ -77,7 +77,7 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
|
||||
}
|
||||
|
||||
// Check if there is a result if we try to map the preferences to the content-type
|
||||
return matchingMediaTypes(preferences, [ contentType ]).length > 0;
|
||||
return matchingMediaTypes(preferences.type, { [contentType]: 1 }).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,13 +46,7 @@ export class ChainedConverter extends TypedRepresentationConverter {
|
||||
public async canHandle(input: RepresentationConverterArgs): Promise<void> {
|
||||
// We assume a chain can be constructed, otherwise there would be a configuration issue
|
||||
// So we only check if the input can be parsed and the preferred type can be written
|
||||
const inTypes = this.getAcceptableTypes(await this.first.getInputTypes());
|
||||
const outTypes = this.getAcceptableTypes(await this.last.getOutputTypes());
|
||||
supportsConversion(input, inTypes, outTypes);
|
||||
}
|
||||
|
||||
private getAcceptableTypes(preferences: ValuePreferences): string[] {
|
||||
return Object.keys(preferences).filter((name): boolean => preferences[name] > 0);
|
||||
supportsConversion(input, await this.first.getInputTypes(), await this.last.getOutputTypes());
|
||||
}
|
||||
|
||||
public async handle(input: RepresentationConverterArgs): Promise<Representation> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences';
|
||||
import type { ValuePreferences } from '../../ldp/representation/RepresentationPreferences';
|
||||
import { INTERNAL_ALL } from '../../util/ContentTypes';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { InternalServerError } from '../../util/errors/InternalServerError';
|
||||
@@ -12,43 +12,42 @@ import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
* Since more specific media ranges override less specific ones,
|
||||
* this will be ignored if there is a specific internal type preference.
|
||||
*
|
||||
* @param preferences - Preferences for output type.
|
||||
* @param types - Media types to compare to the preferences.
|
||||
* @param preferredTypes - Preferences for output type.
|
||||
* @param availableTypes - Media types to compare to the preferences.
|
||||
*
|
||||
* @throws BadRequestHttpError
|
||||
* If the type preferences are undefined or if there are duplicate preferences.
|
||||
*
|
||||
* @returns The weighted and filtered list of matching types.
|
||||
*/
|
||||
export const matchingMediaTypes = (preferences: RepresentationPreferences, available: string[]):
|
||||
export const matchingMediaTypes = (preferredTypes: ValuePreferences = {}, availableTypes: ValuePreferences = {}):
|
||||
string[] => {
|
||||
const preferredTypes = preferences.type;
|
||||
if (!preferredTypes || Object.keys(preferredTypes).length === 0) {
|
||||
throw new BadRequestHttpError('Output type required for conversion.');
|
||||
}
|
||||
|
||||
// No preference means anything is acceptable
|
||||
const preferred = { ...preferredTypes };
|
||||
if (Object.keys(preferredTypes).length === 0) {
|
||||
preferred['*/*'] = 1;
|
||||
// Prevent accidental use of internal types
|
||||
if (!preferredTypes[INTERNAL_ALL]) {
|
||||
preferredTypes[INTERNAL_ALL] = 0;
|
||||
} else if (!(INTERNAL_ALL in preferred)) {
|
||||
preferred[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 = available.map((type): [string, number] => {
|
||||
const weightedSupported = Object.entries(availableTypes).map(([ type, quality ]): [string, number] => {
|
||||
const match = /^([^/]+)\/([^\s;]+)/u.exec(type);
|
||||
if (!match) {
|
||||
throw new InternalServerError(`Unexpected type preference: ${type}`);
|
||||
}
|
||||
const [ , main, sub ] = match;
|
||||
const weight =
|
||||
preferredTypes[type] ??
|
||||
preferredTypes[`${main}/${sub}`] ??
|
||||
preferredTypes[`${main}/*`] ??
|
||||
preferredTypes['*/*'] ??
|
||||
preferred[type] ??
|
||||
preferred[`${main}/${sub}`] ??
|
||||
preferred[`${main}/*`] ??
|
||||
preferred['*/*'] ??
|
||||
0;
|
||||
return [ type, weight ];
|
||||
return [ type, weight * quality ];
|
||||
});
|
||||
|
||||
// Return all non-zero preferences in descending order of weight
|
||||
@@ -96,16 +95,16 @@ export const matchesMediaType = (mediaA: string, mediaB: string): boolean => {
|
||||
* @param supportedIn - Media types that can be parsed by the converter.
|
||||
* @param supportedOut - Media types that can be produced by the converter.
|
||||
*/
|
||||
export const supportsConversion = (request: RepresentationConverterArgs, supportedIn: string[],
|
||||
supportedOut: string[]): void => {
|
||||
export const supportsConversion = (request: RepresentationConverterArgs, supportedIn: ValuePreferences,
|
||||
supportedOut: ValuePreferences): void => {
|
||||
const inType = request.representation.metadata.contentType;
|
||||
if (!inType) {
|
||||
throw new BadRequestHttpError('Input type required for conversion.');
|
||||
throw new BadRequestHttpError('No content type indicated on request.');
|
||||
}
|
||||
if (!supportedIn.some((type): boolean => matchesMediaType(inType, type))) {
|
||||
throw new NotImplementedHttpError(`Can only convert from ${supportedIn} to ${supportedOut}.`);
|
||||
}
|
||||
if (matchingMediaTypes(request.preferences, supportedOut).length <= 0) {
|
||||
throw new NotImplementedHttpError(`Can only convert from ${supportedIn} to ${supportedOut}.`);
|
||||
if (!Object.keys(supportedIn).some((type): boolean => matchesMediaType(inType, type)) ||
|
||||
matchingMediaTypes(request.preferences.type, supportedOut).length === 0) {
|
||||
throw new NotImplementedHttpError(
|
||||
`Can only convert from ${Object.keys(supportedIn)} to ${Object.keys(supportedOut)}.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,8 +29,8 @@ export class QuadToRdfConverter extends TypedRepresentationConverter {
|
||||
return this.quadsToRdf(input.representation, input.preferences);
|
||||
}
|
||||
|
||||
private async quadsToRdf(quads: Representation, preferences: RepresentationPreferences): Promise<Representation> {
|
||||
const contentType = matchingMediaTypes(preferences, await rdfSerializer.getContentTypes())[0];
|
||||
private async quadsToRdf(quads: Representation, { type }: RepresentationPreferences): Promise<Representation> {
|
||||
const contentType = matchingMediaTypes(type, await this.getOutputTypes())[0];
|
||||
const metadata = new RepresentationMetadata(quads.metadata, { [CONTENT_TYPE]: contentType });
|
||||
return {
|
||||
binary: true,
|
||||
|
||||
@@ -23,6 +23,6 @@ export abstract class TypedRepresentationConverter extends RepresentationConvert
|
||||
public async canHandle(args: RepresentationConverterArgs): Promise<void> {
|
||||
const types = [ this.getInputTypes(), this.getOutputTypes() ];
|
||||
const [ inputTypes, outputTypes ] = await Promise.all(types);
|
||||
supportsConversion(args, Object.keys(inputTypes), Object.keys(outputTypes));
|
||||
supportsConversion(args, inputTypes, outputTypes);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user