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}