diff --git a/packages/core/README.md b/packages/core/README.md index 90cc735..115836a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -41,6 +41,8 @@ The following is a list of links to potential starting points: The `AccumulatingSimplisticHandler` class accumulates the problems reported, which can be accessed through its `allProblems` method. The `AggregatingSimplisticHandler` class aggregates the problems reported (with count), which can be output using its `reportAllProblemsOnConsole` method, and accessed through its `allProblems` method. * Pass dependent languages also _as languages_ from languages deserializer to regular deserializer, not just as referable data (which is only useful for the built-ins) +* Fix a bug in the serializer that caused an "unhelpful" exception on an unset or unresolved — i.e., "not-connected", represented by a `null` value — reference target . + Now, such reference targets are simply skipped. ### 0.6.8 diff --git a/packages/core/src/serializer.ts b/packages/core/src/serializer.ts index ba7c90b..9f714e2 100644 --- a/packages/core/src/serializer.ts +++ b/packages/core/src/serializer.ts @@ -2,6 +2,7 @@ import {ExtractionFacade} from "./facade.js" import {currentSerializationFormatVersion, MetaPointer, SerializationChunk, SerializedNode} from "./serialization.js" import {asIds} from "./functions.js" import {Node} from "./types.js" +import {unresolved} from "./references.js" import {DefaultPrimitiveTypeSerializer} from "./m3/builtins.js" import {allFeaturesOf} from "./m3/functions.js" import { @@ -15,6 +16,7 @@ import { } from "./m3/types.js" import {asArray} from "./utils/array-helpers.js" + export interface PrimitiveTypeSerializer { serializeValue(value: unknown, property: Property): string | undefined } @@ -94,15 +96,20 @@ export const serializeNodes = ( return } if (feature instanceof Reference) { - const targets = asArray(value) + // Note: value can be null === typeof unresolved, e.g. on an unset (or previously unresolved) single-valued reference + const targets = asArray(value) as (NT | typeof unresolved)[] serializedNode.references.push({ reference: featureMetaPointer, - targets: (targets as NT[]).map((t) => ({ - resolveInfo: extractionFacade.resolveInfoFor - ? extractionFacade.resolveInfoFor(t) - : simpleNameDeducer(t), - reference: t.id - })) + targets: targets + .filter((tOrNull) => tOrNull !== null) // (skip "non-connected" targets) + .map((t) => t!) + .map((t) => ({ + resolveInfo: extractionFacade.resolveInfoFor + ? extractionFacade.resolveInfoFor(t) + : simpleNameDeducer(t), + reference: t.id + }) + ) }) return } diff --git a/packages/test/src/serializer.test.ts b/packages/test/src/serializer.test.ts index 84870d6..5c52065 100644 --- a/packages/test/src/serializer.test.ts +++ b/packages/test/src/serializer.test.ts @@ -8,6 +8,7 @@ import { DefaultPrimitiveTypeSerializer, Language, SerializationChunk, + serializeLanguages, serializeNodes } from "@lionweb/core" import {dateDatatype, libraryWithDatesLanguage} from "./languages/libraryWithDates.js" @@ -39,10 +40,10 @@ describe("serialization", () => { const expectedSerializationChunk: SerializationChunk = { serializationFormatVersion: currentSerializationFormatVersion, - "languages": [ + languages: [ { - "key": "libraryWithDates", - "version": "1" + key: "libraryWithDates", + version: "1" } ], nodes: [ @@ -73,11 +74,11 @@ describe("serialization", () => { ], containments: [ { - "children": [], - "containment": { - "key": "books", - "language": "libraryWithDates", - "version": "1" + children: [], + containment: { + key: "books", + language: "libraryWithDates", + version: "1" } } ], @@ -105,68 +106,82 @@ describe("serialization", () => { annotatedNode.annotations.push(annotation) const expectedSerializationChunk = { - "serializationFormatVersion": "2023.1", - "languages": [ + serializationFormatVersion: "2023.1", + languages: [ { - "key": "test-language", - "version": "0" + key: "test-language", + version: "0" }, { - "key": "LionCore-builtins", - "version": "2023.1" + key: "LionCore-builtins", + version: "2023.1" } ], - "nodes": [ + nodes: [ { - "id": "1", - "classifier": { - "language": "test-language", - "version": "0", - "key": "Annotated" + id: "1", + classifier: { + language: "test-language", + version: "0", + key: "Annotated" }, - "properties": [ + properties: [ { - "property": { - "language": "LionCore-builtins", - "version": "2023.1", - "key": "LionCore-builtins-INamed-name" + property: { + language: "LionCore-builtins", + version: "2023.1", + key: "LionCore-builtins-INamed-name" }, - "value": "my annotated node" + value: "my annotated node" } ], - "containments": [], - "references": [], - "annotations": [ + containments: [], + references: [], + annotations: [ "0" ], - "parent": null + parent: null }, { - "id": "0", - "classifier": { - "language": "test-language", - "version": "0", - "key": "Annotation" + id: "0", + classifier: { + language: "test-language", + version: "0", + key: "Annotation" }, - "properties": [ + properties: [ { - "property": { - "language": "LionCore-builtins", - "version": "2023.1", - "key": "LionCore-builtins-INamed-name" + property: { + language: "LionCore-builtins", + version: "2023.1", + key: "LionCore-builtins-INamed-name" }, - "value": "my annotation node" + value: "my annotation node" } ], - "containments": [], - "references": [], - "annotations": [], - "parent": "1" + containments: [], + references: [], + annotations: [], + parent: "1" } ] } expect(serializeNodes([annotatedNode], new SimpleNodeReader([language]))).to.eql(expectedSerializationChunk) }) + it(`doesn't fail on "unconnected" (i.e., unset or previously unresolved) null reference target values`, () => { + const language = new Language("test language", "0", "test-language", "test-language") + const annotation = new Annotation(language, "Annotation", "Annotation", "Annotation") + // don't set annotation.annotates! + language.havingEntities(annotation) + + const serializationChunk = serializeLanguages(language) // should not fail + const annotationSerNode = serializationChunk.nodes.find((node) => node.id === "Annotation") + expect(annotationSerNode).to.not.be.null + const referenceSer = annotationSerNode?.references.find((serRef) => serRef.reference.key === "Annotation-annotates") + expect(referenceSer).to.not.be.undefined + expect(referenceSer!.targets).to.eql([]) + }) + })