Skip to main content

linkrs_server/graph_service/
error.rs

1use linkrs_core::types::Position;
2
3/// Error type for graph service operations, carrying an optional source
4/// position for parse errors so the HTTP layer can report it to clients.
5#[derive(Debug, Clone)]
6pub struct GraphServiceError {
7    pub message: String,
8    pub position: Option<Position>,
9}
10
11impl std::fmt::Display for GraphServiceError {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        write!(f, "{}", self.message)
14    }
15}
16
17impl std::error::Error for GraphServiceError {}
18
19impl GraphServiceError {
20    pub fn new(message: impl Into<String>) -> Self {
21        Self {
22            message: message.into(),
23            position: None,
24        }
25    }
26
27    pub fn with_position(message: impl Into<String>, position: Option<Position>) -> Self {
28        Self {
29            message: message.into(),
30            position: position.filter(|p| p.is_valid()),
31        }
32    }
33
34    pub fn message(&self) -> &str {
35        &self.message
36    }
37
38    pub fn position(&self) -> Option<Position> {
39        self.position
40    }
41
42    pub fn from_core_error(e: linkrs_api::api_core::CoreError) -> Self {
43        let message = e.to_string();
44        let position = e.error_position().filter(|p| p.is_valid());
45        let position = match (position, e.error_offset()) {
46            (Some(pos), _) => Some(pos),
47            (None, _) => None,
48        };
49        Self { message, position }
50    }
51
52    pub fn from_core_error_with_query(e: linkrs_api::api_core::CoreError, query: &str) -> Self {
53        let message = e.to_string();
54        if let Some(pos) = e.error_position().filter(|p| p.is_valid()) {
55            return Self {
56                message,
57                position: Some(pos),
58            };
59        }
60        if let Some(offset) = e.error_offset() {
61            return Self {
62                message,
63                position: offset_to_position(query, offset),
64            };
65        }
66        Self {
67            message,
68            position: None,
69        }
70    }
71}
72
73impl From<String> for GraphServiceError {
74    fn from(message: String) -> Self {
75        Self::new(message)
76    }
77}
78
79impl From<&str> for GraphServiceError {
80    fn from(message: &str) -> Self {
81        Self::new(message)
82    }
83}
84
85/// Convert a byte offset in query text to a 1-based line and column.
86pub(crate) fn offset_to_position(query: &str, offset: usize) -> Option<Position> {
87    let offset = offset.min(query.len());
88    let prefix = &query[..offset];
89    let line = prefix.bytes().filter(|b| *b == b'\n').count() + 1;
90    let column = prefix
91        .rsplit('\n')
92        .next()
93        .map(|s| s.chars().count() + 1)
94        .unwrap_or(1);
95    let position = Position::new(line, column);
96    position.is_valid().then_some(position)
97}