Skip to content

Commit

Permalink
fix: Remove metadata content-type assumption from QuadUtil
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimvh committed Dec 18, 2020
1 parent 1464288 commit a114d00
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/storage/DataAccessorBasedStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class DataAccessorBasedStore implements ResourceStore {
if (representation.metadata.contentType === INTERNAL_QUADS) {
quads = await arrayifyStream(representation.data);
} else {
quads = await parseQuads(representation.data);
quads = await parseQuads(representation.data, representation.metadata.contentType);
}
} catch (error: unknown) {
if (error instanceof Error) {
Expand Down
29 changes: 15 additions & 14 deletions src/storage/accessors/FileDataAccessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class FileDataAccessor implements DataAccessor {
} catch (error: unknown) {
// Delete the metadata if there was an error writing the file
if (wroteMetadata) {
await fsPromises.unlink(await this.getMetadataPath(link.identifier));
await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
}
throw error;
}
Expand Down Expand Up @@ -125,7 +125,7 @@ export class FileDataAccessor implements DataAccessor {
const stats = await this.getStats(link.filePath);

try {
await fsPromises.unlink(await this.getMetadataPath(link.identifier));
await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
} catch (error: unknown) {
// Ignore if it doesn't exist
if (!isSystemError(error) || error.code !== 'ENOENT') {
Expand Down Expand Up @@ -161,11 +161,11 @@ export class FileDataAccessor implements DataAccessor {
}

/**
* Generates file path that corresponds to the metadata file of the given identifier.
* Starts from the identifier to make sure any potentially added extension has no impact on the path.
* Generates ResourceLink that corresponds to the metadata resource of the given identifier.
*/
private async getMetadataPath(identifier: ResourceIdentifier): Promise<string> {
return (await this.resourceMapper.mapUrlToFilePath({ path: `${identifier.path}.meta` })).filePath;
private async getMetadataLink(identifier: ResourceIdentifier): Promise<ResourceLink> {
const metaIdentifier = { path: `${identifier.path}.meta` };
return this.resourceMapper.mapUrlToFilePath(metaIdentifier);
}

/**
Expand Down Expand Up @@ -213,19 +213,20 @@ export class FileDataAccessor implements DataAccessor {
metadata.removeAll(RDF.type);
metadata.removeAll(CONTENT_TYPE);
const quads = metadata.quads();
const metadataPath = await this.getMetadataPath(link.identifier);
const metadataLink = await this.getMetadataLink(link.identifier);
let wroteMetadata: boolean;

// Write metadata to file if there are quads remaining
if (quads.length > 0) {
const serializedMetadata = serializeQuads(quads);
await this.writeDataFile(metadataPath, serializedMetadata);
// Determine required content-type based on mapper
const serializedMetadata = serializeQuads(quads, metadataLink.contentType);
await this.writeDataFile(metadataLink.filePath, serializedMetadata);
wroteMetadata = true;

// Delete (potentially) existing metadata file if no metadata needs to be stored
} else {
try {
await fsPromises.unlink(metadataPath);
await fsPromises.unlink(metadataLink.filePath);
} catch (error: unknown) {
// Metadata file doesn't exist so nothing needs to be removed
if (!isSystemError(error) || error.code !== 'ENOENT') {
Expand Down Expand Up @@ -260,13 +261,13 @@ export class FileDataAccessor implements DataAccessor {
*/
private async getRawMetadata(identifier: ResourceIdentifier): Promise<Quad[]> {
try {
const metadataPath = await this.getMetadataPath(identifier);
const metadataLink = await this.getMetadataLink(identifier);

// Check if the metadata file exists first
await fsPromises.lstat(metadataPath);
await fsPromises.lstat(metadataLink.filePath);

const readMetadataStream = guardStream(createReadStream(metadataPath));
return await parseQuads(readMetadataStream);
const readMetadataStream = guardStream(createReadStream(metadataLink.filePath));
return await parseQuads(readMetadataStream, metadataLink.contentType);
} catch (error: unknown) {
// Metadata file doesn't exist so lets keep `rawMetaData` an empty array.
if (!isSystemError(error) || error.code !== 'ENOENT') {
Expand Down
11 changes: 6 additions & 5 deletions src/util/QuadUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import arrayifyStream from 'arrayify-stream';
import { DataFactory, StreamParser, StreamWriter } from 'n3';
import type { Literal, NamedNode, Quad } from 'rdf-js';
import streamifyArray from 'streamify-array';
import { TEXT_TURTLE } from './ContentTypes';
import type { Guarded } from './GuardedStream';
import { pipeSafely } from './StreamUtil';

Expand All @@ -17,17 +16,19 @@ export const pushQuad =
/**
* Helper function for serializing an array of quads, with as result a Readable object.
* @param quads - The array of quads.
* @param contentType - The content-type to serialize to.
*
* @returns The Readable object.
*/
export const serializeQuads = (quads: Quad[]): Guarded<Readable> =>
pipeSafely(streamifyArray(quads), new StreamWriter({ format: TEXT_TURTLE }));
export const serializeQuads = (quads: Quad[], contentType?: string): Guarded<Readable> =>
pipeSafely(streamifyArray(quads), new StreamWriter({ format: contentType }));

/**
* Helper function to convert a Readable into an array of quads.
* @param readable - The readable object.
* @param contentType - The content-type of the stream.
*
* @returns A promise containing the array of quads.
*/
export const parseQuads = async(readable: Guarded<Readable>): Promise<Quad[]> =>
arrayifyStream(pipeSafely(readable, new StreamParser({ format: TEXT_TURTLE })));
export const parseQuads = async(readable: Guarded<Readable>, contentType?: string): Promise<Quad[]> =>
arrayifyStream(pipeSafely(readable, new StreamParser({ format: contentType })));
26 changes: 25 additions & 1 deletion test/unit/util/QuadUtil.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'jest-rdf';
import { DataFactory } from 'n3';
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('#pushQuad', (): void => {
Expand All @@ -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'),
) ]);
});
});
});

0 comments on commit a114d00

Please sign in to comment.