From 4638ba4bce02f166c249337ee5a153a9509c4884 Mon Sep 17 00:00:00 2001 From: Ruben Verborgh Date: Thu, 14 Jan 2021 23:42:18 +0100 Subject: [PATCH] feat: Use baseIRI in QuadToRdfConverter. Closes https://github.com/solid/community-server/issues/512 --- package-lock.json | 6 ++-- package.json | 2 +- src/storage/conversion/QuadToRdfConverter.ts | 6 ++-- .../integration/LdpHandlerWithoutAuth.test.ts | 4 +-- .../conversion/QuadToRdfConverter.test.ts | 32 +++++++++++++------ 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index ee337c9cf..d653ee490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7136,9 +7136,9 @@ "dev": true }, "n3": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/n3/-/n3-1.7.0.tgz", - "integrity": "sha512-8R0Qj545WnVLQxOfxxyFKzOpO13hF3jhSMJfO0FNqvbsPZDiR9ZDmGGjXAlcoZDf/88OsCYd7rHML284vm1h6A==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.8.0.tgz", + "integrity": "sha512-/PEmoB3UJrG6aXGZenDHFBJtmPp2rtfB2YLzAm2dU9stInD+ztvy4fKv5fv2ggsrSlpu7BYDTsz/c6S391uuEg==", "requires": { "queue-microtask": "^1.1.2", "readable-stream": "^3.6.0" diff --git a/package.json b/package.json index e7261358e..ca00298f1 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "fetch-sparql-endpoint": "^1.8.0", "handlebars": "^4.7.6", "mime-types": "^2.1.27", - "n3": "^1.7.0", + "n3": "^1.8.0", "rdf-parse": "^1.5.0", "rdf-serialize": "^1.0.0", "rdf-terms": "^1.5.1", diff --git a/src/storage/conversion/QuadToRdfConverter.ts b/src/storage/conversion/QuadToRdfConverter.ts index a1ec3bd1e..757b28c2d 100644 --- a/src/storage/conversion/QuadToRdfConverter.ts +++ b/src/storage/conversion/QuadToRdfConverter.ts @@ -24,7 +24,8 @@ export class QuadToRdfConverter extends TypedRepresentationConverter { ); } - public async handle({ representation: quads, preferences }: RepresentationConverterArgs): Promise { + public async handle({ identifier, representation: quads, preferences }: RepresentationConverterArgs): + Promise { const contentType = matchingMediaTypes(preferences.type, await this.getOutputTypes())[0]; let data: Readable; @@ -32,7 +33,8 @@ export class QuadToRdfConverter extends TypedRepresentationConverter { if (/(?:turtle|trig)$/u.test(contentType)) { const prefixes = Object.fromEntries(quads.metadata.quads(null, PREFERRED_PREFIX_TERM, null) .map(({ subject, object }): [string, string] => [ object.value, subject.value ])); - data = pipeSafely(quads.data, new StreamWriter({ format: contentType, prefixes })); + const options = { format: contentType, baseIRI: identifier.path, prefixes }; + data = pipeSafely(quads.data, new StreamWriter(options)); // Otherwise, write without prefixes } else { data = rdfSerializer.serialize(quads.data, { contentType }) as Readable; diff --git a/test/integration/LdpHandlerWithoutAuth.test.ts b/test/integration/LdpHandlerWithoutAuth.test.ts index b46564894..84754289d 100644 --- a/test/integration/LdpHandlerWithoutAuth.test.ts +++ b/test/integration/LdpHandlerWithoutAuth.test.ts @@ -64,7 +64,7 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn, expect(response.getHeaders()).toHaveProperty('content-type', 'text/turtle'); const data = response._getData().toString(); - expect(data).toContain(`<${BASE}/> a ldp:Container`); + expect(data).toContain(`<> a ldp:Container`); expect(response.getHeaders().link).toContain(`<${LDP.Container}>; rel="type"`); }); @@ -266,7 +266,7 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn, response = await resourceHelper.performRequest(new URL(`${BASE}/${slug}/`), 'GET', { accept: 'text/turtle' }); expect(response.statusCode).toBe(200); - const parser = new Parser(); + const parser = new Parser({ baseIRI: `${BASE}/${slug}/` }); const quads = parser.parse(response._getData()); expect(quads.some((entry): boolean => entry.equals(quad( namedNode(`${BASE}/${slug}/`), diff --git a/test/unit/storage/conversion/QuadToRdfConverter.test.ts b/test/unit/storage/conversion/QuadToRdfConverter.test.ts index 5a7bfe8c2..35a3131b7 100644 --- a/test/unit/storage/conversion/QuadToRdfConverter.test.ts +++ b/test/unit/storage/conversion/QuadToRdfConverter.test.ts @@ -12,11 +12,11 @@ import { DC, PREFERRED_PREFIX_TERM } from '../../../../src/util/Vocabularies'; describe('A QuadToRdfConverter', (): void => { const converter = new QuadToRdfConverter(); - const identifier: ResourceIdentifier = { path: 'path' }; + const identifier: ResourceIdentifier = { path: 'http://example.org/foo/bar/' }; let metadata: RepresentationMetadata; beforeEach((): void => { - metadata = new RepresentationMetadata(INTERNAL_QUADS); + metadata = new RepresentationMetadata(identifier, INTERNAL_QUADS); }); it('supports parsing quads.', async(): Promise => { @@ -35,7 +35,7 @@ describe('A QuadToRdfConverter', (): void => { .resolves.toEqual(outputPreferences); }); - it('can handle quad to turtle conversions.', async(): Promise => { + it('can handle quad to Turtle conversions.', async(): Promise => { const representation = { metadata } as Representation; const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }}; await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined(); @@ -47,7 +47,7 @@ describe('A QuadToRdfConverter', (): void => { await expect(converter.canHandle({ identifier, representation, preferences })).resolves.toBeUndefined(); }); - it('converts quads to turtle.', async(): Promise => { + it('converts quads to Turtle.', async(): Promise => { const representation = { data: streamifyArray([ triple( namedNode('http://test.com/s'), @@ -69,7 +69,7 @@ describe('A QuadToRdfConverter', (): void => { ); }); - it('converts quads with prefixes to turtle.', async(): Promise => { + it('converts quads with prefixes to Turtle.', async(): Promise => { metadata.addQuad(DC.terms.namespace, PREFERRED_PREFIX_TERM, 'dc'); metadata.addQuad('http://test.com/', PREFERRED_PREFIX_TERM, 'test'); const representation = { @@ -82,10 +82,6 @@ describe('A QuadToRdfConverter', (): void => { } as Representation; const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }}; const result = await converter.handle({ identifier, representation, preferences }); - expect(result).toMatchObject({ - binary: true, - metadata: expect.any(RepresentationMetadata), - }); expect(result.metadata.contentType).toEqual('text/turtle'); await expect(stringifyStream(result.data)).resolves.toEqual( `@prefix dc: . @@ -96,6 +92,24 @@ test:s dc:modified test:o. ); }); + it('uses the base IRI when converting quads to Turtle.', async(): Promise => { + const representation = { + data: streamifyArray([ triple( + namedNode('http://example.org/foo/bar/'), + namedNode('http://example.org/foo/bar/#abc'), + namedNode('http://example.org/foo/bar/def/ghi'), + ) ]), + metadata, + } as Representation; + const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }}; + const result = await converter.handle({ identifier, representation, preferences }); + expect(result.metadata.contentType).toEqual('text/turtle'); + await expect(stringifyStream(result.data)).resolves.toEqual( + `<> <#abc> . +`, + ); + }); + it('converts quads to JSON-LD.', async(): Promise => { metadata.contentType = INTERNAL_QUADS; const representation = {