mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Convert errorCodes using markdown
This commit is contained in:
@@ -229,6 +229,7 @@ export * from './storage/conversion/ConversionUtil';
|
||||
export * from './storage/conversion/ErrorToTemplateConverter';
|
||||
export * from './storage/conversion/ErrorToQuadConverter';
|
||||
export * from './storage/conversion/IfNeededConverter';
|
||||
export * from './storage/conversion/MarkdownToHtmlConverter';
|
||||
export * from './storage/conversion/PassthroughConverter';
|
||||
export * from './storage/conversion/QuadToRdfConverter';
|
||||
export * from './storage/conversion/RdfToQuadConverter';
|
||||
|
||||
@@ -4,25 +4,38 @@ import { BasicRepresentation } from '../../ldp/representation/BasicRepresentatio
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import type { TemplateEngine } from '../../pods/generate/TemplateEngine';
|
||||
import { INTERNAL_ERROR } from '../../util/ContentTypes';
|
||||
import { HttpError } from '../../util/errors/HttpError';
|
||||
import { InternalServerError } from '../../util/errors/InternalServerError';
|
||||
import { resolveAssetPath } from '../../util/PathUtil';
|
||||
import { joinFilePath, resolveAssetPath } from '../../util/PathUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Serializes an Error by filling in the provided template.
|
||||
* Content-type is based on the constructor parameter.
|
||||
*
|
||||
* In case the input Error has an `options.errorCode` value,
|
||||
* the converter will look in the `descriptions` for a file
|
||||
* with the exact same name as that error code + `extension`.
|
||||
* The templating engine will then be applied to that file.
|
||||
* That result will be passed as an additional parameter to the main templating call,
|
||||
* using the variable `codeMessage`.
|
||||
*/
|
||||
export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
||||
private readonly engine: TemplateEngine;
|
||||
private readonly templatePath: string;
|
||||
private readonly descriptions: string;
|
||||
private readonly contentType: string;
|
||||
private readonly extension: string;
|
||||
|
||||
public constructor(engine: TemplateEngine, templatePath: string, contentType: string) {
|
||||
public constructor(engine: TemplateEngine, templatePath: string, descriptions: string, contentType: string,
|
||||
extension: string) {
|
||||
super(INTERNAL_ERROR, contentType);
|
||||
this.engine = engine;
|
||||
this.templatePath = resolveAssetPath(templatePath);
|
||||
this.descriptions = resolveAssetPath(descriptions);
|
||||
this.contentType = contentType;
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||
@@ -34,10 +47,26 @@ export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
||||
|
||||
// Render the template
|
||||
const { name, message, stack } = error;
|
||||
const variables = { name, message, stack };
|
||||
const description = await this.getErrorCodeMessage(error);
|
||||
const variables = { name, message, stack, description };
|
||||
const template = await fsPromises.readFile(this.templatePath, 'utf8');
|
||||
const html = this.engine.apply(template, variables);
|
||||
const rendered = this.engine.apply(template, variables);
|
||||
|
||||
return new BasicRepresentation(html, representation.metadata, this.contentType);
|
||||
return new BasicRepresentation(rendered, representation.metadata, this.contentType);
|
||||
}
|
||||
|
||||
private async getErrorCodeMessage(error: Error): Promise<string | undefined> {
|
||||
if (HttpError.isInstance(error) && error.options.errorCode) {
|
||||
const filePath = joinFilePath(this.descriptions, `${error.options.errorCode}${this.extension}`);
|
||||
let template: string;
|
||||
try {
|
||||
template = await fsPromises.readFile(filePath, 'utf8');
|
||||
} catch {
|
||||
// In case no template is found we still want to convert
|
||||
return;
|
||||
}
|
||||
|
||||
return this.engine.apply(template, (error.options.details ?? {}) as NodeJS.Dict<string>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
src/storage/conversion/MarkdownToHtmlConverter.ts
Normal file
42
src/storage/conversion/MarkdownToHtmlConverter.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import marked from 'marked';
|
||||
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import type { TemplateEngine } from '../../pods/generate/TemplateEngine';
|
||||
import { TEXT_HTML, TEXT_MARKDOWN } from '../../util/ContentTypes';
|
||||
import { resolveAssetPath } from '../../util/PathUtil';
|
||||
import { readableToString } from '../../util/StreamUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts markdown data to HTML.
|
||||
* The generated HTML will be injected into the given template using the parameter `htmlBody`.
|
||||
* 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 {
|
||||
private readonly engine: TemplateEngine;
|
||||
private readonly templatePath: string;
|
||||
|
||||
public constructor(engine: TemplateEngine, templatePath: string) {
|
||||
super(TEXT_MARKDOWN, TEXT_HTML);
|
||||
this.engine = engine;
|
||||
this.templatePath = resolveAssetPath(templatePath);
|
||||
}
|
||||
|
||||
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||
const markdown = await readableToString(representation.data);
|
||||
|
||||
// See if there is a title we can use
|
||||
const match = /^\s*#+\s*([^\n]+)\n/u.exec(markdown);
|
||||
const title = match?.[1];
|
||||
|
||||
const htmlBody = marked(markdown);
|
||||
|
||||
const template = await fsPromises.readFile(this.templatePath, 'utf8');
|
||||
const html = this.engine.apply(template, { htmlBody, title });
|
||||
|
||||
return new BasicRepresentation(html, representation.metadata, TEXT_HTML);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ export const APPLICATION_OCTET_STREAM = 'application/octet-stream';
|
||||
export const APPLICATION_SPARQL_UPDATE = 'application/sparql-update';
|
||||
export const APPLICATION_X_WWW_FORM_URLENCODED = 'application/x-www-form-urlencoded';
|
||||
export const TEXT_HTML = 'text/html';
|
||||
export const TEXT_MARKDOWN = 'text/markdown';
|
||||
export const TEXT_TURTLE = 'text/turtle';
|
||||
|
||||
// Internal content types (not exposed over HTTP)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isError } from './ErrorUtil';
|
||||
export interface HttpErrorOptions {
|
||||
cause?: unknown;
|
||||
errorCode?: string;
|
||||
details?: NodeJS.Dict<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,8 @@ export abstract class BaseIdentifierStrategy implements IdentifierStrategy {
|
||||
|
||||
public getParentContainer(identifier: ResourceIdentifier): ResourceIdentifier {
|
||||
if (!this.supportsIdentifier(identifier)) {
|
||||
throw new InternalServerError(`The identifier ${identifier.path} is outside the configured identifier space.`);
|
||||
throw new InternalServerError(`The identifier ${identifier.path} is outside the configured identifier space.`,
|
||||
{ errorCode: 'E0001', details: { path: identifier.path }});
|
||||
}
|
||||
if (this.isRootContainer(identifier)) {
|
||||
throw new InternalServerError(`Cannot obtain the parent of ${identifier.path} because it is a root container.`);
|
||||
|
||||
Reference in New Issue
Block a user