mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: add CompositeAsyncHandler to support multiple handlers
This commit is contained in:
@@ -6,4 +6,4 @@ import { HttpRequest } from '../server/HttpRequest';
|
||||
* Responsible for extracting credentials from an incoming request.
|
||||
* Will return `null` if no credentials were found.
|
||||
*/
|
||||
export type CredentialsExtractor = AsyncHandler<HttpRequest, Credentials>;
|
||||
export abstract class CredentialsExtractor extends AsyncHandler<HttpRequest, Credentials> {}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
* Verifies if the given credentials have access to the given permissions on the given resource.
|
||||
* An {@link Error} with the necessary explanation will be thrown when permissions are not granted.
|
||||
*/
|
||||
export type Authorizer = AsyncHandler<AuthorizerArgs>;
|
||||
export abstract class Authorizer extends AsyncHandler<AuthorizerArgs> {}
|
||||
|
||||
export interface AuthorizerArgs {
|
||||
/**
|
||||
|
||||
@@ -5,4 +5,4 @@ import { Operation } from '../operations/Operation';
|
||||
/**
|
||||
* Converts an incoming HttpRequest to an Operation.
|
||||
*/
|
||||
export type RequestParser = AsyncHandler<HttpRequest, Operation>;
|
||||
export abstract class RequestParser extends AsyncHandler<HttpRequest, Operation> {}
|
||||
|
||||
@@ -4,4 +4,4 @@ import { Operation } from './Operation';
|
||||
/**
|
||||
* Handler for a specific operation type.
|
||||
*/
|
||||
export type OperationHandler = AsyncHandler<Operation>;
|
||||
export abstract class OperationHandler extends AsyncHandler<Operation> {}
|
||||
|
||||
@@ -5,4 +5,4 @@ import { PermissionSet } from './PermissionSet';
|
||||
/**
|
||||
* Verifies which permissions are requested on a given {@link Operation}.
|
||||
*/
|
||||
export type PermissionsExtractor = AsyncHandler<Operation, PermissionSet>;
|
||||
export abstract class PermissionsExtractor extends AsyncHandler<Operation, PermissionSet> {}
|
||||
|
||||
@@ -5,4 +5,4 @@ import { HttpResponse } from './HttpResponse';
|
||||
/**
|
||||
* An HTTP request handler.
|
||||
*/
|
||||
export type HttpHandler = AsyncHandler<{ request: HttpRequest; response: HttpResponse }>;
|
||||
export abstract class HttpHandler extends AsyncHandler<{ request: HttpRequest; response: HttpResponse }> {}
|
||||
|
||||
@@ -1,17 +1,35 @@
|
||||
/**
|
||||
* Simple interface for classes that can potentially handle a specific kind of data asynchronously.
|
||||
*/
|
||||
export interface AsyncHandler<TInput, TOutput = void> {
|
||||
export abstract class AsyncHandler<TInput, TOutput = void> {
|
||||
/**
|
||||
* Checks if the input data can be handled by this class.
|
||||
* Throws an error if it can't handle the data.
|
||||
* @param input - Input data that would be handled potentially.
|
||||
* @returns A promise resolving to if this input can be handled.
|
||||
*
|
||||
* @returns A promise resolving if this input can be handled, rejecting with an Error if not.
|
||||
*/
|
||||
canHandle: (input: TInput) => Promise<boolean>;
|
||||
public abstract canHandle (input: TInput): Promise<void>;
|
||||
|
||||
/**
|
||||
* Handles the given input. This should only be done if the {@link canHandle} function returned `true`.
|
||||
* @param input - Input data that needs to be handled.
|
||||
*
|
||||
* @returns A promise resolving when the handling is finished. Return value depends on the given type.
|
||||
*/
|
||||
handle: (input: TInput) => Promise<TOutput>;
|
||||
public abstract handle (input: TInput): Promise<TOutput>;
|
||||
|
||||
/**
|
||||
* Helper function that first runs the canHandle function followed by the handle function.
|
||||
* Throws the error of the {@link canHandle} function if the data can't be handled,
|
||||
* or returns the result of the {@link handle} function otherwise.
|
||||
* @param data - The data to handle.
|
||||
*
|
||||
* @returns The result of the handle function of the handler.
|
||||
*/
|
||||
public async handleSafe (data: TInput): Promise<TOutput> {
|
||||
await this.canHandle(data);
|
||||
|
||||
return this.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
86
src/util/CompositeAsyncHandler.ts
Normal file
86
src/util/CompositeAsyncHandler.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { AsyncHandler } from './AsyncHandler';
|
||||
import { UnsupportedHttpError } from './errors/UnsupportedHttpError';
|
||||
|
||||
/**
|
||||
* Handler that combines several other handlers,
|
||||
* thereby allowing other classes that depend on a single handler to still use multiple.
|
||||
*/
|
||||
export class CompositeAsyncHandler<TIn, TOut> implements AsyncHandler<TIn, TOut> {
|
||||
private readonly handlers: AsyncHandler<TIn, TOut>[];
|
||||
|
||||
/**
|
||||
* Creates a new CompositeAsyncHandler that stores the given handlers.
|
||||
* @param handlers - Handlers over which it will run.
|
||||
*/
|
||||
public constructor (handlers: AsyncHandler<TIn, TOut>[]) {
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the stored handlers can handle the given input.
|
||||
* @param input - The data that would need to be handled.
|
||||
*
|
||||
* @returns A promise resolving if at least 1 handler supports to input, or rejecting if none do.
|
||||
*/
|
||||
public async canHandle (input: TIn): Promise<void> {
|
||||
await this.findHandler(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a handler that supports the given input and then lets it handle the given data.
|
||||
* @param input - The data that needs to be handled.
|
||||
*
|
||||
* @returns A promise corresponding to the handle call of a handler that supports the input.
|
||||
* It rejects if no handlers support the given data.
|
||||
*/
|
||||
public async handle (input: TIn): Promise<TOut> {
|
||||
let handler: AsyncHandler<TIn, TOut>;
|
||||
|
||||
try {
|
||||
handler = await this.findHandler(input);
|
||||
} catch (error) {
|
||||
throw new Error('All handlers failed. This might be the consequence of calling handle before canHandle.');
|
||||
}
|
||||
|
||||
return handler.handle(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to {@link AsyncHandler.handleSafe} but optimized for composite by only needing 1 canHandle call on members.
|
||||
* @param input - The input data.
|
||||
*
|
||||
* @returns A promise corresponding to the handle call of a handler that supports the input.
|
||||
* It rejects if no handlers support the given data.
|
||||
*/
|
||||
public async handleSafe (input: TIn): Promise<TOut> {
|
||||
const handler = await this.findHandler(input);
|
||||
|
||||
return handler.handle(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a handler that can handle the given input data.
|
||||
* Otherwise an error gets thrown.
|
||||
*
|
||||
* @param input - The input data.
|
||||
*
|
||||
* @returns A promise resolving to a handler that supports the data or otherwise rejecting.
|
||||
*/
|
||||
private async findHandler (input: TIn): Promise<AsyncHandler<TIn, TOut>> {
|
||||
const errors: Error[] = [];
|
||||
|
||||
for (const handler of this.handlers) {
|
||||
try {
|
||||
await handler.canHandle(input);
|
||||
|
||||
return handler;
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
const joined = errors.map((error: Error): string => error.message).join(', ');
|
||||
|
||||
throw new UnsupportedHttpError(`No handler supports the given input: [${joined}].`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user