refactor: Restructure source code folder

This way the location of certain classes should make more sense
This commit is contained in:
Joachim Van Herwegen
2021-10-08 10:58:35 +02:00
parent 012d9e0864
commit b3da9c9fcf
280 changed files with 684 additions and 673 deletions

View File

@@ -0,0 +1,30 @@
import type { ResourceStore } from '../../storage/ResourceStore';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { ResetResponseDescription } from '../output/response/ResetResponseDescription';
import type { ResponseDescription } from '../output/response/ResponseDescription';
import type { OperationHandlerInput } from './OperationHandler';
import { OperationHandler } from './OperationHandler';
/**
* Handles DELETE {@link Operation}s.
* Calls the deleteResource function from a {@link ResourceStore}.
*/
export class DeleteOperationHandler extends OperationHandler {
private readonly store: ResourceStore;
public constructor(store: ResourceStore) {
super();
this.store = store;
}
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
if (operation.method !== 'DELETE') {
throw new NotImplementedHttpError('This handler only supports DELETE operations');
}
}
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
await this.store.deleteResource(operation.target, operation.conditions);
return new ResetResponseDescription();
}
}

View File

@@ -0,0 +1,31 @@
import type { ResourceStore } from '../../storage/ResourceStore';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { OkResponseDescription } from '../output/response/OkResponseDescription';
import type { ResponseDescription } from '../output/response/ResponseDescription';
import type { OperationHandlerInput } from './OperationHandler';
import { OperationHandler } from './OperationHandler';
/**
* Handles GET {@link Operation}s.
* Calls the getRepresentation function from a {@link ResourceStore}.
*/
export class GetOperationHandler extends OperationHandler {
private readonly store: ResourceStore;
public constructor(store: ResourceStore) {
super();
this.store = store;
}
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
if (operation.method !== 'GET') {
throw new NotImplementedHttpError('This handler only supports GET operations');
}
}
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
const body = await this.store.getRepresentation(operation.target, operation.preferences, operation.conditions);
return new OkResponseDescription(body.metadata, body.data);
}
}

View File

@@ -0,0 +1,34 @@
import type { ResourceStore } from '../../storage/ResourceStore';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { OkResponseDescription } from '../output/response/OkResponseDescription';
import type { ResponseDescription } from '../output/response/ResponseDescription';
import type { OperationHandlerInput } from './OperationHandler';
import { OperationHandler } from './OperationHandler';
/**
* Handles HEAD {@link Operation}s.
* Calls the getRepresentation function from a {@link ResourceStore}.
*/
export class HeadOperationHandler extends OperationHandler {
private readonly store: ResourceStore;
public constructor(store: ResourceStore) {
super();
this.store = store;
}
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
if (operation.method !== 'HEAD') {
throw new NotImplementedHttpError('This handler only supports HEAD operations');
}
}
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
const body = await this.store.getRepresentation(operation.target, operation.preferences, operation.conditions);
// Close the Readable as we will not return it.
body.data.destroy();
return new OkResponseDescription(body.metadata);
}
}

View File

@@ -0,0 +1,12 @@
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
import type { Operation } from '../Operation';
import type { ResponseDescription } from '../output/response/ResponseDescription';
export interface OperationHandlerInput {
operation: Operation;
}
/**
* Handler for a specific operation type.
*/
export abstract class OperationHandler extends AsyncHandler<OperationHandlerInput, ResponseDescription> {}

View File

@@ -0,0 +1,42 @@
import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { ResetResponseDescription } from '../output/response/ResetResponseDescription';
import type { ResponseDescription } from '../output/response/ResponseDescription';
import type { Patch } from '../representation/Patch';
import type { OperationHandlerInput } from './OperationHandler';
import { OperationHandler } from './OperationHandler';
/**
* Handles PATCH {@link Operation}s.
* Calls the modifyResource function from a {@link ResourceStore}.
*/
export class PatchOperationHandler extends OperationHandler {
protected readonly logger = getLoggerFor(this);
private readonly store: ResourceStore;
public constructor(store: ResourceStore) {
super();
this.store = store;
}
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
if (operation.method !== 'PATCH') {
throw new NotImplementedHttpError('This handler only supports PATCH operations.');
}
}
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
// Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests
// without the Content-Type header with a status code of 400."
// https://solid.github.io/specification/protocol#http-server
if (!operation.body?.metadata.contentType) {
this.logger.warn('No Content-Type header specified on PATCH request');
throw new BadRequestHttpError('No Content-Type header specified on PATCH request');
}
await this.store.modifyResource(operation.target, operation.body as Patch, operation.conditions);
return new ResetResponseDescription();
}
}

View File

@@ -0,0 +1,41 @@
import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { CreatedResponseDescription } from '../output/response/CreatedResponseDescription';
import type { ResponseDescription } from '../output/response/ResponseDescription';
import type { OperationHandlerInput } from './OperationHandler';
import { OperationHandler } from './OperationHandler';
/**
* Handles POST {@link Operation}s.
* Calls the addResource function from a {@link ResourceStore}.
*/
export class PostOperationHandler extends OperationHandler {
protected readonly logger = getLoggerFor(this);
private readonly store: ResourceStore;
public constructor(store: ResourceStore) {
super();
this.store = store;
}
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
if (operation.method !== 'POST') {
throw new NotImplementedHttpError('This handler only supports POST operations');
}
}
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
// Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests
// without the Content-Type header with a status code of 400."
// https://solid.github.io/specification/protocol#http-server
if (!operation.body?.metadata.contentType) {
this.logger.warn('No Content-Type header specified on POST request');
throw new BadRequestHttpError('No Content-Type header specified on POST request');
}
const identifier = await this.store.addResource(operation.target, operation.body, operation.conditions);
return new CreatedResponseDescription(identifier);
}
}

View File

@@ -0,0 +1,41 @@
import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { ResetResponseDescription } from '../output/response/ResetResponseDescription';
import type { ResponseDescription } from '../output/response/ResponseDescription';
import type { OperationHandlerInput } from './OperationHandler';
import { OperationHandler } from './OperationHandler';
/**
* Handles PUT {@link Operation}s.
* Calls the setRepresentation function from a {@link ResourceStore}.
*/
export class PutOperationHandler extends OperationHandler {
protected readonly logger = getLoggerFor(this);
private readonly store: ResourceStore;
public constructor(store: ResourceStore) {
super();
this.store = store;
}
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
if (operation.method !== 'PUT') {
throw new NotImplementedHttpError('This handler only supports PUT operations');
}
}
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
// Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests
// without the Content-Type header with a status code of 400."
// https://solid.github.io/specification/protocol#http-server
if (!operation.body?.metadata.contentType) {
this.logger.warn('No Content-Type header specified on PUT request');
throw new BadRequestHttpError('No Content-Type header specified on PUT request');
}
await this.store.setRepresentation(operation.target, operation.body, operation.conditions);
return new ResetResponseDescription();
}
}

View File

@@ -0,0 +1,19 @@
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
import type { Operation } from '../../Operation';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
export interface OperationMetadataCollectorInput {
/**
* Metadata to update with permission knowledge.
*/
metadata: RepresentationMetadata;
/**
* Operation corresponding to the request.
*/
operation: Operation;
}
/**
* Adds metadata about the operation to the provided metadata object.
*/
export abstract class OperationMetadataCollector extends AsyncHandler<OperationMetadataCollectorInput> {}

View File

@@ -0,0 +1,38 @@
import { AclMode } from '../../../authorization/permissions/AclPermission';
import type { AclPermission } from '../../../authorization/permissions/AclPermission';
import { AccessMode } from '../../../authorization/permissions/Permissions';
import { ACL, AUTH } from '../../../util/Vocabularies';
import type { OperationMetadataCollectorInput } from './OperationMetadataCollector';
import { OperationMetadataCollector } from './OperationMetadataCollector';
const VALID_METHODS = new Set([ 'HEAD', 'GET' ]);
const VALID_ACL_MODES = new Set([ AccessMode.read, AccessMode.write, AccessMode.append, AclMode.control ]);
/**
* Indicates which acl permissions are available on the requested resource.
* Only adds public and agent permissions for HEAD/GET requests.
*/
export class WebAclMetadataCollector extends OperationMetadataCollector {
public async handle({ metadata, operation }: OperationMetadataCollectorInput): Promise<void> {
if (!operation.permissionSet || !VALID_METHODS.has(operation.method)) {
return;
}
const user: AclPermission = operation.permissionSet.agent ?? {};
const everyone: AclPermission = operation.permissionSet.public ?? {};
const modes = new Set<AccessMode>([ ...Object.keys(user), ...Object.keys(everyone) ] as AccessMode[]);
for (const mode of modes) {
if (VALID_ACL_MODES.has(mode)) {
const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control';
if (everyone[mode]) {
metadata.add(AUTH.terms.publicMode, ACL.terms[capitalizedMode]);
}
if (user[mode]) {
metadata.add(AUTH.terms.userMode, ACL.terms[capitalizedMode]);
}
}
}
}
}