mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: ExtensionBasedMapper no longer throws if there is no file
This commit is contained in:
parent
36eed5d620
commit
d7434df808
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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'));
|
||||||
|
@ -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> => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user