mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Set default templates for ErrorToTemplateConverter
This commit is contained in:
parent
523390e444
commit
a926839216
@ -9,19 +9,13 @@
|
|||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:ErrorToQuadConverter",
|
"@id": "urn:solid-server:default:ErrorToQuadConverter",
|
||||||
"@type": "ErrorToQuadConverter",
|
"@type": "ErrorToQuadConverter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"comment": "Converts an error into a Markdown description of its details.",
|
"comment": "Converts an error into a Markdown description of its details.",
|
||||||
"@id": "urn:solid-server:default:ErrorToTemplateConverter",
|
"@id": "urn:solid-server:default:ErrorToTemplateConverter",
|
||||||
"@type": "ErrorToTemplateConverter",
|
"@type": "ErrorToTemplateConverter",
|
||||||
"templateEngine": {
|
"templateEngine": { "@type": "HandlebarsTemplateEngine" }
|
||||||
"@type": "HandlebarsTemplateEngine",
|
|
||||||
"template": "$PACKAGE_ROOT/templates/error/main.md.hbs"
|
|
||||||
},
|
|
||||||
"templatePath": "$PACKAGE_ROOT/templates/error/descriptions/",
|
|
||||||
"extension": ".md.hbs",
|
|
||||||
"contentType": "text/markdown"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,21 @@ import type { TemplateEngine } from '../../util/templates/TemplateEngine';
|
|||||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||||
|
|
||||||
|
// Fields optional due to https://github.com/LinkedSoftwareDependencies/Components.js/issues/20
|
||||||
|
export interface TemplateOptions {
|
||||||
|
mainTemplatePath?: string;
|
||||||
|
codeTemplatesPath?: string;
|
||||||
|
extension?: string;
|
||||||
|
contentType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TEMPLATE_OPTIONS: TemplateOptions = {
|
||||||
|
mainTemplatePath: '$PACKAGE_ROOT/templates/error/main.md.hbs',
|
||||||
|
codeTemplatesPath: '$PACKAGE_ROOT/templates/error/descriptions/',
|
||||||
|
extension: '.md.hbs',
|
||||||
|
contentType: 'text/markdown',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes an Error by filling in the provided template.
|
* Serializes an Error by filling in the provided template.
|
||||||
* Content-type is based on the constructor parameter.
|
* Content-type is based on the constructor parameter.
|
||||||
@ -22,16 +37,22 @@ import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
|||||||
*/
|
*/
|
||||||
export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
||||||
private readonly templateEngine: TemplateEngine;
|
private readonly templateEngine: TemplateEngine;
|
||||||
private readonly templatePath: string;
|
private readonly mainTemplatePath: string;
|
||||||
|
private readonly codeTemplatesPath: string;
|
||||||
private readonly extension: string;
|
private readonly extension: string;
|
||||||
private readonly contentType: string;
|
private readonly contentType: string;
|
||||||
|
|
||||||
public constructor(templateEngine: TemplateEngine, templatePath: string, extension: string, contentType: string) {
|
public constructor(templateEngine: TemplateEngine, templateOptions?: TemplateOptions) {
|
||||||
super(INTERNAL_ERROR, contentType);
|
super(INTERNAL_ERROR, templateOptions?.contentType ?? DEFAULT_TEMPLATE_OPTIONS.contentType);
|
||||||
|
// Workaround for https://github.com/LinkedSoftwareDependencies/Components.js/issues/20
|
||||||
|
if (!templateOptions || Object.keys(templateOptions).length === 0) {
|
||||||
|
templateOptions = DEFAULT_TEMPLATE_OPTIONS;
|
||||||
|
}
|
||||||
this.templateEngine = templateEngine;
|
this.templateEngine = templateEngine;
|
||||||
this.templatePath = templatePath;
|
this.mainTemplatePath = templateOptions.mainTemplatePath!;
|
||||||
this.extension = extension;
|
this.codeTemplatesPath = templateOptions.codeTemplatesPath!;
|
||||||
this.contentType = contentType;
|
this.extension = templateOptions.extension!;
|
||||||
|
this.contentType = templateOptions.contentType!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||||
@ -49,7 +70,7 @@ export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
|||||||
const templateFile = `${error.errorCode}${this.extension}`;
|
const templateFile = `${error.errorCode}${this.extension}`;
|
||||||
assert(/^[\w.-]+$/u.test(templateFile), 'Invalid error template name');
|
assert(/^[\w.-]+$/u.test(templateFile), 'Invalid error template name');
|
||||||
description = await this.templateEngine.render(error.details ?? {},
|
description = await this.templateEngine.render(error.details ?? {},
|
||||||
{ templateFile, templatePath: this.templatePath });
|
{ templateFile, templatePath: this.codeTemplatesPath });
|
||||||
} catch {
|
} catch {
|
||||||
// In case no template is found, or rendering errors, we still want to convert
|
// In case no template is found, or rendering errors, we still want to convert
|
||||||
}
|
}
|
||||||
@ -58,7 +79,7 @@ export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
|||||||
// Render the main template, embedding the rendered error description
|
// Render the main template, embedding the rendered error description
|
||||||
const { name, message, stack } = error;
|
const { name, message, stack } = error;
|
||||||
const variables = { name, message, stack, description };
|
const variables = { name, message, stack, description };
|
||||||
const rendered = await this.templateEngine.render(variables);
|
const rendered = await this.templateEngine.render(variables, { templateFile: this.mainTemplatePath });
|
||||||
|
|
||||||
return new BasicRepresentation(rendered, representation.metadata, this.contentType);
|
return new BasicRepresentation(rendered, representation.metadata, this.contentType);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,10 @@ import type { TemplateEngine } from '../../../../src/util/templates/TemplateEngi
|
|||||||
|
|
||||||
describe('An ErrorToTemplateConverter', (): void => {
|
describe('An ErrorToTemplateConverter', (): void => {
|
||||||
const identifier = { path: 'http://test.com/error' };
|
const identifier = { path: 'http://test.com/error' };
|
||||||
const templatePath = '/templates/codes';
|
const mainTemplatePath = '/templates/main.html';
|
||||||
|
const codeTemplatesPath = '/templates/codes';
|
||||||
|
const extension = '.html';
|
||||||
|
const contentType = 'text/html';
|
||||||
const errorCode = 'E0001';
|
const errorCode = 'E0001';
|
||||||
let templateEngine: jest.Mocked<TemplateEngine>;
|
let templateEngine: jest.Mocked<TemplateEngine>;
|
||||||
let converter: ErrorToTemplateConverter;
|
let converter: ErrorToTemplateConverter;
|
||||||
@ -17,7 +20,8 @@ describe('An ErrorToTemplateConverter', (): void => {
|
|||||||
templateEngine = {
|
templateEngine = {
|
||||||
render: jest.fn().mockReturnValue(Promise.resolve('<html>')),
|
render: jest.fn().mockReturnValue(Promise.resolve('<html>')),
|
||||||
};
|
};
|
||||||
converter = new ErrorToTemplateConverter(templateEngine, templatePath, '.html', 'text/html');
|
converter = new ErrorToTemplateConverter(templateEngine,
|
||||||
|
{ mainTemplatePath, codeTemplatesPath, extension, contentType });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports going from errors to the given content type.', async(): Promise<void> => {
|
it('supports going from errors to the given content type.', async(): Promise<void> => {
|
||||||
@ -45,6 +49,7 @@ describe('An ErrorToTemplateConverter', (): void => {
|
|||||||
expect(templateEngine.render).toHaveBeenCalledTimes(1);
|
expect(templateEngine.render).toHaveBeenCalledTimes(1);
|
||||||
expect(templateEngine.render).toHaveBeenLastCalledWith(
|
expect(templateEngine.render).toHaveBeenLastCalledWith(
|
||||||
{ name: 'Error', message: 'error text', stack: error.stack },
|
{ name: 'Error', message: 'error text', stack: error.stack },
|
||||||
|
{ templateFile: mainTemplatePath },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,7 +69,8 @@ describe('An ErrorToTemplateConverter', (): void => {
|
|||||||
{},
|
{},
|
||||||
{ templatePath: '/templates/codes', templateFile: 'H400.html' });
|
{ templatePath: '/templates/codes', templateFile: 'H400.html' });
|
||||||
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
||||||
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack });
|
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack },
|
||||||
|
{ templateFile: mainTemplatePath });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only adds stack if it is defined.', async(): Promise<void> => {
|
it('only adds stack if it is defined.', async(): Promise<void> => {
|
||||||
@ -84,7 +90,8 @@ describe('An ErrorToTemplateConverter', (): void => {
|
|||||||
{},
|
{},
|
||||||
{ templatePath: '/templates/codes', templateFile: 'H400.html' });
|
{ templatePath: '/templates/codes', templateFile: 'H400.html' });
|
||||||
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
||||||
{ name: 'BadRequestHttpError', message: 'error text' });
|
{ name: 'BadRequestHttpError', message: 'error text' },
|
||||||
|
{ templateFile: mainTemplatePath });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds additional information if an error code description is found.', async(): Promise<void> => {
|
it('adds additional information if an error code description is found.', async(): Promise<void> => {
|
||||||
@ -101,7 +108,8 @@ describe('An ErrorToTemplateConverter', (): void => {
|
|||||||
{ key: 'val' },
|
{ key: 'val' },
|
||||||
{ templatePath: '/templates/codes', templateFile: 'E0001.html' });
|
{ templatePath: '/templates/codes', templateFile: 'E0001.html' });
|
||||||
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
||||||
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack, description: '<html>' });
|
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack, description: '<html>' },
|
||||||
|
{ templateFile: mainTemplatePath });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends an empty object for additional error code parameters if none are defined.', async(): Promise<void> => {
|
it('sends an empty object for additional error code parameters if none are defined.', async(): Promise<void> => {
|
||||||
@ -119,7 +127,8 @@ describe('An ErrorToTemplateConverter', (): void => {
|
|||||||
{},
|
{},
|
||||||
{ templatePath: '/templates/codes', templateFile: 'E0001.html' });
|
{ templatePath: '/templates/codes', templateFile: 'E0001.html' });
|
||||||
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
||||||
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack, description: '<html>' });
|
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack, description: '<html>' },
|
||||||
|
{ templateFile: mainTemplatePath });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts errors with a code as usual if no corresponding template is found.', async(): Promise<void> => {
|
it('converts errors with a code as usual if no corresponding template is found.', async(): Promise<void> => {
|
||||||
@ -138,6 +147,26 @@ describe('An ErrorToTemplateConverter', (): void => {
|
|||||||
{},
|
{},
|
||||||
{ templatePath: '/templates/codes', templateFile: 'invalid.html' });
|
{ templatePath: '/templates/codes', templateFile: 'invalid.html' });
|
||||||
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
||||||
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack });
|
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack },
|
||||||
|
{ templateFile: mainTemplatePath });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has default template options.', async(): Promise<void> => {
|
||||||
|
converter = new ErrorToTemplateConverter(templateEngine);
|
||||||
|
const error = new BadRequestHttpError('error text', { errorCode, details: { key: 'val' }});
|
||||||
|
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||||
|
const prom = converter.handle({ identifier, representation, preferences });
|
||||||
|
await expect(prom).resolves.toBeDefined();
|
||||||
|
const result = await prom;
|
||||||
|
expect(result.binary).toBe(true);
|
||||||
|
expect(result.metadata.contentType).toBe('text/markdown');
|
||||||
|
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||||
|
expect(templateEngine.render).toHaveBeenCalledTimes(2);
|
||||||
|
expect(templateEngine.render).toHaveBeenNthCalledWith(1,
|
||||||
|
{ key: 'val' },
|
||||||
|
{ templatePath: '$PACKAGE_ROOT/templates/error/descriptions/', templateFile: 'E0001.md.hbs' });
|
||||||
|
expect(templateEngine.render).toHaveBeenNthCalledWith(2,
|
||||||
|
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack, description: '<html>' },
|
||||||
|
{ templateFile: '$PACKAGE_ROOT/templates/error/main.md.hbs' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user