From fea726ae7db9addbfa138452ad171ed0f6a60cd9 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Wed, 6 Jan 2021 17:19:57 +0100 Subject: [PATCH] fix: Take baseIRI into account when calling parseQuads --- src/storage/DataAccessorBasedStore.ts | 14 ++++++++------ src/storage/accessors/FileDataAccessor.ts | 2 +- src/util/QuadUtil.ts | 7 ++++--- test/unit/storage/DataAccessorBasedStore.test.ts | 8 +++++++- .../storage/accessors/FileDataAccessor.test.ts | 8 ++++---- test/unit/util/QuadUtil.test.ts | 13 +++++++++++-- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/storage/DataAccessorBasedStore.ts b/src/storage/DataAccessorBasedStore.ts index 76e9040a8..30771f174 100644 --- a/src/storage/DataAccessorBasedStore.ts +++ b/src/storage/DataAccessorBasedStore.ts @@ -219,6 +219,12 @@ export class DataAccessorBasedStore implements ResourceStore { */ protected async writeData(identifier: ResourceIdentifier, representation: Representation, isContainer: boolean, createContainers?: boolean): Promise { + // Make sure the metadata has the correct identifier and correct type quads + // Need to do this before handling container data to have the correct identifier + const { metadata } = representation; + metadata.identifier = DataFactory.namedNode(identifier.path); + metadata.addQuads(generateResourceQuads(metadata.identifier, isContainer)); + if (isContainer) { await this.handleContainerData(representation); } @@ -228,11 +234,6 @@ export class DataAccessorBasedStore implements ResourceStore { await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(identifier)); } - // Make sure the metadata has the correct identifier and correct type quads - const { metadata } = representation; - metadata.identifier = DataFactory.namedNode(identifier.path); - metadata.addQuads(generateResourceQuads(metadata.identifier, isContainer)); - await (isContainer ? this.accessor.writeContainer(identifier, representation.metadata) : this.accessor.writeDocument(identifier, representation.data, representation.metadata)); @@ -251,7 +252,8 @@ export class DataAccessorBasedStore implements ResourceStore { if (representation.metadata.contentType === INTERNAL_QUADS) { quads = await arrayifyStream(representation.data); } else { - quads = await parseQuads(representation.data, representation.metadata.contentType); + const { contentType, identifier } = representation.metadata; + quads = await parseQuads(representation.data, { format: contentType, baseIRI: identifier.value }); } } catch (error: unknown) { if (error instanceof Error) { diff --git a/src/storage/accessors/FileDataAccessor.ts b/src/storage/accessors/FileDataAccessor.ts index 73fee2657..0c38069c5 100644 --- a/src/storage/accessors/FileDataAccessor.ts +++ b/src/storage/accessors/FileDataAccessor.ts @@ -266,7 +266,7 @@ export class FileDataAccessor implements DataAccessor { await fsPromises.lstat(metadataLink.filePath); const readMetadataStream = guardStream(createReadStream(metadataLink.filePath)); - return await parseQuads(readMetadataStream, metadataLink.contentType); + return await parseQuads(readMetadataStream, { format: metadataLink.contentType, baseIRI: identifier.path }); } catch (error: unknown) { // Metadata file doesn't exist so lets keep `rawMetaData` an empty array. if (!isSystemError(error) || error.code !== 'ENOENT') { diff --git a/src/util/QuadUtil.ts b/src/util/QuadUtil.ts index 1a1299c5c..3ae742669 100644 --- a/src/util/QuadUtil.ts +++ b/src/util/QuadUtil.ts @@ -1,5 +1,6 @@ import type { Readable, PassThrough } from 'stream'; import arrayifyStream from 'arrayify-stream'; +import type { ParserOptions } from 'n3'; import { DataFactory, StreamParser, StreamWriter } from 'n3'; import type { Literal, NamedNode, Quad } from 'rdf-js'; import streamifyArray from 'streamify-array'; @@ -33,10 +34,10 @@ export function serializeQuads(quads: Quad[], contentType?: string): Guarded, contentType?: string): Promise { - return arrayifyStream(pipeSafely(readable, new StreamParser({ format: contentType }))); +export async function parseQuads(readable: Guarded, options: ParserOptions = {}): Promise { + return arrayifyStream(pipeSafely(readable, new StreamParser(options))); } diff --git a/test/unit/storage/DataAccessorBasedStore.test.ts b/test/unit/storage/DataAccessorBasedStore.test.ts index a32eff880..ba463be00 100644 --- a/test/unit/storage/DataAccessorBasedStore.test.ts +++ b/test/unit/storage/DataAccessorBasedStore.test.ts @@ -1,6 +1,7 @@ import 'jest-rdf'; import type { Readable } from 'stream'; import arrayifyStream from 'arrayify-stream'; +import type { Quad } from 'n3'; import { DataFactory } from 'n3'; import type { Representation } from '../../../src/ldp/representation/Representation'; import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata'; @@ -187,13 +188,18 @@ describe('A DataAccessorBasedStore', (): void => { const resourceID = { path: root }; representation.metadata.add(RDF.type, LDP.terms.Container); representation.metadata.contentType = 'text/turtle'; - representation.data = guardedStreamFrom([ `<${`${root}resource/`}> a .` ]); + representation.data = guardedStreamFrom([ '<> a .' ]); const result = await store.addResource(resourceID, representation); expect(result).toEqual({ path: expect.stringMatching(new RegExp(`^${root}[^/]+/$`, 'u')), }); expect(accessor.data[result.path]).toBeTruthy(); expect(accessor.data[result.path].metadata.contentType).toBeUndefined(); + + const { data } = await store.getRepresentation(result); + const quads: Quad[] = await arrayifyStream(data); + expect(quads.some((entry): boolean => entry.subject.value === result.path && + entry.object.value === 'http://test.com/coolContainer')).toBeTruthy(); }); it('creates a URI based on the incoming slug.', async(): Promise => { diff --git a/test/unit/storage/accessors/FileDataAccessor.test.ts b/test/unit/storage/accessors/FileDataAccessor.test.ts index a317492ea..de5b44cc3 100644 --- a/test/unit/storage/accessors/FileDataAccessor.test.ts +++ b/test/unit/storage/accessors/FileDataAccessor.test.ts @@ -128,13 +128,13 @@ describe('A FileDataAccessor', (): void => { }); it('adds stored metadata when requesting metadata.', async(): Promise => { - cache.data = { resource: 'data', 'resource.meta': ' .' }; + cache.data = { resource: 'data', 'resource.meta': ' .' }; metadata = await accessor.getMetadata({ path: `${base}resource` }); - expect(metadata.quads().some((quad): boolean => quad.subject.value === 'this')).toBe(true); + expect(metadata.quads().some((quad): boolean => quad.subject.value === 'http://this')).toBe(true); - cache.data = { container: { '.meta': ' .' }}; + cache.data = { container: { '.meta': ' .' }}; metadata = await accessor.getMetadata({ path: `${base}container/` }); - expect(metadata.quads().some((quad): boolean => quad.subject.value === 'this')).toBe(true); + expect(metadata.quads().some((quad): boolean => quad.subject.value === 'http://this')).toBe(true); }); it('throws an error if there is a problem with the internal metadata.', async(): Promise => { diff --git a/test/unit/util/QuadUtil.test.ts b/test/unit/util/QuadUtil.test.ts index 914751026..d1e8d03b0 100644 --- a/test/unit/util/QuadUtil.test.ts +++ b/test/unit/util/QuadUtil.test.ts @@ -36,9 +36,18 @@ describe('QuadUtil', (): void => { }); describe('#parseQuads', (): void => { - it('parses quads from the requested format.', async(): Promise => { + it('parses quads.', async(): Promise => { const stream = guardedStreamFrom([ ' "obj".' ]); - await expect(parseQuads(stream, 'application/n-triples')).resolves.toEqualRdfQuadArray([ quad( + await expect(parseQuads(stream)).resolves.toEqualRdfQuadArray([ quad( + namedNode('pre:sub'), + namedNode('pre:pred'), + literal('obj'), + ) ]); + }); + + it('parses quads with the given options.', async(): Promise => { + const stream = guardedStreamFrom([ '<> "obj".' ]); + await expect(parseQuads(stream, { baseIRI: 'pre:sub' })).resolves.toEqualRdfQuadArray([ quad( namedNode('pre:sub'), namedNode('pre:pred'), literal('obj'),