feat: Store original target in error metadata

This commit is contained in:
Joachim Van Herwegen 2024-04-03 15:46:01 +02:00
parent 486241f3d4
commit 419312ee5f
5 changed files with 99 additions and 14 deletions

View File

@ -7,6 +7,11 @@
"@type": "SafeErrorHandler",
"showStackTrace": { "@id": "urn:solid-server:default:variable:showStackTrace" },
"errorHandler": {
"@id": "urn:solid-server:default:TargetExtractorErrorHandler",
"@type": "TargetExtractorErrorHandler",
"targetExtractor": { "@id": "urn:solid-server:default:TargetExtractor" },
"errorHandler": {
"@id": "urn:solid-server:default:WaterfallErrorHandler",
"@type": "WaterfallHandler",
"handlers": [
{
@ -23,5 +28,6 @@
]
}
}
}
]
}

View File

@ -0,0 +1,30 @@
import { DataFactory } from 'n3';
import { SOLID_ERROR } from '../../../util/Vocabularies';
import type { TargetExtractor } from '../../input/identifier/TargetExtractor';
import type { ResponseDescription } from '../response/ResponseDescription';
import type { ErrorHandlerArgs } from './ErrorHandler';
import { ErrorHandler } from './ErrorHandler';
/**
* Adds metadata to an error to indicate what the identifier was of the resource originally being targeted.
*/
export class TargetExtractorErrorHandler extends ErrorHandler {
protected readonly errorHandler: ErrorHandler;
protected readonly targetExtractor: TargetExtractor;
public constructor(errorHandler: ErrorHandler, targetExtractor: TargetExtractor) {
super();
this.errorHandler = errorHandler;
this.targetExtractor = targetExtractor;
}
public async canHandle(input: ErrorHandlerArgs): Promise<void> {
return this.errorHandler.canHandle(input);
}
public async handle(input: ErrorHandlerArgs): Promise<ResponseDescription> {
const target = await this.targetExtractor.handleSafe(input);
input.error.metadata.add(SOLID_ERROR.terms.target, DataFactory.namedNode(target.path));
return this.errorHandler.handle(input);
}
}

View File

@ -100,6 +100,7 @@ export * from './http/output/error/ConvertingErrorHandler';
export * from './http/output/error/ErrorHandler';
export * from './http/output/error/RedirectingErrorHandler';
export * from './http/output/error/SafeErrorHandler';
export * from './http/output/error/TargetExtractorErrorHandler';
// HTTP/Output/Metadata
export * from './http/output/metadata/AllowAcceptHeaderWriter';

View File

@ -283,6 +283,7 @@ export const SOLID_ERROR = createVocabulary(
'errorCode',
'errorResponse',
'stack',
'target',
);
// Used to pass parameters to error templates

View File

@ -0,0 +1,47 @@
import type { TargetExtractor } from '../../../../../src/http/input/identifier/TargetExtractor';
import type { ErrorHandler, ErrorHandlerArgs } from '../../../../../src/http/output/error/ErrorHandler';
import { TargetExtractorErrorHandler } from '../../../../../src/http/output/error/TargetExtractorErrorHandler';
import type { ResourceIdentifier } from '../../../../../src/http/representation/ResourceIdentifier';
import { NotFoundHttpError } from '../../../../../src/util/errors/NotFoundHttpError';
import { SOLID_ERROR } from '../../../../../src/util/Vocabularies';
describe('A TargetExtractorErrorHandler', (): void => {
const identifier: ResourceIdentifier = { path: 'http://example.com/foo' };
let input: ErrorHandlerArgs;
let source: jest.Mocked<ErrorHandler>;
let targetExtractor: jest.Mocked<TargetExtractor>;
let handler: TargetExtractorErrorHandler;
beforeEach(async(): Promise<void> => {
input = {
request: 'request' as any,
error: new NotFoundHttpError(),
};
source = {
canHandle: jest.fn(),
handle: jest.fn().mockResolvedValue('response'),
} satisfies Partial<ErrorHandler> as any;
targetExtractor = {
handleSafe: jest.fn().mockResolvedValue(identifier),
} satisfies Partial<TargetExtractor> as any;
handler = new TargetExtractorErrorHandler(source, targetExtractor);
});
it('can handle input its source can handle.', async(): Promise<void> => {
await expect(handler.canHandle(input)).resolves.toBeUndefined();
expect(source.canHandle).toHaveBeenLastCalledWith(input);
const error = new Error('bad data');
source.canHandle.mockRejectedValueOnce(error);
await expect(handler.canHandle(input)).rejects.toThrow(error);
});
it('adds the identifier as metadata.', async(): Promise<void> => {
await expect(handler.handle(input)).resolves.toBe('response');
expect(input.error.metadata.get(SOLID_ERROR.terms.target)?.value).toEqual(identifier.path);
expect(targetExtractor.handleSafe).toHaveBeenLastCalledWith(input);
});
});