feat: ExtensionBasedMapper no longer throws if there is no file

This commit is contained in:
Joachim Van Herwegen 2020-12-15 14:32:34 +01:00
parent 36eed5d620
commit d7434df808
5 changed files with 33 additions and 22 deletions

View File

@ -350,8 +350,8 @@ export class FileDataAccessor implements DataAccessor {
await fsPromises.unlink(oldLink.filePath); await fsPromises.unlink(oldLink.filePath);
} }
} catch (error: unknown) { } catch (error: unknown) {
// Ignore it if the file didn't exist yet // Ignore it if the file didn't exist yet and couldn't be unlinked
if (!(error instanceof NotFoundHttpError)) { if (!isSystemError(error) || error.code !== 'ENOENT') {
throw error; throw error;
} }
} }

View File

@ -4,7 +4,6 @@ import * as mime from 'mime-types';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil'; import { getLoggerFor } from '../../logging/LogUtil';
import { APPLICATION_OCTET_STREAM, TEXT_TURTLE } from '../../util/ContentTypes'; import { APPLICATION_OCTET_STREAM, TEXT_TURTLE } from '../../util/ContentTypes';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { import {
encodeUriPathComponents, encodeUriPathComponents,
@ -98,22 +97,18 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
); );
} catch { } catch {
// Parent folder does not exist (or is not a folder) // Parent folder does not exist (or is not a folder)
this.logger.warn(`No parent folder for ${identifier.path} found at ${folder}`);
throw new NotFoundHttpError();
} }
// File doesn't exist // Matching file found
if (!fileName) { if (fileName) {
this.logger.warn(`File for URL ${identifier.path} does not exist in ${folder}`); filePath = joinPath(folder, fileName);
throw new NotFoundHttpError();
} }
filePath = joinPath(folder, fileName);
this.logger.info(`The path for ${identifier.path} is ${filePath}`); this.logger.info(`The path for ${identifier.path} is ${filePath}`);
return { return {
identifier, identifier,
filePath, filePath,
contentType: this.getContentTypeFromExtension(fileName), contentType: this.getContentTypeFromExtension(filePath),
}; };
} }

View File

@ -29,7 +29,8 @@ export interface FileIdentifierMapper {
mapFilePathToUrl: (filePath: string, isContainer: boolean) => Promise<ResourceLink>; mapFilePathToUrl: (filePath: string, isContainer: boolean) => Promise<ResourceLink>;
/** /**
* Maps the given resource identifier / URL to a file path. * Maps the given resource identifier / URL to a file path.
* Determines the content-type if no content-type was provided. * Determines the content-type if no content-type was provided by finding the corresponding file.
* If there is no corresponding file a file path will be generated.
* For containers the content-type input gets ignored. * For containers the content-type input gets ignored.
* @param identifier - The input identifier. * @param identifier - The input identifier.
* @param contentType - The (optional) content-type of the resource. * @param contentType - The (optional) content-type of the resource.

View File

@ -223,20 +223,27 @@ describe('A FileDataAccessor', (): void => {
}); });
}); });
it('does not try to update the content-type if there is no original file.', async(): Promise<void> => {
metadata.identifier = DataFactory.namedNode(`${base}resource.txt`);
metadata.contentType = 'text/turtle';
metadata.add('new', 'metadata');
await expect(accessor.writeDocument({ path: `${base}resource.txt` }, data, metadata))
.resolves.toBeUndefined();
expect(cache.data).toEqual({
'resource.txt$.ttl': 'data',
'resource.txt.meta': expect.stringMatching(`<${base}resource.txt> <new> "metadata".`),
});
});
it('throws an error if there is an issue deleting the original file.', async(): Promise<void> => { it('throws an error if there is an issue deleting the original file.', async(): Promise<void> => {
cache.data = { 'resource$.ttl': '<this> <is> <data>.' }; cache.data = { 'resource$.ttl': '<this> <is> <data>.' };
jest.requireMock('fs').promises.unlink = (): any => { jest.requireMock('fs').promises.unlink = (): any => {
const error = new Error('error') as SystemError; const error = new Error('error') as SystemError;
error.code = 'ENOENT'; error.code = 'EISDIR';
error.syscall = 'unlink'; error.syscall = 'unlink';
throw error; throw error;
}; };
// `unlink` throwing ENOENT should not be an issue if the content-type does not change
metadata.contentType = 'text/turtle';
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata))
.resolves.toBeUndefined();
metadata.contentType = 'text/plain'; metadata.contentType = 'text/plain';
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata)) await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata))
.rejects.toThrow(new Error('error')); .rejects.toThrow(new Error('error'));

View File

@ -50,16 +50,24 @@ describe('An ExtensionBasedMapper', (): void => {
.rejects.toThrow(new BadRequestHttpError('Identifiers cannot contain a dollar sign before their extension')); .rejects.toThrow(new BadRequestHttpError('Identifiers cannot contain a dollar sign before their extension'));
}); });
it('throws 404 when looking in a folder that does not exist.', async(): Promise<void> => { it('determines content-type by extension when looking in a folder that does not exist.', async(): Promise<void> => {
fsPromises.readdir.mockImplementation((): void => { fsPromises.readdir.mockImplementation((): void => {
throw new Error('does not exist'); throw new Error('does not exist');
}); });
await expect(mapper.mapUrlToFilePath({ path: `${base}no/test.txt` })).rejects.toThrow(NotFoundHttpError); await expect(mapper.mapUrlToFilePath({ path: `${base}no/test.txt` })).resolves.toEqual({
identifier: { path: `${base}no/test.txt` },
filePath: `${rootFilepath}no/test.txt`,
contentType: 'text/plain',
});
}); });
it('throws 404 when looking for a file that does not exist.', async(): Promise<void> => { it('determines content-type by extension when looking for a file that does not exist.', async(): Promise<void> => {
fsPromises.readdir.mockReturnValue([ 'test.ttl' ]); fsPromises.readdir.mockReturnValue([ 'test.ttl' ]);
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).rejects.toThrow(NotFoundHttpError); await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).resolves.toEqual({
identifier: { path: `${base}test.txt` },
filePath: `${rootFilepath}test.txt`,
contentType: 'text/plain',
});
}); });
it('determines the content-type based on the extension.', async(): Promise<void> => { it('determines the content-type based on the extension.', async(): Promise<void> => {