gravatar/
lib.rs

1//! `rust-gravatar` is a small Rust library that generates Gravatar image URLs based on the
2//! [official Gravatar specification](https://en.gravatar.com/site/implement/images/).
3//!
4//! Example
5//! --------
6//! ```
7//! extern crate gravatar;
8//! use gravatar::{Gravatar, Rating};
9//!
10//! let url = Gravatar::new("email@example.com")
11//!     .set_size(Some(150))
12//!     .set_rating(Some(Rating::Pg))
13//!     .image_url();
14//! assert_eq!(
15//!     url.as_str(),
16//!     "https://secure.gravatar.com/avatar/5658ffccee7f0ebfda2b226238b1eb6e?s=150&r=pg"
17//! );
18//! ```
19
20extern crate md5;
21extern crate url;
22
23use md5::{Digest, Md5};
24use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
25use url::Url;
26
27/// The default image to display if the user's email does not have a Gravatar.
28///
29/// See <https://en.gravatar.com/site/implement/images/#default-image>.
30#[derive(Clone, Debug)]
31pub enum Default {
32    /// The URL of an image file to display as the default.
33    ImageUrl(Url),
34
35    /// Instead of loading an image, the Gravatar URL will return an HTTP 404 (File Not Found)
36    /// response if the email is not found.
37    Http404,
38
39    /// A transparent PNG image.
40    Blank,
41
42    /// A simple, cartoon-style silhouetted outline of a person that does not vary by email hash.
43    MysteryMan,
44
45    /// A geometric pattern based on the email hash.
46    Identicon,
47
48    /// A "monster" with different colors, faces, etc. that are generated by the email hash.
49    MonsterId,
50
51    /// A face with different features and backgrounds, generated by the email hash.
52    Wavatar,
53
54    /// An 8-bit arcade-style pixelated face that is generated by the email hash.
55    Retro,
56}
57
58/// The maximum rating level for which Gravatar will show the user's image instead of the specified
59/// default.
60///
61/// See <https://en.gravatar.com/site/implement/images/#rating>.
62#[derive(Clone, Debug)]
63pub enum Rating {
64    /// Show "G"-rated images only.
65    G,
66
67    /// Show "PG"-rated images or lower only.
68    Pg,
69
70    /// Show "R"-rated images or lower only.
71    R,
72
73    /// Show all images, up to and including "X"-rated ones.
74    X,
75}
76
77/// Representation of a single Gravatar image URL.
78#[derive(Clone, Debug)]
79pub struct Gravatar {
80    email: String,
81    size: Option<u16>,
82    default: Option<Default>,
83    force_default: bool,
84    rating: Option<Rating>,
85    ssl: bool,
86}
87
88impl Gravatar {
89    /// Creates a new Gravatar with the given email and default values for the other parameters.
90    pub fn new(email: &str) -> Gravatar {
91        Gravatar {
92            email: email.to_string(),
93            size: None,
94            default: None,
95            force_default: false,
96            rating: None,
97            ssl: true,
98        }
99    }
100
101    /// Sets the desired image size. If `None` is provided, then no size is passed to Gravatar,
102    /// which will then use a default of 80px by 80px. Gravatar will only provide images between
103    /// 1px and 2048px by size, so this function will use 1px if the desired size is less than that
104    /// and 2048px if the desired size is greater than that.
105    ///
106    /// For more information, see <https://en.gravatar.com/site/implement/images/#size>.
107    ///
108    /// **Default value:** `None`
109    pub fn set_size(&mut self, size: Option<u16>) -> &mut Self {
110        self.size = match size {
111            Some(s) => Some(s.max(1).min(2048)),
112            None => None,
113        };
114        self
115    }
116
117    /// Sets the default image to use if the user does not have a Gravatar. If `None` is provided,
118    /// then Gravatar returns a blue Gravatar logo. The default image can be either a URL or one of
119    /// Gravatar's premade defaults.
120    ///
121    /// For more information, see <https://en.gravatar.com/site/implement/images/#default-image>.
122    ///
123    /// **Default value:** `None`
124    pub fn set_default(&mut self, default: Option<Default>) -> &mut Self {
125        self.default = default;
126        self
127    }
128
129    /// If `force_default` is set to `true`, then Gravatar will always return the specified default
130    /// image, whether or not the user's email exists.
131    ///
132    /// For more information, see <https://en.gravatar.com/site/implement/images/#force-default>.
133    ///
134    /// **Default value:** `false`
135    pub fn set_force_default(&mut self, force_default: bool) -> &mut Self {
136        self.force_default = force_default;
137        self
138    }
139
140    /// Sets the maximum rating level for which Gravatar will show the user's image. If `None` is
141    /// provided, then Gravatar will only deliver "G"-rated images by default. If an image is at a
142    /// higher rating level than the requested one, the default image is returned instead.
143    ///
144    /// For more information, see <https://en.gravatar.com/site/implement/images/#rating>.
145    ///
146    /// **Default value:** `None`
147    pub fn set_rating(&mut self, rating: Option<Rating>) -> &mut Self {
148        self.rating = rating;
149        self
150    }
151
152    /// If `ssl` is set to `true`, Gravatar's secure URL (https://secure.gravatar.com/avatar/...)
153    /// is used. Otherwise, the non-SSL website is used instead
154    /// (http://www.gravatar.com/avatar/...).
155    ///
156    /// **Default value:** `true`
157    pub fn set_ssl(&mut self, ssl: bool) -> &mut Self {
158        self.ssl = ssl;
159        self
160    }
161
162    /// Returns the image URL of the user's Gravatar with all specified parameters.
163    pub fn image_url(self: &Self) -> Url {
164        // Generate MD5 hash of email
165        let digest = Md5::new()
166            .chain(&self.email.trim().to_ascii_lowercase())
167            .result();
168        let hash = format!("{:x}", digest);
169
170        // Create base URL using the hash
171        let mut url = Url::parse(&format!(
172            "{}.gravatar.com/avatar/{}",
173            if self.ssl {
174                "https://secure"
175            } else {
176                "http://www"
177            },
178            hash
179        ))
180        .unwrap();
181
182        if let Some(s) = self.size {
183            url.query_pairs_mut().append_pair("s", &s.to_string());
184        }
185
186        if let Some(ref d) = self.default {
187            let val = match d {
188                Default::ImageUrl(ref u) => {
189                    utf8_percent_encode(u.as_str(), DEFAULT_ENCODE_SET).to_string()
190                }
191                Default::Http404 => "404".to_string(),
192                Default::MysteryMan => "mm".to_string(),
193                Default::Identicon => "identicon".to_string(),
194                Default::MonsterId => "monsterid".to_string(),
195                Default::Wavatar => "wavatar".to_string(),
196                Default::Retro => "retro".to_string(),
197                Default::Blank => "blank".to_string(),
198            };
199            url.query_pairs_mut().append_pair("d", &val);
200        }
201
202        if self.force_default {
203            url.query_pairs_mut().append_pair("f", "y");
204        }
205
206        if let Some(ref r) = self.rating {
207            let val = match r {
208                Rating::G => "g",
209                Rating::Pg => "pg",
210                Rating::R => "r",
211                Rating::X => "x",
212            };
213            url.query_pairs_mut().append_pair("r", val);
214        }
215
216        url
217    }
218}