"""
Report where in the data an error lies.
A path is a sequence of segments which lead from the instance you handed over
down to the erroneous value. It renders as a Python access expression, so that
you can paste it as it stands:
.. code-block::
.things[1].labels[0]
The vocabulary lives on its own so that
:py:mod:`aas_core3_2.verification`, and the verification of
a JSON-able value where the meta-model has one, report with the very same
classes.
"""
# This code has been automatically generated by aas-core-codegen.
# Do NOT edit or append.
import collections
import json
import sys
from typing import Any, Deque, Mapping, Sequence, Union
if sys.version_info >= (3, 8):
from typing import Final
else:
from typing_extensions import Final
import aas_core3_2.types as our_types
# NOTE (mristin):
# The SDK defines three of these path vocabularies: this one, the one in
# :py:mod:`aas_core3_2.jsonization`, and the one in
# :py:mod:`aas_core3_2.xmlcommon`.
# They look alike, and it is tempting to merge them, but they are not
# interchangeable.
#
# Each of them points into a different thing, and every segment carries
# a back-pointer to the container it stepped through, so the type of that
# back-pointer differs from one to the next:
#
# * This one points into the instances which you built, so a property
# segment holds a class of the meta-model.
# * The jsonization's points into the JSON-able structure being read or
# written, so a property segment holds the JSON-able mapping instead:
# while a document is being parsed, the instance which the property would
# belong to does not exist yet.
# * The xmlcommon's points into the XML document, where there are no
# properties at all, only elements, so it names an element instead.
#
# The three also render differently: a path into the instances is a Python
# access expression, a path into a JSON-able structure starts at the root of
# the document and carries no leading dot, and a path into an XML document
# is a relative XPath.
#
# Merging them would mean either dropping the back-pointers, which have been
# part of the public API of this SDK since before these modules were split
# apart, or defining a single path over the union of all the segment kinds --
# in which case every consumer would have to handle segments which can never
# occur in its world. Three small vocabularies which each say exactly what
# they can say cost less than one large one which lies about its range.
[docs]class PropertySegment:
"""Represent a property access on a path to an erroneous value."""
#: Instance containing the property
instance: Final[our_types.Class]
#: Name of the property
name: Final[str]
[docs] def __init__(self, instance: our_types.Class, name: str) -> None:
"""Initialize with the given values."""
self.instance = instance
self.name = name
[docs] def __str__(self) -> str:
return f".{self.name}"
[docs]class IndexSegment:
"""Represent an index access on a path to an erroneous value."""
#: Sequence containing the item at :py:attr:`~index`
sequence: Final[Sequence[Any]]
#: Index of the item
index: Final[int]
[docs] def __init__(self, sequence: Sequence[Any], index: int) -> None:
"""Initialize with the given values."""
self.sequence = sequence
self.index = index
[docs] def __str__(self) -> str:
return f"[{self.index}]"
[docs]class KeySegment:
"""
Represent a member access on a path to an erroneous value.
Unlike a :py:class:`PropertySegment`, which names a property of one of our
classes, a key names a member of an open JSON-able object. It is known only
at run time, and can be any string at all, so it is always rendered
as a subscript.
"""
#: Mapping containing the value at :py:attr:`~key`
mapping: Final[Mapping[str, Any]]
#: Key of the value
key: Final[str]
[docs] def __init__(self, mapping: Mapping[str, Any], key: str) -> None:
"""Initialize with the given values."""
self.mapping = mapping
self.key = key
[docs] def __str__(self) -> str:
return f"[{json.dumps(self.key, ensure_ascii=False)}]"
Segment = Union[PropertySegment, IndexSegment, KeySegment]
[docs]class Path:
"""Represent the relative path to the erroneous value."""
[docs] def __init__(self) -> None:
"""Initialize as an empty path."""
# NOTE (mristin):
# A path is built as the stack unwinds, so every segment is prepended and
# none is ever appended. A list would copy the whole path on each of them,
# which makes a path of depth *d* cost *d^2* to build, while a deque
# prepends in constant time.
self._segments: Deque[Segment] = collections.deque()
@property
def segments(self) -> Sequence[Segment]:
"""Get the segments of the path."""
return self._segments
def _prepend(self, segment: Segment) -> None:
"""Insert the :paramref:`segment` in front of other segments."""
self._segments.appendleft(segment)
[docs] def __str__(self) -> str:
return "".join(str(segment) for segment in self._segments)
[docs]class Error:
"""Represent a verification error in the data."""
#: Human-readable description of the error
cause: Final[str]
#: Path to the erroneous value
path: Final[Path]
[docs] def __init__(self, cause: str) -> None:
"""Initialize as an error with an empty path."""
self.cause = cause
self.path = Path()
[docs] def __repr__(self) -> str:
return f"Error(path={self.path}, cause={self.cause})"