iri_s/
iris.rs

1use oxiri::Iri;
2use oxrdf::NamedNode;
3use oxrdf::Subject;
4use oxrdf::Term;
5use serde::de;
6use serde::de::Visitor;
7use serde::Deserialize;
8use serde::Deserializer;
9use serde::Serialize;
10use serde::Serializer;
11use std::fmt;
12use std::str::FromStr;
13use url::Url;
14
15use crate::IriSError;
16
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct IriS {
19    iri: NamedNode,
20}
21
22impl IriS {
23    pub fn rdf_type() -> IriS {
24        IriS::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
25    }
26
27    pub fn new_unchecked(str: &str) -> IriS {
28        let iri = NamedNode::new_unchecked(str);
29        IriS { iri }
30    }
31
32    pub fn as_str(&self) -> &str {
33        self.iri.as_str()
34    }
35
36    /// Convert a `NamedNode` to an `IriS`
37    pub fn from_named_node(iri: &NamedNode) -> IriS {
38        IriS { iri: iri.clone() }
39    }
40
41    /// Convert an `IriS` to a `NamedNode`
42    pub fn as_named_node(&self) -> &NamedNode {
43        &self.iri
44    }
45
46    pub fn join(&self, str: &str) -> Result<Self, IriSError> {
47        let url = Url::from_str(self.as_str()).map_err(|e| IriSError::IriParseError {
48            str: str.to_string(),
49            err: e.to_string(),
50        })?;
51        let joined = url.join(str).map_err(|e| IriSError::JoinError {
52            str: Box::new(str.to_string()),
53            current: Box::new(self.clone()),
54            err: Box::new(e.to_string()),
55        })?;
56        Ok(IriS::new_unchecked(joined.as_str()))
57    }
58
59    /// Extends the current IRI with a new string
60    ///
61    /// This function checks for possible errors returning a Result
62    pub fn extend(&self, str: &str) -> Result<Self, IriSError> {
63        let current_str = self.iri.as_str();
64        let extended_str = if current_str.ends_with('/') || current_str.ends_with('#') {
65            format!("{}{}", current_str, str)
66        } else {
67            format!("{}/{}", current_str, str)
68        };
69        let iri = NamedNode::new(extended_str.as_str()).map_err(|e| IriSError::IriParseError {
70            str: extended_str,
71            err: e.to_string(),
72        })?;
73        Ok(IriS { iri })
74    }
75
76    /// Extend an IRI with a new string without checking for possible syntactic errors
77    ///
78    pub fn extend_unchecked(&self, str: &str) -> Self {
79        let extended_str = format!("{}{}", self.iri.as_str(), str);
80        let iri = NamedNode::new_unchecked(extended_str);
81        IriS { iri }
82    }
83
84    /// Resolve the IRI `other` with this IRI
85    pub fn resolve(&self, other: IriS) -> Result<Self, IriSError> {
86        let str = self.iri.as_str();
87        let base = Iri::parse(str).map_err(|e| IriSError::IriParseError {
88            str: str.to_string(),
89            err: e.to_string(),
90        })?;
91        let other_str = other.as_str();
92        let resolved = base
93            .resolve(other_str)
94            .map_err(|e| IriSError::IriResolveError {
95                err: Box::new(e.to_string()),
96                base: Box::new(self.clone()),
97                other: Box::new(other.clone()),
98            })?;
99        let iri = NamedNode::new(resolved.as_str()).map_err(|e| IriSError::IriParseError {
100            str: resolved.as_str().to_string(),
101            err: e.to_string(),
102        })?;
103        Ok(IriS { iri })
104    }
105
106    /// [Dereference](https://www.w3.org/wiki/DereferenceURI) the IRI and get the content available from it
107    /// It handles also IRIs with the `file` scheme as local file names. For example: `file:///person.txt`
108    ///
109    #[cfg(not(target_family = "wasm"))]
110    pub fn dereference(&self, base: &Option<IriS>) -> Result<String, IriSError> {
111        use reqwest::header;
112        use reqwest::header::USER_AGENT;
113        use std::fs;
114
115        let url = match base {
116            Some(base_iri) => {
117                let base =
118                    Url::from_str(base_iri.as_str()).map_err(|e| IriSError::UrlParseError {
119                        str: self.iri.as_str().to_string(),
120                        error: format!("{e}"),
121                    })?;
122                Url::options()
123                    .base_url(Some(&base))
124                    .parse(self.iri.as_str())
125                    .map_err(|e| IriSError::IriParseErrorWithBase {
126                        str: self.iri.as_str().to_string(),
127                        base: format!("{base}"),
128                        error: format!("{e}"),
129                    })?
130            }
131            None => Url::from_str(self.iri.as_str()).map_err(|e| IriSError::UrlParseError {
132                str: self.iri.as_str().to_string(),
133                error: format!("{e}"),
134            })?,
135        };
136        match url.scheme() {
137            "file" => {
138                let path = url
139                    .to_file_path()
140                    .map_err(|_| IriSError::ConvertingFileUrlToPath {
141                        url: format!("{url}"),
142                    })?;
143                let path_name = path.to_string_lossy().to_string();
144                let body = fs::read_to_string(path).map_err(|e| IriSError::IOErrorFile {
145                    path: path_name,
146                    url: format!("{url}"),
147                    error: format!("{e}"),
148                })?;
149                Ok(body)
150            }
151            _ => {
152                let mut headers = header::HeaderMap::new();
153                /* TODO: Add a parameter with the Accept header ?
154                headers.insert(
155                    ACCEPT,
156                    header::HeaderValue::from_static(""),
157                );*/
158                headers.insert(USER_AGENT, header::HeaderValue::from_static("rudof"));
159                let client = reqwest::blocking::Client::builder()
160                    .default_headers(headers)
161                    .build()
162                    .map_err(|e| IriSError::ReqwestClientCreation {
163                        error: format!("{e}"),
164                    })?;
165                let body = client
166                    .get(url)
167                    .send()
168                    .map_err(|e| IriSError::ReqwestError {
169                        error: format!("{e}"),
170                    })?
171                    .text()
172                    .map_err(|e| IriSError::ReqwestTextError {
173                        error: format!("{e}"),
174                    })?;
175                Ok(body)
176            }
177        }
178    }
179
180    #[cfg(target_family = "wasm")]
181    pub fn dereference(&self, _base: &Option<IriS>) -> Result<String, IriSError> {
182        return Err(IriSError::ReqwestClientCreation {
183            error: String::from("reqwest is not enabled"),
184        });
185    }
186
187    /*    pub fn is_absolute(&self) -> bool {
188        self.0.is_absolute()
189    } */
190}
191
192impl fmt::Display for IriS {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        write!(f, "{}", self.iri.as_str())
195    }
196}
197
198impl Serialize for IriS {
199    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
200    where
201        S: Serializer,
202    {
203        serializer.serialize_str(self.iri.as_str())
204    }
205}
206
207impl FromStr for IriS {
208    type Err = IriSError;
209
210    fn from_str(s: &str) -> Result<Self, Self::Err> {
211        let iri = NamedNode::new(s).map_err(|e| IriSError::IriParseError {
212            str: s.to_string(),
213            err: e.to_string(),
214        })?;
215        Ok(IriS { iri })
216    }
217}
218
219impl From<IriS> for NamedNode {
220    fn from(iri: IriS) -> Self {
221        NamedNode::new_unchecked(iri.as_str())
222    }
223}
224
225impl From<IriS> for Subject {
226    fn from(value: IriS) -> Self {
227        let named_node: NamedNode = value.into();
228        named_node.into()
229    }
230}
231
232impl From<IriS> for Term {
233    fn from(value: IriS) -> Self {
234        let named_node: NamedNode = value.into();
235        named_node.into()
236    }
237}
238
239impl Default for IriS {
240    fn default() -> Self {
241        IriS::new_unchecked(&String::default())
242    }
243}
244
245struct IriVisitor;
246
247impl Visitor<'_> for IriVisitor {
248    type Value = IriS;
249
250    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
251        formatter.write_str("an IRI")
252    }
253
254    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
255    where
256        E: de::Error,
257    {
258        match IriS::from_str(v) {
259            Ok(iri) => Ok(iri),
260            Err(IriSError::IriParseError { str, err }) => Err(E::custom(format!(
261                "Error parsing value \"{v}\" as IRI. String \"{str}\", Error: {err}"
262            ))),
263            Err(other) => Err(E::custom(format!(
264                "Can not parse value \"{v}\" to IRI. Error: {other}"
265            ))),
266        }
267    }
268}
269
270impl<'de> Deserialize<'de> for IriS {
271    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
272    where
273        D: Deserializer<'de>,
274    {
275        deserializer.deserialize_str(IriVisitor)
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn creating_iris() {
285        let iri = IriS::from_str("http://example.org/").unwrap();
286        assert_eq!(iri.to_string(), "http://example.org/");
287    }
288
289    #[test]
290    fn obtaining_iri_as_str() {
291        let iri = IriS::from_str("http://example.org/p1").unwrap();
292        assert_eq!(iri.as_str(), "http://example.org/p1");
293    }
294
295    #[test]
296    fn extending_iri() {
297        let base = NamedNode::new("http://example.org/").unwrap();
298        let base_iri = IriS::from_named_node(&base);
299        let extended = base_iri.extend("knows").unwrap();
300        assert_eq!(extended.as_str(), "http://example.org/knows");
301    }
302
303    #[test]
304    fn comparing_iris() {
305        let iri1 = IriS::from_named_node(&NamedNode::new_unchecked("http://example.org/name"));
306        let iri2 = IriS::from_named_node(&NamedNode::new_unchecked("http://example.org/name"));
307        assert_eq!(iri1, iri2);
308    }
309}