substrait/parse/
context.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! A parse context.
4
5use thiserror::Error;
6
7use crate::parse::{
8    proto::extensions::SimpleExtensionUri, text::simple_extensions::SimpleExtensions, Anchor, Parse,
9};
10
11/// A parse context.
12///
13/// Parsing Substrait data is context-sensitive. This trait provides methods
14/// that can be used by parser implementations to parse Substrait data.
15pub trait Context {
16    /// Parse an item with this context.
17    ///
18    /// See [Parse::parse].
19    fn parse<T: Parse<Self>>(&mut self, item: T) -> Result<T::Parsed, T::Error>
20    where
21        Self: Sized,
22    {
23        item.parse(self)
24    }
25
26    /// Add a [SimpleExtensionUri] to this context. Must return an error for duplicate
27    /// anchors or when the URI is not supported.
28    ///
29    /// This function must eagerly resolve and parse the simple extension, returning an
30    /// error if either fails.
31    fn add_simple_extension_uri(
32        &mut self,
33        simple_extension_uri: &SimpleExtensionUri,
34    ) -> Result<&SimpleExtensions, ContextError>;
35
36    /// Returns the simple extensions for the given simple extension anchor.
37    fn simple_extensions(
38        &self,
39        anchor: &Anchor<SimpleExtensionUri>,
40    ) -> Result<&SimpleExtensions, ContextError>;
41}
42
43/// Parse context errors.
44#[derive(Debug, Error, PartialEq)]
45pub enum ContextError {
46    /// Undefined reference to simple extension.
47    #[error("undefined reference to simple extension with anchor `{0}`")]
48    UndefinedSimpleExtension(Anchor<SimpleExtensionUri>),
49
50    /// Duplicate anchor for simple extension.
51    #[error("duplicate anchor `{0}` for simple extension")]
52    DuplicateSimpleExtension(Anchor<SimpleExtensionUri>),
53
54    /// Unsupported simple extension URI.
55    #[error("unsupported simple extension URI: {0}")]
56    UnsupportedURI(String),
57}
58
59#[cfg(test)]
60pub(crate) mod tests {
61    use std::collections::{hash_map::Entry, HashMap};
62
63    use crate::parse::{
64        context::ContextError, proto::extensions::SimpleExtensionUri,
65        text::simple_extensions::SimpleExtensions, Anchor,
66    };
67
68    /// A test context.
69    ///
70    /// This currently mocks support for simple extensions (does not resolve or
71    /// parse).
72    pub struct Context {
73        empty_simple_extensions: SimpleExtensions,
74        simple_extensions: HashMap<Anchor<SimpleExtensionUri>, SimpleExtensionUri>,
75    }
76
77    impl Default for Context {
78        fn default() -> Self {
79            Self {
80                empty_simple_extensions: SimpleExtensions {},
81                simple_extensions: Default::default(),
82            }
83        }
84    }
85
86    impl super::Context for Context {
87        fn add_simple_extension_uri(
88            &mut self,
89            simple_extension_uri: &crate::parse::proto::extensions::SimpleExtensionUri,
90        ) -> Result<&SimpleExtensions, ContextError> {
91            match self.simple_extensions.entry(simple_extension_uri.anchor()) {
92                Entry::Occupied(_) => Err(ContextError::DuplicateSimpleExtension(
93                    simple_extension_uri.anchor(),
94                )),
95                Entry::Vacant(entry) => {
96                    // This is where we would resolve and then parse.
97                    // This check shows the use of the unsupported uri error.
98                    if let "http" | "https" | "file" = simple_extension_uri.uri().scheme() {
99                        entry.insert(simple_extension_uri.clone());
100                        // Here we just return an empty simple extensions.
101                        Ok(&self.empty_simple_extensions)
102                    } else {
103                        Err(ContextError::UnsupportedURI(format!(
104                            "`{}` scheme not supported",
105                            simple_extension_uri.uri().scheme()
106                        )))
107                    }
108                }
109            }
110        }
111
112        fn simple_extensions(
113            &self,
114            anchor: &Anchor<SimpleExtensionUri>,
115        ) -> Result<&SimpleExtensions, ContextError> {
116            self.simple_extensions
117                .contains_key(anchor)
118                .then_some(&self.empty_simple_extensions)
119                .ok_or(ContextError::UndefinedSimpleExtension(*anchor))
120        }
121    }
122}