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

Issue:
Update to stable JSON
Now that Rust stable supports custom derive, migrate away from the old build script that is currently in use.


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 7e8b20b..88a5351 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,25 +1,18 @@
 [package]
 name = "sendgrid"
-version = "0.4.3"
+version = "0.5.0"
 authors = ["Garrett Squire <garrettsquire@gmail.com>"]
 description = "An unofficial client library for the SendGrid API"
 repository = "https://github.com/gsquire/sendgrid-rs"
 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.9", optional = true }
+documentation = "https://docs.rs/sendgrid"
 
 [dependencies]
 hyper = "0.10"
 hyper-native-tls = "0.2"
-rustc-serialize = "0.3"
 serde = "0.9"
+serde_derive = "0.9"
 serde_json = "0.9"
 url = "1.2"
-
-[features]
-default = ["serde_codegen"]
diff --git a/README.md b/README.md
index 16f4e03..3952e05 100644
--- a/README.md
+++ b/README.md
@@ -3,23 +3,29 @@ Unofficial Rust library for the SendGrid API.
 
 [![Build Status](https://travis-ci.org/gsquire/sendgrid-rs.svg?branch=master)](https://travis-ci.org/gsquire/sendgrid-rs)
 
+This crate requires Rust 1.15 or higher as it uses a crate that has a custom derive implementation.
+
 sendgrid-rs implements all of the functionality of other supported SendGrid client libraries.
-To use sendgrid-rs you must first create a SendGrid account and generate an API key. To create an API key for your SendGrid account, use the account management interface or see the [SendGrid API Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/API_Keys/index.html).
+To use sendgrid-rs you must first create a SendGrid account and generate an API key. To create an API
+key for your SendGrid account, use the account management interface or see the
+[SendGrid API Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/API_Keys/index.html).
 
 sendgrid-rs is available on [crates.io](https://crates.io/crates/sendgrid) and can be included in your Cargo.toml as follows:
 
 ```toml
 [dependencies]
-sendgrid = "0.4"
+sendgrid = "X.X.X"
 ```
 
 ## Build Dependencies
-
-This library utilises [hyper](https://crates.io/crates/hyper), which in turn requires the OpenSSL headers to be available during compilation. For more information on how to configure OpenSSL, see: [rust-openssl](https://github.com/sfackler/rust-openssl)
+This library utilises [hyper](https://crates.io/crates/hyper) and [hyper-native-tls](https://crates.io/crates/hyper-native-tls).
+This crate enables easy TLS setup for mac OS and Windows users. If you are on Linux, you
+must have OpenSSL installed. [The instructions here](https://github.com/sfackler/rust-openssl) are
+the most comprehensive if you have trouble.
 
 ## Example
-
-An example of using this library can be found in the examples directory. This example code expects to find your SendGrid API key in the process environment. In shells such as Bash or ZSH this can be set as follows:
+An example of using this library can be found in the examples directory. This example code expects to
+find your SendGrid API key in the process environment. In shells such as Bash or ZSH this can be set as follows:
 
 ```shell
 export SENDGRID_API_KEY="SG.my.api.key"
diff --git a/build.rs b/build.rs
deleted file mode 100644
index 43e5235..0000000
--- a/build.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-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/src/lib.rs b/src/lib.rs
index 51760c7..0064aab 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,10 @@
+#[macro_use]
+extern crate serde_derive;
+
 extern crate hyper;
 extern crate hyper_native_tls;
-extern crate rustc_serialize;
+extern crate serde;
+extern crate serde_json;
 extern crate url;
 
 pub mod sg_client;
diff --git a/src/mail.rs b/src/mail.rs
index 1f97970..3c9437a 100644
--- a/src/mail.rs
+++ b/src/mail.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
 use std::fs::File;
 use std::io::Read;
 
-use rustc_serialize::json;
+use serde_json;
 
 #[derive(Debug)]
 /// This is a representation of a valid SendGrid message. It has support for
@@ -142,7 +142,7 @@ impl Mail {
 
     /// Used internally for string encoding. Not needed for message building.
     pub fn make_header_string(&mut self) -> String {
-        let headers = json::encode(&self.headers);
+        let headers = serde_json::to_string(&self.headers);
         match headers {
             Ok(h) => h,
             Err(e) => { panic!("Could not encode headers: {:?}", e); }
diff --git a/src/v3.rs b/src/v3.rs
index b82566c..f28cba9 100644
--- a/src/v3.rs
+++ b/src/v3.rs
@@ -1,4 +1,219 @@
-extern crate serde;
-extern crate serde_json;
+use std::collections::HashMap;
 
-include!(concat!(env!("OUT_DIR"), "/v3.rs"));
+use hyper::Client;
+use hyper::error::Error;
+use hyper::net::HttpsConnector;
+use hyper::header::{Authorization, Bearer, ContentType, Headers, UserAgent};
+use hyper::mime::{Mime, TopLevel, SubLevel};
+use hyper::status::StatusCode;
+
+use hyper_native_tls::NativeTlsClient;
+
+use serde_json;
+
+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 ssl = NativeTlsClient::new().unwrap();
+        let connector = HttpsConnector::new(ssl);
+        let client = Client::with_connector(connector);
+        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); }
+        }
+    }
+}
diff --git a/src/v3.rs.in b/src/v3.rs.in
deleted file mode 100644
index 0e255c2..0000000
--- a/src/v3.rs.in
+++ /dev/null
@@ -1,217 +0,0 @@
-use std::collections::HashMap;
-
-use hyper::Client;
-use hyper::error::Error;
-use hyper::net::HttpsConnector;
-use hyper::header::{Authorization, Bearer, ContentType, Headers, UserAgent};
-use hyper::mime::{Mime, TopLevel, SubLevel};
-use hyper::status::StatusCode;
-
-use hyper_native_tls::NativeTlsClient;
-
-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 ssl = NativeTlsClient::new().unwrap();
-        let connector = HttpsConnector::new(ssl);
-        let client = Client::with_connector(connector);
-        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: