Skip to main content

weavatrix_semantic/error/
display.rs

1use 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}