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>;