A better Rust ATProto crate
at main 150 lines 4.4 kB view raw
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>;