1use url::Url;
17
18use std::fmt;
19
20use std::collections::{HashMap};
21use std::error::{self, Error};
22
23use std::ops::Add;
24
25
26
27#[derive(Debug, Clone)]
28pub struct UrlTemplate(pub String);
29
30
31#[derive(Debug)]
32#[derive(PartialEq)]
33pub enum UrlTemplateErrorKind {
34 IsNotAnUrl,
36 InvalidScheme,
38 InvalidPattern
40}
41
42
43#[derive(Debug)]
44#[derive(PartialEq)]
45pub struct UrlTemplateError {
46 position: usize,
47 kind: UrlTemplateErrorKind
48}
49
50impl From<(UrlTemplateErrorKind, usize)> for UrlTemplateError {
51 fn from((kind, position): (UrlTemplateErrorKind, usize)) -> UrlTemplateError {
52 UrlTemplateError {
53 kind: kind,
54 position: position
55 }
56 }
57}
58
59impl From<UrlTemplateErrorKind> for UrlTemplateError {
60 fn from(kind: UrlTemplateErrorKind) -> UrlTemplateError {
61 UrlTemplateError {
62 kind: kind,
63 position: 0
64 }
65 }
66}
67
68impl fmt::Display for UrlTemplateError {
69 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70 match self.kind {
71 UrlTemplateErrorKind::IsNotAnUrl => {
72 write!(f, "Provided pattern is not a valid URL.")
73 }
74 UrlTemplateErrorKind::InvalidScheme => {
75 write!(f, "URL scheme is differ from expected `http` or `https`.")
76 }
77 UrlTemplateErrorKind::InvalidPattern => {
78 write!(f, "The pattern has invalid syntax.")
79 }
80 }
81 }
82}
83
84impl error::Error for UrlTemplateError {
85 fn cause(&self) -> Option<&Error> { None }
86 fn source(&self) -> Option<&(Error + 'static)> { None }
87}
88
89
90impl From<String> for UrlTemplate {
91 fn from(s: String) -> UrlTemplate {
92 UrlTemplate(s)
93 }
94}
95
96impl From<&str> for UrlTemplate {
97 fn from(s: &str) -> UrlTemplate {
98 UrlTemplate(String::from(s))
99 }
100}
101
102impl Into<String> for UrlTemplate {
103 fn into(self) -> String {
104 self.0.clone()
105 }
106}
107
108impl PartialEq for UrlTemplate {
109 fn eq(&self, other: &UrlTemplate) -> bool {
110 let s: String = other.into();
111 self.0 == s
112 }
113}
114
115impl ToString for UrlTemplate {
116 fn to_string(&self) -> String {
117 self.0.clone()
118 }
119}
120
121impl From<&UrlTemplate> for String {
122 fn from(tpl: &UrlTemplate) -> String { tpl.to_string() }
123}
124
125impl UrlTemplate {
126 pub fn substitute(&self, values: &HashMap<String, String>) -> Result<Url, UrlTemplateError> {
127 match self.substitute_str(values) {
128 Ok(url_string) => {
129 Ok(Url::parse(url_string.as_str()).expect("Valid URL string"))
130 }
131 Err(e) => {
132 Err(e)
133 }
134 }
135 }
136
137 pub fn substitute_str(&self, values: &HashMap<String, String>) -> Result<String, UrlTemplateError> {
138 match Url::parse(self.0.as_str()) {
140 Ok(parsed) => {
141 let scheme_valid = parsed.scheme() == "http" || parsed.scheme() == "https";
142 if !scheme_valid {
143 return Err(UrlTemplateError::from(UrlTemplateErrorKind::InvalidScheme));
144 }
145 }
146 _ => {
147 return Err(UrlTemplateError::from(UrlTemplateErrorKind::IsNotAnUrl));
148 }
149 }
150
151 let mut chars = self.0.char_indices();
152 let mut out = String::new();
153
154 let mut current_placeholder = String::new();
155 let mut inside_placeholder = false;
156
157 loop{
158 match chars.next() {
159 None => {
160 break
161 }
162 Some((charnum, '{')) => {
163 if inside_placeholder {
164 return Err(UrlTemplateError::from((UrlTemplateErrorKind::InvalidPattern, charnum)));
165 }
166 current_placeholder = String::new();
167 inside_placeholder = true;
168 }
169 Some((charnum, '}')) => {
170 if !inside_placeholder {
171 return Err(UrlTemplateError::from((UrlTemplateErrorKind::InvalidPattern, charnum)));
172 }
173
174 match values.get(¤t_placeholder) {
175 Some(s) => {
176 out = out.add(s);
177 }
178 None => {
179 out = out.add("");
180 }
181 }
182 inside_placeholder = false;
183 }
184 Some((_charnum, ch)) => {
185 if inside_placeholder {
186 current_placeholder.push(ch);
187 } else {
188 out.push(ch);
189 }
190 }
191 }
192 }
193
194 if inside_placeholder {
195 return Err(UrlTemplateError::from((UrlTemplateErrorKind::InvalidPattern, self.0.len() - 1)));
196 }
197
198 Ok(out)
199
200 }
201}