fix: Remove metadata content-type assumption from QuadUtil

This commit is contained in:
Joachim Van Herwegen 2020-12-15 16:16:08 +01:00
parent 1464288b0f
commit a114d00827
4 changed files with 47 additions and 21 deletions

View File

@ -246,7 +246,7 @@ export class DataAccessorBasedStore implements ResourceStore {
if (representation.metadata.contentType === INTERNAL_QUADS) { if (representation.metadata.contentType === INTERNAL_QUADS) {
quads = await arrayifyStream(representation.data); quads = await arrayifyStream(representation.data);
} else { } else {
quads = await parseQuads(representation.data); quads = await parseQuads(representation.data, representation.metadata.contentType);
} }
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof Error) { if (error instanceof Error) {

View File

@ -94,7 +94,7 @@ export class FileDataAccessor implements DataAccessor {
} catch (error: unknown) { } catch (error: unknown) {
// Delete the metadata if there was an error writing the file // Delete the metadata if there was an error writing the file
if (wroteMetadata) { if (wroteMetadata) {
await fsPromises.unlink(await this.getMetadataPath(link.identifier)); await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
} }
throw error; throw error;
} }
@ -125,7 +125,7 @@ export class FileDataAccessor implements DataAccessor {
const stats = await this.getStats(link.filePath); const stats = await this.getStats(link.filePath);
try { try {
await fsPromises.unlink(await this.getMetadataPath(link.identifier)); await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
} catch (error: unknown) { } catch (error: unknown) {
// Ignore if it doesn't exist // Ignore if it doesn't exist
if (!isSystemError(error) || error.code !== 'ENOENT') { if (!isSystemError(error) || error.code !== 'ENOENT') {
@ -161,11 +161,11 @@ export class FileDataAccessor implements DataAccessor {
} }
/** /**
* Generates file path that corresponds to the metadata file of the given identifier. * Generates ResourceLink that corresponds to the metadata resource of the given identifier.
* Starts from the identifier to make sure any potentially added extension has no impact on the path.
*/ */
private async getMetadataPath(identifier: ResourceIdentifier): Promise<string> { private async getMetadataLink(identifier: ResourceIdentifier): Promise<ResourceLink> {
return (await this.resourceMapper.mapUrlToFilePath({ path: `${identifier.path}.meta` })).filePath; const metaIdentifier = { path: `${identifier.path}.meta` };
return this.resourceMapper.mapUrlToFilePath(metaIdentifier);
} }
/** /**
@ -213,19 +213,20 @@ export class FileDataAccessor implements DataAccessor {
metadata.removeAll(RDF.type); metadata.removeAll(RDF.type);
metadata.removeAll(CONTENT_TYPE); metadata.removeAll(CONTENT_TYPE);
const quads = metadata.quads(); const quads = metadata.quads();
const metadataPath = await this.getMetadataPath(link.identifier); const metadataLink = await this.getMetadataLink(link.identifier);
let wroteMetadata: boolean; let wroteMetadata: boolean;
// Write metadata to file if there are quads remaining // Write metadata to file if there are quads remaining
if (quads.length > 0) { if (quads.length > 0) {
const serializedMetadata = serializeQuads(quads); // Determine required content-type based on mapper
await this.writeDataFile(metadataPath, serializedMetadata); const serializedMetadata = serializeQuads(quads, metadataLink.contentType);
await this.writeDataFile(metadataLink.filePath, serializedMetadata);
wroteMetadata = true; wroteMetadata = true;
// Delete (potentially) existing metadata file if no metadata needs to be stored // Delete (potentially) existing metadata file if no metadata needs to be stored
} else { } else {
try { try {
await fsPromises.unlink(metadataPath); await fsPromises.unlink(metadataLink.filePath);
} catch (error: unknown) { } catch (error: unknown) {
// Metadata file doesn't exist so nothing needs to be removed // Metadata file doesn't exist so nothing needs to be removed
if (!isSystemError(error) || error.code !== 'ENOENT') { if (!isSystemError(error) || error.code !== 'ENOENT') {
@ -260,13 +261,13 @@ export class FileDataAccessor implements DataAccessor {
*/ */
private async getRawMetadata(identifier: ResourceIdentifier): Promise<Quad[]> { private async getRawMetadata(identifier: ResourceIdentifier): Promise<Quad[]> {
try { try {
const metadataPath = await this.getMetadataPath(identifier); const metadataLink = await this.getMetadataLink(identifier);
// Check if the metadata file exists first // Check if the metadata file exists first
await fsPromises.lstat(metadataPath); await fsPromises.lstat(metadataLink.filePath);
const readMetadataStream = guardStream(createReadStream(metadataPath)); const readMetadataStream = guardStream(createReadStream(metadataLink.filePath));
return await parseQuads(readMetadataStream); return await parseQuads(readMetadataStream, metadataLink.contentType);
} catch (error: unknown) { } catch (error: unknown) {
// Metadata file doesn't exist so lets keep `rawMetaData` an empty array. // Metadata file doesn't exist so lets keep `rawMetaData` an empty array.
if (!isSystemError(error) || error.code !== 'ENOENT') { if (!isSystemError(error) || error.code !== 'ENOENT') {

View File

@ -3,7 +3,6 @@ import arrayifyStream from 'arrayify-stream';
import { DataFactory, StreamParser, StreamWriter } from 'n3'; import { DataFactory, StreamParser, StreamWriter } from 'n3';
import type { Literal, NamedNode, Quad } from 'rdf-js'; import type { Literal, NamedNode, Quad } from 'rdf-js';
import streamifyArray from 'streamify-array'; import streamifyArray from 'streamify-array';
import { TEXT_TURTLE } from './ContentTypes';
import type { Guarded } from './GuardedStream'; import type { Guarded } from './GuardedStream';
import { pipeSafely } from './StreamUtil'; import { pipeSafely } from './StreamUtil';
@ -17,17 +16,19 @@ export const pushQuad =
/** /**
* Helper function for serializing an array of quads, with as result a Readable object. * Helper function for serializing an array of quads, with as result a Readable object.
* @param quads - The array of quads. * @param quads - The array of quads.
* @param contentType - The content-type to serialize to.
* *
* @returns The Readable object. * @returns The Readable object.
*/ */
export const serializeQuads = (quads: Quad[]): Guarded<Readable> => export const serializeQuads = (quads: Quad[], contentType?: string): Guarded<Readable> =>
pipeSafely(streamifyArray(quads), new StreamWriter({ format: TEXT_TURTLE })); pipeSafely(streamifyArray(quads), new StreamWriter({ format: contentType }));
/** /**
* Helper function to convert a Readable into an array of quads. * Helper function to convert a Readable into an array of quads.
* @param readable - The readable object. * @param readable - The readable object.
* @param contentType - The content-type of the stream.
* *
* @returns A promise containing the array of quads. * @returns A promise containing the array of quads.
*/ */
export const parseQuads = async(readable: Guarded<Readable>): Promise<Quad[]> => export const parseQuads = async(readable: Guarded<Readable>, contentType?: string): Promise<Quad[]> =>
arrayifyStream(pipeSafely(readable, new StreamParser({ format: TEXT_TURTLE }))); arrayifyStream(pipeSafely(readable, new StreamParser({ format: contentType })));

View File

@ -1,7 +1,8 @@
import 'jest-rdf'; import 'jest-rdf';
import { DataFactory } from 'n3'; import { DataFactory } from 'n3';
import type { Quad } from 'rdf-js'; import type { Quad } from 'rdf-js';
import { pushQuad } from '../../../src/util/QuadUtil'; import { parseQuads, pushQuad, serializeQuads } from '../../../src/util/QuadUtil';
import { guardedStreamFrom, readableToString } from '../../../src/util/StreamUtil';
describe('QuadUtil', (): void => { describe('QuadUtil', (): void => {
describe('#pushQuad', (): void => { describe('#pushQuad', (): void => {
@ -13,4 +14,27 @@ describe('QuadUtil', (): void => {
]); ]);
}); });
}); });
describe('#serializeQuads', (): void => {
it('converts quads to the requested format.', async(): Promise<void> => {
const quads = [ DataFactory.quad(
DataFactory.namedNode('pre:sub'),
DataFactory.namedNode('pre:pred'),
DataFactory.literal('obj'),
) ];
const stream = serializeQuads(quads, 'application/n-triples');
await expect(readableToString(stream)).resolves.toMatch('<pre:sub> <pre:pred> "obj" .');
});
});
describe('#parseQuads', (): void => {
it('parses quads from the requested format.', async(): Promise<void> => {
const stream = guardedStreamFrom([ '<pre:sub> <pre:pred> "obj".' ]);
await expect(parseQuads(stream, 'application/n-triples')).resolves.toEqualRdfQuadArray([ DataFactory.quad(
DataFactory.namedNode('pre:sub'),
DataFactory.namedNode('pre:pred'),
DataFactory.literal('obj'),
) ]);
});
});
}); });