Repository: gsquire/sendgrid-rs (language: rust)

Issue:
The library would benefit from simpler constructors and shortcuts
Version 0.8 is so much better to use than before ! (#43)
However, not allowing to use `Email::new()` without passing an `email` would make more sense in my opinion, since the "Email" concept doesn't really exist if it is not set (as is very well held by the `Email` struct typing), so we shouldn't allow building an `Email` struct and forgetting to set the `email` field of that struct.
As a consequence, I would replace the :
```rust
sg::Email::new()
	.set_email("some@mail.com")
	.set_name("Some Person")
```
by :
```rust
sg::Email::new("some@mail.com") // Possibly still allow set_name but chances are it wouldn't be needed by any clean code
// and
sg::Email::with_name("some@mail.com", "Some Person")
```
and remove the `Default` derive on that type.

In a similar fashion :
```rust
sg::Personalization::with_to(email)
// as an additional constructor for
sg::Personalization::new().add_to(email)
```
and
```rust
sg::Message::new().add_to(email)
// as a helper for
sg::Message::new().add_personalization(sg::Personalization::new().add_to(email))
```
would make the library even less verbose and more practical to use.



Discussion / hints:
I wouldn't be opposed to adding some of these niceties to a separate module. I will be able to experiment with this in the coming weeks.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index f2a7208..9eab7ba 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "sendgrid"
-version = "0.13.0"
+version = "0.14.0"
 edition = "2018"
 authors = ["Garrett Squire <github@garrettsquire.com>"]
 description = "An unofficial client library for the SendGrid API"
diff --git a/examples/v3.rs b/examples/v3.rs
index 3123750..5b929e4 100644
--- a/examples/v3.rs
+++ b/examples/v3.rs
@@ -3,16 +3,13 @@ use std::collections::HashMap;
 use sendgrid::v3::*;
 
 fn main() {
-    let mut cool_header = HashMap::new();
+    let mut cool_header = HashMap::with_capacity(2);
     cool_header.insert(String::from("x-cool"), String::from("indeed"));
     cool_header.insert(String::from("x-cooler"), String::from("cold"));
 
-    let p = Personalization::new()
-        .add_to(Email::new().set_email("test@example.com"))
-        .add_headers(cool_header);
+    let p = Personalization::new(Email::new("test@example.com")).add_headers(cool_header);
 
-    let m = Message::new()
-        .set_from(Email::new().set_email("g@gmail.com"))
+    let m = Message::new(Email::new("g@gmail.com"))
         .set_subject("Subject")
         .add_content(
             Content::new()
diff --git a/examples/v3_async.rs b/examples/v3_async.rs
index f9936c5..df82749 100644
--- a/examples/v3_async.rs
+++ b/examples/v3_async.rs
@@ -5,16 +5,13 @@ use sendgrid::v3::*;
 
 #[tokio::main]
 async fn main() -> Result<(), SendgridError> {
-    let mut cool_header = HashMap::new();
+    let mut cool_header = HashMap::with_capacity(2);
     cool_header.insert(String::from("x-cool"), String::from("indeed"));
     cool_header.insert(String::from("x-cooler"), String::from("cold"));
 
-    let p = Personalization::new()
-        .add_to(Email::new().set_email("test@test.fr"))
-        .add_headers(cool_header);
+    let p = Personalization::new(Email::new("test@test.fr")).add_headers(cool_header);
 
-    let m = Message::new()
-        .set_from(Email::new().set_email("g@gmail.com"))
+    let m = Message::new(Email::new("g@gmail.com"))
         .set_subject("Subject")
         .add_content(
             Content::new()
diff --git a/src/v3.rs b/src/v3.rs
index 73a5d5e..f87dd6c 100644
--- a/src/v3.rs
+++ b/src/v3.rs
@@ -28,7 +28,7 @@ pub struct Sender {
 
 /// The main structure for a V3 API mail send call. This is composed of many other smaller
 /// structures used to add lots of customization to your message.
-#[derive(Default, Serialize)]
+#[derive(Serialize)]
 pub struct Message {
     from: Email,
     subject: String,
@@ -45,7 +45,7 @@ pub struct Message {
 }
 
 /// An email with a required address and an optional name field.
-#[derive(Clone, Default, Serialize)]
+#[derive(Clone, Serialize)]
 pub struct Email {
     email: String,
 
@@ -63,7 +63,7 @@ pub struct Content {
 
 /// A personalization block for a V3 message. It has to at least contain one email as a to
 /// address. All other fields are optional.
-#[derive(Default, Serialize)]
+#[derive(Serialize)]
 pub struct Personalization {
     to: Vec<Email>,
 
@@ -121,7 +121,7 @@ impl Sender {
     }
 
     fn get_headers(&self) -> Result<HeaderMap, InvalidHeaderValue> {
-        let mut headers = HeaderMap::new();
+        let mut headers = HeaderMap::with_capacity(3);
         headers.insert(
             header::AUTHORIZATION,
             HeaderValue::from_str(&format!("Bearer {}", self.api_key.clone()))?,
@@ -177,8 +177,15 @@ impl Sender {
 
 impl Message {
     /// Construct a new V3 message.
-    pub fn new() -> Message {
-        Message::default()
+    pub fn new(from: Email) -> Message {
+        Message {
+            from,
+            subject: String::new(),
+            personalizations: Vec::new(),
+            content: None,
+            attachments: None,
+            template_id: None,
+        }
     }
 
     /// Set the from address.
@@ -201,14 +208,7 @@ impl Message {
 
     /// Add content to the message.
     pub fn add_content(mut self, c: Content) -> Message {
-        match self.content {
-            None => {
-                let mut content = Vec::new();
-                content.push(c);
-                self.content = Some(content);
-            }
-            Some(ref mut content) => content.push(c),
-        };
+        self.content.get_or_insert_with(Vec::new).push(c);
         self
     }
 
@@ -220,14 +220,7 @@ impl Message {
 
     /// Add an attachment to the message.
     pub fn add_attachment(mut self, a: Attachment) -> Message {
-        match self.attachments {
-            None => {
-                let mut attachments = Vec::new();
-                attachments.push(a);
-                self.attachments = Some(attachments);
-            }
-            Some(ref mut attachments) => attachments.push(a),
-        };
+        self.attachments.get_or_insert_with(Vec::new).push(a);
         self
     }
 
@@ -237,20 +230,29 @@ impl Message {
 }
 
 impl Email {
-    /// Construct a new email type.
-    pub fn new() -> Email {
-        Email::default()
-    }
-
-    /// Set the address for this email.
-    pub fn set_email(mut self, email: &str) -> Email {
-        self.email = String::from(email);
-        self
+    /// Construct a new email type with name set as None.
+    ///
+    /// ```rust
+    /// use sendgrid::v3::Email;
+    ///
+    /// let my_email = Email::new("test@mail.com");
+    /// ```
+    pub fn new<S: Into<String>>(email: S) -> Email {
+        Email {
+            email: email.into(),
+            name: None,
+        }
     }
 
     /// Set an optional name.
-    pub fn set_name(mut self, name: &str) -> Email {
-        self.name = Some(String::from(name));
+    ///
+    /// ```rust
+    /// use sendgrid::v3::Email;
+    ///
+    /// let my_email = Email::new("test@mail.com").set_name("My Name");
+    /// ```
+    pub fn set_name<S: Into<String>>(mut self, name: S) -> Email {
+        self.name = Some(name.into());
         self
     }
 }
@@ -262,22 +264,32 @@ impl Content {
     }
 
     /// Set the type of this content.
-    pub fn set_content_type(mut self, content_type: &str) -> Content {
-        self.content_type = String::from(content_type);
+    pub fn set_content_type<S: Into<String>>(mut self, content_type: S) -> Content {
+        self.content_type = content_type.into();
         self
     }
 
     /// Set the corresponding message for this content.
-    pub fn set_value(mut self, value: &str) -> Content {
-        self.value = String::from(value);
+    pub fn set_value<S: Into<String>>(mut self, value: S) -> Content {
+        self.value = value.into();
         self
     }
 }
 
 impl Personalization {
-    /// Construct a new personalization block for this message.
-    pub fn new() -> Personalization {
-        Personalization::default()
+    /// Construct a new personalization block for this message with a single `to` email address
+    pub fn new(email: Email) -> Personalization {
+        Personalization {
+            to: vec![email],
+            cc: None,
+            bcc: None,
+            subject: None,
+            headers: None,
+            substitutions: None,
+            custom_args: None,
+            dynamic_template_data: None,
+            send_at: None,
+        }
     }
 
     /// Add a to field.
@@ -288,65 +300,33 @@ impl Personalization {
 
     /// Add a CC field.
     pub fn add_cc(mut self, cc: Email) -> Personalization {
-        match self.cc {
-            None => {
-                let mut ccs = Vec::new();
-                ccs.push(cc);
-                self.cc = Some(ccs);
-            }
-            Some(ref mut c) => {
-                c.push(cc);
-            }
-        }
+        self.cc
+            .get_or_insert_with(|| Vec::with_capacity(1))
+            .push(cc);
         self
     }
 
     /// Add a BCC field.
     pub fn add_bcc(mut self, bcc: Email) -> Personalization {
-        match self.bcc {
-            None => {
-                let mut bccs = Vec::new();
-                bccs.push(bcc);
-                self.bcc = Some(bccs);
-            }
-            Some(ref mut b) => {
-                b.push(bcc);
-            }
-        }
+        self.bcc
+            .get_or_insert_with(|| Vec::with_capacity(1))
+            .push(bcc);
         self
     }
 
     /// Add a headers field.
     pub fn add_headers(mut self, headers: SGMap) -> Personalization {
-        match self.headers {
-            None => {
-                let mut h = HashMap::new();
-                for (name, value) in headers {
-                    h.insert(name, value);
-                }
-                self.headers = Some(h);
-            }
-            Some(ref mut h) => {
-                h.extend(headers);
-            }
-        }
+        self.headers
+            .get_or_insert_with(|| SGMap::with_capacity(headers.len()))
+            .extend(headers);
         self
     }
 
     /// Add a dynamic template data field.
     pub fn add_dynamic_template_data(mut self, dynamic_template_data: SGMap) -> Personalization {
-        match self.dynamic_template_data {
-            None => {
-                let mut h = HashMap::new();
-                for (name, value) in dynamic_template_data {
-                    h.insert(name, value);
-                }
-                self.dynamic_template_data = Some(h);
-            }
-            Some(ref mut h) => {
-                h.extend(dynamic_template_data);
-            }
-        }
+        self.dynamic_template_data
+            .get_or_insert_with(|| SGMap::with_capacity(dynamic_template_data.len()))
+            .extend(dynamic_template_data);
         self
     }
 }
@@ -364,27 +344,27 @@ impl Attachment {
     }
 
     /// The base64 body of the attachment.
-    pub fn set_base64_content(mut self, c: &str) -> Attachment {
-        self.content = String::from(c);
+    pub fn set_base64_content<S: Into<String>>(mut self, c: S) -> Attachment {
+        self.content = c.into();
         self
     }
 
     /// Sets the filename for the attachment.
-    pub fn set_filename(mut self, filename: &str) -> Attachment {
+    pub fn set_filename<S: Into<String>>(mut self, filename: S) -> Attachment {
         self.filename = filename.into();
         self
     }
 
     /// Set an optional mime type. Sendgrid will default to 'application/octet-stream'
     /// if unspecified.
-    pub fn set_mime_type(mut self, mime: &str) -> Attachment {
-        self.mime_type = Some(String::from(mime));
+    pub fn set_mime_type<S: Into<String>>(mut self, mime: S) -> Attachment {
+        self.mime_type = Some(mime.into());
         self
     }
 
     /// Set an optional content id.
-    pub fn set_content_id(mut self, content_id: &str) -> Attachment {
-        self.content_id = Some(String::from(content_id));
+    pub fn set_content_idm<S: Into<String>>(mut self, content_id: S) -> Attachment {
+        self.content_id = Some(content_id.into());
         self
     }
 }


Test patch: