o-development / ldo-legacy

Linked Data Objects
Other
19 stars 2 forks source link

Reusing predicates leads to LDO loading data wrong #18

Closed megoth-capgemini closed 1 year ago

megoth-capgemini commented 1 year ago

I'm not 100% sure if this is a bug, but I tried to simplify an ontology by reusing some predicates, which seemingly leeds to LDO not loading data properly.

The original ontology:

@prefix app:     <#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix owl:     <http://www.w3.org/2002/07/owl#> .
@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
@prefix vann:    <http://purl.org/vocab/vann/> .
@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .

app:
    a                             owl:Ontology ;
    owl:versionInfo               "0.1" ;
    dcterms:title                 "Försäkringskassan Semantic PoC"@en-US ;
    dcterms:description           """App vocabulary for Försäkringskassan Semantic PoC."""@en-US ;
    dcterms:creator               <https://icanhasweb.net/#me> ;
    dcterms:issued                "2023-03-09"^^xsd:date ;
    dcterms:license               "http://purl.org/NET/rdflicense/MIT1.0.ttl" ;
    vann:preferredNamespacePrefix "app" ;
    vann:preferredNamespaceUri    "https://icanhasweb.net/vocab/fk-app.ttl#" .

app:Document
    a                rdfs:Class ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App Document" ;
    rdfs:comment     "The class for document that contains data relevant for the app"@en-US .

app:law
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App law reference" ;
    rdfs:comment     "The law that the we're working with on this project" ;
    rdfs:domain      app:Document ;
    rdfs:range       app:Law .

app:Law
    a                rdfs:Class ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App law" ;
    rdfs:comment     "The class for law document"@en-US .

app:lawName
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "Name" ;
    rdfs:comment     "Name for law"@en-US ;
    rdfs:domain      app:Law ;
    rdfs:range       xsd:string .

app:lawPath
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "Path" ;
    rdfs:comment     "Path to the law"@en-US ;
    rdfs:domain      app:Law ;
    rdfs:range       xsd:string .

app:vocabulary
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App vocabulary reference" ;
    rdfs:label       "A vocabulary used by the app"@en-US ;
    rdfs:domain      app:Document ;
    rdfs:range       app:Vocabulary .

app:Vocabulary
    a                rdfs:Class ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App vocabulary" ;
    rdfs:comment     "The class for a vocabulary"@en-US .

app:vocabName
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "Name" ;
    rdfs:comment     "Name for vocabulary"@en-US 
    rdfs:domain      app:Vocabulary ;
    rdfs:range       xsd:string .

app:vocabPath
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "Path" ;
    rdfs:comment     "Path to the vocabulary"@en-US ;
    rdfs:domain      app:Vocabulary ;
    rdfs:range       xsd:string .

The original shape:

PREFIX app:   <https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#>
PREFIX rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX xsd:   <http://www.w3.org/2001/XMLSchema#>

app:DocumentShape {
  rdf:type [ app:Document ] ;
  app:vocabulary @app:VocabularyShape* ;
  app:law @app:LawShape ;
}

app:LawShape {
  rdf:type [ app:Law ] ;
  app:lawName xsd:string ;
  app:lawPath IRI ;
}

app:VocabularyShape {
  rdf:type [ app:Vocabulary ] ;
  app:vocabName xsd:string ;
  app:vocabPath IRI ;
}

I wanted to simplify these by combining app:lawName and app:vocabName into app:name, and app:lawPath and app:vocabPath into app:path. For reference, these are the changed files:

The ontology:

@prefix app:     <#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix owl:     <http://www.w3.org/2002/07/owl#> .
@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
@prefix vann:    <http://purl.org/vocab/vann/> .
@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .

app:
    a                             owl:Ontology ;
    owl:versionInfo               "0.1" ;
    dcterms:title                 "Försäkringskassan Semantic PoC"@en-US ;
    dcterms:description           """App vocabulary for Försäkringskassan Semantic PoC."""@en-US ;
    dcterms:creator               <https://icanhasweb.net/#me> ;
    dcterms:issued                "2023-03-09"^^xsd:date ;
    dcterms:license               "http://purl.org/NET/rdflicense/MIT1.0.ttl" ;
    vann:preferredNamespacePrefix "app" ;
    vann:preferredNamespaceUri    "https://icanhasweb.net/vocab/fk-app.ttl#" .

app:Document
    a                rdfs:Class ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App Document" ;
    rdfs:comment     "The class for document that contains data relevant for the app"@en-US .

app:law
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App law reference" ;
    rdfs:comment     "The law that the we're working with on this project" ;
    rdfs:domain      app:Document ;
    rdfs:range       app:Law .

app:Law
    a                rdfs:Class ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App law" ;
    rdfs:comment     "The class for law document"@en-US .

app:vocabulary
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App vocabulary reference" ;
    rdfs:label       "A vocabulary used by the app"@en-US ;
    rdfs:domain      app:Document ;
    rdfs:range       app:Vocabulary .

app:Vocabulary
    a                rdfs:Class ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "App vocabulary" ;
    rdfs:comment     "The class for a vocabulary"@en-US .

app:name
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "Name" ;
    rdfs:comment     "Name for entity"@en-US ;
    rdfs:range       xsd:string .

app:path
    a                rdf:Property ;
    rdfs:isDefinedBy app: ;
    rdfs:label       "Path" ;
    rdfs:comment     "Path to the document"@en-US ;
    rdfs:range       xsd:string .

The shape:

PREFIX app:   <https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#>
PREFIX rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX xsd:   <http://www.w3.org/2001/XMLSchema#>

app:DocumentShape {
  rdf:type [ app:Document ] ;
  app:vocabulary @app:VocabularyShape* ;
  app:law @app:LawShape ;
}

app:LawShape {
  rdf:type [ app:Law ] ;
  app:name xsd:string ;
  app:path IRI ;
}

app:VocabularyShape {
  rdf:type [ app:Vocabulary ] ;
  app:name xsd:string ;
  app:path IRI ;
}

As I understand it, these shouldn't have any changes except needing to change .vocabName and .lawName to .name and .vocabPath and .lawPath into .path in my code. But for some reason, the code that previously worked, vocab?.vocabPath?.["@id"], now needs to be vocab?.vocabPath[0]?.["@id"].

Again, I'm not sure if this is a bug, or if I've misunderstood something, but thought I should create a bug issue to let you know in case it is actually a bug.

jaxoncreed commented 1 year ago

Interesting. Could you paste the contents of the generated *.context.ts and the *.typings.ts files associated with your ShEx shape?

megoth-capgemini commented 1 year ago

Sure, the contents of app.context.ts:

import { ContextDefinition } from "jsonld";

/**
 * =============================================================================
 * appContext: JSONLD Context for app
 * =============================================================================
 */
export const appContext: ContextDefinition = {
  type: {
    "@id": "@type",
    "@container": "@set",
  },
  Document: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document",
  vocabulary: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary",
    "@type": "@id",
    "@container": "@set",
  },
  Vocabulary:
    "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary",
  name: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name",
    "@type": "http://www.w3.org/2001/XMLSchema#string",
    "@container": "@set",
  },
  path: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path",
    "@type": "@id",
    "@container": "@set",
  },
  law: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law",
    "@type": "@id",
  },
  Law: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law",
};

And the contents of app.typings.ts:

import { ContextDefinition } from "jsonld";

/**
 * =============================================================================
 * Typescript Typings for app
 * =============================================================================
 */

/**
 * DocumentShape Type
 */
export interface DocumentShape {
  "@id"?: string;
  "@context"?: ContextDefinition;
  type: {
    "@id": "Document";
  };
  vocabulary?: VocabularyShape[];
  law: LawShape;
}

/**
 * LawShape Type
 */
export interface LawShape {
  "@id"?: string;
  "@context"?: ContextDefinition;
  type: {
    "@id": "Law";
  };
  name: string;
  path: {
    "@id": string;
  };
}

/**
 * VocabularyShape Type
 */
export interface VocabularyShape {
  "@id"?: string;
  "@context"?: ContextDefinition;
  type: {
    "@id": "Vocabulary";
  };
  name: string;
  path: {
    "@id": string;
  };
}

Something that sticks out to me is the use of "@container": "@set" in app.context.ts, which is not present when the predicates aren't reused across multiple shapes.

jaxoncreed commented 1 year ago

Right, I expect this is a bug. Could you also send the contents of app.schema.ts?

megoth-capgemini commented 1 year ago

The contents of app.schema.ts:

import { Schema } from "shexj";

/**
 * =============================================================================
 * appSchema: ShexJ Schema for app
 * =============================================================================
 */
export const appSchema: Schema = {
  type: "Schema",
  shapes: [
    {
      id: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#DocumentShape",
      type: "ShapeDecl",
      shapeExpr: {
        type: "Shape",
        expression: {
          type: "EachOf",
          expressions: [
            {
              type: "TripleConstraint",
              predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
              valueExpr: {
                type: "NodeConstraint",
                values: [
                  "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document",
                ],
              },
            },
            {
              type: "TripleConstraint",
              predicate:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary",
              valueExpr:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#VocabularyShape",
              min: 0,
              max: -1,
            },
            {
              type: "TripleConstraint",
              predicate:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law",
              valueExpr:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#LawShape",
            },
          ],
        },
      },
    },
    {
      id: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#LawShape",
      type: "ShapeDecl",
      shapeExpr: {
        type: "Shape",
        expression: {
          type: "EachOf",
          expressions: [
            {
              type: "TripleConstraint",
              predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
              valueExpr: {
                type: "NodeConstraint",
                values: [
                  "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law",
                ],
              },
            },
            {
              type: "TripleConstraint",
              predicate:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name",
              valueExpr: {
                type: "NodeConstraint",
                datatype: "http://www.w3.org/2001/XMLSchema#string",
              },
            },
            {
              type: "TripleConstraint",
              predicate:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path",
              valueExpr: {
                type: "NodeConstraint",
                nodeKind: "iri",
              },
            },
          ],
        },
      },
    },
    {
      id: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#VocabularyShape",
      type: "ShapeDecl",
      shapeExpr: {
        type: "Shape",
        expression: {
          type: "EachOf",
          expressions: [
            {
              type: "TripleConstraint",
              predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
              valueExpr: {
                type: "NodeConstraint",
                values: [
                  "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary",
                ],
              },
            },
            {
              type: "TripleConstraint",
              predicate:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name",
              valueExpr: {
                type: "NodeConstraint",
                datatype: "http://www.w3.org/2001/XMLSchema#string",
              },
            },
            {
              type: "TripleConstraint",
              predicate:
                "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path",
              valueExpr: {
                type: "NodeConstraint",
                nodeKind: "iri",
              },
            },
          ],
        },
      },
    },
  ],
};

Thank you so much for looking into it ^_^

jaxoncreed commented 1 year ago

Yep, this is definitely a bug. I'll take a look into it tomorrow.

jaxoncreed commented 1 year ago

Hey, I looked into it and I wasn't able to replicate the issue. Pasting your ShEx in, I see the following:

      import {ContextDefinition} from "jsonld"

      export interface DocumentShape {
          "@id"?: string;
          "@context"?: ContextDefinition;
          type: {
              "@id": "Document";
          };
          vocabulary?: (VocabularyShape)[];
          law: LawShape;
      }

      export interface LawShape {
          "@id"?: string;
          "@context"?: ContextDefinition;
          type: {
              "@id": "Law";
          };
          name: string;
          path: {
              "@id": string;
          };
      }

      export interface VocabularyShape {
          "@id"?: string;
          "@context"?: ContextDefinition;
          type: {
              "@id": "Vocabulary";
          };
          name: string;
          path: {
              "@id": string;
          };
      }

The cardinality on name and path seem correct. Are you using the most recent version of LDO?

megoth-capgemini commented 1 year ago

I was using an older version of LDO (ldo@0.0.12, ldo-cli@2.0.0), and have now upgraded to latest (ldo@1.0.1, ldo-cli@3.0.1), but I'm still having some problems.

The cardinality looks correct, but the problem is in the context I think:

import { ContextDefinition } from "jsonld";

/**
 * =============================================================================
 * appContext: JSONLD Context for app
 * =============================================================================
 */
export const appContext: ContextDefinition = {
  type: {
    "@id": "@type",
    "@container": "@set",
  },
  Document: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document",
  vocabulary: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary",
    "@type": "@id",
    "@container": "@set",
  },
  Vocabulary:
    "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary",
  name: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name",
    "@type": "http://www.w3.org/2001/XMLSchema#string",
    "@container": "@set",
  },
  path: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path",
    "@type": "@id",
    "@container": "@set",
  },
  law: {
    "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law",
    "@type": "@id",
  },
  Law: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law",
};

When I console.log or debug vocab.path in my code, it seems to referring to an array, and if I do console.log("TEST", vocab?.path.map((p) => p["@id"])) it outputs an array with one element, i.e. the value that I'm expecting. So it seems to setting the data as an array, instead of the value itself.

megoth-capgemini commented 1 year ago

A weird thing now though, is that it only seems to be affecting .path, i.e. .name is working as expected.

jaxoncreed commented 1 year ago

Ah, yep. Sorry I missed that. Looking into it now.

jaxoncreed commented 1 year ago

Welp, this bug is going to require a slight re-architecture. So, in the meantime removing the "@container: @set" from your context manually is going to be the best bet.

I'll let you know when this is fixed.

jaxoncreed commented 1 year ago

Closed in favor of https://github.com/o-development/ldo/issues/22