1use proc_macro2::Span;
8use quote::quote;
9
10pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Debug)]
20pub struct Error {
21 span: Span,
22 kind: ErrorKind,
23}
24
25#[derive(Debug)]
27pub enum ErrorKind {
28 MissingWrapper {
30 expected: &'static str,
31 found: String,
32 suggestion: String,
33 },
34 EmptyGenericArgs { wrapper: &'static str },
36 UnsupportedType {
38 name: String,
39 reason: &'static str,
40 suggestion: &'static str,
41 },
42 InvalidAttrValue {
44 attr: String,
45 expected: &'static str,
46 found: String,
47 },
48 MissingRequiredAttr {
50 attr: &'static str,
51 context: &'static str,
52 },
53 ConflictingAttrs {
55 first: String,
56 second: String,
57 suggestion: String,
58 },
59 MissingReturnType { fn_name: String },
61 InvalidHandlerSig {
63 fn_name: String,
64 reason: &'static str,
65 },
66 UnknownKey {
68 key: String,
69 valid_keys: &'static [&'static str],
70 },
71 SynError(syn::Error),
73}
74
75impl Error {
80 pub fn missing_wrapper(span: Span, expected: &'static str, found: &syn::Type) -> Self {
81 let found_str = type_display(found);
82 Self {
83 span,
84 kind: ErrorKind::MissingWrapper {
85 suggestion: format!("wrap the type: `{}<{}>`", expected, found_str),
86 expected,
87 found: found_str,
88 },
89 }
90 }
91
92 pub fn empty_generic_args(span: Span, wrapper: &'static str) -> Self {
93 Self {
94 span,
95 kind: ErrorKind::EmptyGenericArgs { wrapper },
96 }
97 }
98
99 pub fn unsupported_type(
100 span: Span,
101 ty: &syn::Type,
102 reason: &'static str,
103 suggestion: &'static str,
104 ) -> Self {
105 Self {
106 span,
107 kind: ErrorKind::UnsupportedType {
108 name: type_display(ty),
109 reason,
110 suggestion,
111 },
112 }
113 }
114
115 pub fn invalid_attr_value(span: Span, attr: &str, expected: &'static str, found: &str) -> Self {
116 Self {
117 span,
118 kind: ErrorKind::InvalidAttrValue {
119 attr: attr.to_string(),
120 expected,
121 found: found.to_string(),
122 },
123 }
124 }
125
126 pub fn missing_required_attr(span: Span, attr: &'static str, context: &'static str) -> Self {
127 Self {
128 span,
129 kind: ErrorKind::MissingRequiredAttr { attr, context },
130 }
131 }
132
133 pub fn conflicting_attrs(span: Span, first: &str, second: &str) -> Self {
134 Self {
135 span,
136 kind: ErrorKind::ConflictingAttrs {
137 suggestion: format!("remove either `{}` or `{}`", first, second),
138 first: first.to_string(),
139 second: second.to_string(),
140 },
141 }
142 }
143
144 pub fn missing_return_type(span: Span, fn_name: &str) -> Self {
145 Self {
146 span,
147 kind: ErrorKind::MissingReturnType {
148 fn_name: fn_name.to_string(),
149 },
150 }
151 }
152
153 pub fn invalid_handler_sig(span: Span, fn_name: &str, reason: &'static str) -> Self {
154 Self {
155 span,
156 kind: ErrorKind::InvalidHandlerSig {
157 fn_name: fn_name.to_string(),
158 reason,
159 },
160 }
161 }
162
163 pub fn unknown_key(span: Span, key: &str, valid_keys: &'static [&'static str]) -> Self {
164 Self {
165 span,
166 kind: ErrorKind::UnknownKey {
167 key: key.to_string(),
168 valid_keys,
169 },
170 }
171 }
172
173 pub fn to_compile_error(&self) -> proc_macro2::TokenStream {
175 syn::Error::new(self.span, self.to_string()).to_compile_error()
176 }
177}
178
179impl std::fmt::Display for Error {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 use ErrorKind::*;
186 match &self.kind {
187 MissingWrapper {
188 expected,
189 found,
190 suggestion,
191 } => write!(
192 f,
193 "expected `{}` wrapper, found `{}`\n = help: {}",
194 expected, found, suggestion
195 ),
196 EmptyGenericArgs { wrapper } => {
197 write!(f, "`{}` requires at least one type argument", wrapper)
198 }
199 UnsupportedType {
200 name,
201 reason,
202 suggestion,
203 } => write!(
204 f,
205 "unsupported type `{}`\n = note: {}\n = help: {}",
206 name, reason, suggestion
207 ),
208 InvalidAttrValue {
209 attr,
210 expected,
211 found,
212 } => write!(
213 f,
214 "invalid value for `{}`\n = expected: {}\n = found: {}",
215 attr, expected, found
216 ),
217 MissingRequiredAttr { attr, context } => write!(
218 f,
219 "missing required attribute `{}`\n = help: {}",
220 attr, context
221 ),
222 ConflictingAttrs {
223 first,
224 second,
225 suggestion,
226 } => write!(
227 f,
228 "conflicting attributes `{}` and `{}`\n = help: {}",
229 first, second, suggestion
230 ),
231 MissingReturnType { fn_name } => write!(
232 f,
233 "`{}` has no return type\n = help: add `-> Json<T>` or `-> Result<Json<T>, E>`",
234 fn_name
235 ),
236 InvalidHandlerSig { fn_name, reason } => write!(
237 f,
238 "invalid handler signature for `{}`\n = note: {}\n = help: return `Json<T>` or `Result<Json<T>, E>`",
239 fn_name, reason
240 ),
241 UnknownKey { key, valid_keys } => write!(
242 f,
243 "unknown key `{}`\n = valid keys: {}",
244 key,
245 valid_keys.join(", ")
246 ),
247 SynError(e) => write!(f, "{}", e),
248 }
249 }
250}
251
252impl std::error::Error for Error {}
253
254impl From<syn::Error> for Error {
255 fn from(e: syn::Error) -> Self {
256 Self {
257 span: e.span(),
258 kind: ErrorKind::SynError(e),
259 }
260 }
261}
262
263pub(crate) fn type_display(ty: &syn::Type) -> String {
270 quote!(#ty).to_string().replace(' ', "")
271}
272
273#[cfg(test)]
278mod tests {
279 use super::*;
280 use proc_macro2::Span;
281
282 fn span() -> Span {
283 Span::call_site()
284 }
285
286 #[test]
287 fn missing_wrapper_message() {
288 let ty: syn::Type = syn::parse_str("Vec<Planet>").unwrap();
289 let err = Error::missing_wrapper(span(), "Json", &ty);
290 let msg = err.to_string();
291 assert!(msg.contains("expected `Json` wrapper"));
292 assert!(msg.contains("Vec<Planet>"));
293 assert!(msg.contains("help:"));
294 assert!(msg.contains("Json<Vec<Planet>>"));
295 }
296
297 #[test]
298 fn empty_generic_args_message() {
299 let err = Error::empty_generic_args(span(), "Result");
300 let msg = err.to_string();
301 assert!(msg.contains("`Result` requires at least one type argument"));
302 }
303
304 #[test]
305 fn missing_required_attr_message() {
306 let err =
307 Error::missing_required_attr(span(), "method", "add `method = \"GET\"` to #[orpc]");
308 let msg = err.to_string();
309 assert!(msg.contains("missing required attribute `method`"));
310 assert!(msg.contains("help:"));
311 }
312
313 #[test]
314 fn unknown_key_message() {
315 let err = Error::unknown_key(span(), "routes", &["method", "path", "data"]);
316 let msg = err.to_string();
317 assert!(msg.contains("unknown key `routes`"));
318 assert!(msg.contains("method"));
319 assert!(msg.contains("path"));
320 assert!(msg.contains("data"));
321 }
322
323 #[test]
324 fn conflicting_attrs_message() {
325 let err = Error::conflicting_attrs(span(), "method", "methods");
326 let msg = err.to_string();
327 assert!(msg.contains("conflicting attributes"));
328 assert!(msg.contains("help: remove either"));
329 }
330
331 #[test]
332 fn missing_return_type_message() {
333 let err = Error::missing_return_type(span(), "handle_ping");
334 let msg = err.to_string();
335 assert!(msg.contains("`handle_ping` has no return type"));
336 assert!(msg.contains("Json<T>"));
337 }
338
339 #[test]
340 fn invalid_handler_sig_message() {
341 let err = Error::invalid_handler_sig(span(), "my_handler", "return type is not Json<T>");
342 let msg = err.to_string();
343 assert!(msg.contains("invalid handler signature for `my_handler`"));
344 assert!(msg.contains("return type is not Json<T>"));
345 }
346
347 #[test]
348 fn syn_error_forwarded() {
349 let syn_err = syn::Error::new(span(), "raw syn error");
350 let err = Error::from(syn_err);
351 assert!(err.to_string().contains("raw syn error"));
352 }
353
354 #[test]
355 fn to_compile_error_is_nonempty() {
356 let err = Error::empty_generic_args(span(), "Json");
357 let tokens = err.to_compile_error();
358 assert!(!tokens.is_empty());
359 }
360}