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}