From 476344e51168ee249c82972f9fddc4c6e3f1a5fb Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:33:26 +0100 Subject: [PATCH] feat: allow custom context parser as a parameter to the parser & fix context mutations --- lib/JsonLdParser.ts | 6 ++++- lib/ParsingContext.ts | 6 ++--- .../keyword/EntryHandlerKeywordType.ts | 13 +++------- test/JsonLdParser-test.ts | 25 +++++++++++++++++-- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/JsonLdParser.ts b/lib/JsonLdParser.ts index a5a32a9..b379351 100644 --- a/lib/JsonLdParser.ts +++ b/lib/JsonLdParser.ts @@ -1,7 +1,7 @@ import * as RDF from "@rdfjs/types"; // tslint:disable-next-line:no-var-requires const Parser = require('@bergos/jsonparse'); -import {ERROR_CODES, ErrorCoded, IDocumentLoader, JsonLdContext, Util as ContextUtil} from "jsonld-context-parser"; +import {ERROR_CODES, ErrorCoded, IDocumentLoader, JsonLdContext, Util as ContextUtil, ContextParser} from "jsonld-context-parser"; import {PassThrough, Transform, Readable} from "readable-stream"; import {EntryHandlerArrayValue} from "./entryhandler/EntryHandlerArrayValue"; import {EntryHandlerContainer} from "./entryhandler/EntryHandlerContainer"; @@ -672,4 +672,8 @@ export interface IJsonLdParserOptions { * Defaults to false. */ rdfstarReverseInEmbedded?: boolean; + /** + * The the context parser to use. + */ + contextParser?: ContextParser; } diff --git a/lib/ParsingContext.ts b/lib/ParsingContext.ts index f7b6955..7dc0938 100644 --- a/lib/ParsingContext.ts +++ b/lib/ParsingContext.ts @@ -86,7 +86,7 @@ export class ParsingContext { constructor(options: IParsingContextOptions) { // Initialize settings - this.contextParser = new ContextParser({ documentLoader: options.documentLoader, skipValidation: options.skipContextValidation }); + this.contextParser = options.contextParser ?? new ContextParser({ documentLoader: options.documentLoader, skipValidation: options.skipContextValidation }); this.streamingProfile = !!options.streamingProfile; this.baseIRI = options.baseIRI; this.produceGeneralizedRdf = !!options.produceGeneralizedRdf; @@ -207,11 +207,11 @@ export class ParsingContext { || scopedContext[key]['@context']['@propagate']; // Propagation is true by default if (propagate !== false || i === keysOriginal.length - 1 - offset) { - contextRaw = scopedContext; + contextRaw = { ...scopedContext }; // Clean up final context delete contextRaw['@propagate']; - contextRaw[key] = { ...contextRaw[key] }; + contextRaw[key] = { ...contextRaw[key], }; if ('@id' in contextKeyEntry) { contextRaw[key]['@id'] = contextKeyEntry['@id']; } diff --git a/lib/entryhandler/keyword/EntryHandlerKeywordType.ts b/lib/entryhandler/keyword/EntryHandlerKeywordType.ts index ac292b7..08ee24e 100644 --- a/lib/entryhandler/keyword/EntryHandlerKeywordType.ts +++ b/lib/entryhandler/keyword/EntryHandlerKeywordType.ts @@ -69,18 +69,13 @@ export class EntryHandlerKeywordType extends EntryHandlerKeyword { if (hasTypedScopedContext) { // Do not propagate by default scopedContext = scopedContext.then((c) => { - if (!('@propagate' in c.getContextRaw())) { - c.getContextRaw()['@propagate'] = false; - } + let contextRaw = c.getContextRaw(); - // Set the original context at this depth as a fallback - // This is needed when a context was already defined at the given depth, - // and this context needs to remain accessible from child nodes when propagation is disabled. - if (c.getContextRaw()['@propagate'] === false) { - c.getContextRaw()['@__propagateFallback'] = context.getContextRaw(); + if (!('@propagate' in contextRaw) || contextRaw['@propagate'] === false) { + contextRaw = { ...contextRaw, '@propagate': false, '@__propagateFallback': context.getContextRaw() }; } - return c; + return new JsonLdContextNormalized(contextRaw); }); // Set the new context in the context tree diff --git a/test/JsonLdParser-test.ts b/test/JsonLdParser-test.ts index 24ca1ca..567f84c 100644 --- a/test/JsonLdParser-test.ts +++ b/test/JsonLdParser-test.ts @@ -6,7 +6,7 @@ import { EventEmitter } from 'events'; import {DataFactory} from "rdf-data-factory"; import each from 'jest-each'; import "jest-rdf"; -import {ERROR_CODES, ErrorCoded, FetchDocumentLoader, JsonLdContextNormalized} from "jsonld-context-parser"; +import {ContextParser, ERROR_CODES, ErrorCoded, FetchDocumentLoader, IParseOptions, JsonLdContext, JsonLdContextNormalized} from "jsonld-context-parser"; import {PassThrough} from "stream"; import {Util} from "../lib/Util"; import { ParsingContext } from '../lib/ParsingContext'; @@ -14,6 +14,23 @@ import contexts, { MockedDocumentLoader } from '../mocks/contexts'; const DF = new DataFactory(); +const deepFreeze = obj => { + Object.keys(obj).forEach(prop => { + if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) deepFreeze(obj[prop]); + }); + return Object.freeze(obj); +}; + +class FrozenContextParser extends ContextParser { + constructor(options: ConstructorParameters[0]) { + super(options); + } + + public parse(context: JsonLdContext, options?: IParseOptions): Promise { + return super.parse(context, options)// .then(deepFreeze); + } +} + describe('JsonLdParser', () => { describe('Parsing a Verifiable Credential', () => { @@ -22,7 +39,11 @@ describe('JsonLdParser', () => { beforeEach(() => { parser = new JsonLdParser({ dataFactory: DF, - documentLoader: new MockedDocumentLoader(), + // Use the frozen context parser so we can detect if there are + // any attempts at mutations in the unit tests + contextParser: new FrozenContextParser({ + documentLoader: new MockedDocumentLoader() + }), }) });