substrait/parse/proto/extensions/
simple_extension_uri.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Parsing of [proto::extensions::SimpleExtensionUri].
4
5use thiserror::Error;
6use url::Url;
7
8use crate::{
9    parse::{context::ContextError, Anchor, Context, Parse},
10    proto,
11};
12
13/// A parsed [proto::extensions::SimpleExtensionUri].
14#[derive(Clone, Debug, PartialEq)]
15pub struct SimpleExtensionUri {
16    /// The URI of this simple extension.
17    uri: Url,
18
19    /// The anchor value of this simple extension.
20    anchor: Anchor<Self>,
21}
22
23impl SimpleExtensionUri {
24    /// Returns the uri of this simple extension.
25    ///
26    /// See [proto::extensions::SimpleExtensionUri::uri].
27    pub fn uri(&self) -> &Url {
28        &self.uri
29    }
30
31    /// Returns the anchor value of this simple extension.
32    ///
33    /// See [proto::extensions::SimpleExtensionUri::extension_uri_anchor].
34    pub fn anchor(&self) -> Anchor<Self> {
35        self.anchor
36    }
37}
38
39/// Parse errors for [proto::extensions::SimpleExtensionUri].
40#[derive(Debug, Error, PartialEq)]
41pub enum SimpleExtensionUriError {
42    /// Invalid URI
43    #[error("invalid URI: {0}")]
44    InvalidURI(#[from] url::ParseError),
45
46    /// Context error
47    #[error(transparent)]
48    Context(#[from] ContextError),
49}
50
51impl<C: Context> Parse<C> for proto::extensions::SimpleExtensionUri {
52    type Parsed = SimpleExtensionUri;
53    type Error = SimpleExtensionUriError;
54
55    fn parse(self, ctx: &mut C) -> Result<Self::Parsed, Self::Error> {
56        let proto::extensions::SimpleExtensionUri {
57            extension_uri_anchor: anchor,
58            uri,
59        } = self;
60
61        // The uri is is required and must be valid.
62        let uri = Url::parse(&uri)?;
63
64        // Construct the parsed simple extension URI.
65        let simple_extension_uri = SimpleExtensionUri {
66            uri,
67            anchor: Anchor::new(anchor),
68        };
69
70        // Make sure the URI is supported by this parse context, resolves and
71        // parses, and the anchor is unique.
72        ctx.add_simple_extension_uri(&simple_extension_uri)?;
73
74        Ok(simple_extension_uri)
75    }
76}
77
78impl From<SimpleExtensionUri> for proto::extensions::SimpleExtensionUri {
79    fn from(simple_extension_uri: SimpleExtensionUri) -> Self {
80        let SimpleExtensionUri { uri, anchor } = simple_extension_uri;
81        proto::extensions::SimpleExtensionUri {
82            uri: uri.to_string(),
83            extension_uri_anchor: anchor.into_inner(),
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::parse::{context::tests::Context, Context as _};
92
93    #[test]
94    fn parse() -> Result<(), SimpleExtensionUriError> {
95        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
96            extension_uri_anchor: 1,
97            uri: "https://substrait.io".to_string(),
98        };
99        let simple_extension_uri = simple_extension_uri.parse(&mut Context::default())?;
100        assert_eq!(simple_extension_uri.anchor(), Anchor::new(1));
101        assert_eq!(simple_extension_uri.uri().as_str(), "https://substrait.io/");
102        Ok(())
103    }
104
105    #[test]
106    fn invalid_uri() {
107        let simple_extension_uri = proto::extensions::SimpleExtensionUri::default();
108        assert_eq!(
109            simple_extension_uri.parse(&mut Context::default()),
110            Err(SimpleExtensionUriError::InvalidURI(
111                url::ParseError::RelativeUrlWithoutBase
112            ))
113        );
114        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
115            extension_uri_anchor: 1,
116            uri: "http://".to_string(),
117        };
118        assert_eq!(
119            simple_extension_uri.parse(&mut Context::default()),
120            Err(SimpleExtensionUriError::InvalidURI(
121                url::ParseError::EmptyHost
122            ))
123        );
124    }
125
126    #[test]
127    fn duplicate_simple_extension() {
128        let mut ctx = Context::default();
129        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
130            extension_uri_anchor: 1,
131            uri: "https://substrait.io".to_string(),
132        };
133        assert!(ctx.parse(simple_extension_uri.clone()).is_ok());
134        assert_eq!(
135            ctx.parse(simple_extension_uri),
136            Err(SimpleExtensionUriError::Context(
137                ContextError::DuplicateSimpleExtension(Anchor::new(1))
138            ))
139        );
140    }
141
142    #[test]
143    fn unsupported_uri() {
144        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
145            extension_uri_anchor: 1,
146            uri: "ftp://substrait.io".to_string(),
147        };
148        assert_eq!(
149            simple_extension_uri.parse(&mut Context::default()),
150            Err(SimpleExtensionUriError::Context(
151                ContextError::UnsupportedURI("`ftp` scheme not supported".to_string())
152            ))
153        );
154    }
155}