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, PartialEq)]
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, PartialEq)]
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(#452): parse this to a typed representation once type variables are supported.
244    value: simple_extensions::Type,
245
246    /// Whether this argument is required to be a constant for invocation. For
247    /// example, in some system a regular expression pattern would only be
248    /// accepted as a literal and not a column value reference.
249    constant: Option<bool>,
250}
251
252impl ValueArg {
253    /// Returns the name of this argument.
254    ///
255    /// See [`simple_extensions::ValueArg::name`].
256    pub fn name(&self) -> Option<&String> {
257        self.name.as_ref()
258    }
259
260    /// Returns the description of this argument.
261    ///
262    /// See [`simple_extensions::ValueArg::description`].
263    pub fn description(&self) -> Option<&String> {
264        self.description.as_ref()
265    }
266
267    /// Returns the constant of this argument.
268    /// Defaults to `false` if the underlying value is `None`.
269    ///
270    /// See [`simple_extensions::ValueArg::constant`].
271    pub fn constant(&self) -> bool {
272        self.constant.unwrap_or(false)
273    }
274}
275
276impl<C: Context> Parse<C> for simple_extensions::ValueArg {
277    type Parsed = ValueArg;
278
279    type Error = ArgumentsItemError;
280
281    fn parse(self, _ctx: &mut C) -> Result<ValueArg, ArgumentsItemError> {
282        Ok(ValueArg {
283            name: ArgumentsItem::parse_name(self.name)?,
284            description: ArgumentsItem::parse_description(self.description)?,
285            value: self.value,
286            constant: self.constant,
287        })
288    }
289}
290
291impl From<ValueArg> for simple_extensions::ValueArg {
292    fn from(value: ValueArg) -> Self {
293        simple_extensions::ValueArg {
294            name: value.name,
295            description: value.description,
296            value: value.value,
297            constant: value.constant,
298        }
299    }
300}
301
302impl From<ValueArg> for simple_extensions::ArgumentsItem {
303    fn from(value: ValueArg) -> Self {
304        simple_extensions::ArgumentsItem::ValueArg(value.into())
305    }
306}
307
308impl From<ValueArg> for ArgumentsItem {
309    fn from(value: ValueArg) -> Self {
310        ArgumentsItem::ValueArgument(value)
311    }
312}
313
314/// A type argument to a parameterized type, e.g. the `T` in `List<T>`.
315#[derive(Clone, Debug, PartialEq)]
316pub struct TypeArg {
317    /// A human-readable name for this argument to clarify use.
318    name: Option<String>,
319
320    /// Additional description for this argument.
321    description: Option<String>,
322
323    /// A partially or completely parameterized type. E.g. `List<K>` or `K`.
324    ///
325    /// TODO(#452): parse this to a typed representation once type variables are supported.
326    type_: String,
327}
328
329impl TypeArg {
330    /// Returns the name of this argument.
331    ///
332    /// See [`simple_extensions::TypeArg::name`].
333    pub fn name(&self) -> Option<&String> {
334        self.name.as_ref()
335    }
336
337    /// Returns the description of this argument.
338    ///
339    /// See [`simple_extensions::TypeArg::description`].
340    pub fn description(&self) -> Option<&String> {
341        self.description.as_ref()
342    }
343}
344
345impl<C: Context> Parse<C> for simple_extensions::TypeArg {
346    type Parsed = TypeArg;
347
348    type Error = ArgumentsItemError;
349
350    fn parse(self, _ctx: &mut C) -> Result<TypeArg, ArgumentsItemError> {
351        Ok(TypeArg {
352            name: ArgumentsItem::parse_name(self.name)?,
353            description: ArgumentsItem::parse_description(self.description)?,
354            type_: self.type_,
355        })
356    }
357}
358
359impl From<TypeArg> for simple_extensions::TypeArg {
360    fn from(value: TypeArg) -> Self {
361        simple_extensions::TypeArg {
362            name: value.name,
363            description: value.description,
364            type_: value.type_,
365        }
366    }
367}
368
369impl From<TypeArg> for simple_extensions::ArgumentsItem {
370    fn from(value: TypeArg) -> Self {
371        simple_extensions::ArgumentsItem::TypeArg(value.into())
372    }
373}
374
375impl From<TypeArg> for ArgumentsItem {
376    fn from(value: TypeArg) -> Self {
377        ArgumentsItem::TypeArgument(value)
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use crate::text::simple_extensions;
385    use crate::{parse::Context, text};
386
387    /// A test context for parsing simple extensions. These extensions do not need any additional context,
388    pub struct TestContext;
389
390    impl Context for TestContext {}
391
392    #[test]
393    fn parse_enum_argument() -> Result<(), ArgumentsItemError> {
394        let enum_argument =
395            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
396                name: Some("arg".to_string()),
397                description: Some("desc".to_string()),
398                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
399            });
400        let item = enum_argument.parse(&mut TestContext)?;
401        let enum_argument = match item {
402            ArgumentsItem::EnumArgument(enum_argument) => enum_argument,
403            _ => unreachable!(),
404        };
405        assert_eq!(
406            enum_argument,
407            EnumerationArg {
408                name: Some("arg".to_string()),
409                description: Some("desc".to_string()),
410                options: EnumOptions(HashSet::from(["OVERFLOW".to_string()])),
411            }
412        );
413        Ok(())
414    }
415
416    #[test]
417    fn parse_empty_enum_options() -> Result<(), ArgumentsItemError> {
418        let options = simple_extensions::EnumOptions(vec![]);
419        let is_err = options
420            .parse(&mut TestContext)
421            .err()
422            .map(|err| matches!(err, EnumOptionsError::EmptyList));
423        assert_eq!(is_err, Some(true));
424        Ok(())
425    }
426
427    #[test]
428    fn parse_enum_options_with_empty_value() -> Result<(), ArgumentsItemError> {
429        let options = simple_extensions::EnumOptions(vec!["".to_string()]);
430        let is_err = options
431            .parse(&mut TestContext)
432            .err()
433            .map(|err| matches!(err, EnumOptionsError::EmptyOption));
434        assert_eq!(is_err, Some(true));
435        Ok(())
436    }
437
438    #[test]
439    fn parse_enum_argument_with_duplicated_option() -> Result<(), ArgumentsItemError> {
440        let options =
441            simple_extensions::EnumOptions(vec!["OVERFLOW".to_string(), "OVERFLOW".to_string()]);
442        let is_err = options.clone().parse(&mut TestContext).err().map(|err| {
443            matches!(
444                err,
445                EnumOptionsError::DuplicatedOption(opt) if opt == "OVERFLOW"
446            )
447        });
448        assert_eq!(is_err, Some(true));
449        Ok(())
450    }
451
452    #[test]
453    fn parse_value_argument() -> Result<(), ArgumentsItemError> {
454        let item = simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
455            name: Some("arg".to_string()),
456            description: Some("desc".to_string()),
457            value: text::simple_extensions::Type::String("i32".to_string()),
458            constant: Some(true),
459        });
460        let item = item.parse(&mut TestContext)?;
461        match item {
462            ArgumentsItem::ValueArgument(ValueArg {
463                name,
464                description,
465                value,
466                constant,
467            }) => {
468                assert_eq!(name, Some("arg".to_string()));
469                assert_eq!(description, Some("desc".to_string()));
470                assert!(
471                    matches!(value, text::simple_extensions::Type::String(type_) if type_ == "i32")
472                );
473                assert_eq!(constant, Some(true));
474            }
475            _ => unreachable!(),
476        };
477        Ok(())
478    }
479
480    #[test]
481    fn parse_type_argument() -> Result<(), ArgumentsItemError> {
482        let type_argument = simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
483            name: Some("arg".to_string()),
484            description: Some("desc".to_string()),
485            type_: "".to_string(),
486        });
487        let item = type_argument.parse(&mut TestContext)?;
488        match item {
489            ArgumentsItem::TypeArgument(TypeArg {
490                name,
491                description,
492                type_,
493            }) => {
494                assert_eq!(name, Some("arg".to_string()));
495                assert_eq!(description, Some("desc".to_string()));
496                assert_eq!(type_, "");
497            }
498            _ => unreachable!(),
499        };
500        Ok(())
501    }
502
503    #[test]
504    fn parse_argument_with_nones() -> Result<(), ArgumentsItemError> {
505        let items = vec![
506            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
507                name: None,
508                description: None,
509                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
510            }),
511            simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
512                name: None,
513                description: None,
514                value: text::simple_extensions::Type::String("i32".to_string()),
515                constant: None,
516            }),
517            simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
518                name: None,
519                description: None,
520                type_: "".to_string(),
521            }),
522        ];
523
524        for item in items {
525            let item = item.parse(&mut TestContext)?;
526            let (name, description) = match item {
527                ArgumentsItem::EnumArgument(EnumerationArg {
528                    name, description, ..
529                }) => (name, description),
530
531                ArgumentsItem::ValueArgument(ValueArg {
532                    name,
533                    description,
534                    constant,
535                    ..
536                }) => {
537                    assert!(constant.is_none());
538                    (name, description)
539                }
540
541                ArgumentsItem::TypeArgument(TypeArg {
542                    name, description, ..
543                }) => (name, description),
544            };
545            assert!(name.is_none());
546            assert!(description.is_none());
547        }
548
549        Ok(())
550    }
551
552    #[test]
553    fn parse_argument_with_empty_fields() -> Result<(), ArgumentsItemError> {
554        let items = vec![
555            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
556                name: Some("".to_string()),
557                description: None,
558                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
559            }),
560            simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
561                name: Some("".to_string()),
562                description: None,
563                value: text::simple_extensions::Type::String("i32".to_string()),
564                constant: None,
565            }),
566            simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
567                name: Some("".to_string()),
568                description: None,
569                type_: "".to_string(),
570            }),
571        ];
572        for item in items {
573            assert_eq!(
574                item.parse(&mut TestContext).err(),
575                Some(ArgumentsItemError::EmptyOptionalField("name".to_string()))
576            );
577        }
578
579        let items = vec![
580            simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg {
581                name: None,
582                description: Some("".to_string()),
583                options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]),
584            }),
585            simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
586                name: None,
587                description: Some("".to_string()),
588                value: text::simple_extensions::Type::String("i32".to_string()),
589                constant: None,
590            }),
591            simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
592                name: None,
593                description: Some("".to_string()),
594                type_: "".to_string(),
595            }),
596        ];
597        for item in items {
598            assert_eq!(
599                item.parse(&mut TestContext).err(),
600                Some(ArgumentsItemError::EmptyOptionalField(
601                    "description".to_string()
602                ))
603            );
604        }
605
606        Ok(())
607    }
608
609    #[test]
610    fn from_enum_argument() {
611        let item: ArgumentsItem = EnumerationArg {
612            name: Some("arg".to_string()),
613            description: Some("desc".to_string()),
614            options: EnumOptions(HashSet::from(["OVERFLOW".to_string()])),
615        }
616        .into();
617
618        let item: text::simple_extensions::ArgumentsItem = item.into();
619        match item {
620            text::simple_extensions::ArgumentsItem::EnumerationArg(
621                simple_extensions::EnumerationArg {
622                    name,
623                    description,
624                    options,
625                },
626            ) => {
627                assert_eq!(name, Some("arg".to_string()));
628                assert_eq!(description, Some("desc".to_string()));
629                assert_eq!(options.0, vec!["OVERFLOW".to_string()]);
630            }
631            _ => unreachable!(),
632        }
633    }
634
635    #[test]
636    fn from_value_argument() {
637        let item: ArgumentsItem = ValueArg {
638            name: Some("arg".to_string()),
639            description: Some("desc".to_string()),
640            value: text::simple_extensions::Type::String("".to_string()),
641            constant: Some(true),
642        }
643        .into();
644
645        let item: text::simple_extensions::ArgumentsItem = item.into();
646        match item {
647            text::simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg {
648                name,
649                description,
650                value,
651                constant,
652            }) => {
653                assert_eq!(name, Some("arg".to_string()));
654                assert_eq!(description, Some("desc".to_string()));
655                assert!(
656                    matches!(value, text::simple_extensions::Type::String(type_) if type_.is_empty())
657                );
658                assert_eq!(constant, Some(true));
659            }
660            _ => unreachable!(),
661        }
662    }
663
664    #[test]
665    fn from_type_argument() {
666        let item: ArgumentsItem = TypeArg {
667            name: Some("arg".to_string()),
668            description: Some("desc".to_string()),
669            type_: "".to_string(),
670        }
671        .into();
672
673        let item: text::simple_extensions::ArgumentsItem = item.into();
674        match item {
675            text::simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg {
676                name,
677                description,
678                type_,
679            }) => {
680                assert_eq!(name, Some("arg".to_string()));
681                assert_eq!(description, Some("desc".to_string()));
682                assert_eq!(type_, "");
683            }
684            _ => unreachable!(),
685        }
686    }
687
688    #[cfg(feature = "extensions")]
689    #[test]
690    fn parse_extensions() {
691        use crate::extensions::EXTENSIONS;
692
693        macro_rules! parse_arguments {
694            ($url:expr, $fns:expr) => {
695                $fns.iter().for_each(|f| {
696                    f.impls
697                        .iter()
698                        .filter_map(|i| i.args.as_ref())
699                        .flat_map(|a| &a.0)
700                        .for_each(|item| {
701                            item.clone()
702                                .parse(&mut TestContext)
703                                .unwrap_or_else(|err| {
704                                    panic!(
705                                        "found an invalid argument: {}, (url: {}, function: {}, arg: {:?})",
706                                        err,
707                                        $url.to_string(),
708                                        f.name,
709                                        item
710                                    );
711                                });
712                        })
713                });
714            };
715        }
716
717        EXTENSIONS.iter().for_each(|(url, ext)| {
718            parse_arguments!(url, ext.scalar_functions);
719            parse_arguments!(url, ext.aggregate_functions);
720            parse_arguments!(url, ext.window_functions);
721        });
722    }
723}