1"""
2Errors, oh no!
3"""
4
5from __future__ import annotations
6
7from typing import TYPE_CHECKING, Any
8
9import attrs
10
11from referencing._attrs import frozen
12
13if TYPE_CHECKING:
14 from referencing import Resource
15 from referencing.typing import URI
16
17
18@frozen
19class NoSuchResource(KeyError):
20 """
21 The given URI is not present in a registry.
22
23 Unlike most exceptions, this class *is* intended to be publicly
24 instantiable and *is* part of the public API of the package.
25 """
26
27 ref: URI
28
29 def __eq__(self, other: object) -> bool:
30 if self.__class__ is not other.__class__:
31 return NotImplemented
32 return attrs.astuple(self) == attrs.astuple(other)
33
34 def __hash__(self) -> int:
35 return hash(attrs.astuple(self))
36
37
38@frozen
39class NoInternalID(Exception):
40 """
41 A resource has no internal ID, but one is needed.
42
43 E.g. in modern JSON Schema drafts, this is the :kw:`$id` keyword.
44
45 One might be needed if a resource was to-be added to a registry but no
46 other URI is available, and the resource doesn't declare its canonical URI.
47 """
48
49 resource: Resource[Any]
50
51 def __eq__(self, other: object) -> bool:
52 if self.__class__ is not other.__class__:
53 return NotImplemented
54 return attrs.astuple(self) == attrs.astuple(other)
55
56 def __hash__(self) -> int:
57 return hash(attrs.astuple(self))
58
59
60@frozen
61class Unretrievable(KeyError):
62 """
63 The given URI is not present in a registry, and retrieving it failed.
64 """
65
66 ref: URI
67
68 def __eq__(self, other: object) -> bool:
69 if self.__class__ is not other.__class__:
70 return NotImplemented
71 return attrs.astuple(self) == attrs.astuple(other)
72
73 def __hash__(self) -> int:
74 return hash(attrs.astuple(self))
75
76
77@frozen
78class CannotDetermineSpecification(Exception):
79 """
80 Attempting to detect the appropriate `Specification` failed.
81
82 This happens if no discernible information is found in the contents of the
83 new resource which would help identify it.
84 """
85
86 contents: Any
87
88 def __eq__(self, other: object) -> bool:
89 if self.__class__ is not other.__class__:
90 return NotImplemented
91 return attrs.astuple(self) == attrs.astuple(other)
92
93 def __hash__(self) -> int:
94 return hash(attrs.astuple(self))
95
96
97@attrs.frozen # Because here we allow subclassing below.
98class Unresolvable(Exception):
99 """
100 A reference was unresolvable.
101 """
102
103 ref: URI
104
105 def __eq__(self, other: object) -> bool:
106 if self.__class__ is not other.__class__:
107 return NotImplemented
108 return attrs.astuple(self) == attrs.astuple(other)
109
110 def __hash__(self) -> int:
111 return hash(attrs.astuple(self))
112
113
114@frozen
115class PointerToNowhere(Unresolvable):
116 """
117 A JSON Pointer leads to a part of a document that does not exist.
118 """
119
120 resource: Resource[Any]
121
122 def __str__(self) -> str:
123 msg = f"{self.ref!r} does not exist within {self.resource.contents!r}"
124 if self.ref == "/":
125 msg += (
126 ". The pointer '/' is a valid JSON Pointer but it points to "
127 "an empty string property ''. If you intended to point "
128 "to the entire resource, you should use '#'."
129 )
130 return msg
131
132
133@frozen
134class NoSuchAnchor(Unresolvable):
135 """
136 An anchor does not exist within a particular resource.
137 """
138
139 resource: Resource[Any]
140 anchor: str
141
142 def __str__(self) -> str:
143 return (
144 f"{self.anchor!r} does not exist within {self.resource.contents!r}"
145 )
146
147
148@frozen
149class InvalidAnchor(Unresolvable):
150 """
151 An anchor which could never exist in a resource was dereferenced.
152
153 It is somehow syntactically invalid.
154 """
155
156 resource: Resource[Any]
157 anchor: str
158
159 def __str__(self) -> str:
160 return (
161 f"'#{self.anchor}' is not a valid anchor, neither as a "
162 "plain name anchor nor as a JSON Pointer. You may have intended "
163 f"to use '#/{self.anchor}', as the slash is required *before each "
164 "segment* of a JSON pointer."
165 )