From 9c080c2101876e2a0008194cba5416fa4fe0ce15 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Wed, 9 Dec 2020 13:43:57 +0100 Subject: [PATCH] feat: Create ContainerManager for containing container conventions --- src/index.ts | 4 ++ src/util/identifiers/IdentifierStrategy.ts | 26 +++++++++++++ .../SingleRootIdentifierStrategy.ts | 37 +++++++++++++++++++ .../SingleRootIdentifierStrategy.test.ts | 33 +++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 src/util/identifiers/IdentifierStrategy.ts create mode 100644 src/util/identifiers/SingleRootIdentifierStrategy.ts create mode 100644 test/unit/util/identifiers/SingleRootIdentifierStrategy.test.ts diff --git a/src/index.ts b/src/index.ts index cdc5ff814..38d8bfd36 100644 --- a/src/index.ts +++ b/src/index.ts @@ -172,6 +172,10 @@ export * from './util/errors/SystemError'; export * from './util/errors/UnauthorizedHttpError'; export * from './util/errors/UnsupportedMediaTypeHttpError'; +// Util/Identifiers +export * from './util/identifiers/IdentifierStrategy'; +export * from './util/identifiers/SingleRootIdentifierStrategy'; + // Util/Locking export * from './util/locking/ExpiringLock'; export * from './util/locking/ExpiringResourceLocker'; diff --git a/src/util/identifiers/IdentifierStrategy.ts b/src/util/identifiers/IdentifierStrategy.ts new file mode 100644 index 000000000..43f6caca3 --- /dev/null +++ b/src/util/identifiers/IdentifierStrategy.ts @@ -0,0 +1,26 @@ +import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; + +/** + * Captures the behavior of container identifiers in a certain storage configuration. + */ +export interface IdentifierStrategy { + /** + * Verifies if this identifier is supported. + * This does not check if this identifier actually exists, + * but checks if the identifier is in scope for this class. + */ + supportsIdentifier: (identifier: ResourceIdentifier) => boolean; + + /** + * Generates the identifier of the container this resource would be a member of. + * This does not check if that identifier actually exists. + * Will throw an error if the input identifier is a root container or is not supported. + */ + getParentContainer: (identifier: ResourceIdentifier) => ResourceIdentifier; + + /** + * Checks if the input corresponds to the identifier of a root container. + * This does not check if this identifier actually exists. + */ + isRootContainer: (identifier: ResourceIdentifier) => boolean; +} diff --git a/src/util/identifiers/SingleRootIdentifierStrategy.ts b/src/util/identifiers/SingleRootIdentifierStrategy.ts new file mode 100644 index 000000000..bb16c2e01 --- /dev/null +++ b/src/util/identifiers/SingleRootIdentifierStrategy.ts @@ -0,0 +1,37 @@ +import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; +import { InternalServerError } from '../errors/InternalServerError'; +import { ensureTrailingSlash } from '../PathUtil'; +import type { IdentifierStrategy } from './IdentifierStrategy'; + +/** + * An IdentifierStrategy that assumes there is only 1 root and all other identifiers are made by appending to that root. + */ +export class SingleRootIdentifierStrategy implements IdentifierStrategy { + private readonly baseUrl: string; + + public constructor(baseUrl: string) { + this.baseUrl = ensureTrailingSlash(baseUrl); + } + + public supportsIdentifier(identifier: ResourceIdentifier): boolean { + return identifier.path.startsWith(this.baseUrl); + } + + public getParentContainer(identifier: ResourceIdentifier): ResourceIdentifier { + if (!this.supportsIdentifier(identifier)) { + throw new InternalServerError(`${identifier.path} is not supported`); + } + if (this.isRootContainer(identifier)) { + throw new InternalServerError(`${identifier.path} is a root container and has no parent`); + } + + // Trailing slash is necessary for URL library + const parentPath = new URL('..', ensureTrailingSlash(identifier.path)).href; + + return { path: parentPath }; + } + + public isRootContainer(identifier: ResourceIdentifier): boolean { + return identifier.path === this.baseUrl; + } +} diff --git a/test/unit/util/identifiers/SingleRootIdentifierStrategy.test.ts b/test/unit/util/identifiers/SingleRootIdentifierStrategy.test.ts new file mode 100644 index 000000000..9c0df063f --- /dev/null +++ b/test/unit/util/identifiers/SingleRootIdentifierStrategy.test.ts @@ -0,0 +1,33 @@ +import { SingleRootIdentifierStrategy } from '../../../../src/util/identifiers/SingleRootIdentifierStrategy'; + +describe('A SingleRootIdentifierStrategy', (): void => { + const baseUrl = 'http://test.com/'; + const manager = new SingleRootIdentifierStrategy(baseUrl); + + it('verifies if identifiers are in its domain.', async(): Promise => { + expect(manager.supportsIdentifier({ path: 'http://notest.com/' })).toBe(false); + expect(manager.supportsIdentifier({ path: baseUrl })).toBe(true); + expect(manager.supportsIdentifier({ path: `${baseUrl}foo/bar` })).toBe(true); + }); + + it('returns the parent identifier.', async(): Promise => { + expect(manager.getParentContainer({ path: 'http://test.com/foo/bar' })).toEqual({ path: 'http://test.com/foo/' }); + expect(manager.getParentContainer({ path: 'http://test.com/foo/bar/' })).toEqual({ path: 'http://test.com/foo/' }); + }); + + it('errors when attempting to get the parent of an unsupported identifier.', async(): Promise => { + expect((): any => manager.getParentContainer({ path: 'http://nottest.com/' })) + .toThrow('http://nottest.com/ is not supported'); + }); + + it('errors when attempting to get the parent of a root container.', async(): Promise => { + expect((): any => manager.getParentContainer({ path: 'http://test.com/' })) + .toThrow('http://test.com/ is a root container and has no parent'); + }); + + it('checks for the root container by comparing with the base URL.', async(): Promise => { + expect(manager.isRootContainer({ path: 'http://notest.com/' })).toBe(false); + expect(manager.isRootContainer({ path: baseUrl })).toBe(true); + expect(manager.isRootContainer({ path: `${baseUrl}foo/bar` })).toBe(false); + }); +});