1use proc_macro2::{TokenStream, TokenTree};
2use syn::{
3 parse::{Parse, ParseStream, Parser},
4 punctuated::Punctuated,
5 Expr, ExprLit, Lit, LitStr, MetaNameValue,
6};
7
8use super::{path_str, AttrCtxt, CustomMeta};
9
10pub fn require_path_only(meta: &CustomMeta, cx: &AttrCtxt) -> Result<(), ()> {
11 let error_args = || {
12 format!(
13 "unexpected value of {} {} attribute item",
14 cx.attr_type,
15 path_str(meta.path())
16 )
17 };
18
19 match &meta {
20 CustomMeta::Path(_) => Ok(()),
21 CustomMeta::List(meta) => {
22 cx.syn_error(syn::Error::new(meta.delimiter.span().join(), error_args()));
23 Err(())
24 }
25 CustomMeta::NameValue(meta) => {
26 let eq_token = &meta.eq_token;
27 let value = &meta.value;
28 cx.error_spanned_by(quote!(#eq_token #value), error_args());
29 Err(())
30 }
31 CustomMeta::Not(..) => {
32 Err(())
34 }
35 }
36}
37
38pub fn parse_name_value_expr(meta: CustomMeta, cx: &AttrCtxt) -> Result<Expr, ()> {
39 if let CustomMeta::NameValue(m) = meta {
40 Ok(m.value)
41 } else {
42 let name = path_str(meta.path());
43 cx.error_spanned_by(
44 meta,
45 format_args!(
46 "expected {} {} attribute item to have a value: `{} = ...`",
47 cx.attr_type, name, name
48 ),
49 );
50 Err(())
51 }
52}
53
54pub fn require_name_value_lit_str(meta: CustomMeta, cx: &AttrCtxt) -> Result<LitStr, ()> {
55 if let CustomMeta::NameValue(MetaNameValue {
56 value: Expr::Lit(ExprLit {
57 lit: Lit::Str(lit_str),
58 ..
59 }),
60 ..
61 }) = meta
62 {
63 Ok(lit_str)
64 } else {
65 let name = path_str(meta.path());
66 cx.error_spanned_by(
67 meta,
68 format_args!(
69 "expected {} {} attribute item to have a string value: `{} = \"...\"`",
70 cx.attr_type, name, name
71 ),
72 );
73 Err(())
74 }
75}
76
77pub fn parse_name_value_lit_str<T: Parse>(meta: CustomMeta, cx: &AttrCtxt) -> Result<T, ()> {
78 let lit_str = require_name_value_lit_str(meta, cx)?;
79
80 parse_lit_str(&lit_str, cx)
81}
82
83fn parse_lit_str<T: Parse>(lit_str: &LitStr, cx: &AttrCtxt) -> Result<T, ()> {
84 lit_str.parse().map_err(|_| {
85 cx.error_spanned_by(
86 lit_str,
87 format_args!(
88 "failed to parse \"{}\" as a {}",
89 lit_str.value(),
90 std::any::type_name::<T>()
91 .rsplit("::")
92 .next()
93 .unwrap_or_default()
94 .to_ascii_lowercase(),
95 ),
96 );
97 })
98}
99
100pub fn parse_extensions(
101 meta: &CustomMeta,
102 cx: &AttrCtxt,
103) -> Result<impl IntoIterator<Item = Extension>, ()> {
104 let parser = Punctuated::<Extension, Token![,]>::parse_terminated;
105 parse_meta_list_with(meta, cx, parser)
106}
107
108pub fn parse_length_or_range(outer_meta: &CustomMeta, cx: &AttrCtxt) -> Result<LengthOrRange, ()> {
109 let outer_name = path_str(outer_meta.path());
110 let mut result = LengthOrRange::default();
111
112 for nested_meta in parse_nested_meta(outer_meta, cx)? {
113 match path_str(nested_meta.path()).as_str() {
114 "min" => match (&result.min, &result.equal) {
115 (Some(_), _) => cx.duplicate_error(&nested_meta),
116 (_, Some(_)) => cx.mutual_exclusive_error(&nested_meta, "equal"),
117 _ => result.min = parse_name_value_expr_handle_lit_str(nested_meta, cx).ok(),
118 },
119 "max" => match (&result.max, &result.equal) {
120 (Some(_), _) => cx.duplicate_error(&nested_meta),
121 (_, Some(_)) => cx.mutual_exclusive_error(&nested_meta, "equal"),
122 _ => result.max = parse_name_value_expr_handle_lit_str(nested_meta, cx).ok(),
123 },
124 "equal" => match (&result.min, &result.max, &result.equal) {
125 (Some(_), _, _) => cx.mutual_exclusive_error(&nested_meta, "min"),
126 (_, Some(_), _) => cx.mutual_exclusive_error(&nested_meta, "max"),
127 (_, _, Some(_)) => cx.duplicate_error(&nested_meta),
128 _ => result.equal = parse_name_value_expr_handle_lit_str(nested_meta, cx).ok(),
129 },
130 unknown => {
131 if cx.attr_type == "schemars" {
132 cx.error_spanned_by(
133 nested_meta,
134 format_args!(
135 "unknown item in schemars {outer_name} attribute: `{unknown}`",
136 ),
137 );
138 }
139 }
140 }
141 }
142
143 Ok(result)
144}
145
146pub fn parse_pattern(meta: &CustomMeta, cx: &AttrCtxt) -> Result<Expr, ()> {
147 parse_meta_list_with(meta, cx, Expr::parse)
148}
149
150pub fn parse_schemars_regex(outer_meta: &CustomMeta, cx: &AttrCtxt) -> Result<Expr, ()> {
151 let mut pattern = None;
152
153 for nested_meta in parse_nested_meta(outer_meta, cx)? {
154 match path_str(nested_meta.path()).as_str() {
155 "pattern" => match &pattern {
156 Some(_) => cx.duplicate_error(&nested_meta),
157 None => pattern = parse_name_value_expr(nested_meta, cx).ok(),
158 },
159 "path" => {
160 cx.error_spanned_by(nested_meta, "`path` is not supported in `schemars(regex(...))` attribute - use `schemars(regex(pattern = ...))` instead");
161 }
162 unknown => {
163 cx.error_spanned_by(
164 nested_meta,
165 format_args!("unknown item in schemars `regex` attribute: `{unknown}`"),
166 );
167 }
168 }
169 }
170
171 pattern.ok_or_else(|| {
172 cx.error_spanned_by(
173 outer_meta,
174 "`schemars(regex(...))` attribute requires `pattern = ...`",
175 );
176 })
177}
178
179pub fn parse_validate_regex(outer_meta: &CustomMeta, cx: &AttrCtxt) -> Result<Expr, ()> {
180 let mut path = None;
181
182 for nested_meta in parse_nested_meta(outer_meta, cx)? {
183 match path_str(nested_meta.path()).as_str() {
184 "path" => match &path {
185 Some(_) => cx.duplicate_error(&nested_meta),
186 None => path = parse_name_value_expr_handle_lit_str(nested_meta, cx).ok(),
187 },
188 "pattern" => {
189 cx.error_spanned_by(nested_meta, "`pattern` is not supported in `validate(regex(...))` attribute - use either `validate(regex(path = ...))` or `schemars(regex(pattern = ...))` instead");
190 }
191 _ => {
192 }
194 }
195 }
196
197 path.ok_or_else(|| {
198 cx.error_spanned_by(
199 outer_meta,
200 "`validate(regex(...))` attribute requires `path = ...`",
201 );
202 })
203}
204
205pub fn parse_contains(outer_meta: CustomMeta, cx: &AttrCtxt) -> Result<Expr, ()> {
206 enum ContainsFormat {
207 Metas(Punctuated<CustomMeta, Token![,]>),
208 Expr(Expr),
209 }
210
211 impl Parse for ContainsFormat {
212 fn parse(input: ParseStream) -> syn::Result<Self> {
213 if input.peek2(Token![,]) || input.peek2(Token![=]) {
221 Punctuated::parse_terminated(input).map(Self::Metas)
222 } else {
223 input.parse().map(Self::Expr)
224 }
225 }
226 }
227
228 let nested_meta_or_expr = match cx.attr_type {
229 "validate" => parse_meta_list_with(&outer_meta, cx, Punctuated::parse_terminated)
230 .map(ContainsFormat::Metas),
231 "garde" => parse_meta_list_with(&outer_meta, cx, Expr::parse).map(ContainsFormat::Expr),
232 "schemars" => parse_meta_list_with(&outer_meta, cx, ContainsFormat::parse),
233 wat => {
234 unreachable!("Unexpected attr type `{wat}` for `contains` item. This is a bug in schemars, please raise an issue!")
235 }
236 }?;
237
238 let nested_metas = match nested_meta_or_expr {
239 ContainsFormat::Expr(expr) => return Ok(expr),
240 ContainsFormat::Metas(m) => m,
241 };
242
243 let mut pattern = None;
244
245 for nested_meta in nested_metas {
246 match path_str(nested_meta.path()).as_str() {
247 "pattern" => match &pattern {
248 Some(_) => cx.duplicate_error(&nested_meta),
249 None => pattern = parse_name_value_expr(nested_meta, cx).ok(),
250 },
251 unknown => {
252 if cx.attr_type == "schemars" {
253 cx.error_spanned_by(
254 nested_meta,
255 format_args!("unknown item in schemars `contains` attribute: `{unknown}`"),
256 );
257 }
258 }
259 }
260 }
261
262 pattern.ok_or_else(|| {
263 cx.error_spanned_by(
264 outer_meta,
265 "`contains` attribute item requires `pattern = ...`",
266 );
267 })
268}
269
270pub fn parse_nested_meta(
271 meta: &CustomMeta,
272 cx: &AttrCtxt,
273) -> Result<impl IntoIterator<Item = CustomMeta>, ()> {
274 let parser = Punctuated::<CustomMeta, Token![,]>::parse_terminated;
275 parse_meta_list_with(meta, cx, parser)
276}
277
278fn parse_meta_list_with<F: Parser>(
279 meta: &CustomMeta,
280 cx: &AttrCtxt,
281 parser: F,
282) -> Result<F::Output, ()> {
283 let CustomMeta::List(meta_list) = meta else {
284 let name = path_str(meta.path());
285 cx.error_spanned_by(
286 meta,
287 format_args!(
288 "expected {} {} attribute item to be of the form `{}(...)`",
289 cx.attr_type, name, name
290 ),
291 );
292 return Err(());
293 };
294
295 meta_list.parse_args_with(parser).map_err(|err| {
296 cx.syn_error(err);
297 })
298}
299
300pub fn parse_name_value_expr_handle_lit_str(meta: CustomMeta, cx: &AttrCtxt) -> Result<Expr, ()> {
302 let expr = parse_name_value_expr(meta, cx)?;
303
304 if let Expr::Lit(ExprLit {
305 lit: Lit::Str(lit_str),
306 ..
307 }) = &expr
308 {
309 parse_lit_str(lit_str, cx)
310 } else {
311 Ok(expr)
312 }
313}
314
315#[derive(Default)]
316pub struct LengthOrRange {
317 pub min: Option<Expr>,
318 pub max: Option<Expr>,
319 pub equal: Option<Expr>,
320}
321
322pub struct Extension {
323 pub key_str: String,
324 pub key_lit: LitStr,
325 pub value: TokenStream,
326}
327
328impl Parse for Extension {
329 fn parse(input: ParseStream) -> syn::Result<Self> {
330 let key = input.parse::<LitStr>()?;
331 input.parse::<Token![=]>()?;
332 let mut value = TokenStream::new();
333
334 while !input.is_empty() && !input.peek(Token![,]) {
335 value.extend([input.parse::<TokenTree>()?]);
336 }
337
338 if value.is_empty() {
339 return Err(syn::Error::new(input.span(), "Expected extension value"));
340 }
341
342 Ok(Extension {
343 key_str: key.value(),
344 key_lit: key,
345 value,
346 })
347 }
348}