ulule 1.0.0

API bindings for the Ulule v1 HTTP API
Documentation
use std::str::FromStr;
use std::fmt;
use std::num::ParseIntError;

#[derive(Debug, PartialEq)]
pub struct Params {
    query: Vec<(String, String)>,
    extra_fields: Vec<String>,
    lang: Option<String>,
    limit: Option<u64>,
    offset: Option<u64>,
}

impl Params {
    pub fn new() -> Params {
        Params{
            query: Default::default(),
            lang: None,
            limit: None,
            offset: None,
            extra_fields: Default::default(),
        }
    }

    pub fn lang(self, l: impl Into<String>) -> Params {
        let mut params = self;
        params.lang = Some(l.into());
        params
    }

    pub fn limit(self, l: u64) -> Params {
        let mut params = self;
        params.limit = Some(l);
        params
    }

    pub fn offset(self, o: u64) -> Params {
        let mut params = self;
        params.offset = Some(o);
        params
    }

    pub fn add_query<K, V>(self, key: K, value: V) -> Params
        where K: Into<String>, V: Into<String> {
            let mut params = self;
            params.query.push((key.into(), value.into()));
            params
        }

    pub fn with_extra_fields(self, fields: Vec<String>) -> Params {
        let mut params = self;
        params.extra_fields = fields;
        params
    }
}

impl fmt::Display for Params {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut res = Vec::new();

        if !self.query.is_empty() {
            let q: Vec<String> = self.query.iter().map(|(key, value)| {
                let mut tuple = key.clone();
                if !value.is_empty() {
                    tuple.push_str(":");
                    tuple.push_str(&value);
                }; tuple
            }).collect();
            res.push(format!("q={}", q.join("+")));
        }

        if !self.extra_fields.is_empty() {
            res.push(format!("extra_fields={}", &self.extra_fields.join(",")));
        }

        self.lang.as_ref().map(|l|{res.push(format!("lang={}", l));});
        self.limit.map(|l|{res.push(format!("limit={}", l));});
        self.offset.map(|o|{res.push(format!("offset={}", o));});

        write!(f, "?{}", res.join("&"))
    }
}

impl FromStr for Params {
    type Err = ParseIntError;
    fn from_str(input: &str) -> Result<Params, Self::Err> {
        let mut params = Params::new();
        let p: Vec<&str> = input.trim_start_matches('?').split('&').collect();
        for v in &p {
            let tuple: Vec<&str> = v.split('=').collect();
            if tuple.len() > 1 {
                match tuple[0] {
                    "limit" => params.limit = Some(tuple[1].parse::<u64>()?),
                    "offset" => params.offset = Some(tuple[1].parse::<u64>()?),
                    "lang" => params.lang = Some(tuple[1].to_string()),
                    "extra_fields" => params.extra_fields = tuple[1]
                        .split(',').map(|i| i.to_string()).collect(),
                    "q" => {
                        let queries: Vec<&str> = tuple[1].split(|c| c == '+' || c == ' ').collect();
                        for query in queries {
                            let tuple:Vec<&str> = query.split(":").collect();
                            match tuple.len() {
                                1 => params.query.push((tuple[0].to_string(), "".to_string())),
                                2 => params.query.push((tuple[0].to_string(), tuple[1].to_string())),
                                _ => (),
                            }
                        }
                    },
                    _ => (),
                }
            }
        }
        Ok(params)
    }
}

impl From<Params> for std::string::String {
    fn from(p: Params) -> Self {
        p.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::Params;
    use std::str::FromStr;
    #[test]
    fn from_string() {
        assert_eq!(
            Params::from_str("?q=tag_id:1337+beer&extra_fields=user,user.avatar&limit=30&offset=10&lang=fr").unwrap(),
            Params::new()
            .with_extra_fields(vec!["user".to_string(), "user.avatar".to_string()])
            .add_query("tag_id", "1337")
            .add_query("beer", "")
            .limit(30)
            .offset(10)
            .lang("fr"));
    }
    #[test]
    fn to_string() {
        let p = Params::new()
            .with_extra_fields(vec!["user".to_string(), "user.avatar".to_string()])
            .add_query("tag_id", "1337")
            .add_query("beer", "")
            .limit(30)
            .offset(10)
            .lang("fr");
        assert_eq!(p.to_string(), "?q=tag_id:1337+beer&extra_fields=user,user.avatar&lang=fr&limit=30&offset=10");
    }
}