mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
change: Refactor AllVoidCompositeHandler into SequenceHandler.
This commit is contained in:
parent
7cae14acf7
commit
ba47ce7951
@ -20,8 +20,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:HttpHandler",
|
"@id": "urn:solid-server:default:HttpHandler",
|
||||||
"@type": "AllVoidCompositeHandler",
|
"@type": "SequenceHandler",
|
||||||
"AllVoidCompositeHandler:_handlers": [
|
"SequenceHandler:_handlers": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:Middleware"
|
"@id": "urn:solid-server:default:Middleware"
|
||||||
},
|
},
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:Initializer",
|
"@id": "urn:solid-server:default:Initializer",
|
||||||
"@type": "AllVoidCompositeHandler",
|
"@type": "SequenceHandler",
|
||||||
"AllVoidCompositeHandler:_handlers": [
|
"SequenceHandler:_handlers": [
|
||||||
{
|
{
|
||||||
"@type": "LoggerInitializer",
|
"@type": "LoggerInitializer",
|
||||||
"LoggerInitializer:_loggerFactory": {
|
"LoggerInitializer:_loggerFactory": {
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:MetadataSerializer",
|
"@id": "urn:solid-server:default:MetadataSerializer",
|
||||||
"@type": "AllVoidCompositeHandler",
|
"@type": "SequenceHandler",
|
||||||
"AllVoidCompositeHandler:_handlers": [
|
"SequenceHandler:_handlers": [
|
||||||
{
|
{
|
||||||
"@type": "MappedMetadataWriter",
|
"@type": "MappedMetadataWriter",
|
||||||
"MappedMetadataWriter:_headerMap": [
|
"MappedMetadataWriter:_headerMap": [
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:Middleware",
|
"@id": "urn:solid-server:default:Middleware",
|
||||||
"@type": "AllVoidCompositeHandler",
|
"@type": "SequenceHandler",
|
||||||
"AllVoidCompositeHandler:_handlers": [
|
"SequenceHandler:_handlers": [
|
||||||
{
|
{
|
||||||
"@type": "CorsHandler",
|
"@type": "CorsHandler",
|
||||||
"CorsHandler:_options_methods": [
|
"CorsHandler:_options_methods": [
|
||||||
|
@ -181,10 +181,10 @@ export * from './util/locking/SingleThreadedResourceLocker';
|
|||||||
export * from './util/locking/WrappedExpiringResourceLocker';
|
export * from './util/locking/WrappedExpiringResourceLocker';
|
||||||
|
|
||||||
// Util
|
// Util
|
||||||
export * from './util/AllVoidCompositeHandler';
|
|
||||||
export * from './util/AsyncHandler';
|
export * from './util/AsyncHandler';
|
||||||
export * from './util/FirstCompositeHandler';
|
export * from './util/FirstCompositeHandler';
|
||||||
export * from './util/HeaderUtil';
|
export * from './util/HeaderUtil';
|
||||||
export * from './util/PathUtil';
|
export * from './util/PathUtil';
|
||||||
export * from './util/QuadUtil';
|
export * from './util/QuadUtil';
|
||||||
|
export * from './util/SequenceHandler';
|
||||||
export * from './util/StreamUtil';
|
export * from './util/StreamUtil';
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { AsyncHandler } from './AsyncHandler';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A composite handler that runs all of its handlers independent of their result.
|
|
||||||
* The `canHandle` check of this handler will always succeed.
|
|
||||||
*/
|
|
||||||
export class AllVoidCompositeHandler<TIn> extends AsyncHandler<TIn> {
|
|
||||||
private readonly handlers: AsyncHandler<TIn>[];
|
|
||||||
|
|
||||||
public constructor(handlers: AsyncHandler<TIn>[]) {
|
|
||||||
super();
|
|
||||||
this.handlers = handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async handle(input: TIn): Promise<void> {
|
|
||||||
for (const handler of this.handlers) {
|
|
||||||
try {
|
|
||||||
await handler.handleSafe(input);
|
|
||||||
} catch {
|
|
||||||
// Ignore errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Simple interface for classes that can potentially handle a specific kind of data asynchronously.
|
* Simple interface for classes that can potentially handle a specific kind of data asynchronously.
|
||||||
*/
|
*/
|
||||||
export abstract class AsyncHandler<TInput = void, TOutput = void> {
|
export abstract class AsyncHandler<TIn = void, TOut = void> {
|
||||||
/**
|
/**
|
||||||
* Checks if the input data can be handled by this class.
|
* Checks if the input data can be handled by this class.
|
||||||
* Throws an error if it can't handle the data.
|
* Throws an error if it can't handle the data.
|
||||||
@ -10,7 +10,7 @@ export abstract class AsyncHandler<TInput = void, TOutput = void> {
|
|||||||
* @returns A promise resolving if this input can be handled, rejecting with an Error if not.
|
* @returns A promise resolving if this input can be handled, rejecting with an Error if not.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public async canHandle(input: TInput): Promise<void> {
|
public async canHandle(input: TIn): Promise<void> {
|
||||||
// Support any input by default
|
// Support any input by default
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ export abstract class AsyncHandler<TInput = void, TOutput = void> {
|
|||||||
*
|
*
|
||||||
* @returns A promise resolving when the handling is finished. Return value depends on the given type.
|
* @returns A promise resolving when the handling is finished. Return value depends on the given type.
|
||||||
*/
|
*/
|
||||||
public abstract handle(input: TInput): Promise<TOutput>;
|
public abstract handle(input: TIn): Promise<TOut>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function that first runs the canHandle function followed by the handle function.
|
* Helper function that first runs the canHandle function followed by the handle function.
|
||||||
@ -30,7 +30,7 @@ export abstract class AsyncHandler<TInput = void, TOutput = void> {
|
|||||||
*
|
*
|
||||||
* @returns The result of the handle function of the handler.
|
* @returns The result of the handle function of the handler.
|
||||||
*/
|
*/
|
||||||
public async handleSafe(data: TInput): Promise<TOutput> {
|
public async handleSafe(data: TIn): Promise<TOut> {
|
||||||
await this.canHandle(data);
|
await this.canHandle(data);
|
||||||
|
|
||||||
return this.handle(data);
|
return this.handle(data);
|
||||||
|
32
src/util/SequenceHandler.ts
Normal file
32
src/util/SequenceHandler.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { AsyncHandler } from './AsyncHandler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A composite handler that will try to run all supporting handlers sequentially
|
||||||
|
* and return the value of the last supported handler.
|
||||||
|
* The `canHandle` check of this handler will always succeed.
|
||||||
|
*/
|
||||||
|
export class SequenceHandler<TIn = void, TOut = void> extends AsyncHandler<TIn, TOut | undefined> {
|
||||||
|
private readonly handlers: AsyncHandler<TIn, TOut>[];
|
||||||
|
|
||||||
|
public constructor(handlers: AsyncHandler<TIn, TOut>[]) {
|
||||||
|
super();
|
||||||
|
this.handlers = [ ...handlers ];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle(input: TIn): Promise<TOut | undefined> {
|
||||||
|
let result: TOut | undefined;
|
||||||
|
for (const handler of this.handlers) {
|
||||||
|
let supported: boolean;
|
||||||
|
try {
|
||||||
|
await handler.canHandle(input);
|
||||||
|
supported = true;
|
||||||
|
} catch {
|
||||||
|
supported = false;
|
||||||
|
}
|
||||||
|
if (supported) {
|
||||||
|
result = await handler.handle(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ import type {
|
|||||||
OperationHandler,
|
OperationHandler,
|
||||||
} from '../../src/index';
|
} from '../../src/index';
|
||||||
import {
|
import {
|
||||||
AcceptPreferenceParser, AllVoidCompositeHandler,
|
AcceptPreferenceParser,
|
||||||
BasicMetadataExtractor,
|
BasicMetadataExtractor,
|
||||||
BasicRequestParser,
|
BasicRequestParser,
|
||||||
BasicResponseWriter,
|
BasicResponseWriter,
|
||||||
@ -35,6 +35,7 @@ import {
|
|||||||
PutOperationHandler,
|
PutOperationHandler,
|
||||||
RawBodyParser,
|
RawBodyParser,
|
||||||
RepresentationConvertingStore,
|
RepresentationConvertingStore,
|
||||||
|
SequenceHandler,
|
||||||
SingleThreadedResourceLocker,
|
SingleThreadedResourceLocker,
|
||||||
SlugParser,
|
SlugParser,
|
||||||
SparqlUpdatePatchHandler,
|
SparqlUpdatePatchHandler,
|
||||||
@ -117,7 +118,7 @@ export const getOperationHandler = (store: ResourceStore): OperationHandler => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getResponseWriter = (): ResponseWriter => {
|
export const getResponseWriter = (): ResponseWriter => {
|
||||||
const serializer = new AllVoidCompositeHandler([
|
const serializer = new SequenceHandler([
|
||||||
new MappedMetadataWriter({
|
new MappedMetadataWriter({
|
||||||
[CONTENT_TYPE]: 'content-type',
|
[CONTENT_TYPE]: 'content-type',
|
||||||
[HTTP.location]: 'location',
|
[HTTP.location]: 'location',
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import { AllVoidCompositeHandler } from '../../../src/util/AllVoidCompositeHandler';
|
|
||||||
import type { AsyncHandler } from '../../../src/util/AsyncHandler';
|
|
||||||
|
|
||||||
describe('An AllVoidCompositeHandler', (): void => {
|
|
||||||
let handler1: AsyncHandler<string>;
|
|
||||||
let handler2: AsyncHandler<string>;
|
|
||||||
let composite: AllVoidCompositeHandler<string>;
|
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
|
||||||
handler1 = { handleSafe: jest.fn() } as any;
|
|
||||||
handler2 = { handleSafe: jest.fn() } as any;
|
|
||||||
|
|
||||||
composite = new AllVoidCompositeHandler<string>([ handler1, handler2 ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can handle all input.', async(): Promise<void> => {
|
|
||||||
await expect(composite.canHandle('test')).resolves.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('runs all handlers without caring about their result.', async(): Promise<void> => {
|
|
||||||
handler1.handleSafe = jest.fn(async(): Promise<void> => {
|
|
||||||
throw new Error('error');
|
|
||||||
});
|
|
||||||
await expect(composite.handleSafe('test')).resolves.toBeUndefined();
|
|
||||||
expect(handler1.handleSafe).toHaveBeenCalledTimes(1);
|
|
||||||
expect(handler1.handleSafe).toHaveBeenLastCalledWith('test');
|
|
||||||
expect(handler2.handleSafe).toHaveBeenCalledTimes(1);
|
|
||||||
expect(handler2.handleSafe).toHaveBeenLastCalledWith('test');
|
|
||||||
});
|
|
||||||
});
|
|
64
test/unit/util/SequenceHandler.test.ts
Normal file
64
test/unit/util/SequenceHandler.test.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import type { AsyncHandler } from '../../../src/util/AsyncHandler';
|
||||||
|
import { SequenceHandler } from '../../../src/util/SequenceHandler';
|
||||||
|
|
||||||
|
describe('A SequenceHandler', (): void => {
|
||||||
|
const handlers: jest.Mocked<AsyncHandler<string, string>>[] = [
|
||||||
|
{
|
||||||
|
canHandle: jest.fn(),
|
||||||
|
handle: jest.fn().mockResolvedValue('0'),
|
||||||
|
} as any,
|
||||||
|
{
|
||||||
|
canHandle: jest.fn().mockRejectedValue(new Error('not supported')),
|
||||||
|
handle: jest.fn().mockRejectedValue(new Error('should not be called')),
|
||||||
|
} as any,
|
||||||
|
{
|
||||||
|
canHandle: jest.fn(),
|
||||||
|
handle: jest.fn().mockResolvedValue('2'),
|
||||||
|
} as any,
|
||||||
|
];
|
||||||
|
let composite: SequenceHandler<string, string>;
|
||||||
|
|
||||||
|
beforeEach(async(): Promise<void> => {
|
||||||
|
composite = new SequenceHandler<string, string>(handlers);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle all input.', async(): Promise<void> => {
|
||||||
|
await expect(composite.canHandle('test')).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs all supported handlers.', async(): Promise<void> => {
|
||||||
|
await composite.handleSafe('test');
|
||||||
|
|
||||||
|
expect(handlers[0].canHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handlers[0].canHandle).toHaveBeenLastCalledWith('test');
|
||||||
|
expect(handlers[0].handle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handlers[0].handle).toHaveBeenLastCalledWith('test');
|
||||||
|
|
||||||
|
expect(handlers[1].canHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handlers[1].canHandle).toHaveBeenLastCalledWith('test');
|
||||||
|
expect(handlers[1].handle).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
expect(handlers[2].canHandle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handlers[2].canHandle).toHaveBeenLastCalledWith('test');
|
||||||
|
expect(handlers[2].handle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handlers[2].handle).toHaveBeenLastCalledWith('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the result of the last supported handler.', async(): Promise<void> => {
|
||||||
|
await expect(composite.handleSafe('test')).resolves.toBe('2');
|
||||||
|
|
||||||
|
handlers[2].canHandle.mockRejectedValueOnce(new Error('not supported'));
|
||||||
|
await expect(composite.handleSafe('test')).resolves.toBe('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined if no handler is supported.', async(): Promise<void> => {
|
||||||
|
handlers[0].canHandle.mockRejectedValueOnce(new Error('not supported'));
|
||||||
|
handlers[2].canHandle.mockRejectedValueOnce(new Error('not supported'));
|
||||||
|
await expect(composite.handleSafe('test')).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors if a handler errors.', async(): Promise<void> => {
|
||||||
|
handlers[2].handle.mockRejectedValueOnce(new Error('failure'));
|
||||||
|
await expect(composite.handleSafe('test')).rejects.toThrow('failure');
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user