1use std::{fmt::Display, str::FromStr};
2
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Serialize, Deserialize, Debug, Hash, Eq)]
8pub struct Uri {
9 authority: String,
11 path: String,
13 uri: String,
15}
16
17#[derive(Debug, Clone, PartialEq)]
19pub struct ParseError(pub String);
20
21impl Display for ParseError {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{:?}", self.0)
24 }
25}
26
27impl std::error::Error for ParseError {}
28
29impl Uri {
30 fn try_from_string(uri: &str) -> Result<Uri, ParseError> {
31 let mut processed = uri.to_string();
32
33 while processed.starts_with('/') {
34 processed = processed[1..].to_string();
35 }
36
37 let wrap_scheme_idx = processed.find("wrap://");
38
39 if wrap_scheme_idx.is_none() {
40 processed = format!("wrap://{processed}");
41 }
42
43 if wrap_scheme_idx.is_some() && wrap_scheme_idx.unwrap() != 0 {
44 return Err(ParseError(String::from(
45 "The wrap:// scheme must be at the beginning of the URI string".to_string(),
46 )));
47 }
48
49 let reg = Regex::new("wrap://([a-z][a-z0-9-_.]+)/(.*)").unwrap();
50
51 let captures = reg.captures(&processed);
52
53 if captures.as_ref().is_none() || captures.as_ref().unwrap().len() != 3 {
54 return Err(ParseError(format!(
55 r#"URI is malformed, here are some examples of valid URIs:
56 wrap://ipfs/QmHASH
57 wrap://wrapscan.io/polywrap/cool-wrap
58 wrapscan.io/user/wrap
59 wrap://ens/domain.eth
60 Invalid URI Received: {uri}"#,
61 )));
62 }
63
64 let result = captures.unwrap();
65
66 Ok(Uri {
67 authority: result[1].to_string(),
68 path: result[2].to_string(),
69 uri: processed.to_string(),
70 })
71 }
72
73 pub unsafe fn from_parts(authority: String, path: String, uri: String) -> Uri {
79 Uri {
80 authority: authority,
81 path: path,
82 uri: uri,
83 }
84 }
85
86 pub fn authority(&self) -> &str {
88 &self.authority
89 }
90
91 pub fn path(&self) -> &str {
93 &self.path
94 }
95
96 pub fn uri(&self) -> &str {
98 &self.uri
99 }
100}
101
102impl PartialEq for Uri {
103 fn eq(&self, other: &Self) -> bool {
104 self.uri == other.uri
105 }
106}
107
108impl Display for Uri {
109 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
110 write!(f, "{}", self.uri)
111 }
112}
113
114impl From<Uri> for String {
115 fn from(uri: Uri) -> Self {
116 uri.to_string()
117 }
118}
119
120impl TryFrom<String> for Uri {
121 type Error = ParseError;
122
123 fn try_from(uri: String) -> Result<Self, Self::Error> {
124 Uri::try_from_string(&uri)
125 }
126}
127
128impl TryFrom<&str> for Uri {
129 type Error = ParseError;
130
131 fn try_from(uri: &str) -> Result<Self, Self::Error> {
132 Uri::try_from_string(uri)
133 }
134}
135
136impl FromStr for Uri {
137 type Err = ParseError;
138
139 fn from_str(name: &str) -> Result<Self, Self::Err> {
140 Uri::try_from_string(name)
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use std::convert::TryInto;
148
149 #[test]
150 fn try_from_string_valid() {
151 assert!(Uri::try_from_string("wrap://ipfs/QmHASH").is_ok());
152 assert!(Uri::try_from_string("////wrap://ipfs/QmHASH").is_ok());
153 assert!(Uri::try_from_string("wrapscan.io/user/cool-wrap").is_ok());
154 }
155
156 #[test]
157 fn try_from_string_invalid() {
158 assert!(Uri::try_from_string("wraps://ipfs/QmHASH").is_err());
159 assert!(Uri::try_from_string("ipfs/QmHASHwrap://").is_err());
160 assert!(Uri::try_from_string("").is_err());
161 }
162
163 #[test]
164 fn from_parts() {
165 let uri =
166 unsafe { Uri::from_parts("authority".to_owned(), "path".to_owned(), "uri".to_owned()) };
167 assert_eq!(uri.authority(), "authority");
168 assert_eq!(uri.path(), "path");
169 assert_eq!(uri.uri(), "uri");
170 }
171
172 #[test]
173 fn equality() {
174 let (uri1, uri2, uri3) = unsafe {
175 (
176 Uri::from_parts("authority".to_owned(), "path".to_owned(), "uri".to_owned()),
177 Uri::from_parts("authority".to_owned(), "path".to_owned(), "uri".to_owned()),
178 Uri::from_parts(
179 "authority".to_owned(),
180 "path".to_owned(),
181 "different".to_owned(),
182 ),
183 )
184 };
185
186 assert_eq!(uri1, uri2);
187 assert_ne!(uri1, uri3);
188 }
189
190 #[test]
191 fn from() {
192 let uri = Uri::try_from_string("wrap://auth/path").unwrap();
193 let string: String = uri.into();
194 assert_eq!(string, "wrap://auth/path");
195 }
196
197 #[test]
198 fn string_try_into() {
199 let uri: Result<Uri, ParseError> = "wrap://ipfs/QmHASH".try_into();
200 assert!(uri.is_ok());
201
202 let bad_uri: Result<Uri, ParseError> = "bad_uri".try_into();
203 assert!(bad_uri.is_err());
204 }
205
206 #[test]
207 fn parse_str() {
208 let uri: Uri = "wrap://ipfs/QmHASH".parse().unwrap();
209 assert_eq!(uri.uri(), "wrap://ipfs/QmHASH");
210
211 let bad_uri: Result<Uri, ParseError> = "bad_uri".parse();
212 assert!(bad_uri.is_err());
213 }
214
215 #[test]
216 fn display() {
217 let uri = Uri::try_from_string("wrap://authority/uri").unwrap();
218 assert_eq!(format!("{}", uri), "wrap://authority/uri");
219 }
220}