substrait/parse/text/simple_extensions/
argument.rs

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