mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Store methods in MethodNotAllowedHttpError
This commit is contained in:
parent
f3dedf4e27
commit
effc20a270
@ -22,7 +22,7 @@ export abstract class BaseInteractionHandler extends InteractionHandler {
|
|||||||
await super.canHandle(input);
|
await super.canHandle(input);
|
||||||
const { method } = input.operation;
|
const { method } = input.operation;
|
||||||
if (method !== 'GET' && method !== 'POST') {
|
if (method !== 'GET' && method !== 'POST') {
|
||||||
throw new MethodNotAllowedHttpError('Only GET/POST requests are supported.');
|
throw new MethodNotAllowedHttpError([ method ], 'Only GET/POST requests are supported.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export abstract class BaseInteractionHandler extends InteractionHandler {
|
|||||||
switch (input.operation.method) {
|
switch (input.operation.method) {
|
||||||
case 'GET': return this.handleGet(input);
|
case 'GET': return this.handleGet(input);
|
||||||
case 'POST': return this.handlePost(input);
|
case 'POST': return this.handlePost(input);
|
||||||
default: throw new MethodNotAllowedHttpError();
|
default: throw new MethodNotAllowedHttpError([ input.operation.method ]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export class HtmlViewHandler extends InteractionHandler {
|
|||||||
|
|
||||||
public async canHandle({ operation }: InteractionHandlerInput): Promise<void> {
|
public async canHandle({ operation }: InteractionHandlerInput): Promise<void> {
|
||||||
if (operation.method !== 'GET') {
|
if (operation.method !== 'GET') {
|
||||||
throw new MethodNotAllowedHttpError();
|
throw new MethodNotAllowedHttpError([ operation.method ]);
|
||||||
}
|
}
|
||||||
if (!this.templates[operation.target.path]) {
|
if (!this.templates[operation.target.path]) {
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
|
@ -68,7 +68,7 @@ export class SetupHttpHandler extends OperationHttpHandler {
|
|||||||
switch (operation.method) {
|
switch (operation.method) {
|
||||||
case 'GET': return this.handleGet(operation);
|
case 'GET': return this.handleGet(operation);
|
||||||
case 'POST': return this.handlePost(operation);
|
case 'POST': return this.handlePost(operation);
|
||||||
default: throw new MethodNotAllowedHttpError();
|
default: throw new MethodNotAllowedHttpError([ operation.method ]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ export class RouterHandler extends HttpHandler {
|
|||||||
throw new BadRequestHttpError('Cannot handle request without a method');
|
throw new BadRequestHttpError('Cannot handle request without a method');
|
||||||
}
|
}
|
||||||
if (!this.allMethods && !this.allowedMethods.includes(request.method)) {
|
if (!this.allMethods && !this.allowedMethods.includes(request.method)) {
|
||||||
throw new MethodNotAllowedHttpError(`${request.method} is not allowed.`);
|
throw new MethodNotAllowedHttpError([ request.method ], `${request.method} is not allowed.`);
|
||||||
}
|
}
|
||||||
const pathName = await getRelativeUrl(this.baseUrl, request, this.targetExtractor);
|
const pathName = await getRelativeUrl(this.baseUrl, request, this.targetExtractor);
|
||||||
if (!this.allowedPathNamesRegEx.some((regex): boolean => regex.test(pathName))) {
|
if (!this.allowedPathNamesRegEx.some((regex): boolean => regex.test(pathName))) {
|
||||||
|
@ -155,7 +155,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
// that are not supported by the target resource."
|
// that are not supported by the target resource."
|
||||||
// https://solid.github.io/specification/protocol#reading-writing-resources
|
// https://solid.github.io/specification/protocol#reading-writing-resources
|
||||||
if (!isContainerPath(parentMetadata.identifier.value)) {
|
if (!isContainerPath(parentMetadata.identifier.value)) {
|
||||||
throw new MethodNotAllowedHttpError('The given path is not a container.');
|
throw new MethodNotAllowedHttpError([ 'POST' ], 'The given path is not a container.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validateConditions(conditions, parentMetadata);
|
this.validateConditions(conditions, parentMetadata);
|
||||||
@ -240,14 +240,15 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
// the server MUST respond with the 405 status code."
|
// the server MUST respond with the 405 status code."
|
||||||
// https://solid.github.io/specification/protocol#deleting-resources
|
// https://solid.github.io/specification/protocol#deleting-resources
|
||||||
if (this.isRootStorage(metadata)) {
|
if (this.isRootStorage(metadata)) {
|
||||||
throw new MethodNotAllowedHttpError('Cannot delete a root storage container.');
|
throw new MethodNotAllowedHttpError([ 'DELETE' ], 'Cannot delete a root storage container.');
|
||||||
}
|
}
|
||||||
if (this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) &&
|
if (this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) &&
|
||||||
this.auxiliaryStrategy.isRequiredInRoot(identifier)) {
|
this.auxiliaryStrategy.isRequiredInRoot(identifier)) {
|
||||||
const subjectIdentifier = this.auxiliaryStrategy.getSubjectIdentifier(identifier);
|
const subjectIdentifier = this.auxiliaryStrategy.getSubjectIdentifier(identifier);
|
||||||
const parentMetadata = await this.accessor.getMetadata(subjectIdentifier);
|
const parentMetadata = await this.accessor.getMetadata(subjectIdentifier);
|
||||||
if (this.isRootStorage(parentMetadata)) {
|
if (this.isRootStorage(parentMetadata)) {
|
||||||
throw new MethodNotAllowedHttpError(`Cannot delete ${identifier.path} from a root storage container.`);
|
throw new MethodNotAllowedHttpError([ 'DELETE' ],
|
||||||
|
`Cannot delete ${identifier.path} from a root storage container.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +141,7 @@ export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const SOLID_ERROR = createUriAndTermNamespace('urn:npm:solid:community-server:error:',
|
export const SOLID_ERROR = createUriAndTermNamespace('urn:npm:solid:community-server:error:',
|
||||||
|
'disallowedMethod',
|
||||||
'errorResponse',
|
'errorResponse',
|
||||||
'stack',
|
'stack',
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,32 @@
|
|||||||
|
import { DataFactory } from 'n3';
|
||||||
|
import type { Quad, Quad_Subject } from 'rdf-js';
|
||||||
|
import { toNamedTerm, toObjectTerm } from '../TermUtil';
|
||||||
|
import { SOLID_ERROR } from '../Vocabularies';
|
||||||
import type { HttpErrorOptions } from './HttpError';
|
import type { HttpErrorOptions } from './HttpError';
|
||||||
import { HttpError } from './HttpError';
|
import { generateHttpErrorClass } from './HttpError';
|
||||||
|
import quad = DataFactory.quad;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
const BaseHttpError = generateHttpErrorClass(405, 'MethodNotAllowedHttpError');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error thrown when data was found for the requested identifier, but is not supported by the target resource.
|
* An error thrown when data was found for the requested identifier, but is not supported by the target resource.
|
||||||
|
* Can keep track of the methods that are not allowed.
|
||||||
*/
|
*/
|
||||||
export class MethodNotAllowedHttpError extends HttpError {
|
export class MethodNotAllowedHttpError extends BaseHttpError {
|
||||||
public constructor(message?: string, options?: HttpErrorOptions) {
|
public readonly methods: Readonly<string[]>;
|
||||||
super(405, 'MethodNotAllowedHttpError', message, options);
|
|
||||||
|
public constructor(methods: string[] = [], message?: string, options?: HttpErrorOptions) {
|
||||||
|
super(message ?? `${methods} are not allowed.`, options);
|
||||||
|
this.methods = methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isInstance(error: any): error is MethodNotAllowedHttpError {
|
public generateMetadata(subject: Quad_Subject | string): Quad[] {
|
||||||
return HttpError.isInstance(error) && error.statusCode === 405;
|
const term = toNamedTerm(subject);
|
||||||
|
const quads = super.generateMetadata(term);
|
||||||
|
for (const method of this.methods) {
|
||||||
|
quads.push(quad(term, SOLID_ERROR.terms.disallowedMethod, toObjectTerm(method, true)));
|
||||||
|
}
|
||||||
|
return quads;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ describe('HttpError', (): void => {
|
|||||||
[ 'UnauthorizedHttpError', 401, UnauthorizedHttpError ],
|
[ 'UnauthorizedHttpError', 401, UnauthorizedHttpError ],
|
||||||
[ 'ForbiddenHttpError', 403, ForbiddenHttpError ],
|
[ 'ForbiddenHttpError', 403, ForbiddenHttpError ],
|
||||||
[ 'NotFoundHttpError', 404, NotFoundHttpError ],
|
[ 'NotFoundHttpError', 404, NotFoundHttpError ],
|
||||||
[ 'MethodNotAllowedHttpError', 405, MethodNotAllowedHttpError ],
|
|
||||||
[ 'ConflictHttpError', 409, ConflictHttpError ],
|
[ 'ConflictHttpError', 409, ConflictHttpError ],
|
||||||
[ 'PreconditionFailedHttpError', 412, PreconditionFailedHttpError ],
|
[ 'PreconditionFailedHttpError', 412, PreconditionFailedHttpError ],
|
||||||
[ 'PayloadHttpError', 413, PayloadHttpError ],
|
[ 'PayloadHttpError', 413, PayloadHttpError ],
|
||||||
@ -84,4 +83,32 @@ describe('HttpError', (): void => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Separate test due to different constructor
|
||||||
|
describe('MethodNotAllowedHttpError', (): void => {
|
||||||
|
const options = {
|
||||||
|
cause: new Error('cause'),
|
||||||
|
errorCode: 'E1234',
|
||||||
|
details: { some: 'detail' },
|
||||||
|
};
|
||||||
|
const instance = new MethodNotAllowedHttpError([ 'GET' ], 'my message', options);
|
||||||
|
|
||||||
|
it('is valid.', async(): Promise<void> => {
|
||||||
|
expect(new MethodNotAllowedHttpError().methods).toHaveLength(0);
|
||||||
|
expect(MethodNotAllowedHttpError.isInstance(instance)).toBe(true);
|
||||||
|
expect(MethodNotAllowedHttpError.uri).toEqualRdfTerm(generateHttpErrorUri(405));
|
||||||
|
expect(instance.name).toBe('MethodNotAllowedHttpError');
|
||||||
|
expect(instance.statusCode).toBe(405);
|
||||||
|
expect(instance.message).toBe('my message');
|
||||||
|
expect(instance.cause).toBe(options.cause);
|
||||||
|
expect(instance.errorCode).toBe(options.errorCode);
|
||||||
|
expect(new MethodNotAllowedHttpError([ 'GET' ]).errorCode).toBe(`H${405}`);
|
||||||
|
|
||||||
|
const subject = namedNode('subject');
|
||||||
|
expect(instance.generateMetadata(subject)).toBeRdfIsomorphic([
|
||||||
|
quad(subject, SOLID_ERROR.terms.errorResponse, MethodNotAllowedHttpError.uri),
|
||||||
|
quad(subject, SOLID_ERROR.terms.disallowedMethod, literal('GET')),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user