diff --git a/examples/certificate_client.rs b/examples/certificate_client.rs index 9f08d8bb..5d5acc49 100644 --- a/examples/certificate_client.rs +++ b/examples/certificate_client.rs @@ -43,7 +43,11 @@ async fn main() -> Result<(), Box> { }; let mut certificate = std::fs::File::open(certificate_file)?; - Ok(Client::certificate(&mut certificate, &password, endpoint)?) + + // Create config with the given endpoint and default timeouts + let client_config = a2::ClientConfig::new(endpoint); + + Ok(Client::certificate(&mut certificate, &password, client_config)?) } #[cfg(all(not(feature = "openssl"), feature = "ring"))] { diff --git a/examples/token_client.rs b/examples/token_client.rs index b60cff02..747d0590 100644 --- a/examples/token_client.rs +++ b/examples/token_client.rs @@ -1,7 +1,9 @@ use argparse::{ArgumentParser, Store, StoreOption, StoreTrue}; use std::fs::File; -use a2::{Client, DefaultNotificationBuilder, Endpoint, NotificationBuilder, NotificationOptions}; +use a2::{ + client::ClientConfig, Client, DefaultNotificationBuilder, Endpoint, NotificationBuilder, NotificationOptions, +}; // An example client connectiong to APNs with a JWT token #[tokio::main] @@ -46,8 +48,11 @@ async fn main() -> Result<(), Box> { Endpoint::Production }; + // Create config with the given endpoint and default timeouts + let client_config = ClientConfig::new(endpoint); + // Connecting to APNs - let client = Client::token(&mut private_key, key_id, team_id, endpoint).unwrap(); + let client = Client::token(&mut private_key, key_id, team_id, client_config).unwrap(); let options = NotificationOptions { apns_topic: topic.as_deref(), diff --git a/src/client.rs b/src/client.rs index f5564682..9da3c8d4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -59,27 +59,48 @@ pub struct Client { http_client: HttpClient>, } -/// Uses [`Endpoint::Production`] by default. #[derive(Debug, Clone)] -pub struct ClientBuilder { +/// The default implementation uses [`Endpoint::Production`] and can be created +/// trough calling [`ClientConfig::default`]. +pub struct ClientConfig { + /// The endpoint where the requests are sent to + pub endpoint: Endpoint, /// The timeout of the HTTP requests pub request_timeout_secs: Option, /// The timeout for idle sockets being kept alive pub pool_idle_timeout_secs: Option, - /// The endpoint where the requests are sent to - pub endpoint: Endpoint, - /// See [`crate::signer::Signer`] - pub signer: Option, - /// The HTTPS connector used to connect to APNs - pub connector: Option, } -impl Default for ClientBuilder { +impl Default for ClientConfig { fn default() -> Self { Self { - pool_idle_timeout_secs: Some(600), - request_timeout_secs: Some(DEFAULT_REQUEST_TIMEOUT_SECS), endpoint: Endpoint::Production, + request_timeout_secs: Some(DEFAULT_REQUEST_TIMEOUT_SECS), + pool_idle_timeout_secs: Some(600), + } + } +} + +impl ClientConfig { + pub fn new(endpoint: Endpoint) -> Self { + ClientConfig { + endpoint, + ..Default::default() + } + } +} + +#[derive(Debug, Clone)] +struct ClientBuilder { + config: ClientConfig, + signer: Option, + connector: Option, +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self { + config: Default::default(), signer: None, connector: Some(default_connector()), } @@ -87,36 +108,29 @@ impl Default for ClientBuilder { } impl ClientBuilder { - pub fn connector(mut self, connector: HyperConnector) -> Self { + fn connector(mut self, connector: HyperConnector) -> Self { self.connector = Some(connector); self } - pub fn signer(mut self, signer: Signer) -> Self { + fn signer(mut self, signer: Signer) -> Self { self.signer = Some(signer); self } - pub fn request_timeout(mut self, seconds: u64) -> Self { - self.request_timeout_secs = Some(seconds); + fn config(mut self, config: ClientConfig) -> Self { + self.config = config; self } - pub fn pool_idle_timeout(mut self, seconds: u64) -> Self { - self.pool_idle_timeout_secs = Some(seconds); - self - } - - pub fn endpoint(mut self, endpoint: Endpoint) -> Self { - self.endpoint = endpoint; - self - } - - pub fn build(self) -> Client { + fn build(self) -> Client { let ClientBuilder { - request_timeout_secs, - pool_idle_timeout_secs, - endpoint, + config: + ClientConfig { + endpoint, + request_timeout_secs, + pool_idle_timeout_secs, + }, signer, connector, } = self; @@ -163,7 +177,7 @@ impl Client { /// /// Only works with the `openssl` feature. #[cfg(feature = "openssl")] - pub fn certificate(certificate: &mut R, password: &str, endpoint: Endpoint) -> Result + pub fn certificate(certificate: &mut R, password: &str, config: ClientConfig) -> Result where R: Read, { @@ -176,23 +190,23 @@ impl Client { }; let connector = client_cert_connector(&cert.to_pem()?, &pkey.private_key_to_pem_pkcs8()?)?; - Ok(Self::builder().connector(connector).endpoint(endpoint).build()) + Ok(Self::builder().connector(connector).config(config).build()) } /// Create a connection to APNs using the raw PEM-formatted certificate and /// key, extracted from the provider client certificate you obtain from your /// [Apple developer account](https://developer.apple.com/account/) - pub fn certificate_parts(cert_pem: &[u8], key_pem: &[u8], endpoint: Endpoint) -> Result { + pub fn certificate_parts(cert_pem: &[u8], key_pem: &[u8], config: ClientConfig) -> Result { let connector = client_cert_connector(cert_pem, key_pem)?; - Ok(Self::builder().endpoint(endpoint).connector(connector).build()) + Ok(Self::builder().config(config).connector(connector).build()) } /// Create a connection to APNs using system certificates, signing every /// request with a signature using a private key, key id and team id /// provisioned from your [Apple developer /// account](https://developer.apple.com/account/). - pub fn token(pkcs8_pem: R, key_id: S, team_id: T, endpoint: Endpoint) -> Result + pub fn token(pkcs8_pem: R, key_id: S, team_id: T, config: ClientConfig) -> Result where S: Into, T: Into, @@ -201,7 +215,7 @@ impl Client { let signature_ttl = Duration::from_secs(60 * 55); let signer = Signer::new(pkcs8_pem, key_id, team_id, signature_ttl)?; - Ok(Self::builder().endpoint(endpoint).signer(signer).build()) + Ok(Self::builder().config(config).signer(signer).build()) } /// Send a notification payload. @@ -349,7 +363,12 @@ jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ fn test_sandbox_request_uri() { let builder = DefaultNotificationBuilder::new(); let payload = builder.build("a_test_id", Default::default()); - let client = Client::builder().endpoint(Endpoint::Sandbox).build(); + let client = Client::builder() + .config(ClientConfig { + endpoint: Endpoint::Sandbox, + ..Default::default() + }) + .build(); let request = client.build_request(payload).unwrap(); let uri = format!("{}", request.uri()); @@ -370,7 +389,7 @@ jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ fn test_request_invalid() { let builder = DefaultNotificationBuilder::new(); let payload = builder.build("\r\n", Default::default()); - let client = Client::builder().endpoint(Endpoint::Production).build(); + let client = Client::builder().build(); let request = client.build_request(payload); assert!(matches!(request, Err(Error::BuildRequestError(_)))); @@ -420,7 +439,7 @@ jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ let builder = DefaultNotificationBuilder::new(); let payload = builder.build("a_test_id", Default::default()); - let client = Client::builder().endpoint(Endpoint::Production).signer(signer).build(); + let client = Client::builder().signer(signer).build(); let request = client.build_request(payload).unwrap(); assert_ne!(None, request.headers().get(AUTHORIZATION)); @@ -639,7 +658,7 @@ jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ let key: Vec = include_str!("../test_cert/test.key").bytes().collect(); let cert: Vec = include_str!("../test_cert/test.crt").bytes().collect(); - let c = Client::certificate_parts(&cert, &key, Endpoint::Sandbox)?; + let c = Client::certificate_parts(&cert, &key, ClientConfig::default())?; assert!(c.options.signer.is_none()); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index a59beb59..513fac04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! ## Example sending a plain notification using token authentication: //! //! ```no_run -//! # use a2::{DefaultNotificationBuilder, NotificationBuilder, Client, Endpoint}; +//! # use a2::{DefaultNotificationBuilder, NotificationBuilder, Client, ClientConfig, Endpoint}; //! # use std::fs::File; //! # #[tokio::main] //! # async fn main() -> Result<(), Box> { @@ -48,7 +48,7 @@ //! &mut file, //! "KEY_ID", //! "TEAM_ID", -//! Endpoint::Production).unwrap(); +//! ClientConfig::default()).unwrap(); //! //! let response = client.send(payload).await?; //! println!("Sent: {:?}", response); @@ -64,7 +64,7 @@ //! # { //! //! use a2::{ -//! Client, Endpoint, DefaultNotificationBuilder, NotificationBuilder, NotificationOptions, +//! Client, ClientConfig, Endpoint, DefaultNotificationBuilder, NotificationBuilder, NotificationOptions, //! Priority, //! }; //! use std::fs::File; @@ -97,7 +97,7 @@ //! let client = Client::certificate( //! &mut file, //! "Correct Horse Battery Stable", -//! Endpoint::Production)?; +//! ClientConfig::default())?; //! //! let response = client.send(payload).await?; //! println!("Sent: {:?}", response); @@ -131,6 +131,6 @@ pub use crate::request::notification::{ pub use crate::response::{ErrorBody, ErrorReason, Response}; -pub use crate::client::{Client, Endpoint}; +pub use crate::client::{Client, ClientConfig, Endpoint}; pub use crate::error::Error; diff --git a/src/request/payload.rs b/src/request/payload.rs index db509873..dcdcba16 100644 --- a/src/request/payload.rs +++ b/src/request/payload.rs @@ -27,11 +27,9 @@ pub struct Payload<'a> { /// /// # Example /// ```no_run -/// use a2::client::Endpoint; /// use a2::request::notification::{NotificationBuilder, NotificationOptions}; /// use a2::request::payload::{PayloadLike, APS}; -/// use a2::Client; -/// use a2::DefaultNotificationBuilder; +/// use a2::{Client, ClientConfig, DefaultNotificationBuilder, Endpoint}; /// use serde::Serialize; /// use std::fs::File; /// @@ -45,7 +43,7 @@ pub struct Payload<'a> { /// let payload = builder.build("device-token-from-the-user", Default::default()); /// let mut file = File::open("/path/to/private_key.p8")?; /// -/// let client = Client::token(&mut file, "KEY_ID", "TEAM_ID", Endpoint::Production).unwrap(); +/// let client = Client::token(&mut file, "KEY_ID", "TEAM_ID", ClientConfig::default()).unwrap(); /// /// let response = client.send(payload).await?; /// println!("Sent: {:?}", response);