From 33ca58f95cdfe189c56fd4ca8d2bbbd02e483584 Mon Sep 17 00:00:00 2001 From: Jens Pots Date: Sun, 7 Apr 2024 22:11:17 +0200 Subject: [PATCH] feat: set mime type of incoming data --- README.md | 5 +++-- package-lock.json | 24 +++++++++++------------ processors.ttl | 5 +++++ src/index.ts | 14 ++++++++++++-- tests/data/valid.jsonld | 1 + tests/data/valid.nt | 3 +++ tests/error.test.ts | 34 ++++++++++++++++++++++++++++++++ tests/index.test.ts | 43 ++++++++++++++++++++++++++++++++--------- 8 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 tests/data/valid.jsonld create mode 100644 tests/data/valid.nt diff --git a/README.md b/README.md index f26ccd0..8f3314a 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,13 @@ Validate an incoming data stream using SHACL. If the incoming data is valid, it - `outgoing`: channel into which valid data is written. - `report`: an optional channel into which the SHACL reports of invalid input data is written. (default: `null`) - `validationIsFatal`: throw a fatal error if validation fails. (default: `false`) +- `mime`: the internet media type of the incoming data used to initialize the parser. (default: `text/turtle`) ## Limitations -At the time of writing, all files are read and serialized in the Turtle format. Additional options may be available in the future. +The file type of the incoming data must be known beforehand and be set using the `mime` parameter in order to initialize the parser. Type agnostic parsers may be available in the feature, making this setting redundant. -Turtle prefixes are hard coded for the time being. Ideally, these should be based on the prefixes used in the input data, or omitted at the user's request. +SHACL reports are outputted in Turtle using humanized formatting. Prefixes are hard coded for the time being. Ideally, these should be based on the prefixes used in the input data, or omitted at the user's request. Other file types should be made available as well. ```ts const prefixes = new PrefixMapFactory().prefixMap(); diff --git a/package-lock.json b/package-lock.json index ab3968c..4d09780 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3052,6 +3052,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -5253,18 +5265,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", diff --git a/processors.ttl b/processors.ttl index 627d0bc..23d3bbd 100644 --- a/processors.ttl +++ b/processors.ttl @@ -48,6 +48,11 @@ js:Validate a js:JsProcess; sh:path js:validationIsFatal; sh:name "validationIsFatal"; sh:maxCount 1; + ], [ + sh:datatype xsd:string; + sh:path js:mime; + sh:name "MIME type"; + sh:maxCount 1; ]. [ ] a sh:NodeShape; diff --git a/src/index.ts b/src/index.ts index 76c4fda..3b7b620 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,17 +11,27 @@ type ValidateArguments = { incoming: Stream; outgoing: Writer; report?: Writer; + mime?: string; validationIsFatal?: boolean; }; export async function validate(args: ValidateArguments): Promise<() => void> { - const { shaclPath, incoming, outgoing, report, validationIsFatal } = args; + const { shaclPath, incoming, outgoing, report } = args; + + // Default arguments. + const mime = args.mime ?? "text/turtle"; + const validationIsFatal = args.validationIsFatal ?? false; // Initialize the shared serializer. const prefixes = new PrefixMapFactory().prefixMap(); prefixes.set("sh", rdf.namedNode("http://www.w3.org/ns/shacl#")); const serializer = new Serializer({ prefixes }); - const parser = rdf.formats.parsers.get("text/turtle")!; + + // Initialize the data parser. + const parser = rdf.formats.parsers.get(mime); + if (!parser) { + throw ShaclError.invalidRdfFormat(); + } // Extend formatting with pretty formats. rdf.formats.import(formatsPretty); diff --git a/tests/data/valid.jsonld b/tests/data/valid.jsonld new file mode 100644 index 0000000..c21ab48 --- /dev/null +++ b/tests/data/valid.jsonld @@ -0,0 +1 @@ +[{"@id":"http://example.org#Point"},{"@id":"http://example.org#ValidPoint","@type":["http://example.org#Point"],"http://example.org#x":[{"@value":1}],"http://example.org#y":[{"@value":2}]}] diff --git a/tests/data/valid.nt b/tests/data/valid.nt new file mode 100644 index 0000000..98c8263 --- /dev/null +++ b/tests/data/valid.nt @@ -0,0 +1,3 @@ + . + "1"^^ . + "2"^^ . diff --git a/tests/error.test.ts b/tests/error.test.ts index aa93c42..ef9846b 100644 --- a/tests/error.test.ts +++ b/tests/error.test.ts @@ -22,6 +22,19 @@ describe("errors", () => { expect(func()).rejects.toThrow(ShaclError.fileSystemError()); }); + test("invalid data rdf format", async () => { + expect.assertions(1); + + const func = validate({ + shaclPath, + incoming: new SimpleStream(), + outgoing: new SimpleStream(), + mime: "text/invalid", + }); + + expect(func).rejects.toThrowError(ShaclError.invalidRdfFormat()); + }); + test("invalid shacl rdf format", async () => { expect.assertions(1); @@ -74,4 +87,25 @@ describe("errors", () => { await incoming.end(); }); + + test("incorrect mime", async () => { + expect.assertions(1); + + const validJsonLd = fs + .readFileSync("./tests/data/valid.jsonld") + .toString(); + const incoming = new SimpleStream(); + + const func = await validate({ + shaclPath, + incoming, + outgoing: new SimpleStream(), + mime: "text/turtle", + }); + func(); + + expect(async () => { + await incoming.push(validJsonLd); + }).rejects.toThrow(ShaclError.invalidRdfFormat()); + }); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index d4c3f9c..c5243dc 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -2,6 +2,7 @@ import { validate } from "../src"; import { SimpleStream } from "@ajuvercr/js-runner"; import { describe, test, expect, beforeEach } from "vitest"; import * as fs from "fs"; +import { ShaclError } from "../src/error"; // Channel which streams incoming RDF. let incoming: SimpleStream; @@ -16,6 +17,7 @@ let reportData: string; // Valid point. const validRdfData = fs.readFileSync("./tests/data/valid.ttl").toString(); +const validNTriples = fs.readFileSync("./tests/data/valid.nt").toString(); // Invalid point. const invalidRdfData = fs.readFileSync("./tests/data/invalid.ttl").toString(); @@ -53,18 +55,20 @@ beforeEach(async () => { report.on("data", (data) => { reportData += data; }); - - // Restart the processor, which is the same for each test. - const func = await validate({ - shaclPath, - incoming, - outgoing, - report, - }); - func(); }); describe("shacl", () => { + beforeEach(async () => { + // Restart the processor, which is the same for each test. + const func = await validate({ + shaclPath, + incoming, + outgoing, + report, + }); + func(); + }); + test("successful", async () => { expect.assertions(2); @@ -95,3 +99,24 @@ describe("shacl", () => { expect(reportData).toEqual(""); }); }); + +describe("shacl - config", () => { + test("mime", async () => { + expect.assertions(2); + + const func = await validate({ + shaclPath, + incoming, + outgoing, + report, + mime: "application/n-triples", + }); + func(); + + await incoming.push(validNTriples); + await endAll(); + + expect(outgoingData).toEqual(validNTriples); + expect(reportData).toEqual(""); + }); +});