A better Rust ATProto crate
at main 145 lines 4.3 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!("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>;