mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Create BaseTypedRepresentationConverter
This commit is contained in:
parent
3861e64398
commit
27306d6e3f
@ -250,6 +250,7 @@ export * from './storage/accessors/InMemoryDataAccessor';
|
||||
export * from './storage/accessors/SparqlDataAccessor';
|
||||
|
||||
// Storage/Conversion
|
||||
export * from './storage/conversion/BaseTypedRepresentationConverter';
|
||||
export * from './storage/conversion/ChainedConverter';
|
||||
export * from './storage/conversion/ConstantConverter';
|
||||
export * from './storage/conversion/ContainerToTemplateConverter';
|
||||
|
76
src/storage/conversion/BaseTypedRepresentationConverter.ts
Normal file
76
src/storage/conversion/BaseTypedRepresentationConverter.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import type { ValuePreferences } from '../../http/representation/RepresentationPreferences';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { getConversionTarget, getTypeWeight } from './ConversionUtil';
|
||||
import { RepresentationConverter } from './RepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
|
||||
type PromiseOrValue<T> = T | Promise<T>;
|
||||
type ValuePreferencesArg =
|
||||
PromiseOrValue<string> |
|
||||
PromiseOrValue<string[]> |
|
||||
PromiseOrValue<ValuePreferences>;
|
||||
|
||||
async function toValuePreferences(arg: ValuePreferencesArg): Promise<ValuePreferences> {
|
||||
const resolved = await arg;
|
||||
if (typeof resolved === 'string') {
|
||||
return { [resolved]: 1 };
|
||||
}
|
||||
if (Array.isArray(resolved)) {
|
||||
return Object.fromEntries(resolved.map((type): [string, number] => [ type, 1 ]));
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link RepresentationConverter} that allows requesting the supported types.
|
||||
*/
|
||||
export abstract class BaseTypedRepresentationConverter extends RepresentationConverter {
|
||||
protected inputTypes: Promise<ValuePreferences>;
|
||||
protected outputTypes: Promise<ValuePreferences>;
|
||||
|
||||
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.
|
||||
*/
|
||||
public async getInputTypes(): Promise<ValuePreferences> {
|
||||
return this.inputTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the supported output content types for this converter, mapped to a numerical quality.
|
||||
*/
|
||||
public async getOutputTypes(): Promise<ValuePreferences> {
|
||||
return this.outputTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given conversion request is supported,
|
||||
* given the available content type conversions:
|
||||
* - Checks if there is a content type for the input.
|
||||
* - Checks if the input type is supported by the parser.
|
||||
* - Checks if the parser can produce one of the preferred output types.
|
||||
* 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 outputPreferences = args.preferences.type ?? {};
|
||||
if (getTypeWeight(contentType, inputTypes) === 0 || !getConversionTarget(outputTypes, outputPreferences)) {
|
||||
throw new NotImplementedHttpError(
|
||||
`Cannot convert from ${contentType} to ${Object.keys(outputPreferences)
|
||||
}, only from ${Object.keys(inputTypes)} to ${Object.keys(outputTypes)}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,8 +11,8 @@ import { isContainerIdentifier, isContainerPath } from '../../util/PathUtil';
|
||||
import { endOfStream } from '../../util/StreamUtil';
|
||||
import type { TemplateEngine } from '../../util/templates/TemplateEngine';
|
||||
import { LDP } from '../../util/Vocabularies';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
interface ResourceDetails {
|
||||
name: string;
|
||||
@ -23,7 +23,7 @@ interface ResourceDetails {
|
||||
/**
|
||||
* A {@link RepresentationConverter} that creates a templated representation of a container.
|
||||
*/
|
||||
export class ContainerToTemplateConverter extends TypedRepresentationConverter {
|
||||
export class ContainerToTemplateConverter extends BaseTypedRepresentationConverter {
|
||||
private readonly identifierStrategy: IdentifierStrategy;
|
||||
private readonly templateEngine: TemplateEngine;
|
||||
private readonly contentType: string;
|
||||
|
@ -3,13 +3,13 @@ import type { Representation } from '../../http/representation/Representation';
|
||||
import { APPLICATION_JSON, INTERNAL_ERROR } from '../../util/ContentTypes';
|
||||
import { HttpError } from '../../util/errors/HttpError';
|
||||
import { getSingleItem } from '../../util/StreamUtil';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts an Error object to JSON by copying its fields.
|
||||
*/
|
||||
export class ErrorToJsonConverter extends TypedRepresentationConverter {
|
||||
export class ErrorToJsonConverter extends BaseTypedRepresentationConverter {
|
||||
public constructor() {
|
||||
super(INTERNAL_ERROR, APPLICATION_JSON);
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ import { RepresentationMetadata } from '../../http/representation/Representation
|
||||
import { INTERNAL_ERROR, INTERNAL_QUADS } from '../../util/ContentTypes';
|
||||
import { getSingleItem } from '../../util/StreamUtil';
|
||||
import { DC, SOLID_ERROR } from '../../util/Vocabularies';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts an error object into quads by creating a triple for each of name/message/stack.
|
||||
*/
|
||||
export class ErrorToQuadConverter extends TypedRepresentationConverter {
|
||||
export class ErrorToQuadConverter extends BaseTypedRepresentationConverter {
|
||||
public constructor() {
|
||||
super(INTERNAL_ERROR, INTERNAL_QUADS);
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import { HttpError } from '../../util/errors/HttpError';
|
||||
import { modulePathPlaceholder } from '../../util/PathUtil';
|
||||
import { getSingleItem } from '../../util/StreamUtil';
|
||||
import type { TemplateEngine } from '../../util/templates/TemplateEngine';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
// Fields optional due to https://github.com/LinkedSoftwareDependencies/Components.js/issues/20
|
||||
export interface TemplateOptions {
|
||||
@ -35,7 +35,7 @@ const DEFAULT_TEMPLATE_OPTIONS: TemplateOptions = {
|
||||
* That result will be passed as an additional parameter to the main templating call,
|
||||
* using the variable `codeMessage`.
|
||||
*/
|
||||
export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
||||
export class ErrorToTemplateConverter extends BaseTypedRepresentationConverter {
|
||||
private readonly templateEngine: TemplateEngine;
|
||||
private readonly mainTemplatePath: string;
|
||||
private readonly codeTemplatesPath: string;
|
||||
|
@ -5,14 +5,14 @@ import { RepresentationMetadata } from '../../http/representation/Representation
|
||||
import { APPLICATION_JSON, APPLICATION_X_WWW_FORM_URLENCODED } from '../../util/ContentTypes';
|
||||
import { readableToString } from '../../util/StreamUtil';
|
||||
import { CONTENT_TYPE } from '../../util/Vocabularies';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts application/x-www-form-urlencoded data to application/json.
|
||||
* Due to the nature of form data, the result will be a simple key/value JSON object.
|
||||
*/
|
||||
export class FormToJsonConverter extends TypedRepresentationConverter {
|
||||
export class FormToJsonConverter extends BaseTypedRepresentationConverter {
|
||||
public constructor() {
|
||||
super(APPLICATION_X_WWW_FORM_URLENCODED, APPLICATION_JSON);
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import type { Representation } from '../../http/representation/Representation';
|
||||
import { TEXT_HTML, TEXT_MARKDOWN } from '../../util/ContentTypes';
|
||||
import { readableToString } from '../../util/StreamUtil';
|
||||
import type { TemplateEngine } from '../../util/templates/TemplateEngine';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts Markdown data to HTML.
|
||||
@ -13,7 +13,7 @@ import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
* A standard Markdown string will be converted to a <p> tag, so html and body tags should be part of the template.
|
||||
* In case the Markdown body starts with a header (#), that value will also be used as `title` parameter.
|
||||
*/
|
||||
export class MarkdownToHtmlConverter extends TypedRepresentationConverter {
|
||||
export class MarkdownToHtmlConverter extends BaseTypedRepresentationConverter {
|
||||
private readonly templateEngine: TemplateEngine;
|
||||
|
||||
public constructor(templateEngine: TemplateEngine) {
|
||||
|
@ -7,14 +7,14 @@ import type { ValuePreferences } from '../../http/representation/RepresentationP
|
||||
import { INTERNAL_QUADS } from '../../util/ContentTypes';
|
||||
import { pipeSafely } from '../../util/StreamUtil';
|
||||
import { PREFERRED_PREFIX_TERM } from '../../util/Vocabularies';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import { getConversionTarget } from './ConversionUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts `internal/quads` to most major RDF serializations.
|
||||
*/
|
||||
export class QuadToRdfConverter extends TypedRepresentationConverter {
|
||||
export class QuadToRdfConverter extends BaseTypedRepresentationConverter {
|
||||
private readonly outputPreferences?: ValuePreferences;
|
||||
|
||||
public constructor(options: { outputPreferences?: Record<string, number> } = {}) {
|
||||
|
@ -5,13 +5,13 @@ import type { Representation } from '../../http/representation/Representation';
|
||||
import { INTERNAL_QUADS } from '../../util/ContentTypes';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { pipeSafely } from '../../util/StreamUtil';
|
||||
import { BaseTypedRepresentationConverter } from './BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts most major RDF serializations to `internal/quads`.
|
||||
*/
|
||||
export class RdfToQuadConverter extends TypedRepresentationConverter {
|
||||
export class RdfToQuadConverter extends BaseTypedRepresentationConverter {
|
||||
public constructor() {
|
||||
super(rdfParser.getContentTypesPrioritized(), INTERNAL_QUADS);
|
||||
}
|
||||
|
@ -1,76 +1,17 @@
|
||||
import type { ValuePreferences } from '../../http/representation/RepresentationPreferences';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { getConversionTarget, getTypeWeight } from './ConversionUtil';
|
||||
import { RepresentationConverter } from './RepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
|
||||
type PromiseOrValue<T> = T | Promise<T>;
|
||||
type ValuePreferencesArg =
|
||||
PromiseOrValue<string> |
|
||||
PromiseOrValue<string[]> |
|
||||
PromiseOrValue<ValuePreferences>;
|
||||
|
||||
async function toValuePreferences(arg: ValuePreferencesArg): Promise<ValuePreferences> {
|
||||
const resolved = await arg;
|
||||
if (typeof resolved === 'string') {
|
||||
return { [resolved]: 1 };
|
||||
}
|
||||
if (Array.isArray(resolved)) {
|
||||
return Object.fromEntries(resolved.map((type): [string, number] => [ type, 1 ]));
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link RepresentationConverter} that allows requesting the supported types.
|
||||
*/
|
||||
export abstract class TypedRepresentationConverter extends RepresentationConverter {
|
||||
protected inputTypes: Promise<ValuePreferences>;
|
||||
protected outputTypes: Promise<ValuePreferences>;
|
||||
|
||||
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.
|
||||
*/
|
||||
public async getInputTypes(): Promise<ValuePreferences> {
|
||||
return this.inputTypes;
|
||||
}
|
||||
public abstract getInputTypes(): Promise<ValuePreferences>;
|
||||
|
||||
/**
|
||||
* Gets the supported output content types for this converter, mapped to a numerical quality.
|
||||
*/
|
||||
public async getOutputTypes(): Promise<ValuePreferences> {
|
||||
return this.outputTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given conversion request is supported,
|
||||
* given the available content type conversions:
|
||||
* - Checks if there is a content type for the input.
|
||||
* - Checks if the input type is supported by the parser.
|
||||
* - Checks if the parser can produce one of the preferred output types.
|
||||
* 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 outputPreferences = args.preferences.type ?? {};
|
||||
if (getTypeWeight(contentType, inputTypes) === 0 || !getConversionTarget(outputTypes, outputPreferences)) {
|
||||
throw new NotImplementedHttpError(
|
||||
`Cannot convert from ${contentType} to ${Object.keys(outputPreferences)
|
||||
}, only from ${Object.keys(inputTypes)} to ${Object.keys(outputTypes)}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
public abstract getOutputTypes(): Promise<ValuePreferences>;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
RepresentationMetadata,
|
||||
TypedRepresentationConverter,
|
||||
readableToString,
|
||||
ChainedConverter,
|
||||
guardedStreamFrom,
|
||||
@ -12,6 +11,7 @@ import {
|
||||
import type { Representation,
|
||||
RepresentationConverterArgs,
|
||||
Logger } from '../../src';
|
||||
import { BaseTypedRepresentationConverter } from '../../src/storage/conversion/BaseTypedRepresentationConverter';
|
||||
|
||||
jest.mock('../../src/logging/LogUtil', (): any => {
|
||||
const logger: Logger =
|
||||
@ -20,7 +20,7 @@ jest.mock('../../src/logging/LogUtil', (): any => {
|
||||
});
|
||||
const logger: jest.Mocked<Logger> = getLoggerFor('GuardedStream') as any;
|
||||
|
||||
class DummyConverter extends TypedRepresentationConverter {
|
||||
class DummyConverter extends BaseTypedRepresentationConverter {
|
||||
public constructor() {
|
||||
super('*/*', 'custom/type');
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { BaseTypedRepresentationConverter } from '../../../../src/storage/conversion/BaseTypedRepresentationConverter';
|
||||
import type { RepresentationConverterArgs } from '../../../../src/storage/conversion/RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from '../../../../src/storage/conversion/TypedRepresentationConverter';
|
||||
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
class CustomTypedRepresentationConverter extends TypedRepresentationConverter {
|
||||
class CustomTypedRepresentationConverter extends BaseTypedRepresentationConverter {
|
||||
public handle = jest.fn();
|
||||
}
|
||||
|
||||
describe('A TypedRepresentationConverter', (): void => {
|
||||
describe('A BaseTypedRepresentationConverter', (): void => {
|
||||
it('defaults to allowing everything.', async(): Promise<void> => {
|
||||
const converter = new CustomTypedRepresentationConverter();
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({
|
@ -4,13 +4,13 @@ import type {
|
||||
RepresentationPreferences,
|
||||
ValuePreferences,
|
||||
} from '../../../../src/http/representation/RepresentationPreferences';
|
||||
import { BaseTypedRepresentationConverter } from '../../../../src/storage/conversion/BaseTypedRepresentationConverter';
|
||||
import { ChainedConverter } from '../../../../src/storage/conversion/ChainedConverter';
|
||||
import { matchesMediaType } 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';
|
||||
|
||||
class DummyConverter extends TypedRepresentationConverter {
|
||||
class DummyConverter extends BaseTypedRepresentationConverter {
|
||||
private readonly inTypes: ValuePreferences;
|
||||
private readonly outTypes: ValuePreferences;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user