codama_syn_helpers/extensions/
expr.rs

1use super::ToTokensExtension;
2use syn::{Expr, ExprLit, ExprPath, ExprUnary};
3
4pub trait ExprExtension {
5    fn get_self(&self) -> &Expr;
6
7    /// Returns the integer value of the expression if it is a literal unsigned integer.
8    fn as_unsigned_integer<T>(&self) -> syn::Result<T>
9    where
10        T: std::str::FromStr,
11        T::Err: std::fmt::Display,
12    {
13        let this = self.get_self();
14        match this {
15            Expr::Lit(ExprLit {
16                lit: syn::Lit::Int(value),
17                ..
18            }) => value.base10_parse::<T>(),
19            _ => Err(this.error("expected an unsigned integer")),
20        }
21    }
22
23    /// Returns the integer value of the expression if it is a literal signed integer.
24    fn as_signed_integer<T>(&self) -> syn::Result<T>
25    where
26        T: std::str::FromStr + std::ops::Neg<Output = T>,
27        T::Err: std::fmt::Display,
28    {
29        let this = self.get_self();
30        let result = match this {
31            Expr::Unary(ExprUnary {
32                op: syn::UnOp::Neg(_),
33                expr: unsigned_expr,
34                ..
35            }) => unsigned_expr
36                .as_unsigned_integer::<T>()
37                .map(|value| value.neg()),
38            _ => this.as_unsigned_integer::<T>(),
39        };
40
41        result.map_err(|_| this.error("expected a signed integer"))
42    }
43
44    fn as_unsigned_float<T>(&self) -> syn::Result<T>
45    where
46        T: std::str::FromStr,
47        T::Err: std::fmt::Display,
48    {
49        let this = self.get_self();
50        match this {
51            Expr::Lit(ExprLit {
52                lit: syn::Lit::Float(value),
53                ..
54            }) => value.base10_parse::<T>(),
55            _ => Err(this.error("expected an unsigned float")),
56        }
57    }
58
59    fn as_float<T>(&self) -> syn::Result<T>
60    where
61        T: std::str::FromStr + std::ops::Neg<Output = T>,
62        T::Err: std::fmt::Display,
63    {
64        let this = self.get_self();
65        let result = match this {
66            Expr::Unary(ExprUnary {
67                op: syn::UnOp::Neg(_),
68                expr: float_expr,
69                ..
70            }) => float_expr.as_unsigned_float::<T>().map(|value| value.neg()),
71            _ => this.as_unsigned_float::<T>(),
72        };
73
74        result.map_err(|_| this.error("expected a float"))
75    }
76
77    /// Returns the string value of the expression if it is a literal string.
78    fn as_string(&self) -> syn::Result<String> {
79        let this = self.get_self();
80        match this {
81            Expr::Lit(ExprLit {
82                lit: syn::Lit::Str(value),
83                ..
84            }) => Ok(value.value()),
85            _ => Err(this.error("expected a string")),
86        }
87    }
88
89    /// Returns the boolean value of the expression if it is a literal bool.
90    fn as_bool(&self) -> syn::Result<bool> {
91        let this = self.get_self();
92        match this {
93            Expr::Lit(ExprLit {
94                lit: syn::Lit::Bool(value),
95                ..
96            }) => Ok(value.value()),
97            _ => Err(this.error("expected a boolean")),
98        }
99    }
100
101    fn as_u8_array(&self) -> syn::Result<Vec<u8>> {
102        let this = self.get_self();
103        match this {
104            Expr::Array(array) => array
105                .elems
106                .iter()
107                .map(|expr| expr.as_unsigned_integer::<u8>())
108                .collect(),
109            _ => Err(this.error("expected an array")),
110        }
111    }
112
113    /// Returns the path of the expression if it is a path.
114    fn as_path(&self) -> syn::Result<&syn::Path> {
115        let this = self.get_self();
116        match this {
117            Expr::Path(ExprPath { path, .. }) => Ok(path),
118            _ => Err(this.error("expected a path")),
119        }
120    }
121}
122
123impl ExprExtension for Expr {
124    fn get_self(&self) -> &Expr {
125        self
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::extensions::*;
133
134    #[test]
135    fn as_unsigned_integer_ok() {
136        let expr: Expr = syn::parse_quote! { 42 };
137        let result = expr.as_unsigned_integer::<usize>().unwrap();
138        assert_eq!(result, 42usize);
139    }
140
141    #[test]
142    fn as_unsigned_integer_err() {
143        let expr: Expr = syn::parse_quote! { -42 };
144        let error = expr.as_unsigned_integer::<usize>().unwrap_err();
145        assert_eq!(error.to_string(), "expected an unsigned integer");
146    }
147
148    #[test]
149    fn as_signed_integer_ok() {
150        let expr: Expr = syn::parse_quote! { -42 };
151        let result = expr.as_signed_integer::<isize>().unwrap();
152        assert_eq!(result, -42isize);
153    }
154
155    #[test]
156    fn as_signed_integer_ok_with_unsigned() {
157        let expr: Expr = syn::parse_quote! { 42 };
158        let result = expr.as_signed_integer::<isize>().unwrap();
159        assert_eq!(result, 42isize);
160    }
161
162    #[test]
163    fn as_signed_integer_err() {
164        let expr: Expr = syn::parse_quote! { -42.5 };
165        let error = expr.as_signed_integer::<isize>().unwrap_err();
166        assert_eq!(error.to_string(), "expected a signed integer");
167    }
168
169    #[test]
170    fn as_float_ok() {
171        let expr: Expr = syn::parse_quote! { 1.5 };
172        let result = expr.as_float::<f64>().unwrap();
173        assert_eq!(result, 1.5f64);
174    }
175
176    #[test]
177    fn as_float_ok_negative() {
178        let expr: Expr = syn::parse_quote! { -1.5 };
179        let result = expr.as_float::<f64>().unwrap();
180        assert_eq!(result, -1.5f64);
181    }
182
183    #[test]
184    fn as_float_err() {
185        let expr: Expr = syn::parse_quote! { "3.14" };
186        let error = expr.as_float::<f64>().unwrap_err();
187        assert_eq!(error.to_string(), "expected a float");
188    }
189
190    #[test]
191    fn as_string_ok() {
192        let expr: Expr = syn::parse_quote! { "hello" };
193        let result = expr.as_string().unwrap();
194        assert_eq!(result, "hello");
195    }
196
197    #[test]
198    fn as_string_err() {
199        let expr: Expr = syn::parse_quote! { 40 + 2 };
200        let error = expr.as_string().unwrap_err();
201        assert_eq!(error.to_string(), "expected a string");
202    }
203
204    #[test]
205    fn as_bool_ok() {
206        let expr: Expr = syn::parse_quote! { true };
207        let result = expr.as_bool().unwrap();
208        assert!(result);
209    }
210
211    #[test]
212    fn as_bool_err() {
213        let expr: Expr = syn::parse_quote! { 42 };
214        let error = expr.as_bool().unwrap_err();
215        assert_eq!(error.to_string(), "expected a boolean");
216    }
217
218    #[test]
219    fn as_path_ok() {
220        let expr: Expr = syn::parse_quote! { hello::world };
221        let result = expr.as_path().unwrap().to_string();
222        assert_eq!(result, "hello::world");
223    }
224
225    #[test]
226    fn as_path_err() {
227        let expr: Expr = syn::parse_quote! { 40 + 2 };
228        let error = expr.as_path().unwrap_err();
229        assert_eq!(error.to_string(), "expected a path");
230    }
231
232    #[test]
233    fn as_u8_array_ok() {
234        let expr: Expr = syn::parse_quote! { [1, 2, 3, 4] };
235        let result = expr.as_u8_array().unwrap();
236        assert_eq!(result, vec![1, 2, 3, 4]);
237    }
238
239    #[test]
240    fn as_u8_array_err() {
241        let expr: Expr = syn::parse_quote! { "hello world" };
242        let error = expr.as_u8_array().unwrap_err();
243        assert_eq!(error.to_string(), "expected an array");
244    }
245}