A better Rust ATProto crate
at lifetimes 5.7 kB view raw
1use miette::{Diagnostic, SourceSpan}; 2use std::io; 3use std::path::PathBuf; 4use thiserror::Error; 5 6/// Errors that can occur during lexicon code generation 7#[derive(Debug, Error, Diagnostic)] 8pub enum CodegenError { 9 /// IO error when reading lexicon files 10 #[error("IO error: {0}")] 11 Io(#[from] io::Error), 12 13 /// Failed to parse lexicon JSON 14 #[error("Failed to parse lexicon JSON in {}", path.display())] 15 #[diagnostic( 16 code(lexicon::parse_error), 17 help("Check that the lexicon file is valid JSON and follows the lexicon schema") 18 )] 19 ParseError { 20 #[source] 21 source: serde_json::Error, 22 /// Path to the file that failed to parse 23 path: PathBuf, 24 /// Source text that failed to parse 25 #[source_code] 26 src: Option<String>, 27 /// Location of the error in the source 28 #[label("parse error here")] 29 span: Option<SourceSpan>, 30 }, 31 32 /// Reference to non-existent lexicon or def 33 #[error("Reference to unknown type: {ref_string}")] 34 #[diagnostic( 35 code(lexicon::unknown_ref), 36 help("Add the referenced lexicon to your corpus or use Data<'a> as a fallback type") 37 )] 38 UnknownRef { 39 /// The ref string that couldn't be resolved 40 ref_string: String, 41 /// NSID of lexicon containing the ref 42 lexicon_nsid: String, 43 /// Def name containing the ref 44 def_name: String, 45 /// Field path containing the ref 46 field_path: String, 47 }, 48 49 /// Circular reference detected in type definitions 50 #[error("Circular reference detected")] 51 #[diagnostic( 52 code(lexicon::circular_ref), 53 help("The code generator uses Box<T> for union variants to handle circular references") 54 )] 55 CircularRef { 56 /// The ref string that forms a cycle 57 ref_string: String, 58 /// The cycle path 59 cycle: Vec<String>, 60 }, 61 62 /// Invalid lexicon structure 63 #[error("Invalid lexicon: {message}")] 64 #[diagnostic(code(lexicon::invalid))] 65 InvalidLexicon { 66 message: String, 67 /// NSID of the invalid lexicon 68 lexicon_nsid: String, 69 }, 70 71 /// Unsupported lexicon feature 72 #[error("Unsupported feature: {feature}")] 73 #[diagnostic( 74 code(lexicon::unsupported), 75 help("This lexicon feature is not yet supported by the code generator") 76 )] 77 Unsupported { 78 /// Description of the unsupported feature 79 feature: String, 80 /// NSID of lexicon containing the feature 81 lexicon_nsid: String, 82 /// Optional suggestion for workaround 83 suggestion: Option<String>, 84 }, 85 86 /// Name collision 87 #[error("Name collision: {name}")] 88 #[diagnostic( 89 code(lexicon::name_collision), 90 help("Multiple types would generate the same Rust identifier. Module paths will disambiguate.") 91 )] 92 NameCollision { 93 /// The colliding name 94 name: String, 95 /// NSIDs that would generate this name 96 nsids: Vec<String>, 97 }, 98 99 /// Code formatting error 100 #[error("Failed to format generated code")] 101 #[diagnostic(code(lexicon::format_error))] 102 FormatError { 103 #[source] 104 source: syn::Error, 105 }, 106 107 /// Generic error with context 108 #[error("{message}")] 109 #[diagnostic(code(lexicon::error))] 110 Other { 111 message: String, 112 /// Optional source error 113 #[source] 114 source: Option<Box<dyn std::error::Error + Send + Sync>>, 115 }, 116} 117 118impl CodegenError { 119 /// Create a parse error with context 120 pub fn parse_error(source: serde_json::Error, path: impl Into<PathBuf>) -> Self { 121 Self::ParseError { 122 source, 123 path: path.into(), 124 src: None, 125 span: None, 126 } 127 } 128 129 /// Create a parse error with source text 130 pub fn parse_error_with_source( 131 source: serde_json::Error, 132 path: impl Into<PathBuf>, 133 src: String, 134 ) -> Self { 135 // Try to extract error location from serde_json error 136 let span = if let Some(line) = source.line().checked_sub(1) { 137 let col = source.column().saturating_sub(1); 138 // Approximate byte offset (not perfect but good enough for display) 139 Some((line * 80 + col, 1).into()) 140 } else { 141 None 142 }; 143 144 Self::ParseError { 145 source, 146 path: path.into(), 147 src: Some(src), 148 span, 149 } 150 } 151 152 /// Create an unknown ref error 153 pub fn unknown_ref( 154 ref_string: impl Into<String>, 155 lexicon_nsid: impl Into<String>, 156 def_name: impl Into<String>, 157 field_path: impl Into<String>, 158 ) -> Self { 159 Self::UnknownRef { 160 ref_string: ref_string.into(), 161 lexicon_nsid: lexicon_nsid.into(), 162 def_name: def_name.into(), 163 field_path: field_path.into(), 164 } 165 } 166 167 /// Create an invalid lexicon error 168 pub fn invalid_lexicon(message: impl Into<String>, lexicon_nsid: impl Into<String>) -> Self { 169 Self::InvalidLexicon { 170 message: message.into(), 171 lexicon_nsid: lexicon_nsid.into(), 172 } 173 } 174 175 /// Create an unsupported feature error 176 pub fn unsupported( 177 feature: impl Into<String>, 178 lexicon_nsid: impl Into<String>, 179 suggestion: Option<impl Into<String>>, 180 ) -> Self { 181 Self::Unsupported { 182 feature: feature.into(), 183 lexicon_nsid: lexicon_nsid.into(), 184 suggestion: suggestion.map(|s| s.into()), 185 } 186 } 187} 188 189/// Result type for codegen operations 190pub type Result<T> = std::result::Result<T, CodegenError>;