weavatrix_semantic/error/
display.rs1use super::SemanticError;
2use std::fmt::{Display, Formatter};
3use weavatrix_graph::GraphError;
4
5impl Display for SemanticError {
6 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
7 if let Some(result) = [
8 format_vector_error(self, formatter),
9 format_seo_error(self, formatter),
10 format_anchor_error(self, formatter),
11 ]
12 .into_iter()
13 .flatten()
14 .next()
15 {
16 return result;
17 }
18 match self {
19 #[cfg(feature = "vector-search")]
20 Self::VectorSearch(error) => Display::fmt(error, formatter),
21 Self::Graph(error) => Display::fmt(error, formatter),
22 _ => formatter.write_str("unclassified semantic error"),
23 }
24 }
25}
26
27fn format_vector_error(
28 error: &SemanticError,
29 formatter: &mut Formatter<'_>,
30) -> Option<std::fmt::Result> {
31 Some(match error {
32 SemanticError::EmptyModel => {
33 formatter.write_str("embedding model identifier cannot be empty")
34 }
35 SemanticError::ModelHasSurroundingWhitespace => {
36 formatter.write_str("embedding model identifier cannot have surrounding whitespace")
37 }
38 SemanticError::InvalidSimilarityThreshold => {
39 formatter.write_str("minimum cosine similarity must be finite and within [0, 1]")
40 }
41 SemanticError::ZeroTopK => formatter.write_str("top_k must be greater than zero"),
42 SemanticError::ZeroMaxVectors => {
43 formatter.write_str("max_vectors must be greater than zero")
44 }
45 SemanticError::EmptyVector { node } => {
46 write!(formatter, "semantic vector for {node} has no dimensions")
47 }
48 SemanticError::NonFiniteVectorValue { node, index } => write!(
49 formatter,
50 "semantic vector for {node} has a non-finite value at dimension {index}"
51 ),
52 SemanticError::ZeroVector { node } => {
53 write!(formatter, "semantic vector for {node} has zero magnitude")
54 }
55 SemanticError::TooManyVectors { count, maximum } => write!(
56 formatter,
57 "semantic linker received {count} vectors; configured maximum is {maximum}"
58 ),
59 SemanticError::DuplicateNode { node } => {
60 write!(
61 formatter,
62 "multiple semantic vectors target graph node {node}"
63 )
64 }
65 SemanticError::MissingGraphNode { node } => {
66 write!(
67 formatter,
68 "semantic vector targets missing graph node {node}"
69 )
70 }
71 SemanticError::DimensionMismatch {
72 node,
73 expected,
74 actual,
75 } => write!(
76 formatter,
77 "semantic vector for {node} has {actual} dimensions; expected {expected}"
78 ),
79 SemanticError::NumericOverflow => {
80 formatter.write_str("semantic-link metadata exceeds supported numeric range")
81 }
82 SemanticError::AllocationFailed => {
83 formatter.write_str("semantic-link storage allocation failed")
84 }
85 SemanticError::ZeroCandidatePoolMultiplier => {
86 formatter.write_str("vector candidate-pool multiplier must be greater than zero")
87 }
88 SemanticError::CandidateKeyOutOfRange { key, vector_count } => write!(
89 formatter,
90 "vector candidate key {key} is outside semantic input of {vector_count} vectors"
91 ),
92 _ => return None,
93 })
94}
95
96fn format_seo_error(
97 error: &SemanticError,
98 formatter: &mut Formatter<'_>,
99) -> Option<std::fmt::Result> {
100 Some(match error {
101 SemanticError::EmptySeoSite { node } => {
102 write!(formatter, "SEO site identifier for {node} cannot be empty")
103 }
104 SemanticError::SeoSiteHasSurroundingWhitespace { node } => write!(
105 formatter,
106 "SEO site identifier for {node} cannot have surrounding whitespace"
107 ),
108 SemanticError::EmptySeoCanonical { node } => {
109 write!(
110 formatter,
111 "SEO canonical identifier for {node} cannot be empty"
112 )
113 }
114 SemanticError::SeoCanonicalHasSurroundingWhitespace { node } => write!(
115 formatter,
116 "SEO canonical identifier for {node} cannot have surrounding whitespace"
117 ),
118 SemanticError::EmptySeoLanguage { node } => {
119 write!(
120 formatter,
121 "SEO language identifier for {node} cannot be empty"
122 )
123 }
124 SemanticError::SeoLanguageHasSurroundingWhitespace { node } => write!(
125 formatter,
126 "SEO language identifier for {node} cannot have surrounding whitespace"
127 ),
128 SemanticError::DuplicateSeoProfile { node } => {
129 write!(formatter, "multiple SEO profiles target graph node {node}")
130 }
131 SemanticError::MissingSeoProfile { node } => {
132 write!(formatter, "semantic vector for {node} has no SEO profile")
133 }
134 SemanticError::SeoProfileMissingGraphNode { node } => {
135 write!(formatter, "SEO profile targets missing graph node {node}")
136 }
137 _ => return None,
138 })
139}
140
141fn format_anchor_error(
142 error: &SemanticError,
143 formatter: &mut Formatter<'_>,
144) -> Option<std::fmt::Result> {
145 Some(match error {
146 SemanticError::EmptyAnchorModel => {
147 formatter.write_str("anchor embedding model identifier cannot be empty")
148 }
149 SemanticError::AnchorModelHasSurroundingWhitespace => formatter
150 .write_str("anchor embedding model identifier cannot have surrounding whitespace"),
151 SemanticError::InvalidAnchorSimilarityThreshold => {
152 formatter.write_str("minimum anchor cosine similarity must be finite and within [0, 1]")
153 }
154 SemanticError::ZeroAnchorSuggestions => {
155 formatter.write_str("maximum anchor suggestions must be greater than zero")
156 }
157 SemanticError::EmptyAnchorLocator { source } => write!(
158 formatter,
159 "anchor candidate for {source} has an empty locator"
160 ),
161 SemanticError::EmptyAnchorText { source, locator } => write!(
162 formatter,
163 "anchor candidate {source} at {locator} has empty anchor text"
164 ),
165 SemanticError::EmptyAnchorContext { source, locator } => write!(
166 formatter,
167 "anchor candidate {source} at {locator} has empty context"
168 ),
169 SemanticError::AnchorTextOutsideContext { source, locator } => write!(
170 formatter,
171 "anchor candidate {source} at {locator} has anchor text outside its context"
172 ),
173 SemanticError::AnchorTextHasSurroundingWhitespace {
174 source,
175 locator,
176 field,
177 } => write!(
178 formatter,
179 "anchor candidate {source} at {locator} has surrounding whitespace in {field}"
180 ),
181 SemanticError::DuplicateAnchorCandidate { source, locator } => write!(
182 formatter,
183 "multiple anchor candidates target {source} at {locator}"
184 ),
185 SemanticError::MissingAnchorTargetVector { target } => write!(
186 formatter,
187 "semantic link target {target} has no vector for anchor matching"
188 ),
189 SemanticError::AnchorDimensionMismatch {
190 source,
191 locator,
192 expected,
193 actual,
194 } => write!(
195 formatter,
196 "anchor candidate {source} at {locator} has {actual} dimensions; expected {expected}"
197 ),
198 SemanticError::AnchorModelMismatch { expected, actual } => write!(
199 formatter,
200 "semantic edge uses embedding model {actual}; anchor matcher expects {expected}"
201 ),
202 SemanticError::MissingSemanticEdgeAttribute { attribute } => write!(
203 formatter,
204 "semantic edge is missing required {attribute} attribute"
205 ),
206 _ => return None,
207 })
208}
209
210impl std::error::Error for SemanticError {
211 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
212 match self {
213 #[cfg(feature = "vector-search")]
214 Self::VectorSearch(error) => Some(error),
215 Self::Graph(error) => Some(error),
216 _ => None,
217 }
218 }
219}
220
221impl From<GraphError> for SemanticError {
222 fn from(error: GraphError) -> Self {
223 Self::Graph(error)
224 }
225}
226
227#[cfg(feature = "vector-search")]
228impl From<weavatrix_search_vector::SearchError> for SemanticError {
229 fn from(error: weavatrix_search_vector::SearchError) -> Self {
230 Self::VectorSearch(error)
231 }
232}