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

Issue:
Upgrade to SendGrid v3 Web API
https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html
https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html

Thanks!



Discussion / hints:
I was going to make a tracking issue for V3, so thanks for adding this. I will start reading up on what needs to get done for this library.

Could use this for JSON: https://github.com/maciejhirsz/json-rust


Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 57f8a49..4662dd7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "sendgrid"
-version = "0.3.0"
+version = "0.4.0"
 authors = ["Garrett Squire <garrettsquire@gmail.com>"]
 description = "An unofficial client library for the SendGrid API"
 repository = "https://github.com/gsquire/sendgrid-rs"
@@ -8,13 +8,17 @@ license = "MIT"
 homepage = "https://sendgrid.com"
 keywords = ["email"]
 documentation = "https://gsquire.github.io/doc/sendgrid/sendgrid"
+build = "build.rs"
+
+[build-dependencies]
+serde_codegen = { version = "0.8", optional = true }
 
 [dependencies]
 hyper = "0.9"
 rustc-serialize = "0.3"
+serde = "0.8"
+serde_json = "0.8"
 url = "1.1.1"
 
-[[bin]]
-name = "main"
-doc = false
-path = "src/main.rs"
+[features]
+default = ["serde_codegen"]
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..43e5235
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,13 @@
+extern crate serde_codegen;
+
+use std::env;
+use std::path::Path;
+
+pub fn main() {
+    let out_dir = env::var_os("OUT_DIR").unwrap();
+
+    let src = Path::new("src/v3.rs.in");
+    let dst = Path::new(&out_dir).join("v3.rs");
+
+    serde_codegen::expand(&src, &dst).unwrap();
+}
diff --git a/examples/v3.rs b/examples/v3.rs
new file mode 100644
index 0000000..129f824
--- /dev/null
+++ b/examples/v3.rs
@@ -0,0 +1,28 @@
+extern crate sendgrid;
+
+use sendgrid::v3::*;
+
+fn main() {
+	let mut m = SGMailV3::new();
+
+	let mut e = Email::new();
+	e.set_email("g@gmail.com");
+
+	let mut c = Content::new();
+	c.set_content_type("text/html");
+	c.set_value("Test");
+
+	let mut p = Personalization::new();
+	p.add_to(e.clone());
+
+	m.set_from(e.clone());
+	m.set_subject("Subject");
+	m.add_content(c);
+	m.add_personalization(p);
+
+	let mut env_vars = ::std::env::vars();
+	let api_key = env_vars.find(|v| v.0 == "SG_API_KEY").unwrap();
+	let sender = V3Sender::new(api_key.1);
+	let code = sender.send(&m);
+	println!("{:?}", code);
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7b3c78b..64f8f53 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,3 +4,4 @@ extern crate url;
 
 pub mod sg_client;
 pub mod mail;
+pub mod v3;
diff --git a/src/mail/mod.rs b/src/mail.rs
similarity index 100%
rename from src/mail/mod.rs
rename to src/mail.rs
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index d4d009c..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
-    println!("See the examples folder!");
-}
diff --git a/src/sg_client/mod.rs b/src/sg_client.rs
similarity index 100%
rename from src/sg_client/mod.rs
rename to src/sg_client.rs
diff --git a/src/v3.rs b/src/v3.rs
new file mode 100644
index 0000000..b82566c
--- /dev/null
+++ b/src/v3.rs
@@ -0,0 +1,4 @@
+extern crate serde;
+extern crate serde_json;
+
+include!(concat!(env!("OUT_DIR"), "/v3.rs"));
diff --git a/src/v3.rs.in b/src/v3.rs.in
new file mode 100644
index 0000000..1e30b0d
--- /dev/null
+++ b/src/v3.rs.in
@@ -0,0 +1,212 @@
+use std::collections::HashMap;
+
+use hyper::Client;
+use hyper::error::Error;
+use hyper::header::{Authorization, Bearer, ContentType, Headers, UserAgent};
+use hyper::mime::{Mime, TopLevel, SubLevel};
+use hyper::status::StatusCode;
+
+const V3_API_URL: &'static str = "https://api.sendgrid.com/v3/mail/send";
+
+/// Just a redefinition of a map to store string keys and values.
+pub type SGMap = HashMap<String, String>;
+
+/// Used to send a V3 message body.
+pub struct V3Sender {
+    api_key: String
+}
+
+/// 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(Serialize)]
+pub struct SGMailV3 {
+    from: Email,
+    subject: String,
+    content: Vec<Content>,
+    personalizations: Vec<Personalization>,
+}
+
+/// An email with a required address and an optional name field.
+#[derive(Clone, Serialize)]
+pub struct Email {
+    email: String,
+
+    #[serde(skip_serializing_if="check_encode")]
+    name: Option<String>,
+}
+
+/// The body of an email with the content type and the message.
+#[derive(Clone, Serialize)]
+pub struct Content {
+    #[serde(rename="type")]
+    content_type: String,
+    value: String,
+}
+
+/// 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(Serialize)]
+pub struct Personalization {
+    to: Vec<Email>,
+
+    #[serde(skip_serializing_if="check_encode")]
+    cc: Option<Vec<Email>>,
+
+    #[serde(skip_serializing_if="check_encode")]
+    bcc: Option<Vec<Email>>,
+
+    #[serde(skip_serializing_if="check_encode")]
+    subject: Option<String>,
+
+    #[serde(skip_serializing_if="check_encode")]
+    headers: Option<SGMap>,
+
+    #[serde(skip_serializing_if="check_encode")]
+    substitutions: Option<SGMap>,
+
+    #[serde(skip_serializing_if="check_encode")]
+    custom_args: Option<SGMap>,
+
+    #[serde(skip_serializing_if="check_encode")]
+    send_at: Option<u64>,
+}
+
+// Checks if a value in the V3 message should be added to the JSON or not.
+fn check_encode<T>(value: &Option<T>) -> bool {
+    match *value {
+        Some(_) => false,
+        None => true
+    }
+}
+
+impl V3Sender {
+    /// Construct a new V3 message sender.
+    pub fn new(api_key: String) -> V3Sender {
+        V3Sender { api_key: api_key }
+    }
+
+    /// Send a V3 message and return the status code or an error from the request.
+    pub fn send(&self, mail: &SGMailV3) -> Result<StatusCode, Error> {
+        let client = Client::new();
+        let mut headers = Headers::new();
+        headers.set(Authorization(Bearer { token: self.api_key.to_owned() }));
+        headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, vec![])));
+        headers.set(UserAgent("sendgrid-rs".to_owned()));
+
+        let body = mail.gen_json();
+        let res = try!(client.post(V3_API_URL)
+                           .headers(headers)
+                           .body(&body)
+                           .send());
+        Ok(res.status)
+    }
+}
+
+impl SGMailV3 {
+    /// Construct a new V3 message.
+    pub fn new() -> SGMailV3 {
+        SGMailV3 {
+            from: Email::new(),
+            subject: String::new(),
+            content: Vec::new(),
+            personalizations: Vec::new(),
+        }
+    }
+
+    /// Set the from address.
+    pub fn set_from(&mut self, from: Email) {
+        self.from = from;
+    }
+
+    /// Set the subject.
+    pub fn set_subject(&mut self, subject: &str) {
+        self.subject = String::from(subject);
+    }
+
+    /// Add content to the message.
+    pub fn add_content(&mut self, content: Content) {
+        self.content.push(content);
+    }
+
+    /// Add a personalization to the message.
+    pub fn add_personalization(&mut self, p: Personalization) {
+        self.personalizations.push(p);
+    }
+
+    fn gen_json(&self) -> String {
+        serde_json::to_string(self).unwrap()
+    }
+}
+
+impl Email {
+    /// Construct a new email type.
+    pub fn new() -> Email {
+        Email {
+            email: String::new(),
+            name: None,
+        }
+    }
+
+    /// Set the address for this email.
+    pub fn set_email(&mut self, email: &str) {
+        self.email = String::from(email);
+    }
+
+    /// Set an optional name.
+    pub fn set_name(&mut self, name: &str) {
+        self.name = Some(String::from(name));
+    }
+}
+
+impl Content {
+    /// Construct a new content type.
+    pub fn new() -> Content {
+        Content {
+            content_type: String::new(),
+            value: String::new(),
+        }
+    }
+
+    /// Set the type of this content.
+    pub fn set_content_type(&mut self, content_type: &str) {
+        self.content_type = String::from(content_type);
+    }
+
+    /// Set the corresponding message for this content.
+    pub fn set_value(&mut self, value: &str) {
+        self.value = String::from(value);
+    }
+}
+
+impl Personalization {
+    /// Construct a new personalization block for this message.
+    pub fn new() -> Personalization {
+        Personalization {
+            to: Vec::new(),
+            cc: None,
+            bcc: None,
+            subject: None,
+            headers: None,
+            substitutions: None,
+            custom_args: None,
+            send_at: None,
+        }
+    }
+
+    /// Add a to field.
+    pub fn add_to(&mut self, to: Email) {
+        self.to.push(to);
+    }
+
+    /// Add a CC field.
+    pub fn add_cc(&mut self, cc: Email) {
+        match self.cc {
+            None => {
+                let mut ccs = Vec::new();
+                ccs.push(cc);
+                self.cc = Some(ccs);
+            },
+            Some(ref mut c) => { c.push(cc); }
+        }
+    }
+}


Test patch: