gravatar 0.2.0

A small library that generates Gravatar image URLs.
Documentation
//! `rust-gravatar` is a small Rust library that generates Gravatar image URLs based on the
//! [official Gravatar specification](https://en.gravatar.com/site/implement/images/).
//!
//! Example
//! --------
//! ```
//! extern crate gravatar;
//! use gravatar::{Gravatar, Rating};
//!
//! let url = Gravatar::new("email@example.com")
//!     .set_size(Some(150))
//!     .set_rating(Some(Rating::Pg))
//!     .image_url();
//! assert_eq!(
//!     url.as_str(),
//!     "https://secure.gravatar.com/avatar/5658ffccee7f0ebfda2b226238b1eb6e?s=150&r=pg"
//! );
//! ```

extern crate md5;
extern crate url;

use md5::{Digest, Md5};
use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use url::Url;

/// The default image to display if the user's email does not have a Gravatar.
///
/// See <https://en.gravatar.com/site/implement/images/#default-image>.
#[derive(Clone, Debug)]
pub enum Default {
    /// The URL of an image file to display as the default.
    ImageUrl(Url),

    /// Instead of loading an image, the Gravatar URL will return an HTTP 404 (File Not Found)
    /// response if the email is not found.
    Http404,

    /// A transparent PNG image.
    Blank,

    /// A simple, cartoon-style silhouetted outline of a person that does not vary by email hash.
    MysteryMan,

    /// A geometric pattern based on the email hash.
    Identicon,

    /// A "monster" with different colors, faces, etc. that are generated by the email hash.
    MonsterId,

    /// A face with different features and backgrounds, generated by the email hash.
    Wavatar,

    /// An 8-bit arcade-style pixelated face that is generated by the email hash.
    Retro,
}

/// The maximum rating level for which Gravatar will show the user's image instead of the specified
/// default.
///
/// See <https://en.gravatar.com/site/implement/images/#rating>.
#[derive(Clone, Debug)]
pub enum Rating {
    /// Show "G"-rated images only.
    G,

    /// Show "PG"-rated images or lower only.
    Pg,

    /// Show "R"-rated images or lower only.
    R,

    /// Show all images, up to and including "X"-rated ones.
    X,
}

/// Representation of a single Gravatar image URL.
#[derive(Clone, Debug)]
pub struct Gravatar {
    email: String,
    size: Option<u16>,
    default: Option<Default>,
    force_default: bool,
    rating: Option<Rating>,
    ssl: bool,
}

impl Gravatar {
    /// Creates a new Gravatar with the given email and default values for the other parameters.
    pub fn new(email: &str) -> Gravatar {
        Gravatar {
            email: email.to_string(),
            size: None,
            default: None,
            force_default: false,
            rating: None,
            ssl: true,
        }
    }

    /// Sets the desired image size. If `None` is provided, then no size is passed to Gravatar,
    /// which will then use a default of 80px by 80px. Gravatar will only provide images between
    /// 1px and 2048px by size, so this function will use 1px if the desired size is less than that
    /// and 2048px if the desired size is greater than that.
    ///
    /// For more information, see <https://en.gravatar.com/site/implement/images/#size>.
    ///
    /// **Default value:** `None`
    pub fn set_size(&mut self, size: Option<u16>) -> &mut Self {
        self.size = match size {
            Some(s) => Some(s.max(1).min(2048)),
            None => None,
        };
        self
    }

    /// Sets the default image to use if the user does not have a Gravatar. If `None` is provided,
    /// then Gravatar returns a blue Gravatar logo. The default image can be either a URL or one of
    /// Gravatar's premade defaults.
    ///
    /// For more information, see <https://en.gravatar.com/site/implement/images/#default-image>.
    ///
    /// **Default value:** `None`
    pub fn set_default(&mut self, default: Option<Default>) -> &mut Self {
        self.default = default;
        self
    }

    /// If `force_default` is set to `true`, then Gravatar will always return the specified default
    /// image, whether or not the user's email exists.
    ///
    /// For more information, see <https://en.gravatar.com/site/implement/images/#force-default>.
    ///
    /// **Default value:** `false`
    pub fn set_force_default(&mut self, force_default: bool) -> &mut Self {
        self.force_default = force_default;
        self
    }

    /// Sets the maximum rating level for which Gravatar will show the user's image. If `None` is
    /// provided, then Gravatar will only deliver "G"-rated images by default. If an image is at a
    /// higher rating level than the requested one, the default image is returned instead.
    ///
    /// For more information, see <https://en.gravatar.com/site/implement/images/#rating>.
    ///
    /// **Default value:** `None`
    pub fn set_rating(&mut self, rating: Option<Rating>) -> &mut Self {
        self.rating = rating;
        self
    }

    /// If `ssl` is set to `true`, Gravatar's secure URL (https://secure.gravatar.com/avatar/...)
    /// is used. Otherwise, the non-SSL website is used instead
    /// (http://www.gravatar.com/avatar/...).
    ///
    /// **Default value:** `true`
    pub fn set_ssl(&mut self, ssl: bool) -> &mut Self {
        self.ssl = ssl;
        self
    }

    /// Returns the image URL of the user's Gravatar with all specified parameters.
    pub fn image_url(self: &Self) -> Url {
        // Generate MD5 hash of email
        let digest = Md5::new()
            .chain(&self.email.trim().to_ascii_lowercase())
            .result();
        let hash = format!("{:x}", digest);

        // Create base URL using the hash
        let mut url = Url::parse(&format!(
            "{}.gravatar.com/avatar/{}",
            if self.ssl {
                "https://secure"
            } else {
                "http://www"
            },
            hash
        ))
        .unwrap();

        if let Some(s) = self.size {
            url.query_pairs_mut().append_pair("s", &s.to_string());
        }

        if let Some(ref d) = self.default {
            let val = match d {
                Default::ImageUrl(ref u) => {
                    utf8_percent_encode(u.as_str(), DEFAULT_ENCODE_SET).to_string()
                }
                Default::Http404 => "404".to_string(),
                Default::MysteryMan => "mm".to_string(),
                Default::Identicon => "identicon".to_string(),
                Default::MonsterId => "monsterid".to_string(),
                Default::Wavatar => "wavatar".to_string(),
                Default::Retro => "retro".to_string(),
                Default::Blank => "blank".to_string(),
            };
            url.query_pairs_mut().append_pair("d", &val);
        }

        if self.force_default {
            url.query_pairs_mut().append_pair("f", "y");
        }

        if let Some(ref r) = self.rating {
            let val = match r {
                Rating::G => "g",
                Rating::Pg => "pg",
                Rating::R => "r",
                Rating::X => "x",
            };
            url.query_pairs_mut().append_pair("r", val);
        }

        url
    }
}