iceberg_rust_spec/spec/
namespace.rs

1/*!
2Defining the [Namespace] struct for handling namespaces in the catalog.
3*/
4
5use core::fmt::{self, Display};
6use itertools::Itertools;
7use serde_derive::{Deserialize, Serialize};
8use std::ops::Deref;
9
10use crate::{error::Error, identifier::SEPARATOR};
11
12/// Namespace struct for iceberg catalogs
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
14pub struct Namespace(pub(crate) Vec<String>);
15
16impl Namespace {
17    /// Try to create new namespace with sequence of strings.
18    pub fn try_new(levels: &[String]) -> Result<Self, Error> {
19        if levels.iter().any(|x| x.is_empty()) {
20            Err(Error::InvalidFormat("namespace sequence".to_string()))
21        } else {
22            Ok(Namespace(levels.to_vec()))
23        }
24    }
25    /// Create empty namespace
26    pub fn empty() -> Self {
27        Namespace(vec![])
28    }
29    /// Url encodes the namespace
30    pub fn url_encode(&self) -> String {
31        url::form_urlencoded::byte_serialize(self.0.join("\u{1F}").as_bytes()).collect()
32    }
33    /// Create namespace from url encoded string
34    pub fn from_url_encoded(namespace: &str) -> Result<Self, Error> {
35        Ok(Namespace(
36            url::form_urlencoded::parse(namespace.as_bytes())
37                .next()
38                .ok_or(Error::InvalidFormat(format!(
39                    "Namespace {namespace} is empty"
40                )))?
41                .0
42                .split('\u{1F}')
43                .map(ToString::to_string)
44                .collect(),
45        ))
46    }
47}
48
49impl Deref for Namespace {
50    type Target = [String];
51    fn deref(&self) -> &Self::Target {
52        &self.0
53    }
54}
55
56impl Display for Namespace {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(
59            f,
60            "{}",
61            Itertools::intersperse(self.0.iter().map(|x| x as &str), SEPARATOR).collect::<String>()
62        )
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use serde_json::{json, Value};
69
70    use super::Namespace;
71
72    #[test]
73    fn test_new() {
74        let namespace = Namespace::try_new(&[
75            "level1".to_string(),
76            "level2".to_string(),
77            "level3".to_string(),
78        ])
79        .unwrap();
80        assert_eq!(&format!("{namespace}"), "level1.level2.level3");
81    }
82    #[test]
83    #[should_panic]
84    fn test_empty() {
85        let _ = Namespace::try_new(&["".to_string(), "level2".to_string()]).unwrap();
86    }
87
88    #[test]
89    fn test_namespace_serialization() {
90        let namespace = Namespace(vec!["foo".to_string(), "bar".to_string()]);
91        let serialized = serde_json::to_string(&namespace).unwrap();
92        assert_eq!(serialized, r#"["foo","bar"]"#);
93    }
94
95    #[test]
96    fn test_namespace_deserialization() {
97        let json_value: Value = json!(["foo", "bar"]);
98        let namespace: Namespace = serde_json::from_value(json_value).unwrap();
99        assert_eq!(
100            namespace,
101            Namespace(vec!["foo".to_string(), "bar".to_string()])
102        );
103    }
104
105    #[test]
106    fn test_namespace_roundtrip() {
107        let original = Namespace(vec!["foo".to_string(), "bar".to_string()]);
108        let serialized = serde_json::to_string(&original).unwrap();
109        let deserialized: Namespace = serde_json::from_str(&serialized).unwrap();
110        assert_eq!(original, deserialized);
111    }
112}