1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// SPDX-License-Identifier: Apache-2.0

//! Parsing of [proto::extensions::SimpleExtensionUri].

use thiserror::Error;
use url::Url;

use crate::{
    parse::{context::ContextError, Anchor, Context, Parse},
    proto,
};

/// A parsed [proto::extensions::SimpleExtensionUri].
#[derive(Clone, Debug, PartialEq)]
pub struct SimpleExtensionUri {
    /// The URI of this simple extension.
    uri: Url,

    /// The anchor value of this simple extension.
    anchor: Anchor<Self>,
}

impl SimpleExtensionUri {
    /// Returns the uri of this simple extension.
    ///
    /// See [proto::extensions::SimpleExtensionUri::uri].
    pub fn uri(&self) -> &Url {
        &self.uri
    }

    /// Returns the anchor value of this simple extension.
    ///
    /// See [proto::extensions::SimpleExtensionUri::extension_uri_anchor].
    pub fn anchor(&self) -> Anchor<Self> {
        self.anchor
    }
}

/// Parse errors for [proto::extensions::SimpleExtensionUri].
#[derive(Debug, Error, PartialEq)]
pub enum SimpleExtensionUriError {
    /// Invalid URI
    #[error("invalid URI: {0}")]
    InvalidURI(#[from] url::ParseError),

    /// Context error
    #[error(transparent)]
    Context(#[from] ContextError),
}

impl<C: Context> Parse<C> for proto::extensions::SimpleExtensionUri {
    type Parsed = SimpleExtensionUri;
    type Error = SimpleExtensionUriError;

    fn parse(self, ctx: &mut C) -> Result<Self::Parsed, Self::Error> {
        let proto::extensions::SimpleExtensionUri {
            extension_uri_anchor: anchor,
            uri,
        } = self;

        // The uri is is required and must be valid.
        let uri = Url::parse(&uri)?;

        // Construct the parsed simple extension URI.
        let simple_extension_uri = SimpleExtensionUri {
            uri,
            anchor: Anchor::new(anchor),
        };

        // Make sure the URI is supported by this parse context, resolves and
        // parses, and the anchor is unique.
        ctx.add_simple_extension_uri(&simple_extension_uri)?;

        Ok(simple_extension_uri)
    }
}

impl From<SimpleExtensionUri> for proto::extensions::SimpleExtensionUri {
    fn from(simple_extension_uri: SimpleExtensionUri) -> Self {
        let SimpleExtensionUri { uri, anchor } = simple_extension_uri;
        proto::extensions::SimpleExtensionUri {
            uri: uri.to_string(),
            extension_uri_anchor: anchor.into_inner(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parse::{context::tests::Context, Context as _};

    #[test]
    fn parse() -> Result<(), SimpleExtensionUriError> {
        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
            extension_uri_anchor: 1,
            uri: "https://substrait.io".to_string(),
        };
        let simple_extension_uri = simple_extension_uri.parse(&mut Context::default())?;
        assert_eq!(simple_extension_uri.anchor(), Anchor::new(1));
        assert_eq!(simple_extension_uri.uri().as_str(), "https://substrait.io/");
        Ok(())
    }

    #[test]
    fn invalid_uri() {
        let simple_extension_uri = proto::extensions::SimpleExtensionUri::default();
        assert_eq!(
            simple_extension_uri.parse(&mut Context::default()),
            Err(SimpleExtensionUriError::InvalidURI(
                url::ParseError::RelativeUrlWithoutBase
            ))
        );
        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
            extension_uri_anchor: 1,
            uri: "http://".to_string(),
        };
        assert_eq!(
            simple_extension_uri.parse(&mut Context::default()),
            Err(SimpleExtensionUriError::InvalidURI(
                url::ParseError::EmptyHost
            ))
        );
    }

    #[test]
    fn duplicate_simple_extension() {
        let mut ctx = Context::default();
        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
            extension_uri_anchor: 1,
            uri: "https://substrait.io".to_string(),
        };
        assert!(ctx.parse(simple_extension_uri.clone()).is_ok());
        assert_eq!(
            ctx.parse(simple_extension_uri),
            Err(SimpleExtensionUriError::Context(
                ContextError::DuplicateSimpleExtension(Anchor::new(1))
            ))
        );
    }

    #[test]
    fn unsupported_uri() {
        let simple_extension_uri = proto::extensions::SimpleExtensionUri {
            extension_uri_anchor: 1,
            uri: "ftp://substrait.io".to_string(),
        };
        assert_eq!(
            simple_extension_uri.parse(&mut Context::default()),
            Err(SimpleExtensionUriError::Context(
                ContextError::UnsupportedURI("`ftp` scheme not supported".to_string())
            ))
        );
    }
}