1use crate::error::IriSError;
2use oxiri::Iri;
3use oxrdf::{NamedNode, NamedOrBlankNode, Term};
4use serde::{Serialize, Serializer};
5use std::fmt;
6use std::fmt::{Display, Formatter};
7use std::str::FromStr;
8use url::Url;
9#[cfg(not(target_family = "wasm"))]
10use {std::fs::canonicalize, std::path::Path};
11
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct IriS {
14 iri: NamedNode,
15}
16
17impl IriS {
18 pub fn rdf_type() -> IriS {
20 IriS::new_unchecked("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
21 }
22
23 pub fn from_str_base(str: &str, base: Option<&str>) -> Result<IriS, IriSError> {
25 match base {
26 Some(base_str) => IriS::from_str(base_str)?.resolve_str(str),
27 None => IriS::from_str(str),
28 }
29 }
30
31 pub fn from_str_base_iri(str: &str, base_iri: Option<&IriS>) -> Result<IriS, IriSError> {
33 match base_iri {
34 Some(base_iri) => base_iri.resolve_str(str),
35 None => IriS::from_str(str),
36 }
37 }
38
39 pub fn as_str(&self) -> &str {
41 self.iri.as_str()
42 }
43
44 pub fn named_node(&self) -> &NamedNode {
46 &self.iri
47 }
48
49 pub fn new_unchecked(str: &str) -> IriS {
51 let iri = NamedNode::new_unchecked(str);
52 IriS { iri }
53 }
54
55 pub fn new(str: &str) -> Result<IriS, IriSError> {
56 let iri = NamedNode::new(str).map_err(|e| IriSError::IriParseError {
57 str: str.to_string(),
58 error: e.to_string(),
59 })?;
60 Ok(IriS { iri })
61 }
62
63 pub fn join(self, str: &str) -> Result<Self, IriSError> {
65 let url = Url::from_str(self.as_str()).map_err(|e| IriSError::IriParseError {
66 str: str.to_string(),
67 error: e.to_string(),
68 })?;
69
70 let joined = url.join(str).map_err(|e| IriSError::JoinError {
71 str: str.to_string(),
72 current: Box::new(self),
73 error: e.to_string(),
74 })?;
75
76 Ok(IriS::new_unchecked(joined.as_str()))
77 }
78
79 pub fn extend(&self, str: &str) -> Result<Self, IriSError> {
83 let current_str = self.iri.as_str();
84 let extend_str = if current_str.ends_with("/") || current_str.ends_with("#") {
85 format!("{current_str}{str}")
86 } else {
87 format!("{current_str}/{str}")
88 };
89
90 let iri = NamedNode::new(extend_str.as_str()).map_err(|e| IriSError::IriParseError {
91 str: extend_str,
92 error: e.to_string(),
93 })?;
94
95 Ok(IriS { iri })
96 }
97
98 pub fn extend_unchecked(&self, str: &str) -> Self {
100 let extended_str = format!("{}{}", self.iri.as_str(), str);
101 let iri = NamedNode::new_unchecked(extended_str);
102
103 IriS { iri }
104 }
105
106 pub fn resolve(&self, other: IriS) -> Result<Self, IriSError> {
108 let resolved = self.resolve_iri(other.as_str())?;
109 let iri = IriS::namednode_from_iri(resolved)?;
110 Ok(IriS { iri })
111 }
112
113 pub fn resolve_str(&self, other: &str) -> Result<Self, IriSError> {
115 let resolved = self.resolve_iri(other)?;
116 let iri = IriS::namednode_from_iri(resolved)?;
117 Ok(IriS { iri })
118 }
119
120 pub fn resolve_iri(&self, other: &str) -> Result<Iri<String>, IriSError> {
122 let base = Iri::parse(self.as_str()).map_err(|e| IriSError::IriParseError {
123 str: self.to_string(),
124 error: e.to_string(),
125 })?;
126
127 base.resolve(other).map_err(|e| IriSError::IriResolveError {
128 error: e.to_string(),
129 base: Box::new(IriS::new_unchecked(base.as_str())),
130 other: other.to_string(),
131 })
132 }
133
134 pub fn namednode_from_iri(iri: Iri<String>) -> Result<NamedNode, IriSError> {
136 NamedNode::new(iri.as_str()).map_err(|e| IriSError::IriParseError {
137 str: iri.as_str().to_string(),
138 error: e.to_string(),
139 })
140 }
141
142 pub fn parse_turtle(str: &str) -> Result<Self, IriSError> {
143 let str = str.trim();
144 if str.starts_with('<') && str.ends_with('>') {
145 let inner_str = &str[1..str.len() - 1];
146 IriS::new(inner_str)
147 } else {
148 Err(IriSError::TurtleParseError {
149 str: str.to_string(),
150 error: "IRI must be enclosed in angle brackets".to_string(),
151 })
152 }
153 }
154
155 #[cfg(not(target_family = "wasm"))]
158 pub fn dereference(&self, base: Option<&IriS>) -> Result<String, IriSError> {
159 use reqwest::blocking::Client;
160 use reqwest::header;
161 use reqwest::header::HeaderMap;
162 use reqwest::header::USER_AGENT;
163 use std::fs;
164
165 let url = match base {
166 Some(base_iri) => {
167 let base = Url::from_str(base_iri.as_str()).map_err(|e| IriSError::UrlParseError {
168 str: base_iri.to_string(),
169 error: e.to_string(),
170 })?;
171 Url::options()
172 .base_url(Some(&base))
173 .parse(self.iri.as_str())
174 .map_err(|e| IriSError::IriParseErrorWithBase {
175 str: self.iri.as_str().to_string(),
176 base: format!("{base}"),
177 error: e.to_string(),
178 })?
179 },
180 None => Url::from_str(self.iri.as_str()).map_err(|e| IriSError::UrlParseError {
181 str: self.iri.as_str().to_string(),
182 error: e.to_string(),
183 })?,
184 };
185
186 match url.scheme() {
187 "file" => {
188 let path = url
189 .to_file_path()
190 .map_err(|_| IriSError::ConvertingFileUrlToPath { url: url.to_string() })?;
191 let path_name = path.to_string_lossy().to_string();
192 let body = fs::read_to_string(path).map_err(|e| IriSError::IOErrorFile {
193 path: path_name,
194 url: url.to_string(),
195 error: e.to_string(),
196 })?;
197 Ok(body)
198 },
199 _ => {
200 let mut headers = HeaderMap::new();
201 headers.insert(USER_AGENT, header::HeaderValue::from_static("rudof"));
207 let client = Client::builder()
208 .default_headers(headers)
209 .build()
210 .map_err(|e| IriSError::ReqwestClientCreation { error: e.to_string() })?;
211 let res = client
212 .get(url)
213 .send()
214 .map_err(|e| IriSError::ReqwestError { error: e.to_string() })?
215 .text()
216 .map_err(|e| IriSError::ReqwestTextError { error: e.to_string() })?;
217 Ok(res)
218 },
219 }
220 }
221
222 #[cfg(target_family = "wasm")]
223 pub fn dereference(&self, _base: Option<&IriS>) -> Result<String, IriSError> {
224 Err(IriSError::ReqwestClientCreation {
225 error: String::from("rewquest is not enabled"),
226 })
227 }
228}
229
230impl Display for IriS {
231 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
232 write!(f, "{}", self.iri.as_str())
233 }
234}
235
236impl Serialize for IriS {
237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: Serializer,
240 {
241 serializer.serialize_str(self.iri.as_str())
242 }
243}
244
245impl From<NamedNode> for IriS {
246 fn from(iri: NamedNode) -> Self {
247 IriS { iri }
248 }
249}
250
251impl From<Url> for IriS {
252 fn from(value: Url) -> Self {
253 IriS {
254 iri: NamedNode::new_unchecked(value),
255 }
256 }
257}
258
259#[cfg(not(target_family = "wasm"))]
260impl TryFrom<&Path> for IriS {
261 type Error = IriSError;
262
263 fn try_from(value: &Path) -> Result<Self, Self::Error> {
264 let abs_path = if value.is_absolute() {
265 Ok(value.to_path_buf())
266 } else {
267 canonicalize(value).map_err(|e| IriSError::ConvertingPathToIri {
268 path: value.to_string_lossy().to_string(),
269 error: e.to_string(),
270 })
271 }?;
272
273 let url = Url::from_file_path(&abs_path).map_err(|_| IriSError::ConvertingPathToIri {
274 path: abs_path.to_string_lossy().to_string(),
275 error: String::from("Cannot convert path to file URL"),
276 })?;
277
278 let iri = NamedNode::new(url.as_str()).map_err(|e| IriSError::IriParseError {
279 str: url.as_str().to_string(),
280 error: e.to_string(),
281 })?;
282
283 Ok(IriS { iri })
284 }
285}
286
287impl FromStr for IriS {
288 type Err = IriSError;
289
290 fn from_str(s: &str) -> Result<Self, Self::Err> {
291 let iri = NamedNode::new(s).map_err(|e| IriSError::IriParseError {
292 str: s.to_string(),
293 error: e.to_string(),
294 })?;
295 Ok(IriS { iri })
296 }
297}
298
299impl From<IriS> for NamedNode {
300 fn from(iri: IriS) -> Self {
301 NamedNode::new_unchecked(iri.as_str())
302 }
303}
304
305impl From<IriS> for NamedOrBlankNode {
306 fn from(value: IriS) -> Self {
307 let named_node: NamedNode = value.into();
308 named_node.into()
309 }
310}
311
312impl From<IriS> for Term {
313 fn from(value: IriS) -> Self {
314 let named_node: NamedNode = value.into();
315 named_node.into()
316 }
317}
318
319impl Default for IriS {
320 fn default() -> Self {
321 IriS::new_unchecked(&String::default())
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_iri_creation() {
331 let iri = IriS::new("http://example.org/test").unwrap();
332 assert_eq!(iri.as_str(), "http://example.org/test");
333 }
334
335 #[test]
336 fn test_iri_join() {
337 let base = IriS::new("http://example.org/").unwrap();
338 let joined = base.join("test").unwrap();
339 assert_eq!(joined.as_str(), "http://example.org/test");
340 }
341
342 #[test]
343 fn test_iri_extend() {
344 let base = IriS::new("http://example.org").unwrap();
345 let extended = base.extend("test").unwrap();
346 assert_eq!(extended.as_str(), "http://example.org/test");
347 }
348
349 #[test]
350 fn test_iri_resolve() {
351 let base = IriS::new("http://example.org/").unwrap();
352 let resolved = base.resolve_str("test").unwrap();
353 assert_eq!(resolved.as_str(), "http://example.org/test");
354 }
355
356 #[test]
357 fn parse_iri_turtle() {
358 let str = "<http://example.org/test>";
359 let iri = IriS::parse_turtle(str).unwrap();
360 assert_eq!(iri.as_str(), "http://example.org/test");
361 }
362}