refactor: Add isContainerPath function

This commit is contained in:
Joachim Van Herwegen 2020-11-18 15:58:22 +01:00
parent 1073c2ff4c
commit 75e4f73c3f
6 changed files with 35 additions and 15 deletions

View File

@ -12,7 +12,13 @@ import { MethodNotAllowedHttpError } from '../util/errors/MethodNotAllowedHttpEr
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError'; import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { NotImplementedError } from '../util/errors/NotImplementedError'; import { NotImplementedError } from '../util/errors/NotImplementedError';
import { UnsupportedHttpError } from '../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../util/errors/UnsupportedHttpError';
import { ensureTrailingSlash, getParentContainer, trimTrailingSlashes } from '../util/PathUtil'; import {
ensureTrailingSlash,
getParentContainer,
isContainerIdentifier,
isContainerPath,
trimTrailingSlashes,
} from '../util/PathUtil';
import { parseQuads } from '../util/QuadUtil'; import { parseQuads } from '../util/QuadUtil';
import { generateResourceQuads } from '../util/ResourceUtil'; import { generateResourceQuads } from '../util/ResourceUtil';
import { CONTENT_TYPE, HTTP, LDP, RDF } from '../util/UriConstants'; import { CONTENT_TYPE, HTTP, LDP, RDF } from '../util/UriConstants';
@ -93,7 +99,7 @@ export class DataAccessorBasedStore implements ResourceStore {
// When a POST method request targets a non-container resource without an existing representation, // When a POST method request targets a non-container resource without an existing representation,
// the server MUST respond with the 404 status code. // the server MUST respond with the 404 status code.
if (!parentMetadata && !container.path.endsWith('/')) { if (!parentMetadata && !isContainerIdentifier(container)) {
throw new NotFoundHttpError(); throw new NotFoundHttpError();
} }
@ -104,7 +110,7 @@ export class DataAccessorBasedStore implements ResourceStore {
const newID = this.createSafeUri(container, representation.metadata, parentMetadata); const newID = this.createSafeUri(container, representation.metadata, parentMetadata);
// Write the data. New containers will need to be created if there is no parent. // Write the data. New containers will need to be created if there is no parent.
await this.writeData(newID, representation, newID.path.endsWith('/'), !parentMetadata); await this.writeData(newID, representation, isContainerIdentifier(newID), !parentMetadata);
return newID; return newID;
} }
@ -128,7 +134,7 @@ export class DataAccessorBasedStore implements ResourceStore {
if (oldMetadata && isContainer !== this.isExistingContainer(oldMetadata)) { if (oldMetadata && isContainer !== this.isExistingContainer(oldMetadata)) {
throw new ConflictHttpError('Input resource type does not match existing resource type.'); throw new ConflictHttpError('Input resource type does not match existing resource type.');
} }
if (isContainer !== identifier.path.endsWith('/')) { if (isContainer !== isContainerIdentifier(identifier)) {
throw new UnsupportedHttpError('Containers should have a `/` at the end of their path, resources should not.'); throw new UnsupportedHttpError('Containers should have a `/` at the end of their path, resources should not.');
} }
@ -172,7 +178,7 @@ export class DataAccessorBasedStore implements ResourceStore {
* @param identifier - Identifier that needs to be checked. * @param identifier - Identifier that needs to be checked.
*/ */
protected async getNormalizedMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> { protected async getNormalizedMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {
const hasSlash = identifier.path.endsWith('/'); const hasSlash = isContainerIdentifier(identifier);
try { try {
return await this.accessor.getMetadata(identifier); return await this.accessor.getMetadata(identifier);
} catch (error: unknown) { } catch (error: unknown) {
@ -312,7 +318,7 @@ export class DataAccessorBasedStore implements ResourceStore {
isContainer = this.isExistingContainer(metadata); isContainer = this.isExistingContainer(metadata);
} catch { } catch {
const slug = suffix ?? metadata.get(HTTP.slug)?.value; const slug = suffix ?? metadata.get(HTTP.slug)?.value;
isContainer = Boolean(slug?.endsWith('/')); isContainer = Boolean(slug && isContainerPath(slug));
} }
return isContainer; return isContainer;
} }

View File

@ -10,6 +10,7 @@ import {
decodeUriPathComponents, decodeUriPathComponents,
encodeUriPathComponents, encodeUriPathComponents,
ensureTrailingSlash, ensureTrailingSlash,
isContainerIdentifier,
trimTrailingSlashes, trimTrailingSlashes,
} from '../util/PathUtil'; } from '../util/PathUtil';
import type { FileIdentifierMapper, ResourceLink } from './FileIdentifierMapper'; import type { FileIdentifierMapper, ResourceLink } from './FileIdentifierMapper';
@ -74,7 +75,7 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
let filePath = this.getAbsolutePath(path); let filePath = this.getAbsolutePath(path);
// Container // Container
if (identifier.path.endsWith('/')) { if (isContainerIdentifier(identifier)) {
this.logger.debug(`URL ${identifier.path} points to the container ${filePath}`); this.logger.debug(`URL ${identifier.path} points to the container ${filePath}`);
return { return {
identifier, identifier,

View File

@ -12,6 +12,7 @@ import { ConflictHttpError } from '../../util/errors/ConflictHttpError';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError'; import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { isSystemError } from '../../util/errors/SystemError'; import { isSystemError } from '../../util/errors/SystemError';
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError'; import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
import { isContainerIdentifier } from '../../util/PathUtil';
import { parseQuads, pushQuad, serializeQuads } from '../../util/QuadUtil'; import { parseQuads, pushQuad, serializeQuads } from '../../util/QuadUtil';
import { generateContainmentQuads, generateResourceQuads } from '../../util/ResourceUtil'; import { generateContainmentQuads, generateResourceQuads } from '../../util/ResourceUtil';
import { CONTENT_TYPE, DCTERMS, POSIX, RDF, XSD } from '../../util/UriConstants'; import { CONTENT_TYPE, DCTERMS, POSIX, RDF, XSD } from '../../util/UriConstants';
@ -62,10 +63,10 @@ export class FileDataAccessor implements DataAccessor {
public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> { public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {
const link = await this.resourceMapper.mapUrlToFilePath(identifier); const link = await this.resourceMapper.mapUrlToFilePath(identifier);
const stats = await this.getStats(link.filePath); const stats = await this.getStats(link.filePath);
if (!identifier.path.endsWith('/') && stats.isFile()) { if (!isContainerIdentifier(identifier) && stats.isFile()) {
return this.getFileMetadata(link, stats); return this.getFileMetadata(link, stats);
} }
if (identifier.path.endsWith('/') && stats.isDirectory()) { if (isContainerIdentifier(identifier) && stats.isDirectory()) {
return this.getDirectoryMetadata(link, stats); return this.getDirectoryMetadata(link, stats);
} }
throw new NotFoundHttpError(); throw new NotFoundHttpError();
@ -131,9 +132,9 @@ export class FileDataAccessor implements DataAccessor {
} }
} }
if (!identifier.path.endsWith('/') && stats.isFile()) { if (!isContainerIdentifier(identifier) && stats.isFile()) {
await fsPromises.unlink(link.filePath); await fsPromises.unlink(link.filePath);
} else if (identifier.path.endsWith('/') && stats.isDirectory()) { } else if (isContainerIdentifier(identifier) && stats.isDirectory()) {
await fsPromises.rmdir(link.filePath); await fsPromises.rmdir(link.filePath);
} else { } else {
throw new NotFoundHttpError(); throw new NotFoundHttpError();

View File

@ -5,7 +5,7 @@ import type { NamedNode } from 'rdf-js';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata'; import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError'; import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { ensureTrailingSlash } from '../../util/PathUtil'; import { ensureTrailingSlash, isContainerIdentifier } from '../../util/PathUtil';
import { generateContainmentQuads, generateResourceQuads } from '../../util/ResourceUtil'; import { generateContainmentQuads, generateResourceQuads } from '../../util/ResourceUtil';
import type { DataAccessor } from './DataAccessor'; import type { DataAccessor } from './DataAccessor';
@ -66,7 +66,7 @@ export class InMemoryDataAccessor implements DataAccessor {
public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> { public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {
const entry = this.getEntry(identifier); const entry = this.getEntry(identifier);
if (this.isDataEntry(entry) === identifier.path.endsWith('/')) { if (this.isDataEntry(entry) === isContainerIdentifier(identifier)) {
throw new NotFoundHttpError(); throw new NotFoundHttpError();
} }
return this.generateMetadata(identifier, entry); return this.generateMetadata(identifier, entry);

View File

@ -23,7 +23,7 @@ import { ConflictHttpError } from '../../util/errors/ConflictHttpError';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError'; import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError'; import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
import { ensureTrailingSlash, getParentContainer } from '../../util/PathUtil'; import { ensureTrailingSlash, getParentContainer, isContainerIdentifier } from '../../util/PathUtil';
import { generateResourceQuads } from '../../util/ResourceUtil'; import { generateResourceQuads } from '../../util/ResourceUtil';
import { CONTENT_TYPE, LDP } from '../../util/UriConstants'; import { CONTENT_TYPE, LDP } from '../../util/UriConstants';
import { toNamedNode } from '../../util/UriUtil'; import { toNamedNode } from '../../util/UriUtil';
@ -81,7 +81,7 @@ export class SparqlDataAccessor implements DataAccessor {
*/ */
public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> { public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {
const name = namedNode(identifier.path); const name = namedNode(identifier.path);
const query = identifier.path.endsWith('/') ? const query = isContainerIdentifier(identifier) ?
this.sparqlConstructContainer(name) : this.sparqlConstructContainer(name) :
this.sparqlConstruct(this.getMetadataNode(name)); this.sparqlConstruct(this.getMetadataNode(name));
const stream = await this.sendSparqlConstruct(query); const stream = await this.sendSparqlConstruct(query);

View File

@ -58,3 +58,15 @@ export const getParentContainer = (id: ResourceIdentifier): ResourceIdentifier =
return { path: parentPath }; return { path: parentPath };
}; };
/**
* Checks if the path corresponds to a container path (ending in a /).
* @param path - Path to check.
*/
export const isContainerPath = (path: string): boolean => path.endsWith('/');
/**
* Checks if the identifier corresponds to a container identifier.
* @param identifier - Identifier to check.
*/
export const isContainerIdentifier = (identifier: ResourceIdentifier): boolean => isContainerPath(identifier.path);