mod percent_encoding;
mod templatevar;
use crate::percent_encoding::{encode_reserved, encode_unreserved};
use std::collections::HashMap;
use std::str::FromStr;
pub use crate::templatevar::{IntoTemplateVar, TemplateVar};
enum VarSpecType {
Raw,
Prefixed(u16),
Exploded,
}
struct VarSpec {
name: String,
var_type: VarSpecType,
}
#[derive(PartialEq)]
enum Operator {
Null,
Plus,
Dot,
Slash,
Semi,
Question,
Ampersand,
Hash,
}
enum TemplateComponent {
Literal(String),
VarList(Operator, Vec<VarSpec>),
}
pub struct UriTemplate {
components: Vec<TemplateComponent>,
vars: HashMap<String, TemplateVar>,
}
fn prefixed(s: &str, prefix: u16) -> String {
let prefix = prefix as usize;
if prefix >= s.len() {
s.to_string()
} else {
s[0..prefix].to_string()
}
}
fn parse_varlist(varlist: &str) -> TemplateComponent {
let mut varlist = varlist.to_string();
let operator = match varlist.chars().nth(0) {
Some(ch) => ch,
None => {
return TemplateComponent::VarList(Operator::Null, Vec::new());
}
};
let operator = match operator {
'+' => Operator::Plus,
'.' => Operator::Dot,
'/' => Operator::Slash,
';' => Operator::Semi,
'?' => Operator::Question,
'&' => Operator::Ampersand,
'#' => Operator::Hash,
_ => Operator::Null,
};
if operator != Operator::Null {
varlist.remove(0);
}
let varspecs = varlist.split(",");
let mut varspec_list = Vec::new();
for varspec in varspecs {
let mut varspec = varspec.to_string();
let len = varspec.len();
if len >= 1 && varspec.chars().nth(len - 1).unwrap() == '*' {
varspec.pop();
varspec_list.push(VarSpec {
name: varspec,
var_type: VarSpecType::Exploded,
});
continue;
}
if varspec.contains(":") {
let parts: Vec<_> = varspec.splitn(2, ":").collect();
let prefix = u16::from_str(parts[1]).ok();
let prefix = match prefix {
Some(p) => p,
None => 9999u16,
};
varspec_list.push(VarSpec {
name: parts[0].to_string(),
var_type: VarSpecType::Prefixed(prefix),
});
continue;
}
varspec_list.push(VarSpec {
name: varspec,
var_type: VarSpecType::Raw,
});
}
TemplateComponent::VarList(operator, varspec_list)
}
fn encode_vec<E>(v: &Vec<String>, encoder: E) -> Vec<String>
where
E: Fn(&str) -> String,
{
v.iter().map(|s| encoder(&s)).collect()
}
impl UriTemplate {
pub fn new(template: &str) -> UriTemplate {
let mut components = Vec::new();
let mut buf = String::new();
let mut in_varlist = false;
for ch in template.chars() {
if in_varlist && ch == '}' {
components.push(parse_varlist(&buf));
buf = String::new();
in_varlist = false;
continue;
}
if !in_varlist && ch == '{' {
if buf.len() > 0 {
components.push(TemplateComponent::Literal(buf));
buf = String::new();
}
in_varlist = true;
continue;
}
buf.push(ch);
}
if buf.len() > 0 {
components.push(TemplateComponent::Literal(buf));
}
UriTemplate {
components: components,
vars: HashMap::new(),
}
}
pub fn set<I: IntoTemplateVar>(&mut self, varname: &str, var: I) -> &mut UriTemplate {
self.vars
.insert(varname.to_string(), var.into_template_var());
self
}
pub fn delete(&mut self, varname: &str) -> bool {
match self.vars.remove(varname) {
Some(_) => true,
None => false,
}
}
pub fn delete_all(&mut self) {
self.vars.clear();
}
fn build_varspec<E>(
&self,
v: &VarSpec,
sep: &str,
named: bool,
ifemp: &str,
encoder: E,
) -> Option<String>
where
E: Fn(&str) -> String,
{
let mut res = String::new();
let var = match self.vars.get(&v.name) {
Some(v) => v,
None => return None,
};
match *var {
TemplateVar::Scalar(ref s) => {
if named {
res.push_str(&encode_reserved(&v.name));
if s == "" {
res.push_str(ifemp);
return Some(res);
}
res.push('=');
}
match v.var_type {
VarSpecType::Raw | VarSpecType::Exploded => {
res.push_str(&encoder(s));
}
VarSpecType::Prefixed(p) => {
res.push_str(&encoder(&prefixed(s, p)));
}
};
}
TemplateVar::List(ref l) => {
if l.len() == 0 {
return None;
}
match v.var_type {
VarSpecType::Raw | VarSpecType::Prefixed(_) => {
if named {
res.push_str(&encode_reserved(&v.name));
if l.join("").len() == 0 {
res.push_str(ifemp);
return Some(res);
}
res.push('=');
}
res.push_str(&encode_vec(l, encoder).join(","));
}
VarSpecType::Exploded => {
if named {
let pairs: Vec<String> = l
.iter()
.map(|x| {
let val: String = if x == "" {
format!("{}{}", &encode_reserved(&v.name), ifemp)
} else {
format!("{}={}", &encode_reserved(&v.name), &encoder(x))
};
val
})
.collect();
res.push_str(&pairs.join(sep));
} else {
res.push_str(&encode_vec(&l, encoder).join(sep));
}
}
}
}
TemplateVar::AssociativeArray(ref a) => {
if a.len() == 0 {
return None;
}
match v.var_type {
VarSpecType::Raw | VarSpecType::Prefixed(_) => {
if named {
res.push_str(&encode_reserved(&v.name));
res.push('=');
}
let pairs: Vec<String> = a
.iter()
.map(|&(ref k, ref v)| {
format!("{},{}", &encode_reserved(k), &encoder(v))
})
.collect();
res.push_str(&pairs.join(","));
}
VarSpecType::Exploded => {
if named {
let pairs: Vec<String> = a
.iter()
.map(|&(ref k, ref v)| {
let val: String = if v == "" {
format!("{}{}", &encode_reserved(k), ifemp)
} else {
format!("{}={}", &encode_reserved(k), &encoder(v))
};
val
})
.collect();
res.push_str(&pairs.join(sep));
} else {
let pairs: Vec<String> = a
.iter()
.map(|&(ref k, ref v)| {
format!("{}={}", &encode_reserved(k), &encoder(v))
})
.collect();
res.push_str(&pairs.join(sep));
}
}
}
}
}
Some(res)
}
fn build_varlist(&self, operator: &Operator, varlist: &Vec<VarSpec>) -> String {
let mut values: Vec<String> = Vec::new();
let (first, sep, named, ifemp, allow_reserved) = match *operator {
Operator::Null => ("", ",", false, "", false),
Operator::Plus => ("", ",", false, "", true),
Operator::Dot => (".", ".", false, "", false),
Operator::Slash => ("/", "/", false, "", false),
Operator::Semi => (";", ";", true, "", false),
Operator::Question => ("?", "&", true, "=", false),
Operator::Ampersand => ("&", "&", true, "=", false),
Operator::Hash => ("#", ",", false, "", true),
};
for varspec in varlist {
let built = if allow_reserved {
self.build_varspec(varspec, sep, named, ifemp, encode_reserved)
} else {
self.build_varspec(varspec, sep, named, ifemp, encode_unreserved)
};
match built {
Some(s) => values.push(s),
None => {}
}
}
let mut res = String::new();
if values.len() != 0 {
res.push_str(first);
res.push_str(&values.join(sep));
}
res
}
pub fn build(&self) -> String {
let mut res = String::new();
for component in &self.components {
let next = match *component {
TemplateComponent::Literal(ref s) => encode_reserved(s),
TemplateComponent::VarList(ref op, ref varlist) => self.build_varlist(op, varlist),
};
res.push_str(&next);
}
res
}
}