From feaac1cf56eea1b739bb8042cf9bf3ba336f8710 Mon Sep 17 00:00:00 2001 From: Ruben Verborgh Date: Sat, 2 Jan 2021 18:13:28 +0100 Subject: [PATCH] feat: Support strings in addQuad. --- .../representation/RepresentationMetadata.ts | 8 ++-- src/util/QuadUtil.ts | 14 ++++-- src/util/UriUtil.ts | 26 ++++++++--- test/unit/util/QuadUtil.test.ts | 30 ++++++++----- test/unit/util/UriUtil.test.ts | 45 ++++++++++++++++--- 5 files changed, 93 insertions(+), 30 deletions(-) diff --git a/src/ldp/representation/RepresentationMetadata.ts b/src/ldp/representation/RepresentationMetadata.ts index 35f0b58db..9ee337ddb 100644 --- a/src/ldp/representation/RepresentationMetadata.ts +++ b/src/ldp/representation/RepresentationMetadata.ts @@ -78,8 +78,8 @@ export class RepresentationMetadata { if (!Array.isArray(objects)) { objects = [ objects ]; } - for (const object of objects.map(toObjectTerm)) { - this.store.addQuad(this.id, namedPredicate, object); + for (const object of objects) { + this.store.addQuad(this.id, namedPredicate, toObjectTerm(object, true)); } } } @@ -149,7 +149,7 @@ export class RepresentationMetadata { * @param object - Value to add. */ public add(predicate: NamedNode | string, object: NamedNode | Literal | string): this { - this.store.addQuad(this.id, toCachedNamedNode(predicate), toObjectTerm(object)); + this.store.addQuad(this.id, toCachedNamedNode(predicate), toObjectTerm(object, true)); return this; } @@ -159,7 +159,7 @@ export class RepresentationMetadata { * @param object - Value to remove. */ public remove(predicate: NamedNode | string, object: NamedNode | Literal | string): this { - this.store.removeQuad(this.id, toCachedNamedNode(predicate), toObjectTerm(object)); + this.store.removeQuad(this.id, toCachedNamedNode(predicate), toObjectTerm(object, true)); return this; } diff --git a/src/util/QuadUtil.ts b/src/util/QuadUtil.ts index 8d3e1328f..d8444c51a 100644 --- a/src/util/QuadUtil.ts +++ b/src/util/QuadUtil.ts @@ -1,17 +1,23 @@ -import type { Readable } from 'stream'; +import type { Readable, PassThrough } from 'stream'; 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 type { Guarded } from './GuardedStream'; import { pipeSafely } from './StreamUtil'; +import { toSubjectTerm, toPredicateTerm, toObjectTerm } from './UriUtil'; /** * Generates a quad with the given subject/predicate/object and pushes it to the given array. */ -export const pushQuad = - (quads: Quad[], subject: NamedNode, predicate: NamedNode, object: NamedNode | Literal): number => - quads.push(DataFactory.quad(subject, predicate, object)); +export const pushQuad = ( + quads: Quad[] | PassThrough, + subject: string | NamedNode, + predicate: string | NamedNode, + object: string | NamedNode | Literal, +): void => { + quads.push(DataFactory.quad(toSubjectTerm(subject), toPredicateTerm(predicate), toObjectTerm(object))); +}; /** * Helper function for serializing an array of quads, with as result a Readable object. diff --git a/src/util/UriUtil.ts b/src/util/UriUtil.ts index e83e878a1..d3d1b9137 100644 --- a/src/util/UriUtil.ts +++ b/src/util/UriUtil.ts @@ -2,6 +2,8 @@ import { DataFactory } from 'n3'; import type { Literal, NamedNode, Term } from 'rdf-js'; import { CONTENT_TYPE } from './UriConstants'; +const { namedNode, literal } = DataFactory; + // Shorthands for commonly used predicates const shorthands: Record = { contentType: DataFactory.namedNode(CONTENT_TYPE), @@ -28,7 +30,7 @@ export const toCachedNamedNode = (name: NamedNode | string): NamedNode => { return shorthands[name]; } if (!termMap[name]) { - termMap[name] = DataFactory.namedNode(name); + termMap[name] = namedNode(name); } return termMap[name]; } @@ -36,11 +38,25 @@ export const toCachedNamedNode = (name: NamedNode | string): NamedNode => { }; /** - * Converts an object to a literal when needed. - * @param object - Object to potentially transform. + * Converts a subject to a named node when needed. + * @param subject - Subject to potentially transform. */ -export const toObjectTerm = (object: NamedNode | Literal | string): NamedNode | Literal => - typeof object === 'string' ? DataFactory.literal(object) : object; +export const toSubjectTerm = (subject: NamedNode | string): NamedNode => + typeof subject === 'string' ? namedNode(subject) : subject; + +export const toPredicateTerm = toSubjectTerm; + +/** + * Converts an object term when needed. + * @param object - Object to potentially transform. + * @param preferLiteral - Whether strings are converted to literals or named nodes. + */ +export const toObjectTerm = (object: T | string, preferLiteral = false): T => { + if (typeof object === 'string') { + return (preferLiteral ? literal(object) : namedNode(object)) as any; + } + return object; +}; /** * Creates a literal by first converting the dataType string to a named node. diff --git a/test/unit/util/QuadUtil.test.ts b/test/unit/util/QuadUtil.test.ts index e52f25ef7..914751026 100644 --- a/test/unit/util/QuadUtil.test.ts +++ b/test/unit/util/QuadUtil.test.ts @@ -1,5 +1,5 @@ import 'jest-rdf'; -import { DataFactory } from 'n3'; +import { literal, namedNode, quad } from '@rdfjs/data-model'; import type { Quad } from 'rdf-js'; import { parseQuads, pushQuad, serializeQuads } from '../../../src/util/QuadUtil'; import { guardedStreamFrom, readableToString } from '../../../src/util/StreamUtil'; @@ -8,19 +8,27 @@ describe('QuadUtil', (): void => { describe('#pushQuad', (): void => { it('creates a quad and adds it to the given array.', async(): Promise => { const quads: Quad[] = []; - pushQuad(quads, DataFactory.namedNode('sub'), DataFactory.namedNode('pred'), DataFactory.literal('obj')); + pushQuad(quads, namedNode('sub'), namedNode('pred'), literal('obj')); expect(quads).toEqualRdfQuadArray([ - DataFactory.quad(DataFactory.namedNode('sub'), DataFactory.namedNode('pred'), DataFactory.literal('obj')), + quad(namedNode('sub'), namedNode('pred'), literal('obj')), + ]); + }); + + it('creates a quad from strings and adds it to the given array.', async(): Promise => { + const quads: Quad[] = []; + pushQuad(quads, 'sub', 'pred', 'obj'); + expect(quads).toEqualRdfQuadArray([ + quad(namedNode('sub'), namedNode('pred'), namedNode('obj')), ]); }); }); describe('#serializeQuads', (): void => { it('converts quads to the requested format.', async(): Promise => { - const quads = [ DataFactory.quad( - DataFactory.namedNode('pre:sub'), - DataFactory.namedNode('pre:pred'), - DataFactory.literal('obj'), + const quads = [ quad( + namedNode('pre:sub'), + namedNode('pre:pred'), + literal('obj'), ) ]; const stream = serializeQuads(quads, 'application/n-triples'); await expect(readableToString(stream)).resolves.toMatch(' "obj" .'); @@ -30,10 +38,10 @@ describe('QuadUtil', (): void => { describe('#parseQuads', (): void => { it('parses quads from the requested format.', async(): Promise => { const stream = guardedStreamFrom([ ' "obj".' ]); - await expect(parseQuads(stream, 'application/n-triples')).resolves.toEqualRdfQuadArray([ DataFactory.quad( - DataFactory.namedNode('pre:sub'), - DataFactory.namedNode('pre:pred'), - DataFactory.literal('obj'), + await expect(parseQuads(stream, 'application/n-triples')).resolves.toEqualRdfQuadArray([ quad( + namedNode('pre:sub'), + namedNode('pre:pred'), + literal('obj'), ) ]); }); }); diff --git a/test/unit/util/UriUtil.test.ts b/test/unit/util/UriUtil.test.ts index 2cef7c368..d750c87c3 100644 --- a/test/unit/util/UriUtil.test.ts +++ b/test/unit/util/UriUtil.test.ts @@ -1,7 +1,14 @@ import 'jest-rdf'; import { literal, namedNode } from '@rdfjs/data-model'; import { CONTENT_TYPE, XSD } from '../../../src/util/UriConstants'; -import { toCachedNamedNode, toObjectTerm, toLiteral, isTerm } from '../../../src/util/UriUtil'; +import { + toCachedNamedNode, + toSubjectTerm, + toPredicateTerm, + toObjectTerm, + toLiteral, + isTerm, +} from '../../../src/util/UriUtil'; describe('An UriUtil', (): void => { describe('isTerm function', (): void => { @@ -14,7 +21,7 @@ describe('An UriUtil', (): void => { }); }); - describe('getNamedNode function', (): void => { + describe('toCachedNamedNode function', (): void => { it('returns the input if it was a named node.', async(): Promise => { const term = namedNode('name'); expect(toCachedNamedNode(term)).toBe(term); @@ -35,7 +42,29 @@ describe('An UriUtil', (): void => { }); }); - describe('getObjectTerm function', (): void => { + describe('toSubjectTerm function', (): void => { + it('returns the input if it was a term.', async(): Promise => { + const nn = namedNode('name'); + expect(toSubjectTerm(nn)).toBe(nn); + }); + + it('returns a named node when a string is used.', async(): Promise => { + expect(toSubjectTerm('nn')).toEqualRdfTerm(namedNode('nn')); + }); + }); + + describe('toPredicateTerm function', (): void => { + it('returns the input if it was a term.', async(): Promise => { + const nn = namedNode('name'); + expect(toPredicateTerm(nn)).toBe(nn); + }); + + it('returns a named node when a string is used.', async(): Promise => { + expect(toPredicateTerm('nn')).toEqualRdfTerm(namedNode('nn')); + }); + }); + + describe('toObjectTerm function', (): void => { it('returns the input if it was a term.', async(): Promise => { const nn = namedNode('name'); const lit = literal('lit'); @@ -43,12 +72,16 @@ describe('An UriUtil', (): void => { expect(toObjectTerm(lit)).toBe(lit); }); - it('returns a literal when a string is used.', async(): Promise => { - expect(toObjectTerm('lit')).toEqualRdfTerm(literal('lit')); + it('returns a named node when a string is used.', async(): Promise => { + expect(toObjectTerm('nn')).toEqualRdfTerm(namedNode('nn')); + }); + + it('returns a literal when a string is used with preferLiteral.', async(): Promise => { + expect(toObjectTerm('lit', true)).toEqualRdfTerm(literal('lit')); }); }); - describe('getTypedLiteral function', (): void => { + describe('toLiteral function', (): void => { it('converts the input to a valid literal with the given type.', async(): Promise => { const expected = literal('5', namedNode(XSD.integer)); expect(toLiteral(5, XSD.integer)).toEqualRdfTerm(expected);