substrait/parse/text/simple_extensions/
argument.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Parsing of [simple_extensions::ArgumentsItem].
4
5use std::{collections::HashSet, ops::Deref};
6
7use thiserror::Error;
8
9use crate::{
10    parse::{Context, Parse},
11    text::simple_extensions,
12};
13
14/// A parsed [simple_extensions::ArgumentsItem].
15#[derive(Clone, Debug)]
16pub enum ArgumentsItem {
17    /// Arguments that support a fixed set of declared values as constant arguments.
18    EnumArgument(EnumerationArg),
19
20    /// Arguments that refer to a data value.
21    ValueArgument(ValueArg),
22
23    /// Arguments that are used only to inform the evaluation and/or type derivation of the function.
24    TypeArgument(TypeArg),
25}
26
27impl ArgumentsItem {
28    /// Parses an `Option<String>` field, rejecting it if an empty string is provided.
29    #[inline]
30    fn parse_optional_string(
31        name: &str,
32        value: Option<String>,
33    ) -> Result<Option<String>, ArgumentsItemError> {
34        match value {
35            Some(s) if s.is_empty() => {
36                Err(ArgumentsItemError::EmptyOptionalField(name.to_string()))
37            }
38            _ => Ok(value),
39        }
40    }
41
42    #[inline]
43    fn parse_name(name: Option<String>) -> Result<Option<String>, ArgumentsItemError> {
44        ArgumentsItem::parse_optional_string("name", name)
45    }
46
47    #[inline]
48    fn parse_description(
49        description: Option<String>,
50    ) -> Result<Option<String>, ArgumentsItemError> {
51        ArgumentsItem::parse_optional_string("description", description)
52    }
53}
54
55impl<C: Context> Parse<C> for simple_extensions::ArgumentsItem {
56    type Parsed = ArgumentsItem;
57    type Error = ArgumentsItemError;
58
59    fn parse(self, ctx: &mut C) -> Result<Self::Parsed, Self::Error> {
60        match self {
61            simple_extensions::ArgumentsItem::EnumerationArg(arg) => Ok(ctx.parse(arg)?.into()),
62            simple_extensions::ArgumentsItem::ValueArg(arg) => Ok(ctx.parse(arg)?.into()),
63            simple_extensions::ArgumentsItem::TypeArg(arg) => Ok(ctx.parse(arg)?.into()),
64        }
65    }
66}
67
68impl From<ArgumentsItem> for simple_extensions::ArgumentsItem {
69    fn from(value: ArgumentsItem) -> Self {
70        match value {
71            ArgumentsItem::EnumArgument(arg) => arg.into(),
72            ArgumentsItem::ValueArgument(arg) => arg.into(),
73            ArgumentsItem::TypeArgument(arg) => arg.into(),
74        }
75    }
76}
77
78/// Parse errors for [simple_extensions::ArgumentsItem].
79#[derive(Debug, Error, PartialEq)]
80pub enum ArgumentsItemError {
81    /// Invalid enumeration options.
82    #[error("invalid enumeration options: {0}")]
83    InvalidEnumOptions(#[from] EnumOptionsError),
84
85    /// Empty optional field.
86    #[error("the optional field `{0}` is empty and should be removed")]
87    EmptyOptionalField(String),
88}
89
90/// Arguments that support a fixed set of declared values as constant arguments.
91#[derive(Clone, Debug, PartialEq)]
92pub struct EnumerationArg {
93    /// A human-readable name for this argument to help clarify use.
94    name: Option<String>,
95
96    /// Additional description for this argument.
97    description: Option<String>,
98
99    /// Set of valid string options for this argument.
100    options: EnumOptions,
101}
102
103impl EnumerationArg {
104    /// Returns the name of this argument.
105    ///
106    /// See [simple_extensions::EnumerationArg::name].
107    pub fn name(&self) -> Option<&String> {
108        self.name.as_ref()
109    }
110
111    /// Returns the description of this argument.
112    ///
113    /// See [simple_extensions::EnumerationArg::description].
114    pub fn description(&self) -> Option<&String> {
115        self.description.as_ref()
116    }
117
118    /// Returns the options of this argument.
119    ///
120    /// See [simple_extensions::EnumerationArg::options].
121    pub fn options(&self) -> &EnumOptions {
122        &self.options
123    }
124}
125
126impl<C: Context> Parse<C> for simple_extensions::EnumerationArg {
127    type Parsed = EnumerationArg;
128
129    type Error = ArgumentsItemError;
130
131    fn parse(self, ctx: &mut C) -> Result<EnumerationArg, ArgumentsItemError> {
132        Ok(EnumerationArg {
133            name: ArgumentsItem::parse_name(self.name)?,
134            description: ArgumentsItem::parse_description(self.description)?,
135            options: ctx.parse(self.options)?,
136        })
137    }
138}
139
140impl From<EnumerationArg> for simple_extensions::EnumerationArg {
141    fn from(value: EnumerationArg) -> Self {
142        simple_extensions::EnumerationArg {
143            name: value.name,
144            description: value.description,
145            options: value.options.into(),
146        }
147    }
148}
149
150impl From<EnumerationArg> for simple_extensions::ArgumentsItem {
151    fn from(value: EnumerationArg) -> Self {
152        simple_extensions::ArgumentsItem::EnumerationArg(value.into())
153    }
154}
155
156impl From<EnumerationArg> for ArgumentsItem {
157    fn from(value: EnumerationArg) -> Self {
158        ArgumentsItem::EnumArgument(value)
159    }
160}
161
162/// Set of valid string options
163#[derive(Clone, Debug, PartialEq)]
164pub struct EnumOptions(HashSet<String>);
165
166impl Deref for EnumOptions {
167    type Target = HashSet<String>;
168
169    fn deref(&self) -> &Self::Target {
170        &self.0
171    }
172}
173
174impl<C: Context> Parse<C> for simple_extensions::EnumOptions {
175    type Parsed = EnumOptions;
176
177    type Error = EnumOptionsError;
178
179    fn parse(self, _ctx: &mut C) -> Result<EnumOptions, EnumOptionsError> {
180        let options = self.0;
181        if options.is_empty() {
182            return Err(EnumOptionsError::EmptyList);
183        }
184
185        let mut unique_options = HashSet::new();
186        for option in options.iter() {
187            if option.is_empty() {
188                return Err(EnumOptionsError::EmptyOption);
189            }
190            if !unique_options.insert(option.clone()) {
191                return Err(EnumOptionsError::DuplicatedOption(option.clone()));
192            }
193        }
194
195        Ok(EnumOptions(unique_options))
196    }
197}
198
199impl From<EnumOptions> for simple_extensions::EnumOptions {
200    fn from(value: EnumOptions) -> Self {
201        simple_extensions::EnumOptions(Vec::from_iter(value.0))
202    }
203}
204
205/// Parse errors for [simple_extensions::EnumOptions].
206#[derive(Debug, Error, PartialEq)]
207pub enum EnumOptionsError {
208    /// Empty list.
209    #[error("empty list")]
210    EmptyList,
211
212    /// Duplicated option.
213    #[error("duplicated option: {0}")]
214    DuplicatedOption(String),
215
216    /// Empty option.
217    #[error("empty option")]
218    EmptyOption,
219}
220
221/// Arguments that refer to a data value.
222#[derive(Clone, Debug)]
223pub struct ValueArg {
224    /// A human-readable name for this argument to help clarify use.
225    name: Option<String>,
226
227    /// Additional description for this argument.
228    description: Option<String>,
229
230    /// A fully defined type or a type expression.
231    ///
232    /// todo: implement parsed [simple_extensions::Type].
233    value: simple_extensions::Type,
234
235    /// Whether this argument is required to be a constant for invocation.
236    /// For example, in some system a regular expression pattern would only be accepted as a literal
237    /// and not a column value reference.
238    constant: Option<bool>,
239}
240
241impl ValueArg {
242    /// Returns the name of this argument.
243    ///
244    /// See [simple_extensions::ValueArg::name].
245    pub fn name(&self) -> Option<&String> {
246        self.name.as_ref()
247    }
248
249    /// Returns the description of this argument.
250    ///
251    /// See [simple_extensions::ValueArg::description].
252    pub fn description(&self) -> Option<&String> {
253        self.description.as_ref()
254    }
255
256    /// Returns the constant of this argument.
257    /// Defaults to `false` if the underlying value is `None`.
258    ///
259    /// See [simple_extensions::ValueArg::constant].
260    pub fn constant(&self) -> bool {
261        self.constant.unwrap_or(false)
262    }
263}
264
265impl<C: Context> Parse<C> for simple_extensions::ValueArg {
266    type Parsed = ValueArg;
267
268    type Error = ArgumentsItemError;
269
270    fn parse(self, _ctx: &mut C) -> Result<ValueArg, ArgumentsItemError> {
271        Ok(ValueArg {
272            name: ArgumentsItem::parse_name(self.name)?,
273            description: ArgumentsItem::parse_description(self.description)?,
274            value: self.value,
275            constant: self.constant,
276        })
277    }
278}
279
280impl From<ValueArg> for simple_extensions::ValueArg {
281    fn from(value: ValueArg) -> Self {
282        simple_extensions::ValueArg {
283            name: value.name,
284            description: value.description,
285            value: value.value,
286            constant: value.constant,
287        }
288    }
289}
290
291impl From<ValueArg> for simple_extensions::ArgumentsItem {
292    fn from(value: ValueArg) -> Self {
293        simple_extensions::ArgumentsItem::ValueArg(value.into())
294    }
295}
296
297impl From<ValueArg> for ArgumentsItem {
298    fn from(value: ValueArg) -> Self {
299        ArgumentsItem::ValueArgument(value)
300    }
301}
302
303/// Arguments that are used only to inform the evaluation and/or type derivation of the function.
304#[derive(Clone, Debug, PartialEq)]
305pub struct TypeArg {
306    /// A human-readable name for this argument to help clarify use.
307    name: Option<String>,
308
309    /// Additional description for this argument.
310    description: Option<String>,
311
312    /// A partially or completely parameterized type. E.g. `List<K>` or `K`.
313    ///
314    /// todo: implement parsed [simple_extensions::Type].
315    type_: String,
316}
317
318impl TypeArg {
319    /// Returns the name of this argument.
320    ///
321    /// See [simple_extensions::TypeArg::name].
322    pub fn name(&self) -> Option<&String> {
323        self.name.as_ref()
324    }
325
326    /// Returns the description of this argument.
327    ///
328    /// See [simple_extensions::TypeArg::description].
329    pub fn description(&self) -> Option<&String> {
330        self.description.as_ref()
331    }
332}
333
334impl<C: Context> Parse<C> for simple_extensions::TypeArg {
335    type Parsed = TypeArg;
336
337    type Error = ArgumentsItemError;
338
339    fn parse(self, _ctx: &mut C) -> Result<TypeArg, ArgumentsItemError> {
340        Ok(TypeArg {
341            name: ArgumentsItem::parse_name(self.name)?,
342            description: ArgumentsItem::parse_description(self.description)?,
343            type_: self.type_,
344        })
345    }
346}
347
348impl From<TypeArg> for simple_extensions::TypeArg {
349    fn from(value: TypeArg) -> Self {
350        simple_extensions::TypeArg {
351            name: value.name,
352            description: value.description,
353            type_: value.type_,
354        }
355    }
356}
357
358impl From<TypeArg> for simple_extensions::ArgumentsItem {
359    fn from(value: TypeArg) -> Self {
360        simple_extensions::ArgumentsItem::TypeArg(value.into())
361    }
362}
363
364impl From<TypeArg> for ArgumentsItem {
365    fn from(value: TypeArg) -> Self {
366        ArgumentsItem::TypeArgument(value)
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373    use crate::text::simple_extensions;
374    use crate::{parse::context::tests::Context, text};
375
376    #[test]
377    fn parse_enum_argument() -> Result<(), ArgumentsItemError> {
378        let enum_argument =
379            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
380                name: Some("arg".to_string()),
381                description: Some("desc".to_string()),
382                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
383            });
384        let item = enum_argument.parse(&mut Context::default())?;
385        let enum_argument = match item {
386            ArgumentsItem::EnumArgument(enum_argument) => enum_argument,
387            _ => unreachable!(),
388        };
389        assert_eq!(
390            enum_argument,
391            EnumerationArg {
392                name: Some("arg".to_string()),
393                description: Some("desc".to_string()),
394                options: EnumOptions(HashSet::from(["OVERFLOW".to_string()])),
395            }
396        );
397        Ok(())
398    }
399
400    #[test]
401    fn parse_empty_enum_options() -> Result<(), ArgumentsItemError> {
402        let options = simple_extensions::EnumOptions(vec![]);
403        let is_err = options
404            .parse(&mut Context::default())
405            .err()
406            .map(|err| matches!(err, EnumOptionsError::EmptyList));
407        assert_eq!(is_err, Some(true));
408        Ok(())
409    }
410
411    #[test]
412    fn parse_enum_options_with_empty_value() -> Result<(), ArgumentsItemError> {
413        let options = simple_extensions::EnumOptions(vec!["".to_string()]);
414        let is_err = options
415            .parse(&mut Context::default())
416            .err()
417            .map(|err| matches!(err, EnumOptionsError::EmptyOption));
418        assert_eq!(is_err, Some(true));
419        Ok(())
420    }
421
422    #[test]
423    fn parse_enum_argument_with_duplicated_option() -> Result<(), ArgumentsItemError> {
424        let options =
425            simple_extensions::EnumOptions(vec!["OVERFLOW".to_string(), "OVERFLOW".to_string()]);
426        let is_err = options
427            .clone()
428            .parse(&mut Context::default())
429            .err()
430            .map(|err| {
431                matches!(
432                    err,
433                    EnumOptionsError::DuplicatedOption(opt) if opt == "OVERFLOW"
434                )
435            });
436        assert_eq!(is_err, Some(true));
437        Ok(())
438    }
439
440    #[test]
441    fn parse_value_argument() -> Result<(), ArgumentsItemError> {
442        let item = simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
443            name: Some("arg".to_string()),
444            description: Some("desc".to_string()),
445            value: text::simple_extensions::Type::Variant0("i32".to_string()),
446            constant: Some(true),
447        });
448        let item = item.parse(&mut Context::default())?;
449        match item {
450            ArgumentsItem::ValueArgument(ValueArg {
451                name,
452                description,
453                value,
454                constant,
455            }) => {
456                assert_eq!(name, Some("arg".to_string()));
457                assert_eq!(description, Some("desc".to_string()));
458                assert!(
459                    matches!(value, text::simple_extensions::Type::Variant0(type_) if type_ == "i32")
460                );
461                assert_eq!(constant, Some(true));
462            }
463            _ => unreachable!(),
464        };
465        Ok(())
466    }
467
468    #[test]
469    fn parse_type_argument() -> Result<(), ArgumentsItemError> {
470        let type_argument = simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
471            name: Some("arg".to_string()),
472            description: Some("desc".to_string()),
473            type_: "".to_string(),
474        });
475        let item = type_argument.parse(&mut Context::default())?;
476        match item {
477            ArgumentsItem::TypeArgument(TypeArg {
478                name,
479                description,
480                type_,
481            }) => {
482                assert_eq!(name, Some("arg".to_string()));
483                assert_eq!(description, Some("desc".to_string()));
484                assert_eq!(type_, "");
485            }
486            _ => unreachable!(),
487        };
488        Ok(())
489    }
490
491    #[test]
492    fn parse_argument_with_nones() -> Result<(), ArgumentsItemError> {
493        let items = vec![
494            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
495                name: None,
496                description: None,
497                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
498            }),
499            simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
500                name: None,
501                description: None,
502                value: text::simple_extensions::Type::Variant0("i32".to_string()),
503                constant: None,
504            }),
505            simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
506                name: None,
507                description: None,
508                type_: "".to_string(),
509            }),
510        ];
511
512        for item in items {
513            let item = item.parse(&mut Context::default())?;
514            let (name, description) = match item {
515                ArgumentsItem::EnumArgument(EnumerationArg {
516                    name, description, ..
517                }) => (name, description),
518
519                ArgumentsItem::ValueArgument(ValueArg {
520                    name,
521                    description,
522                    constant,
523                    ..
524                }) => {
525                    assert!(constant.is_none());
526                    (name, description)
527                }
528
529                ArgumentsItem::TypeArgument(TypeArg {
530                    name, description, ..
531                }) => (name, description),
532            };
533            assert!(name.is_none());
534            assert!(description.is_none());
535        }
536
537        Ok(())
538    }
539
540    #[test]
541    fn parse_argument_with_empty_fields() -> Result<(), ArgumentsItemError> {
542        let items = vec![
543            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
544                name: Some("".to_string()),
545                description: None,
546                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
547            }),
548            simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
549                name: Some("".to_string()),
550                description: None,
551                value: text::simple_extensions::Type::Variant0("i32".to_string()),
552                constant: None,
553            }),
554            simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
555                name: Some("".to_string()),
556                description: None,
557                type_: "".to_string(),
558            }),
559        ];
560        for item in items {
561            assert_eq!(
562                item.parse(&mut Context::default()).err(),
563                Some(ArgumentsItemError::EmptyOptionalField("name".to_string()))
564            );
565        }
566
567        let items = vec![
568            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
569                name: None,
570                description: Some("".to_string()),
571                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
572            }),
573            simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
574                name: None,
575                description: Some("".to_string()),
576                value: text::simple_extensions::Type::Variant0("i32".to_string()),
577                constant: None,
578            }),
579            simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
580                name: None,
581                description: Some("".to_string()),
582                type_: "".to_string(),
583            }),
584        ];
585        for item in items {
586            assert_eq!(
587                item.parse(&mut Context::default()).err(),
588                Some(ArgumentsItemError::EmptyOptionalField(
589                    "description".to_string()
590                ))
591            );
592        }
593
594        Ok(())
595    }
596
597    #[test]
598    fn from_enum_argument() {
599        let item: ArgumentsItem = EnumerationArg {
600            name: Some("arg".to_string()),
601            description: Some("desc".to_string()),
602            options: EnumOptions(HashSet::from(["OVERFLOW".to_string()])),
603        }
604        .into();
605
606        let item: text::simple_extensions::ArgumentsItem = item.into();
607        match item {
608            text::simple_extensions::ArgumentsItem::EnumerationArg(
609                simple_extensions::EnumerationArg {
610                    name,
611                    description,
612                    options,
613                },
614            ) => {
615                assert_eq!(name, Some("arg".to_string()));
616                assert_eq!(description, Some("desc".to_string()));
617                assert_eq!(options.0, vec!["OVERFLOW".to_string()]);
618            }
619            _ => unreachable!(),
620        }
621    }
622
623    #[test]
624    fn from_value_argument() {
625        let item: ArgumentsItem = ValueArg {
626            name: Some("arg".to_string()),
627            description: Some("desc".to_string()),
628            value: text::simple_extensions::Type::Variant0("".to_string()),
629            constant: Some(true),
630        }
631        .into();
632
633        let item: text::simple_extensions::ArgumentsItem = item.into();
634        match item {
635            text::simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
636                name,
637                description,
638                value,
639                constant,
640            }) => {
641                assert_eq!(name, Some("arg".to_string()));
642                assert_eq!(description, Some("desc".to_string()));
643                assert!(
644                    matches!(value, text::simple_extensions::Type::Variant0(type_) if type_.is_empty())
645                );
646                assert_eq!(constant, Some(true));
647            }
648            _ => unreachable!(),
649        }
650    }
651
652    #[test]
653    fn from_type_argument() {
654        let item: ArgumentsItem = TypeArg {
655            name: Some("arg".to_string()),
656            description: Some("desc".to_string()),
657            type_: "".to_string(),
658        }
659        .into();
660
661        let item: text::simple_extensions::ArgumentsItem = item.into();
662        match item {
663            text::simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
664                name,
665                description,
666                type_,
667            }) => {
668                assert_eq!(name, Some("arg".to_string()));
669                assert_eq!(description, Some("desc".to_string()));
670                assert_eq!(type_, "");
671            }
672            _ => unreachable!(),
673        }
674    }
675
676    #[cfg(feature = "extensions")]
677    #[test]
678    fn parse_extensions() {
679        use crate::extensions::EXTENSIONS;
680        use crate::parse::context::tests::Context;
681
682        macro_rules! parse_arguments {
683            ($url:expr, $fns:expr) => {
684                $fns.iter().for_each(|f| {
685                    f.impls
686                        .iter()
687                        .filter_map(|i| i.args.as_ref())
688                        .flat_map(|a| &a.0)
689                        .for_each(|item| {
690                            item.clone()
691                                .parse(&mut Context::default())
692                                .unwrap_or_else(|err| {
693                                    panic!(
694                                        "found an invalid argument: {}, (url: {}, function: {}, arg: {:?})",
695                                        err,
696                                        $url.to_string(),
697                                        f.name,
698                                        item
699                                    );
700                                });
701                        })
702                });
703            };
704        }
705
706        EXTENSIONS.iter().for_each(|(url, ext)| {
707            parse_arguments!(url, ext.scalar_functions);
708            parse_arguments!(url, ext.aggregate_functions);
709            parse_arguments!(url, ext.window_functions);
710        });
711    }
712}