feat: Make internal/quads unacceptable output

This commit is contained in:
Joachim Van Herwegen 2020-11-09 11:33:35 +01:00
parent 69ed2e069f
commit 715ba126f9
3 changed files with 35 additions and 6 deletions

View File

@ -1,5 +1,6 @@
import type { RepresentationPreference } from '../../ldp/representation/RepresentationPreference'; import type { RepresentationPreference } from '../../ldp/representation/RepresentationPreference';
import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences'; import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences';
import { INTERNAL_ALL } from '../../util/ContentTypes';
import { InternalServerError } from '../../util/errors/InternalServerError'; import { InternalServerError } from '../../util/errors/InternalServerError';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { matchingMediaType } from '../../util/Util'; import { matchingMediaType } from '../../util/Util';
@ -8,6 +9,9 @@ import type { RepresentationConverterArgs } from './RepresentationConverter';
/** /**
* Filters media types based on the given preferences. * Filters media types based on the given preferences.
* Based on RFC 7231 - Content negotiation. * Based on RFC 7231 - Content negotiation.
* Will add a default `internal/*;q=0` to the preferences to prevent accidental use of internal types.
* 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 preferences - Preferences for output type.
* @param types - Media types to compare to the preferences. * @param types - Media types to compare to the preferences.
@ -31,6 +35,11 @@ RepresentationPreference[] => {
return map; return map;
}, {}); }, {});
// Prevent accidental use of internal types
if (!prefMap[INTERNAL_ALL]) {
prefMap[INTERNAL_ALL] = 0;
}
// RFC 7231 // RFC 7231
// Media ranges can be overridden by more specific media ranges or // Media ranges can be overridden by more specific media ranges or
// specific media types. If more than one media range applies to a // specific media types. If more than one media range applies to a

View File

@ -4,4 +4,5 @@ export const APPLICATION_OCTET_STREAM = 'application/octet-stream';
export const APPLICATION_SPARQL_UPDATE = 'application/sparql-update'; export const APPLICATION_SPARQL_UPDATE = 'application/sparql-update';
// Internal (non-exposed) content types // Internal (non-exposed) content types
export const INTERNAL_ALL = 'internal/*';
export const INTERNAL_QUADS = 'internal/quads'; export const INTERNAL_QUADS = 'internal/quads';

View File

@ -19,22 +19,22 @@ describe('A ConversionUtil', (): void => {
describe('#checkRequest', (): void => { describe('#checkRequest', (): void => {
it('requires an input type.', async(): Promise<void> => { it('requires an input type.', async(): Promise<void> => {
const preferences: RepresentationPreferences = {}; const preferences: RepresentationPreferences = {};
expect((): any => checkRequest({ identifier, representation, preferences }, [ '*/*' ], [ '*/*' ])) expect((): any => checkRequest({ identifier, representation, preferences }, [ 'a/x' ], [ 'a/x' ]))
.toThrow('Input type required for conversion.'); .toThrow('Input type required for conversion.');
}); });
it('requires a matching input type.', async(): Promise<void> => { it('requires a matching input type.', async(): Promise<void> => {
metadata.contentType = 'a/x'; metadata.contentType = 'a/x';
const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]}; const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]};
expect((): any => checkRequest({ identifier, representation, preferences }, [ 'c/x' ], [ '*/*' ])) expect((): any => checkRequest({ identifier, representation, preferences }, [ 'c/x' ], [ 'a/x' ]))
.toThrow('Can only convert from c/x to */*.'); .toThrow('Can only convert from c/x to a/x.');
}); });
it('requires a matching output type.', async(): Promise<void> => { it('requires a matching output type.', async(): Promise<void> => {
metadata.contentType = 'a/x'; metadata.contentType = 'a/x';
const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]}; const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]};
expect((): any => checkRequest({ identifier, representation, preferences }, [ '*/*' ], [ 'c/x' ])) expect((): any => checkRequest({ identifier, representation, preferences }, [ 'a/x' ], [ 'c/x' ]))
.toThrow('Can only convert from */* to c/x.'); .toThrow('Can only convert from a/x to c/x.');
}); });
it('succeeds with a valid input and output type.', async(): Promise<void> => { it('succeeds with a valid input and output type.', async(): Promise<void> => {
@ -48,7 +48,7 @@ describe('A ConversionUtil', (): void => {
describe('#matchingTypes', (): void => { describe('#matchingTypes', (): void => {
it('requires type preferences.', async(): Promise<void> => { it('requires type preferences.', async(): Promise<void> => {
const preferences: RepresentationPreferences = {}; const preferences: RepresentationPreferences = {};
expect((): any => matchingTypes(preferences, [ '*/*' ])) expect((): any => matchingTypes(preferences, [ 'a/b' ]))
.toThrow('Output type required for conversion.'); .toThrow('Output type required for conversion.');
}); });
@ -71,5 +71,24 @@ describe('A ConversionUtil', (): void => {
expect((): any => matchingTypes(preferences, [ 'noType' ])) expect((): any => matchingTypes(preferences, [ 'noType' ]))
.toThrow(new InternalServerError(`Unexpected type preference: noType`)); .toThrow(new InternalServerError(`Unexpected type preference: noType`));
}); });
it('filters out internal types.', async(): Promise<void> => {
const preferences: RepresentationPreferences = { type: [{ value: '*/*', weight: 1 }]};
expect(matchingTypes(preferences, [ 'a/x', 'internal/quads' ])).toEqual([{ value: 'a/x', weight: 1 }]);
});
it('keeps internal types that are specifically requested.', async(): Promise<void> => {
const preferences: RepresentationPreferences =
{ type: [{ value: '*/*', weight: 1 }, { value: 'internal/*', weight: 0.5 }]};
expect(matchingTypes(preferences, [ 'a/x', 'internal/quads' ]))
.toEqual([{ value: 'a/x', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]);
});
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 }]};
expect(matchingTypes(preferences, [ 'a/x', 'internal/quads' ]))
.toEqual([{ value: 'a/x', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]);
});
}); });
}); });