Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions docs/generators/shacl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,83 @@ Example Output:
shacl:targetClass <https://w3id.org/linkml/tests/kitchen_sink/Person> .


Rule constraints (SHACL-SPARQL)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

LinkML `rules <https://linkml.io/linkml/schemas/advanced.html#rules>`_ express
cross-parameter, conditional validation ("if slot A holds X, slot B must
..."). Plain per-slot SHACL property shapes cannot express these, so the
generator translates recognised rule shapes into
`SHACL-SPARQL constraints <https://www.w3.org/TR/shacl/#sparql-constraints>`_
(``sh:sparql`` / ``sh:SPARQLConstraint``) on the class's ``sh:NodeShape``.
Generation is controlled by ``--emit-rules/--no-emit-rules`` (default: on).

Three named patterns are recognised first:

* **Boolean guard** — precondition ``value_presence: PRESENT`` on a value
slot, postcondition ``equals_string: "true"`` on a *boolean-range* flag
slot: if the value is present, the flag must be true.
* **Presence implies value** — precondition ``value_presence: PRESENT``,
postcondition ``equals_string`` / ``equals_string_in`` on a target slot:
if the guard is present, the target must hold one of the allowed values.
Enum values resolve to their ``meaning`` IRIs; values without ``meaning``
compare as string literals.
* **Exclusive value** — precondition ``equals_string`` and postcondition
``maximum_cardinality`` on the *same* multivalued slot: if the value is
present, the slot has at most N values.

Combinations outside the named patterns are handled by a compositional
fallback that conjoins the preconditions and negates a single postcondition:
conditional-required (``required: true``), conditional-absent
(``value_presence: ABSENT``), numeric threshold preconditions
(``minimum_value`` / ``maximum_value``), a one-hop nested precondition into
an inlined child object (``range_expression.slot_conditions``), and
``has_member`` list membership.

The translation contract is *skip, never mis-translate*: a rule whose
conditions set any operator outside the translated set (including
expression-level ``any_of``/``all_of``/``none_of``/``exactly_one_of``), or
whose slot keys resolve to no slot, is skipped and logged at ``DEBUG``.
``deactivated`` rules are skipped; ``bidirectional``, ``open_world``, and
``elseconditions`` warn (the forward direction is emitted).

Example:

.. code-block:: yaml

classes:
Weather:
slots: [sun_altitude, daytime]
rules:
- description: If sun_altitude is present, daytime must be day or twilight.
preconditions:
slot_conditions:
sun_altitude:
value_presence: PRESENT
postconditions:
slot_conditions:
daytime:
equals_string_in: [day, twilight]

generates (abridged):

.. code-block:: turtle

ex:Weather a sh:NodeShape ;
sh:sparql [ a sh:SPARQLConstraint ;
sh:message "If sun_altitude is present, daytime must be day or twilight." ;
sh:select """SELECT $this WHERE {
$this <https://example.org/sun_altitude> ?value .
OPTIONAL { $this <https://example.org/daytime> ?target . }
FILTER ( !BOUND(?target) || ?target NOT IN (<https://example.org/Day>, <https://example.org/Twilight>) )
}""" ] .

``$this`` is pre-bound to each focus node per
`SHACL §5.3.1 <https://www.w3.org/TR/shacl/#sparql-constraints-prebound>`_.
Note that SPARQL-based constraints require a SHACL processor with
SHACL-SPARQL support (e.g. ``pyshacl`` with ``advanced=True``).


Command Line
^^^^^^^^^^^^

Expand Down
Loading