ad4m_client/
literal.rs

1use anyhow::{anyhow, Result};
2use std::fmt::Display;
3
4#[derive(Clone, Debug, PartialEq)]
5pub enum LiteralValue {
6    String(String),
7    Number(f64),
8    Json(serde_json::Value),
9}
10
11impl Display for LiteralValue {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        match self {
14            LiteralValue::String(string) => write!(f, "{}", string),
15            LiteralValue::Number(number) => write!(f, "{}", number),
16            LiteralValue::Json(json) => write!(f, "{}", json),
17        }
18    }
19}
20
21pub struct Literal {
22    value: Option<LiteralValue>,
23    url: Option<String>,
24}
25
26impl Literal {
27    pub fn from_url(url: String) -> Result<Self> {
28        if url.starts_with("literal://") {
29            Ok(Self {
30                value: None,
31                url: Some(url),
32            })
33        } else {
34            Err(anyhow!("Not a literal URL"))
35        }
36    }
37
38    pub fn from_string(string: String) -> Self {
39        Self {
40            value: Some(LiteralValue::String(string)),
41            url: None,
42        }
43    }
44
45    pub fn from_number(number: f64) -> Self {
46        Self {
47            value: Some(LiteralValue::Number(number)),
48            url: None,
49        }
50    }
51
52    pub fn from_json(json: serde_json::Value) -> Self {
53        Self {
54            value: Some(LiteralValue::Json(json)),
55            url: None,
56        }
57    }
58
59    pub fn to_url(&self) -> Result<String> {
60        if let Some(url) = &self.url {
61            Ok(url.clone())
62        } else if let Some(value) = &self.value {
63            match value {
64                LiteralValue::String(string) => {
65                    let encoded = urlencoding::encode(string);
66                    Ok(format!("literal://string:{}", encoded))
67                }
68                LiteralValue::Number(number) => Ok(format!("literal://number:{}", number)),
69                LiteralValue::Json(json) => {
70                    let encoded = urlencoding::encode(&json.to_string()).to_string();
71                    Ok(format!("literal://json:{}", encoded))
72                }
73            }
74        } else {
75            Err(anyhow!("No value or URL"))
76        }
77    }
78
79    pub fn parse_url(&self) -> Result<LiteralValue> {
80        if let Some(url) = &self.url {
81            if url.starts_with("literal://") {
82                let literal = url.replace("literal://", "");
83                if literal.starts_with("string:") {
84                    let string = literal.replace("string:", "");
85                    let decoded = urlencoding::decode(&string)?;
86                    Ok(LiteralValue::String(decoded.into()))
87                } else if literal.starts_with("number:") {
88                    let number = literal.replace("number:", "");
89                    let parsed = number.parse::<f64>()?;
90                    Ok(LiteralValue::Number(parsed))
91                } else if literal.starts_with("json:") {
92                    let json = literal.replace("json:", "");
93                    let decoded = urlencoding::decode(&json)?;
94                    let parsed = serde_json::from_str::<serde_json::Value>(&decoded)?;
95                    Ok(LiteralValue::Json(parsed))
96                } else {
97                    Err(anyhow!("Unknown literal type"))
98                }
99            } else {
100                Err(anyhow!("Not a literal URL"))
101            }
102        } else {
103            Err(anyhow!("No URL"))
104        }
105    }
106
107    pub fn get(&self) -> Result<LiteralValue> {
108        if let Some(value) = &self.value {
109            Ok(value.clone())
110        } else if self.url.is_some() {
111            self.parse_url()
112        } else {
113            Err(anyhow!("No value or URL"))
114        }
115    }
116
117    pub fn convert(&mut self) -> Result<()> {
118        if self.value.is_some() {
119            self.url = Some(self.to_url()?);
120            Ok(())
121        } else if self.url.is_some() {
122            self.value = Some(self.parse_url()?);
123            Ok(())
124        } else {
125            Err(anyhow!("No value or URL"))
126        }
127    }
128}
129
130#[cfg(test)]
131mod test {
132    use serde_json::json;
133
134    #[test]
135    fn can_handle_strings() {
136        let test_string = "test string";
137        let test_url = "literal://string:test%20string";
138
139        let literal = super::Literal::from_string(test_string.into());
140        assert_eq!(literal.to_url().unwrap(), test_url);
141
142        let mut literal2 = super::Literal::from_url(test_url.into()).unwrap();
143        assert_eq!(
144            literal2.get().unwrap(),
145            super::LiteralValue::String(test_string.into())
146        );
147
148        literal2.convert().expect("Failed to convert");
149        assert_eq!(
150            literal2.value.unwrap(),
151            super::LiteralValue::String(test_string.into())
152        );
153    }
154
155    #[test]
156    fn can_handle_numbers() {
157        let test_number = 3.1;
158        let test_url = "literal://number:3.1";
159
160        let literal = super::Literal::from_number(test_number);
161        assert_eq!(literal.to_url().unwrap(), test_url);
162
163        let mut literal2 = super::Literal::from_url(test_url.into()).unwrap();
164        assert_eq!(
165            literal2.get().unwrap(),
166            super::LiteralValue::Number(test_number)
167        );
168
169        literal2.convert().expect("Failed to convert");
170        assert_eq!(
171            literal2.value.unwrap(),
172            super::LiteralValue::Number(test_number)
173        );
174    }
175
176    #[test]
177    fn can_handle_objects() {
178        let test_object = json!({
179            "testString": "test",
180            "testNumber": "1337",
181        });
182        let test_url =
183            "literal://json:%7B%22testNumber%22%3A%221337%22%2C%22testString%22%3A%22test%22%7D";
184
185        let literal = super::Literal::from_json(test_object.clone());
186        assert_eq!(literal.to_url().unwrap(), test_url);
187
188        let mut literal2 = super::Literal::from_url(test_url.into()).unwrap();
189        assert_eq!(
190            literal2.get().unwrap(),
191            super::LiteralValue::Json(test_object.clone())
192        );
193
194        literal2.convert().expect("Failed to convert");
195        assert_eq!(
196            literal2.value.unwrap(),
197            super::LiteralValue::Json(test_object)
198        );
199    }
200
201    #[test]
202    fn can_handle_special_characters() {
203        let test_string = "message(X) :- triple('ad4m://self', _, X).";
204        let test_url = "literal://string:message%28X%29%20%3A-%20triple%28%27ad4m%3A%2F%2Fself%27%2C%20_%2C%20X%29.";
205
206        let literal = super::Literal::from_string(test_string.into());
207        assert_eq!(literal.to_url().unwrap(), test_url);
208
209        let mut literal2 = super::Literal::from_url(test_url.into()).unwrap();
210        assert_eq!(
211            literal2.get().unwrap(),
212            super::LiteralValue::String(test_string.into())
213        );
214
215        literal2.convert().expect("Failed to convert");
216        assert_eq!(
217            literal2.value.unwrap(),
218            super::LiteralValue::String(test_string.into())
219        );
220    }
221}