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 pub fn from_named_node(iri: &NamedNode) -> IriS {
38 IriS { iri: iri.clone() }
39 }
40
41 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 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 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 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 #[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 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 }
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}