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