A better Rust ATProto crate
1use miette::{Diagnostic, SourceSpan};
2use std::io;
3use std::path::PathBuf;
4use thiserror::Error;
5
6fn format_parse_error(path: &PathBuf, json_path: Option<&str>, message: &str) -> String {
7 match json_path {
8 Some(jp) if !jp.is_empty() => {
9 format!(
10 "failed to parse lexicon {}: at {}: {}",
11 path.display(),
12 jp,
13 message
14 )
15 }
16 _ => format!("failed to parse lexicon {}: {}", path.display(), message),
17 }
18}
19
20/// Errors that can occur during lexicon code generation
21#[derive(Debug, Error, Diagnostic)]
22#[non_exhaustive]
23pub enum CodegenError {
24 /// IO error when reading lexicon files
25 #[error("IO error: {0}")]
26 Io(#[from] io::Error),
27
28 /// Failed to parse lexicon JSON
29 #[error("{}", format_parse_error(path, json_path.as_deref(), message))]
30 #[diagnostic(
31 code(lexicon::parse_error),
32 help("Check that the lexicon file is valid JSON and follows the lexicon schema")
33 )]
34 ParseError {
35 /// Path to the file that failed to parse
36 path: PathBuf,
37 /// JSON path where the error occurred (from serde_path_to_error)
38 json_path: Option<String>,
39 /// The underlying error message
40 message: String,
41 /// Source text that failed to parse
42 #[source_code]
43 src: Option<String>,
44 /// Location of the error in the source
45 #[label("parse error here")]
46 span: Option<SourceSpan>,
47 },
48
49 /// Name collision
50 #[error("Name collision: {name}")]
51 #[diagnostic(
52 code(lexicon::name_collision),
53 help(
54 "Multiple types would generate the same Rust identifier. Module paths will disambiguate."
55 )
56 )]
57 NameCollision {
58 /// The colliding name
59 name: String,
60 /// NSIDs that would generate this name
61 nsids: Vec<String>,
62 },
63
64 /// Code formatting error
65 #[error("Failed to format generated code")]
66 #[diagnostic(code(lexicon::format_error))]
67 FormatError {
68 #[source]
69 source: syn::Error,
70 },
71
72 /// Failed to parse generated tokens back into syn AST
73 #[error("Failed to parse generated code for {path:?}")]
74 #[diagnostic(code(lexicon::token_parse_error))]
75 TokenParseError {
76 path: PathBuf,
77 #[source]
78 source: syn::Error,
79 tokens: String,
80 },
81
82 /// Unsupported lexicon feature
83 #[error("Unsupported: {message}")]
84 #[diagnostic(
85 code(lexicon::unsupported),
86 help("This lexicon feature is not yet supported by code generation")
87 )]
88 Unsupported {
89 /// Description of the unsupported feature
90 message: String,
91 /// NSID of the lexicon containing the unsupported feature
92 nsid: Option<String>,
93 /// Definition name if applicable
94 def_name: Option<String>,
95 },
96
97 /// Failed to parse generated path string
98 #[error("Failed to parse path '{path_str}'")]
99 #[diagnostic(code(lexicon::path_parse_error))]
100 PathParseError {
101 path_str: String,
102 #[source]
103 source: syn::Error,
104 },
105}
106
107impl CodegenError {
108 /// Create a parse error with context
109 pub fn parse_error(message: impl Into<String>, path: impl Into<PathBuf>) -> Self {
110 Self::ParseError {
111 path: path.into(),
112 json_path: None,
113 message: message.into(),
114 src: None,
115 span: None,
116 }
117 }
118
119 /// Create a parse error with source text and JSON path
120 pub fn parse_error_with_context(
121 message: impl Into<String>,
122 path: impl Into<PathBuf>,
123 json_path: Option<String>,
124 src: String,
125 ) -> Self {
126 Self::ParseError {
127 path: path.into(),
128 json_path,
129 message: message.into(),
130 src: Some(src),
131 span: None,
132 }
133 }
134
135 /// Create an unsupported feature error
136 pub fn unsupported(
137 message: impl Into<String>,
138 nsid: impl Into<String>,
139 def_name: Option<impl Into<String>>,
140 ) -> Self {
141 Self::Unsupported {
142 message: message.into(),
143 nsid: Some(nsid.into()),
144 def_name: def_name.map(|s| s.into()),
145 }
146 }
147}
148
149/// Result type for codegen operations
150pub type Result<T> = std::result::Result<T, CodegenError>;