Skip to content

Commit

Permalink
feat: Store status, body and metadata in ResponseDescription
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimvh committed Nov 6, 2020
1 parent e8fdcb0 commit 1260c5c
Show file tree
Hide file tree
Showing 31 changed files with 209 additions and 184 deletions.
27 changes: 16 additions & 11 deletions index.ts
Expand Up @@ -22,6 +22,12 @@ export * from './src/ldp/http/metadata/MetadataExtractor';
export * from './src/ldp/http/metadata/MetadataParser';
export * from './src/ldp/http/metadata/SlugParser';

// LDP/HTTP/Response
export * from './src/ldp/http/response/CreatedResponseDescription';
export * from './src/ldp/http/response/OkResponseDescription';
export * from './src/ldp/http/response/ResetResponseDescription';
export * from './src/ldp/http/response/ResponseDescription';

// LDP/HTTP
export * from './src/ldp/http/AcceptPreferenceParser';
export * from './src/ldp/http/BasicRequestParser';
Expand All @@ -38,16 +44,6 @@ export * from './src/ldp/http/SparqlUpdateBodyParser';
export * from './src/ldp/http/SparqlUpdatePatch';
export * from './src/ldp/http/TargetExtractor';

// Logging
export * from './src/logging/LazyLogger';
export * from './src/logging/LazyLoggerFactory';
export * from './src/logging/Logger';
export * from './src/logging/LoggerFactory';
export * from './src/logging/LogLevel';
export * from './src/logging/LogUtil';
export * from './src/logging/VoidLoggerFactory';
export * from './src/logging/WinstonLoggerFactory';

// LDP/Operations
export * from './src/ldp/operations/DeleteOperationHandler';
export * from './src/ldp/operations/GetOperationHandler';
Expand All @@ -57,7 +53,6 @@ export * from './src/ldp/operations/OperationHandler';
export * from './src/ldp/operations/PatchOperationHandler';
export * from './src/ldp/operations/PostOperationHandler';
export * from './src/ldp/operations/PutOperationHandler';
export * from './src/ldp/operations/ResponseDescription';

// LDP/Permissions
export * from './src/ldp/permissions/PermissionSet';
Expand All @@ -75,6 +70,16 @@ export * from './src/ldp/representation/ResourceIdentifier';
// LDP
export * from './src/ldp/AuthenticatedLdpHandler';

// Logging
export * from './src/logging/LazyLogger';
export * from './src/logging/LazyLoggerFactory';
export * from './src/logging/Logger';
export * from './src/logging/LoggerFactory';
export * from './src/logging/LogLevel';
export * from './src/logging/LogUtil';
export * from './src/logging/VoidLoggerFactory';
export * from './src/logging/WinstonLoggerFactory';

// Server
export * from './src/server/ExpressHttpServer';
export * from './src/server/HttpHandler';
Expand Down
2 changes: 1 addition & 1 deletion src/ldp/AuthenticatedLdpHandler.ts
Expand Up @@ -5,10 +5,10 @@ import { HttpHandler } from '../server/HttpHandler';
import type { HttpRequest } from '../server/HttpRequest';
import type { HttpResponse } from '../server/HttpResponse';
import type { RequestParser } from './http/RequestParser';
import type { ResponseDescription } from './http/response/ResponseDescription';
import type { ResponseWriter } from './http/ResponseWriter';
import type { Operation } from './operations/Operation';
import type { OperationHandler } from './operations/OperationHandler';
import type { ResponseDescription } from './operations/ResponseDescription';
import type { PermissionSet } from './permissions/PermissionSet';
import type { PermissionsExtractor } from './permissions/PermissionsExtractor';

Expand Down
28 changes: 16 additions & 12 deletions src/ldp/http/BasicResponseWriter.ts
@@ -1,36 +1,40 @@
import { getLoggerFor } from '../../logging/LogUtil';
import type { HttpResponse } from '../../server/HttpResponse';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import type { ResponseDescription } from '../operations/ResponseDescription';
import { HTTP } from '../../util/UriConstants';
import type { ResponseDescription } from './response/ResponseDescription';
import { ResponseWriter } from './ResponseWriter';

/**
* Writes to an {@link HttpResponse} based on the incoming {@link ResponseDescription}.
* Still needs a way to write correct status codes for successful operations.
*/
export class BasicResponseWriter extends ResponseWriter {
protected readonly logger = getLoggerFor(this);

public async canHandle(input: { response: HttpResponse; result: ResponseDescription | Error }): Promise<void> {
if ((input.result instanceof Error) || (input.result.body && !input.result.body.binary)) {
this.logger.warn('This writer can only write binary bodies');
throw new UnsupportedHttpError('Only binary results are supported');
if (input.result instanceof Error || input.result.metadata?.contentType === INTERNAL_QUADS) {
this.logger.warn('This writer only supports binary ResponseDescriptions');
throw new UnsupportedHttpError('Only successful binary responses are supported');
}
}

public async handle(input: { response: HttpResponse; result: ResponseDescription }): Promise<void> {
input.response.setHeader('location', input.result.identifier.path);
if (input.result.body) {
const contentType = input.result.body.metadata.contentType ?? 'text/plain';
const location = input.result.metadata?.get(HTTP.location);
if (location) {
input.response.setHeader('location', location.value);
}
if (input.result.data) {
const contentType = input.result.metadata?.contentType ?? 'text/plain';
input.response.setHeader('content-type', contentType);
}

input.response.writeHead(200);
input.response.writeHead(input.result.statusCode);

if (input.result.body) {
input.result.body.data.pipe(input.response);
if (input.result.data) {
input.result.data.pipe(input.response);
} else {
// If there is an input body the response will end once the input stream ends
// If there is input data the response will end once the input stream ends
input.response.end();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/ldp/http/ErrorResponseWriter.ts
Expand Up @@ -2,7 +2,7 @@ import { getLoggerFor } from '../../logging/LogUtil';
import type { HttpResponse } from '../../server/HttpResponse';
import { HttpError } from '../../util/errors/HttpError';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import type { ResponseDescription } from '../operations/ResponseDescription';
import type { ResponseDescription } from './response/ResponseDescription';
import { ResponseWriter } from './ResponseWriter';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/ldp/http/ResponseWriter.ts
@@ -1,6 +1,6 @@
import type { HttpResponse } from '../../server/HttpResponse';
import { AsyncHandler } from '../../util/AsyncHandler';
import type { ResponseDescription } from '../operations/ResponseDescription';
import type { ResponseDescription } from './response/ResponseDescription';

/**
* Writes to the HttpResponse.
Expand Down
15 changes: 15 additions & 0 deletions src/ldp/http/response/CreatedResponseDescription.ts
@@ -0,0 +1,15 @@
import { DataFactory } from 'n3';
import { HTTP } from '../../../util/UriConstants';
import { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../representation/ResourceIdentifier';
import { ResponseDescription } from './ResponseDescription';

/**
* Corresponds to a 201 response, containing the relevant link metadata.
*/
export class CreatedResponseDescription extends ResponseDescription {
public constructor(location: ResourceIdentifier) {
const metadata = new RepresentationMetadata({ [HTTP.location]: DataFactory.namedNode(location.path) });
super(201, metadata);
}
}
16 changes: 16 additions & 0 deletions src/ldp/http/response/OkResponseDescription.ts
@@ -0,0 +1,16 @@
import type { Readable } from 'stream';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import { ResponseDescription } from './ResponseDescription';

/**
* Corresponds to a 200 response, containing relevant metadata and potentially data.
*/
export class OkResponseDescription extends ResponseDescription {
/**
* @param metadata - Metadata concerning the response.
* @param data - Potential data. @ignored
*/
public constructor(metadata: RepresentationMetadata, data?: Readable) {
super(200, metadata, data);
}
}
10 changes: 10 additions & 0 deletions src/ldp/http/response/ResetResponseDescription.ts
@@ -0,0 +1,10 @@
import { ResponseDescription } from './ResponseDescription';

/**
* Corresponds to a 205 response.
*/
export class ResetResponseDescription extends ResponseDescription {
public constructor() {
super(205);
}
}
22 changes: 22 additions & 0 deletions src/ldp/http/response/ResponseDescription.ts
@@ -0,0 +1,22 @@
import type { Readable } from 'stream';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';

/**
* The result of executing an operation.
*/
export class ResponseDescription {
public readonly statusCode: number;
public readonly metadata?: RepresentationMetadata;
public readonly data?: Readable;

/**
* @param statusCode - Status code to return.
* @param metadata - Metadata corresponding to the response (and data potentially).
* @param data - Data that needs to be returned. @ignored
*/
public constructor(statusCode: number, metadata?: RepresentationMetadata, data?: Readable) {
this.statusCode = statusCode;
this.metadata = metadata;
this.data = data;
}
}
5 changes: 3 additions & 2 deletions src/ldp/operations/DeleteOperationHandler.ts
@@ -1,8 +1,9 @@
import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { ResetResponseDescription } from '../http/response/ResetResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';

/**
* Handles DELETE {@link Operation}s.
Expand All @@ -24,6 +25,6 @@ export class DeleteOperationHandler extends OperationHandler {

public async handle(input: Operation): Promise<ResponseDescription> {
await this.store.deleteResource(input.target);
return { identifier: input.target };
return new ResetResponseDescription();
}
}
5 changes: 3 additions & 2 deletions src/ldp/operations/GetOperationHandler.ts
@@ -1,8 +1,9 @@
import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { OkResponseDescription } from '../http/response/OkResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';

/**
* Handles GET {@link Operation}s.
Expand All @@ -24,6 +25,6 @@ export class GetOperationHandler extends OperationHandler {

public async handle(input: Operation): Promise<ResponseDescription> {
const body = await this.store.getRepresentation(input.target, input.preferences);
return { identifier: input.target, body };
return new OkResponseDescription(body.metadata, body.data);
}
}
11 changes: 4 additions & 7 deletions src/ldp/operations/HeadOperationHandler.ts
@@ -1,9 +1,9 @@
import { Readable } from 'stream';
import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { OkResponseDescription } from '../http/response/OkResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';

/**
* Handles HEAD {@link Operation}s.
Expand All @@ -28,10 +28,7 @@ export class HeadOperationHandler extends OperationHandler {

// Close the Readable as we will not return it.
body.data.destroy();
body.data = new Readable();
body.data._read = function(): void {
body.data.push(null);
};
return { identifier: input.target, body };

return new OkResponseDescription(body.metadata);
}
}
2 changes: 1 addition & 1 deletion src/ldp/operations/OperationHandler.ts
@@ -1,6 +1,6 @@
import { AsyncHandler } from '../../util/AsyncHandler';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation';
import type { ResponseDescription } from './ResponseDescription';

/**
* Handler for a specific operation type.
Expand Down
5 changes: 3 additions & 2 deletions src/ldp/operations/PatchOperationHandler.ts
@@ -1,9 +1,10 @@
import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import type { Patch } from '../http/Patch';
import { ResetResponseDescription } from '../http/response/ResetResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';

export class PatchOperationHandler extends OperationHandler {
private readonly store: ResourceStore;
Expand All @@ -21,6 +22,6 @@ export class PatchOperationHandler extends OperationHandler {

public async handle(input: Operation): Promise<ResponseDescription> {
await this.store.modifyResource(input.target, input.body as Patch);
return { identifier: input.target };
return new ResetResponseDescription();
}
}
5 changes: 3 additions & 2 deletions src/ldp/operations/PostOperationHandler.ts
@@ -1,9 +1,10 @@
import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { CreatedResponseDescription } from '../http/response/CreatedResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';

/**
* Handles POST {@link Operation}s.
Expand Down Expand Up @@ -31,6 +32,6 @@ export class PostOperationHandler extends OperationHandler {
throw new UnsupportedHttpError('POST operations require a body');
}
const identifier = await this.store.addResource(input.target, input.body);
return { identifier };
return new CreatedResponseDescription(identifier);
}
}
5 changes: 3 additions & 2 deletions src/ldp/operations/PutOperationHandler.ts
@@ -1,9 +1,10 @@
import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { ResetResponseDescription } from '../http/response/ResetResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';

/**
* Handles PUT {@link Operation}s.
Expand Down Expand Up @@ -31,6 +32,6 @@ export class PutOperationHandler extends OperationHandler {
throw new UnsupportedHttpError('PUT operations require a body');
}
await this.store.setRepresentation(input.target, input.body);
return { identifier: input.target };
return new ResetResponseDescription();
}
}
10 changes: 0 additions & 10 deletions src/ldp/operations/ResponseDescription.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/util/UriConstants.ts
Expand Up @@ -28,6 +28,7 @@ export const FOAF = {

const HTTP_PREFIX = createNamespace('urn:solid:http:');
export const HTTP = {
location: HTTP_PREFIX('location'),
slug: HTTP_PREFIX('slug'),
};

Expand Down

0 comments on commit 1260c5c

Please sign in to comment.