uri-builder 0.1.0

Simple URI builder library
Documentation
use std::default::Default;
use itertools::Itertools;

/// Struct that corresponds with URI.
pub struct URI<'a> {
    scheme: &'a str,
    userinfo: Option<&'a str>,
    host: Option<&'a str>,
    port: Option<u16>,
    path: Vec<&'a str>,
    query: Vec<(String, String)>,
    fragment: Option<&'a str>,
}

impl<'a> Default for URI<'a> {
    /// Construct URI `http://localhost:80/`.
    fn default() -> URI<'a> {
        URI {
            scheme: "http",
            userinfo: None,
            host: Some("localhost"),
            port: Some(80),
            path: Vec::new(),
            query: Vec::new(),
            fragment: None,
        }
    }
}

impl<'a> URI<'a> {
    /// Construct empty URI with scheme only.
    pub fn new(scheme: &'a str) -> URI<'a> {
        URI {
            scheme,
            userinfo: None,
            host: None,
            port: None,
            path: Vec::new(),
            query: Vec::new(),
            fragment: None,
        }
    }


    /// Set userinfo.
    pub fn userinfo(&'a mut self, value: &'a str) -> &'a mut URI {
        self.userinfo = Some(value);
        self
    }

    /// Set host.
    pub fn host(&'a mut self, value: &'a str) -> &'a mut URI {
        self.host = Some(value);
        self
    }

    /// Set port.
    pub fn port(&'a mut self, value: u16) -> &'a mut URI {
        self.port = Some(value);
        self
    }

    /// Add passed segment to path.
    pub fn path(&'a mut self, segment: &'a str) -> &'a mut URI {
        self.path.push(segment);
        self
    }

    /// Add passed multiple segments to path.
    pub fn path_vec(&'a mut self, segments: &Vec<&'a str>) -> &'a mut URI {
        self.path.extend(segments);
        self
    }

    /// Add passed segment to query.
    pub fn query<T, U>(&'a mut self, attribute: U, value: T) -> &'a mut URI
        where T: ToString,
              U: ToString,
    {
        self.query.push((attribute.to_string(), value.to_string()));
        self
    }

    /// Add passed multiple segments to query.
    pub fn query_vec<T, U>(&'a mut self, values: &Vec<(T, U)>) -> &'a mut URI
        where T: ToString,
              U: ToString,
    {
        self.query.extend(values.iter()
            .map(|(a, v)| (a.to_string(), v.to_string()))
            .collect::<Vec<(String, String)>>()
        );
        self
    }

    /// Set fragment.
    pub fn fragment(&'a mut self, value: &'a str) -> &'a mut URI {
        self.fragment = Some(value);
        self
    }

    /// Build URI form received parts.
    ///
    /// ```rust
    /// use uri_builder::URI;
    ///
    /// let uri: String = URI::new("https")
    ///     .host("github.com")
    ///     .path("repos")
    ///     .query("page", 1)
    ///     .build();
    /// ```
    ///
    /// The example above transforms to `https://github.com/repos?page=1`.
    pub fn build(&'a self) -> String {
        let authority = match self.userinfo {
            Some(a) => format!("{}@", a),
            None => String::new(),
        };
        let authority = match self.host {
            Some(h) => format!("{}{}", authority, h),
            None => authority,
        };
        let authority = match self.port {
            Some(p) => format!("{}:{}", authority, p),
            None => authority,
        };

        let scheme = if authority.is_empty() {
            format!("{}:", self.scheme)
        } else {
            format!("{}://", self.scheme)
        };

        let path = self.path.iter()
            .format("/")
            .to_string();
        let path = if authority.is_empty() {
            path
        } else {
            format!("/{}", path)
        };

        let query = self.query.iter()
            .map(|(a, v)| format!("{}={}", a, v))
            .format("&")
            .to_string();
        let query = if query.is_empty() {
            query
        } else {
            format!("?{}", query)
        };

        let fragment = match self.fragment {
            Some(f) => format!("#{}", f),
            None => String::new(),
        };

        format!("{}{}{}{}{}",
                scheme, authority, path, query, fragment)
    }
}

#[cfg(test)]
mod tests {
    use crate::URI;

    #[test]
    fn test_builder() {
        let uri = URI::default().build();
        assert_eq!("http://localhost:80/", uri);

        let uri = URI::new("https")
            .host("github.com")
            .path("repos")
            .query("page", 1)
            .build();
        assert_eq!("https://github.com/repos?page=1", uri);

        let uri = URI::new("https")
            .userinfo("john.doe")
            .host("www.example.com")
            .port(123)
            .path_vec(vec!["forum", "questions"].as_ref())
            .query_vec(vec![("tag", "networking"), ("order", "newest")].as_ref())
            .fragment("top")
            .build();
        assert_eq!("https://john.doe@www.example.com:123/forum/questions?tag=networking&order=newest#top", uri);

        let uri = URI::new("mailto")
            .path("John.Doe@example.com")
            .build();
        assert_eq!("mailto:John.Doe@example.com", uri);

        let uri = URI::new("news")
            .path("comp.infosystems.www.servers.unix")
            .build();
        assert_eq!("news:comp.infosystems.www.servers.unix", uri);

        let uri = URI::new("telnet")
            .host("192.0.2.16")
            .port(80)
            .path("")
            .build();
        assert_eq!("telnet://192.0.2.16:80/", uri);

        let uri = URI::new("tel")
            .path("+1-816-555-1212")
            .build();
        assert_eq!("tel:+1-816-555-1212", uri);
    }
}