uritemplate/
lib.rs

1//! `rust-uritemplate` is a Rust implementation of
2//! [RFC6570  - URI Template](http://tools.ietf.org/html/rfc6570) that can
3//! process URI Templates up to and including ones designated as Level 4 by the
4//! specification. It passes all of the tests in the
5//! [uritemplate-test](https://github.com/uri-templates/uritemplate-test) test
6//! suite.
7//!
8//! Basic Usage
9//! -----------
10//! Variable setting can be chained for nice, clean code.
11//!
12//! ```ignore
13//! extern crate uritemplate;
14//! use uritemplate::UriTemplate;
15//!
16//! let uri = UriTemplate::new("/view/{object:1}/{/object,names}{?query*}")
17//!     .set("object", "lakes")
18//!     .set("names", &["Erie", "Superior", "Ontario"])
19//!     .set("query", &[("size", "15"), ("lang", "en")])
20//!     .build();
21//!
22//! assert_eq!(uri, "/view/l/lakes/Erie,Superior,Ontario?size=15&lang=en");
23//! ```
24//!
25//! It is not possible to set a variable to the value "undefined". Instead,
26//! simply delete the variable if you have already set it.
27//!
28//! ```ignore
29//! let mut t = UriTemplate::new("{hello}");
30//! t.set("hello", "Hello World!");
31//! assert_eq!(t.build(), "Hello%20World%21");
32//!
33//! t.delete("hello");
34//! assert_eq!(t.build(), "");
35//! ```
36//!
37//! The `delete` function returns `true` if the variable existed and `false`
38//! otherwise.
39//!
40//! Supported Types
41//! ---------------
42//! Any type that implements `IntoTemplateVar` can be set as the value of a
43//! `UriTemplate` variable. The following implementations are provided by
44//! default for each type of variable:
45//!
46//! - Scalar Values: `String`, `&str`
47//! - Lists: `Vec<String>`, `&[String]`, `&[str]`
48//! - Associative Arrays: `Vec<(String, String)>`, `&[(String, String)]`,
49//!   `&[(&str, &str)]`, `&HashMap<String, String>`
50//!
51//! In addition, you can implement `IntoTemplateVar` for your own types. View the
52//! documentation for `IntoTemplateVar` for information on how that works.
53
54mod percent_encoding;
55mod templatevar;
56
57use crate::percent_encoding::{encode_reserved, encode_unreserved};
58use std::collections::HashMap;
59use std::str::FromStr;
60
61pub use crate::templatevar::{IntoTemplateVar, TemplateVar};
62
63enum VarSpecType {
64    Raw,
65    Prefixed(u16),
66    Exploded,
67}
68
69struct VarSpec {
70    name: String,
71    var_type: VarSpecType,
72}
73
74#[derive(PartialEq)]
75enum Operator {
76    Null,
77    Plus,
78    Dot,
79    Slash,
80    Semi,
81    Question,
82    Ampersand,
83    Hash,
84}
85
86enum TemplateComponent {
87    Literal(String),
88    VarList(Operator, Vec<VarSpec>),
89}
90
91/// The main struct that processes and builds URI Templates.
92pub struct UriTemplate {
93    components: Vec<TemplateComponent>,
94    vars: HashMap<String, TemplateVar>,
95}
96
97fn prefixed(s: &str, prefix: u16) -> String {
98    let prefix = prefix as usize;
99    if prefix >= s.len() {
100        s.to_string()
101    } else {
102        s[0..prefix].to_string()
103    }
104}
105
106fn parse_varlist(varlist: &str) -> TemplateComponent {
107    let mut varlist = varlist.to_string();
108    let operator = match varlist.chars().nth(0) {
109        Some(ch) => ch,
110        None => {
111            return TemplateComponent::VarList(Operator::Null, Vec::new());
112        }
113    };
114    let operator = match operator {
115        '+' => Operator::Plus,
116        '.' => Operator::Dot,
117        '/' => Operator::Slash,
118        ';' => Operator::Semi,
119        '?' => Operator::Question,
120        '&' => Operator::Ampersand,
121        '#' => Operator::Hash,
122        _ => Operator::Null,
123    };
124    if operator != Operator::Null {
125        varlist.remove(0);
126    }
127    let varspecs = varlist.split(",");
128    let mut varspec_list = Vec::new();
129    for varspec in varspecs {
130        let mut varspec = varspec.to_string();
131        let len = varspec.len();
132        if len >= 1 && varspec.chars().nth(len - 1).unwrap() == '*' {
133            varspec.pop();
134            varspec_list.push(VarSpec {
135                name: varspec,
136                var_type: VarSpecType::Exploded,
137            });
138            continue;
139        }
140        if varspec.contains(":") {
141            let parts: Vec<_> = varspec.splitn(2, ":").collect();
142            let prefix = u16::from_str(parts[1]).ok();
143            let prefix = match prefix {
144                Some(p) => p,
145                None => 9999u16,
146            };
147            varspec_list.push(VarSpec {
148                name: parts[0].to_string(),
149                var_type: VarSpecType::Prefixed(prefix),
150            });
151            continue;
152        }
153        varspec_list.push(VarSpec {
154            name: varspec,
155            var_type: VarSpecType::Raw,
156        });
157    }
158
159    TemplateComponent::VarList(operator, varspec_list)
160}
161
162fn encode_vec<E>(v: &Vec<String>, encoder: E) -> Vec<String>
163where
164    E: Fn(&str) -> String,
165{
166    v.iter().map(|s| encoder(&s)).collect()
167}
168
169impl UriTemplate {
170    /// Creates a new URI Template from the given template string.
171    ///
172    /// Example
173    /// -------
174    /// ```ignore
175    /// let t = UriTemplate::new("http://example.com/{name}");
176    /// ```
177    pub fn new(template: &str) -> UriTemplate {
178        let mut components = Vec::new();
179        let mut buf = String::new();
180        let mut in_varlist = false;
181
182        for ch in template.chars() {
183            if in_varlist && ch == '}' {
184                components.push(parse_varlist(&buf));
185                buf = String::new();
186                in_varlist = false;
187                continue;
188            }
189            if !in_varlist && ch == '{' {
190                if buf.len() > 0 {
191                    components.push(TemplateComponent::Literal(buf));
192                    buf = String::new();
193                }
194                in_varlist = true;
195                continue;
196            }
197            buf.push(ch);
198        }
199
200        if buf.len() > 0 {
201            components.push(TemplateComponent::Literal(buf));
202        }
203
204        UriTemplate {
205            components: components,
206            vars: HashMap::new(),
207        }
208    }
209
210    /// Sets the value of a variable in the URI Template.
211    ///
212    /// Example
213    /// -------
214    /// ```ignore
215    /// let mut t = UriTemplate::new("{name}");
216    /// t.set("name", "John Smith");
217    /// ```
218    ///
219    /// This function returns the `URITemplate` so that the `set()` calls can
220    /// be chained before building.
221    ///
222    /// ```ignore
223    /// let uri = UriTemplate::new("{firstname}/{lastname}")
224    ///     .set("firstname", "John")
225    ///     .set("lastname", "Smith")
226    ///     .build();
227    /// assert_eq!(uri, "John/Smith");
228    /// ```
229    pub fn set<I: IntoTemplateVar>(&mut self, varname: &str, var: I) -> &mut UriTemplate {
230        self.vars
231            .insert(varname.to_string(), var.into_template_var());
232        self
233    }
234
235    /// Deletes the value of a variable in the URI Template. Returns `true` if
236    /// the variable existed and `false` otherwise.
237    ///
238    /// Example
239    ///
240    /// ```ignore
241    /// let mut t = UriTemplate::new("{animal}");
242    /// t.set("animal", "dog");
243    /// assert_eq!(t.delete("house"), false);
244    /// assert_eq!(t.delete("animal"), true);
245    /// ```
246    pub fn delete(&mut self, varname: &str) -> bool {
247        match self.vars.remove(varname) {
248            Some(_) => true,
249            None => false,
250        }
251    }
252
253    /// Deletes the values of all variables currently set in the `URITemplate`.
254    pub fn delete_all(&mut self) {
255        self.vars.clear();
256    }
257
258    fn build_varspec<E>(
259        &self,
260        v: &VarSpec,
261        sep: &str,
262        named: bool,
263        ifemp: &str,
264        encoder: E,
265    ) -> Option<String>
266    where
267        E: Fn(&str) -> String,
268    {
269        let mut res = String::new();
270
271        let var = match self.vars.get(&v.name) {
272            Some(v) => v,
273            None => return None,
274        };
275
276        match *var {
277            TemplateVar::Scalar(ref s) => {
278                if named {
279                    res.push_str(&encode_reserved(&v.name));
280                    if s == "" {
281                        res.push_str(ifemp);
282                        return Some(res);
283                    }
284                    res.push('=');
285                }
286                match v.var_type {
287                    VarSpecType::Raw | VarSpecType::Exploded => {
288                        res.push_str(&encoder(s));
289                    }
290                    VarSpecType::Prefixed(p) => {
291                        res.push_str(&encoder(&prefixed(s, p)));
292                    }
293                };
294            }
295            TemplateVar::List(ref l) => {
296                if l.len() == 0 {
297                    return None;
298                }
299                match v.var_type {
300                    VarSpecType::Raw | VarSpecType::Prefixed(_) => {
301                        if named {
302                            res.push_str(&encode_reserved(&v.name));
303                            if l.join("").len() == 0 {
304                                res.push_str(ifemp);
305                                return Some(res);
306                            }
307                            res.push('=');
308                        }
309                        res.push_str(&encode_vec(l, encoder).join(","));
310                    }
311                    VarSpecType::Exploded => {
312                        if named {
313                            let pairs: Vec<String> = l
314                                .iter()
315                                .map(|x| {
316                                    let val: String = if x == "" {
317                                        format!("{}{}", &encode_reserved(&v.name), ifemp)
318                                    } else {
319                                        format!("{}={}", &encode_reserved(&v.name), &encoder(x))
320                                    };
321                                    val
322                                })
323                                .collect();
324                            res.push_str(&pairs.join(sep));
325                        } else {
326                            res.push_str(&encode_vec(&l, encoder).join(sep));
327                        }
328                    }
329                }
330            }
331            TemplateVar::AssociativeArray(ref a) => {
332                if a.len() == 0 {
333                    return None;
334                }
335                match v.var_type {
336                    VarSpecType::Raw | VarSpecType::Prefixed(_) => {
337                        if named {
338                            res.push_str(&encode_reserved(&v.name));
339                            res.push('=');
340                        }
341                        let pairs: Vec<String> = a
342                            .iter()
343                            .map(|&(ref k, ref v)| {
344                                format!("{},{}", &encode_reserved(k), &encoder(v))
345                            })
346                            .collect();
347                        res.push_str(&pairs.join(","));
348                    }
349                    VarSpecType::Exploded => {
350                        if named {
351                            let pairs: Vec<String> = a
352                                .iter()
353                                .map(|&(ref k, ref v)| {
354                                    let val: String = if v == "" {
355                                        format!("{}{}", &encode_reserved(k), ifemp)
356                                    } else {
357                                        format!("{}={}", &encode_reserved(k), &encoder(v))
358                                    };
359                                    val
360                                })
361                                .collect();
362                            res.push_str(&pairs.join(sep));
363                        } else {
364                            let pairs: Vec<String> = a
365                                .iter()
366                                .map(|&(ref k, ref v)| {
367                                    format!("{}={}", &encode_reserved(k), &encoder(v))
368                                })
369                                .collect();
370                            res.push_str(&pairs.join(sep));
371                        }
372                    }
373                }
374            }
375        }
376
377        Some(res)
378    }
379
380    fn build_varlist(&self, operator: &Operator, varlist: &Vec<VarSpec>) -> String {
381        let mut values: Vec<String> = Vec::new();
382        let (first, sep, named, ifemp, allow_reserved) = match *operator {
383            Operator::Null => ("", ",", false, "", false),
384            Operator::Plus => ("", ",", false, "", true),
385            Operator::Dot => (".", ".", false, "", false),
386            Operator::Slash => ("/", "/", false, "", false),
387            Operator::Semi => (";", ";", true, "", false),
388            Operator::Question => ("?", "&", true, "=", false),
389            Operator::Ampersand => ("&", "&", true, "=", false),
390            Operator::Hash => ("#", ",", false, "", true),
391        };
392
393        for varspec in varlist {
394            let built = if allow_reserved {
395                self.build_varspec(varspec, sep, named, ifemp, encode_reserved)
396            } else {
397                self.build_varspec(varspec, sep, named, ifemp, encode_unreserved)
398            };
399            match built {
400                Some(s) => values.push(s),
401                None => {}
402            }
403        }
404
405        let mut res = String::new();
406        if values.len() != 0 {
407            res.push_str(first);
408            res.push_str(&values.join(sep));
409        }
410
411        res
412    }
413
414    /// Expands the template using the set variable values and returns the
415    /// final String.
416    ///
417    /// Example
418    /// -------
419    ///
420    /// ```ignore
421    /// let mut t = UriTemplate::new("{hello}");
422    /// t.set("hello", "Hello World!");
423    /// assert_eq!(t.build(), "Hello%20World%21");
424    /// ```
425    pub fn build(&self) -> String {
426        let mut res = String::new();
427        for component in &self.components {
428            let next = match *component {
429                TemplateComponent::Literal(ref s) => encode_reserved(s),
430                TemplateComponent::VarList(ref op, ref varlist) => self.build_varlist(op, varlist),
431            };
432            res.push_str(&next);
433        }
434        res
435    }
436}