amplify_syn/
req.rs

1// Rust language amplification derive library providing multiple generic trait
2// implementations, type wrappers, derive macros and other language enhancements
3//
4// Written in 2019-2021 by
5//     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
6//
7// To the extent possible under law, the author(s) have dedicated all
8// copyright and related and neighboring rights to this software to
9// the public domain worldwide. This software is distributed without
10// any warranty.
11//
12// You should have received a copy of the MIT License
13// along with this software.
14// If not, see <https://opensource.org/licenses/MIT>.
15
16use std::collections::HashMap;
17use std::convert::TryInto;
18
19use quote::ToTokens;
20use syn::{LitChar, LitFloat, LitInt, Path};
21
22use crate::{ArgValue, Error, ValueClass};
23
24/// Structure requirements for parametrized attribute
25#[derive(Clone)]
26pub struct AttrReq {
27    /// Specifies all named arguments and which requirements they must meet
28    pub arg_req: HashMap<String, ArgValueReq>,
29
30    /// Specifies whether path arguments are allowed and with which
31    /// requirements.
32    pub path_req: ListReq<Path>,
33
34    /// Whether integer literals are allowed as an attribute argument and, if
35    /// yes, with which requirements
36    pub char_req: ListReq<LitChar>,
37
38    /// Whether integer literals are allowed as an attribute argument and, if
39    /// yes, with which requirements
40    pub integer_req: ListReq<LitInt>,
41
42    /// Whether integer literals are allowed as an attribute argument and, if
43    /// yes, with which requirements
44    pub float_req: ListReq<LitFloat>,
45
46    /// Whether string literal is allowed as an attribute argument and, if
47    /// yes, with which requirements
48    pub string_req: ValueReq,
49
50    /// Whether byte string literal is allowed as an attribute argument and, if
51    /// yes, with which requirements
52    pub bytes_req: ValueReq,
53
54    /// Whether boolean literal is allowed as an attribute argument and, if
55    /// yes, with which requirements
56    pub bool_req: ValueReq,
57}
58
59impl AttrReq {
60    /// Constructor creating [`AttrReq`] accepting only name-value arguments
61    /// with the provided parameters
62    pub fn with(args: HashMap<&str, ArgValueReq>) -> AttrReq {
63        let args = args
64            .into_iter()
65            .map(|(name, req)| (name.to_owned(), req))
66            .collect();
67
68        AttrReq {
69            arg_req: args,
70            path_req: ListReq::Deny,
71            char_req: ListReq::Deny,
72            integer_req: ListReq::Deny,
73            float_req: ListReq::Deny,
74            string_req: ValueReq::Prohibited,
75            bytes_req: ValueReq::Prohibited,
76            bool_req: ValueReq::Prohibited,
77        }
78    }
79}
80
81/// Requirements for attribute or named argument value presence
82#[derive(Clone)]
83pub enum ArgValueReq {
84    /// Argument must hold a value with the provided class
85    Required {
86        /// Default value
87        default: Option<ArgValue>,
88        /// Type of the value literal
89        class: ValueClass,
90    },
91
92    /// Argument or an attribute may or may not hold a value
93    Optional(ValueClass),
94
95    /// Argument or an attribute must not hold a value
96    Prohibited,
97}
98
99impl ArgValueReq {
100    /// Constructs argument requirements object with default value
101    pub fn with_default(default: impl Into<ArgValue>) -> ArgValueReq {
102        let value = default.into();
103        ArgValueReq::Required {
104            class: value
105                .value_class()
106                .expect("Default argument value must not be `ArgValue::None`"),
107            default: Some(value),
108        }
109    }
110
111    /// Construct [`ArgValueReq::Required`] variant with no default value
112    pub fn required(class: impl Into<ValueClass>) -> ArgValueReq {
113        ArgValueReq::Required {
114            default: None,
115            class: class.into(),
116        }
117    }
118
119    /// Construct [`ArgValueReq::Optional`] variant
120    pub fn optional(class: impl Into<ValueClass>) -> ArgValueReq {
121        ArgValueReq::Optional(class.into())
122    }
123
124    /// Returns value class requirements, if any
125    pub fn value_class(&self) -> Option<ValueClass> {
126        match self {
127            ArgValueReq::Required { class, .. } | ArgValueReq::Optional(class) => Some(*class),
128            ArgValueReq::Prohibited => None,
129        }
130    }
131
132    /// Returns default argument value. If not default is provided within the
133    /// requirement, returns [`ArgValue::None`] (since this is *de facto*
134    /// default value for any argument).
135    pub fn default_value(&self) -> ArgValue {
136        match self {
137            ArgValueReq::Required {
138                default: Some(d), ..
139            } => d.clone(),
140            _ => ArgValue::None,
141        }
142    }
143
144    /// Determines whether argument is required to have a value
145    pub fn is_required(&self) -> bool {
146        // Ancient rust versions do not known about `matches!` macro
147        #[allow(clippy::match_like_matches_macro)]
148        match self {
149            ArgValueReq::Required { default: None, .. } => true,
150            _ => false,
151        }
152    }
153
154    /// Checks the argument against current requirements, generating [`Error`]
155    /// if the requirements are not met.
156    pub fn check(
157        &self,
158        value: &mut ArgValue,
159        attr: impl ToString,
160        arg: impl ToString,
161    ) -> Result<(), Error> {
162        let value = match (value, self) {
163            (ref val, ArgValueReq::Required { default: None, .. }) if val.is_none() => {
164                return Err(Error::ArgValueRequired {
165                    attr: attr.to_string(),
166                    arg: arg.to_string(),
167                });
168            }
169
170            (ref val, ArgValueReq::Prohibited) if val.is_some() => {
171                return Err(Error::ArgMustNotHaveValue {
172                    attr: attr.to_string(),
173                    arg: arg.to_string(),
174                });
175            }
176
177            (
178                ref val,
179                ArgValueReq::Required {
180                    default: Some(d), ..
181                },
182            ) if val.value_class() != d.value_class() && val.value_class().is_some() => {
183                panic!(
184                    "Default value class {:?} does not match argument value class {:?} for \
185                     attribute {}, argument {}",
186                    d.value_class(),
187                    val.value_class(),
188                    attr.to_string(),
189                    arg.to_string()
190                );
191            }
192
193            (val, req) => {
194                if val.is_none() {
195                    if let ArgValueReq::Required {
196                        default: Some(d), ..
197                    } = req
198                    {
199                        *val = d.clone();
200                    }
201                }
202                val
203            }
204        };
205
206        if let Some(value_class) = self.value_class() {
207            value_class.check(value, attr, arg)?;
208        }
209
210        Ok(())
211    }
212}
213
214/// Requirements for attribute or named argument value presence for a values
215/// with known class. If the value class is not known, use [`ArgValueReq`]
216/// instead.
217#[derive(Clone)]
218pub enum ValueReq {
219    /// Argument or an attribute must hold a value
220    Required,
221
222    /// Argument or an attribute must hold a value; if the value is not present
223    /// it will be substituted for the default value provided as the inner field
224    Default(ArgValue),
225
226    /// Argument or an attribute may or may not hold a value
227    Optional,
228
229    /// Argument or an attribute must not hold a value
230    Prohibited,
231}
232
233impl ValueReq {
234    /// Detects if the presence of the value is required
235    #[inline]
236    pub fn is_required(&self) -> bool {
237        // Ancient rust versions do not known about `matches!` macro
238        #[allow(clippy::match_like_matches_macro)]
239        match self {
240            ValueReq::Required => true,
241            _ => false,
242        }
243    }
244
245    /// Checks the value against current requirements, generating [`Error`] if
246    /// the requirements are not met.
247    pub fn check<T>(
248        &self,
249        value: &mut T,
250        attr: impl ToString,
251        arg: impl ToString,
252    ) -> Result<(), Error>
253    where
254        T: Clone,
255        ArgValue: From<T> + TryInto<T>,
256        Error: From<<ArgValue as TryInto<T>>::Error>,
257    {
258        let attr = attr.to_string();
259        let arg_value = ArgValue::from(value.clone());
260        match (self, value) {
261            (ValueReq::Required, _) if arg_value.is_none() => Err(Error::ArgValueRequired {
262                attr,
263                arg: arg.to_string(),
264            }),
265            (ValueReq::Prohibited, _) if arg_value.is_some() => Err(Error::ArgMustNotHaveValue {
266                attr,
267                arg: arg.to_string(),
268            }),
269            (ValueReq::Default(ref val), ref mut v) if arg_value.is_none() => {
270                **v = val.clone().try_into()?;
271                Ok(())
272            }
273            _ => Ok(()),
274        }
275    }
276}
277
278/// Requirements for list elements. For instance, used in [`AttrReq`] for
279/// providing [`crate::ParametrizedAttr`] fields requirements.
280#[derive(Clone)]
281pub enum ListReq<T> {
282    /// Only a single value allowed and it must be present
283    Single {
284        /// Restricts set of possible values to the given whitelist
285        ///
286        /// NB: If whitelist does not contain values from the `default` field,
287        /// they are still accepted as valid, i.e. "automatically whitelisted"
288        whitelist: Option<Vec<T>>,
289
290        /// Default value assigned as a signe list item if no values are
291        /// provided
292        ///
293        /// NB: If whitelist does not contain values from the `default` field,
294        /// they are still accepted as valid, i.e. "automatically whitelisted"
295        default: Option<T>,
296    },
297
298    /// Any number of any elements may be present
299    Many {
300        /// Restricts set of possible values to the given whitelist
301        whitelist: Option<Vec<T>>,
302
303        /// Require that at least one value is present
304        required: bool,
305
306        /// Restricts the maximum number of items
307        max_no: Option<u8>,
308    },
309
310    /// Any number of any elements may not be present; if none of the elements
311    /// is present the list will use default vec of the values
312    Predefined {
313        /// Restricts set of possible values to the given whitelist.
314        ///
315        /// NB: If whitelist does not contain values from the `default` field,
316        /// they are still accepted as valid, i.e. "automatically whitelisted"
317        whitelist: Option<Vec<T>>,
318
319        /// Default set of values for the list used if no values are provided
320        ///
321        /// NB: If whitelist does not contain values from the `default` field,
322        /// they are still accepted as valid, i.e. "automatically whitelisted"
323        default: Vec<T>,
324    },
325
326    /// Element must not be present
327    Deny,
328}
329
330impl<T> ListReq<T> {
331    /// Convenience constructor for list requiring presence of an optional
332    /// single element.
333    pub fn maybe_one(name: T) -> Self {
334        ListReq::Many {
335            whitelist: Some(vec![name]),
336            required: false,
337            max_no: Some(1),
338        }
339    }
340
341    /// Convenience constructor for list requiring presence of a single element
342    /// from a list of possible values.
343    pub fn one_of(names: Vec<T>) -> Self {
344        ListReq::Single {
345            whitelist: Some(names),
346            default: None,
347        }
348    }
349
350    /// Convenience constructor for list requiring presence of multiple elements
351    /// from a list of possible values.
352    pub fn any_of(names: Vec<T>, required: bool) -> Self {
353        ListReq::Many {
354            whitelist: Some(names),
355            required,
356            max_no: None,
357        }
358    }
359}
360
361impl<T> ListReq<T>
362where T: Clone + ToTokens
363{
364    /// Checks the value against the list requirements, generating [`Error`] if
365    /// the requirements are not met.
366    pub fn check(
367        &self,
368        value: &mut Vec<T>,
369        attr: impl ToString,
370        arg: impl ToString,
371    ) -> Result<(), Error> {
372        match (self, value.len()) {
373            // Checking are we allowed to have a value
374            (ListReq::Deny, x) if x > 0 => {
375                return Err(Error::ArgTypeProhibited {
376                    attr: attr.to_string(),
377                    arg: arg.to_string(),
378                });
379            }
380
381            // Checking are we required to have a value while no value is available
382            (ListReq::Many { required: true, .. }, 0) |
383            (ListReq::Single { default: None, .. }, 0) => {
384                return Err(Error::ArgRequired {
385                    attr: attr.to_string(),
386                    arg: arg.to_string(),
387                });
388            }
389
390            // Checking not to the exceed maximally allowed number of values
391            (
392                ListReq::Many {
393                    max_no: Some(max_no),
394                    ..
395                },
396                no,
397            ) if no > *max_no as usize => {
398                return Err(Error::ArgNumberExceedsMax {
399                    attr: attr.to_string(),
400                    type_name: arg.to_string(),
401                    no,
402                    max_no: *max_no,
403                });
404            }
405
406            // Checking that arguments are matching whitelist
407            (
408                ListReq::Many {
409                    whitelist: Some(whitelist),
410                    ..
411                },
412                len,
413            ) |
414            (
415                ListReq::Predefined {
416                    whitelist: Some(whitelist),
417                    ..
418                },
419                len,
420            ) |
421            (
422                ListReq::Single {
423                    whitelist: Some(whitelist),
424                    ..
425                },
426                len,
427            ) if len > 0 => {
428                for item in value {
429                    if !whitelist.iter().any(|i| {
430                        i.to_token_stream().to_string() == item.to_token_stream().to_string()
431                    }) {
432                        return Err(Error::AttributeUnknownArgument {
433                            attr: attr.to_string(),
434                            arg: arg.to_string(),
435                        });
436                    }
437                }
438            }
439
440            // Defaulting if no value is provided
441            (
442                ListReq::Single {
443                    default: Some(d), ..
444                },
445                0,
446            ) => value.push(d.clone()),
447            (ListReq::Predefined { default, .. }, 0) => *value = default.clone(),
448
449            // Otherwise we are good
450            _ => {}
451        }
452        Ok(())
453    }
454}