queryweave_codegen/
errors.rs1use proc_macro2::Span;
2use quote::ToTokens;
3
4pub fn abort<T>(span: Span, msg: &str, help: &str) -> syn::Result<T> {
10 Err(syn::Error::new(span, message(msg, help)))
11}
12
13pub fn abort_call_site<T>(msg: &str, help: &str) -> syn::Result<T> {
19 abort(Span::call_site(), msg, help)
20}
21
22pub fn abort_spanned<T>(tokens: impl ToTokens, msg: &str, help: &str) -> syn::Result<T> {
28 Err(syn::Error::new_spanned(tokens, message(msg, help)))
29}
30
31#[must_use]
33pub fn message(msg: &str, help: &str) -> String {
34 format!("{msg}\n\n= help: {help}\n\n")
35}
36
37pub fn both<A, B>(first: syn::Result<A>, second: syn::Result<B>) -> syn::Result<(A, B)> {
43 match (first, second) {
44 (Ok(first), Ok(second)) => Ok((first, second)),
45 (Err(mut error), Err(other)) => {
46 error.combine(other);
47 Err(error)
48 },
49 (Err(error), Ok(_)) | (Ok(_), Err(error)) => Err(error),
50 }
51}
52
53#[derive(Default)]
56pub struct Errors {
57 combined: Option<syn::Error>,
58}
59
60impl Errors {
61 pub fn push(&mut self, error: syn::Error) {
63 match &mut self.combined {
64 Some(combined) => combined.combine(error),
65 None => self.combined = Some(error),
66 }
67 }
68
69 pub fn check<T>(&mut self, result: syn::Result<T>) -> Option<T> {
71 result.map_err(|error| self.push(error)).ok()
72 }
73
74 pub fn finish<T>(self, value: T) -> syn::Result<T> {
80 self.combined.map_or(Ok(value), Err)
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::{Errors, abort, abort_call_site, abort_spanned, both, message};
87 use pretty_assertions::assert_eq;
88
89 fn failure(text: &str) -> syn::Error {
90 syn::Error::new(proc_macro2::Span::call_site(), text)
91 }
92
93 fn messages(error: syn::Error) -> Vec<String> {
94 error.into_iter().map(|error| error.to_string()).collect()
95 }
96
97 #[test]
98 fn both_keeps_both_values_or_every_error_in_order() {
99 assert_eq!(both::<_, u8>(Ok(1), Ok(2)).unwrap(), (1, 2));
100 assert_eq!(
101 messages(both::<u8, u8>(Err(failure("first")), Err(failure("second"))).unwrap_err()),
102 vec!["first", "second"]
103 );
104 assert_eq!(
105 messages(both::<u8, u8>(Ok(1), Err(failure("second"))).unwrap_err()),
106 vec!["second"]
107 );
108 }
109
110 #[test]
111 fn errors_finish_with_the_value_or_every_collected_error() {
112 assert_eq!(Errors::default().finish(7).unwrap(), 7);
113
114 let mut errors = Errors::default();
115 errors.push(failure("first"));
116 assert_eq!(errors.check::<u8>(Err(failure("second"))), None);
117 assert_eq!(errors.check::<u8>(Ok(3)), Some(3));
118
119 assert_eq!(
120 messages(errors.finish(7).unwrap_err()),
121 vec!["first", "second"]
122 );
123 }
124
125 #[test]
129 fn message_lays_out_the_error_and_the_help_hint() {
130 assert_eq!(
131 message("Something went wrong", "Try this instead"),
132 "Something went wrong\n\n= help: Try this instead\n\n"
133 );
134 }
135
136 #[test]
137 fn message_keeps_both_halves_distinct() {
138 let formatted = message("the-message", "the-help");
139
140 assert!(formatted.starts_with("the-message"));
141 assert!(formatted.contains("= help: the-help"));
142 }
143
144 #[test]
145 fn abort_returns_an_error_carrying_the_formatted_message() {
146 let error = abort::<()>(proc_macro2::Span::call_site(), "msg", "help").unwrap_err();
147
148 assert_eq!(error.to_string(), message("msg", "help"));
149 }
150
151 #[test]
152 fn abort_call_site_returns_an_error_carrying_the_formatted_message() {
153 let error = abort_call_site::<()>("msg", "help").unwrap_err();
154
155 assert_eq!(error.to_string(), message("msg", "help"));
156 }
157
158 #[test]
159 fn abort_spanned_returns_an_error_carrying_the_formatted_message() {
160 let tokens = quote::quote! { struct UserModel; };
161 let error = abort_spanned::<()>(tokens, "msg", "help").unwrap_err();
162
163 assert_eq!(error.to_string(), message("msg", "help"));
164 }
165}