change the way sandbox is handled, allow to provide a mock server url

This commit is contained in:
Edgar 2022-09-28 16:25:44 +02:00
parent 556ec2edeb
commit 2b23f6a8db
No known key found for this signature in database
6 changed files with 47 additions and 41 deletions

View file

@ -1,12 +1,12 @@
[package] [package]
name = "paypal-rs" name = "paypal-rs"
version = "0.2.2" version = "0.2.3"
authors = ["Edgar <git@edgarluque.com>"] authors = ["Edgar <git@edgarluque.com>"]
description = "A library that wraps the paypal api asynchronously." description = "A library that wraps the paypal api asynchronously."
repository = "https://github.com/edg-l/paypal-rs/" repository = "https://github.com/edg-l/paypal-rs/"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
keywords = ["paypal", "api", "async"] keywords = ["paypal", "paypal-api"]
categories = ["api-bindings"] categories = ["api-bindings", "web-programming::http-client"]
documentation = "https://docs.rs/paypal-rs" documentation = "https://docs.rs/paypal-rs"
readme = "README.md" readme = "README.md"
edition = "2021" edition = "2021"
@ -14,7 +14,7 @@ edition = "2021"
[dependencies] [dependencies]
reqwest = { version = "0.11.12", default-features = false, features = ["json"] } reqwest = { version = "0.11.12", default-features = false, features = ["json"] }
serde = { version = "1.0.144", features = ["derive"] } serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.85" serde_json = "1.0.85"
serde_with = "2.0.1" serde_with = "2.0.1"
chrono = { version = "0.4.22", features = ["serde"] } chrono = { version = "0.4.22", features = ["serde"] }
@ -26,10 +26,11 @@ derive_builder = "0.11.2"
serde_qs = "0.10.1" serde_qs = "0.10.1"
[dev-dependencies] [dev-dependencies]
tokio = { version = "1.21.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] }
dotenv = "0.15.0" dotenv = "0.15.0"
anyhow = "1.0.65" anyhow = "1.0.65"
color-eyre = "0.6.2" color-eyre = "0.6.2"
wiremock = "0.5.14"
[features] [features]
default = ["reqwest/native-tls"] default = ["reqwest/native-tls"]

View file

@ -1,6 +1,6 @@
use color_eyre::Result; use color_eyre::Result;
use paypal_rs::data::invoice::*; use paypal_rs::data::invoice::*;
use paypal_rs::{api::invoice::*, data::common::Money}; use paypal_rs::{api::invoice::*, data::common::Money, PaypalEnv};
use paypal_rs::{data::common::Currency, Client}; use paypal_rs::{data::common::Currency, Client};
#[tokio::main] #[tokio::main]
@ -11,7 +11,7 @@ async fn main() -> Result<()> {
let clientid = std::env::var("PAYPAL_CLIENTID")?; let clientid = std::env::var("PAYPAL_CLIENTID")?;
let secret = std::env::var("PAYPAL_SECRET")?; let secret = std::env::var("PAYPAL_SECRET")?;
let mut client = Client::new(clientid, secret, true); let mut client = Client::new(clientid, secret, PaypalEnv::Sandbox);
client.get_access_token().await?; client.get_access_token().await?;
let payload = InvoicePayloadBuilder::default() let payload = InvoicePayloadBuilder::default()

View file

@ -401,7 +401,7 @@ mod tests {
let clientid = std::env::var("PAYPAL_CLIENTID").unwrap(); let clientid = std::env::var("PAYPAL_CLIENTID").unwrap();
let secret = std::env::var("PAYPAL_SECRET").unwrap(); let secret = std::env::var("PAYPAL_SECRET").unwrap();
let mut client = Client::new(clientid, secret, true); let mut client = Client::new(clientid, secret, crate::PaypalEnv::Sandbox);
client.get_access_token().await.unwrap(); client.get_access_token().await.unwrap();
client client
} }

View file

@ -14,7 +14,7 @@ use crate::{
/// Represents the access token returned by the OAuth2 authentication. /// Represents the access token returned by the OAuth2 authentication.
/// ///
/// <https://developer.paypal.com/docs/api/get-an-access-token-postman/> /// <https://developer.paypal.com/docs/api/get-an-access-token-postman/>
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AccessToken { pub struct AccessToken {
/// The OAuth2 scopes. /// The OAuth2 scopes.
pub scope: String, pub scope: String,
@ -31,7 +31,7 @@ pub struct AccessToken {
} }
/// Stores OAuth2 information. /// Stores OAuth2 information.
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Auth { pub struct Auth {
/// Your client id. /// Your client id.
pub client_id: String, pub client_id: String,
@ -44,16 +44,44 @@ pub struct Auth {
} }
/// Represents a client used to interact with the paypal api. /// Represents a client used to interact with the paypal api.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Client { pub struct Client {
/// Internal http client /// Internal http client
pub(crate) client: reqwest::Client, pub(crate) client: reqwest::Client,
/// Whether you are or not in a sandbox enviroment. /// Whether you are or not in a sandbox enviroment.
pub sandbox: bool, pub env: PaypalEnv,
/// Api Auth information /// Api Auth information
pub auth: Auth, pub auth: Auth,
} }
/// The paypal api environment.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PaypalEnv {
/// The live environment.
Live,
/// The sandbox environment.
Sandbox,
/// For mocking.
Mock(String),
}
impl PaypalEnv {
/// Returns the endpoint of this environment.
pub fn endpoint(&self) -> &str {
match &self {
PaypalEnv::Live => LIVE_ENDPOINT,
PaypalEnv::Sandbox => SANDBOX_ENDPOINT,
PaypalEnv::Mock(endpoint) => endpoint.as_str(),
}
}
/// Constructs a url from the target.
pub fn make_url(&self, target: &str) -> String {
assert!(target.starts_with('/'), "target path must start with '/'");
format!("{}{}", self.endpoint(), target)
}
}
impl Client { impl Client {
/// Returns a new client, you must get_access_token afterwards to interact with the api. /// Returns a new client, you must get_access_token afterwards to interact with the api.
/// ///
@ -76,10 +104,10 @@ impl Client {
/// client.get_access_token().await.unwrap(); /// client.get_access_token().await.unwrap();
/// } /// }
/// ``` /// ```
pub fn new(client_id: String, secret: String, sandbox: bool) -> Client { pub fn new(client_id: String, secret: String, env: PaypalEnv) -> Client {
Client { Client {
client: reqwest::Client::new(), client: reqwest::Client::new(),
sandbox, env,
auth: Auth { auth: Auth {
client_id, client_id,
secret, secret,
@ -89,14 +117,6 @@ impl Client {
} }
} }
fn endpoint(&self) -> &str {
if self.sandbox {
SANDBOX_ENDPOINT
} else {
LIVE_ENDPOINT
}
}
/// Sets up the request headers as required on https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers /// Sets up the request headers as required on https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers
async fn setup_headers( async fn setup_headers(
&self, &self,
@ -158,7 +178,7 @@ impl Client {
} }
let res = self let res = self
.client .client
.post(format!("{}/v1/oauth2/token", self.endpoint()).as_str()) .post(self.env.make_url("/v1/oauth2/token"))
.basic_auth(&self.auth.client_id, Some(&self.auth.secret)) .basic_auth(&self.auth.client_id, Some(&self.auth.secret))
.header("Content-Type", "x-www-form-urlencoded") .header("Content-Type", "x-www-form-urlencoded")
.header("Accept", "application/json") .header("Accept", "application/json")
@ -193,7 +213,7 @@ impl Client {
where where
E: Endpoint, E: Endpoint,
{ {
let mut url = endpoint.full_path(self.sandbox); let mut url = self.env.make_url(&endpoint.relative_path());
if let Some(query) = endpoint.query() { if let Some(query) = endpoint.query() {
let query_string = serde_qs::to_string(&query).expect("serialize the query correctly"); let query_string = serde_qs::to_string(&query).expect("serialize the query correctly");

View file

@ -1,6 +1,6 @@
//! This module contains the endpoint trait used to implemented api endpoints. //! This module contains the endpoint trait used to implemented api endpoints.
use crate::{LIVE_ENDPOINT, SANDBOX_ENDPOINT}; use crate::{LIVE_ENDPOINT, SANDBOX_ENDPOINT, PaypalEnv};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
@ -28,19 +28,4 @@ pub trait Endpoint {
fn body(&self) -> Option<Self::Body> { fn body(&self) -> Option<Self::Body> {
None None
} }
/// The full path of this endpoint.
///
/// Automatically implemented.
fn full_path(&self, is_sandbox: bool) -> String {
let relative_path = self.relative_path();
assert!(relative_path.starts_with('/'), "relative path must start with '/'");
if is_sandbox {
format!("{}{}", SANDBOX_ENDPOINT, relative_path)
} else {
format!("{}{}", LIVE_ENDPOINT, relative_path)
}
}
} }

View file

@ -184,7 +184,7 @@ mod tests {
let clientid = env::var("PAYPAL_CLIENTID").unwrap(); let clientid = env::var("PAYPAL_CLIENTID").unwrap();
let secret = env::var("PAYPAL_SECRET").unwrap(); let secret = env::var("PAYPAL_SECRET").unwrap();
Client::new(clientid, secret, true) Client::new(clientid, secret, crate::PaypalEnv::Sandbox)
} }
#[test] #[test]