atproto blogging
1//! Error types for weaver - thin wrapper over jacquard errors
2
3use jacquard::{types::string::AtStrError, xrpc::GenericXrpcError};
4use miette::{Diagnostic, NamedSource, SourceOffset, SourceSpan};
5use std::borrow::Cow;
6
7/// Main error type for weaver operations
8#[derive(thiserror::Error, Debug, Diagnostic)]
9pub enum WeaverError {
10 /// Jacquard Agent error
11 #[error(transparent)]
12 #[diagnostic_source]
13 Agent(#[from] jacquard::client::error::AgentError),
14
15 /// Jacquard Identity resolution error
16 #[error(transparent)]
17 #[diagnostic_source]
18 Identity(#[from] jacquard::identity::resolver::IdentityError),
19
20 /// Invalid notebook structure
21 #[error("invalid notebook structure: {0}")]
22 InvalidNotebook(String),
23
24 /// Markdown parsing/rendering error
25 #[error("markdown error: {0}")]
26 Markdown(String),
27
28 /// IO error
29 #[error(transparent)]
30 Io(#[from] n0_future::io::Error),
31
32 /// Parse error with source location
33 #[error(transparent)]
34 #[diagnostic_source]
35 Parse(#[from] ParseError),
36
37 /// Serialization/deserialization error
38 #[error(transparent)]
39 #[diagnostic_source]
40 Serde(#[from] SerDeError),
41
42 /// Task join error
43 #[error(transparent)]
44 Task(#[from] n0_future::task::JoinError),
45
46 /// atproto string parsing error
47 #[error(transparent)]
48 AtprotoString(#[from] AtStrError),
49
50 /// XRPC error
51 #[error(transparent)]
52 Xrpc(#[from] jacquard::xrpc::XrpcError<GenericXrpcError>),
53}
54
55/// Parse error with source code location information
56#[derive(thiserror::Error, Debug, Diagnostic)]
57#[error("parse error: {}",self.kind)]
58#[diagnostic(code(weaver::parse))]
59pub struct ParseError {
60 #[diagnostic_source]
61 kind: ParseErrorKind,
62 #[source_code]
63 src: NamedSource<Cow<'static, str>>,
64 #[label("error")]
65 err_location: SourceSpan,
66 err_line_col: Option<(usize, usize)>,
67 #[help]
68 advice: Option<String>,
69}
70
71impl ParseError {
72 pub fn with_source(self, src: NamedSource<Cow<'static, str>>) -> Self {
73 if let Some((line, column)) = self.err_line_col {
74 let location = SourceSpan::new(
75 SourceOffset::from_location(src.inner(), line, column),
76 self.err_location.len(),
77 );
78 Self {
79 kind: self.kind,
80 src,
81 err_location: location,
82 err_line_col: Some((line, column)),
83 advice: self.advice,
84 }
85 } else {
86 let (line, col) = offset_to_line_col(self.err_location.offset(), &self.src);
87 let len = self.err_location.len();
88 let location =
89 SourceSpan::new(SourceOffset::from_location(src.inner(), line, col), len);
90 Self {
91 kind: self.kind,
92 src,
93 err_location: location,
94 err_line_col: self.err_line_col,
95 advice: self.advice,
96 }
97 }
98 }
99}
100
101#[derive(thiserror::Error, Debug, Diagnostic)]
102#[non_exhaustive]
103pub enum ParseErrorKind {
104 #[error(transparent)]
105 SerdeError(#[from] SerDeError),
106 #[error("error in markdown parsing or rendering: {0}")]
107 MarkdownError(markdown_weaver::CowStr<'static>),
108}
109
110/// Serialization/deserialization errors
111#[derive(thiserror::Error, Debug, Diagnostic)]
112#[non_exhaustive]
113pub enum SerDeError {
114 #[error(transparent)]
115 #[diagnostic_source]
116 Json(#[from] serde_json::Error),
117}
118
119impl From<serde_json::Error> for ParseError {
120 fn from(err: serde_json::Error) -> Self {
121 let line = err.line();
122 let column = err.column();
123 let location = SourceSpan::new(SourceOffset::from_location("", line, column), 0);
124 Self {
125 kind: ParseErrorKind::SerdeError(SerDeError::Json(err)),
126 src: NamedSource::new(Cow::Borrowed("json"), Cow::Borrowed("")),
127 err_location: location,
128 advice: None,
129 err_line_col: Some((line, column)),
130 }
131 }
132}
133
134fn offset_to_line_col(offset: usize, src: &NamedSource<Cow<'static, str>>) -> (usize, usize) {
135 let mut acc_chars = 0usize;
136
137 for (i, line) in src.inner().split_inclusive('\n').enumerate() {
138 acc_chars += line.len();
139 if offset < acc_chars {
140 let mut col = 0usize;
141 let line_offset = offset - acc_chars;
142 for (byte_idx, _) in line.char_indices() {
143 if byte_idx >= line_offset {
144 return (i + 1, col);
145 }
146 col += 1;
147 }
148 return (i + 1, col);
149 }
150 }
151 (src.inner().lines().count(), 0)
152}