mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
fix: percentage float inconsistency problem and api models changes to support surcharge feature (#2550)
This commit is contained in:
@ -21,6 +21,7 @@ mime = "0.3.17"
|
||||
reqwest = { version = "0.11.18", optional = true }
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
serde_json = "1.0.96"
|
||||
serde_with = "3.0.0"
|
||||
strum = { version = "0.24.1", features = ["derive"] }
|
||||
time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] }
|
||||
url = { version = "2.4.0", features = ["serde"] }
|
||||
|
||||
@ -16,6 +16,5 @@ pub mod payments;
|
||||
#[cfg(feature = "payouts")]
|
||||
pub mod payouts;
|
||||
pub mod refunds;
|
||||
pub mod types;
|
||||
pub mod verifications;
|
||||
pub mod webhooks;
|
||||
|
||||
@ -3,8 +3,10 @@ use std::collections::HashMap;
|
||||
use cards::CardNumber;
|
||||
use common_utils::{
|
||||
consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, pii,
|
||||
types::Percentage,
|
||||
};
|
||||
use serde::de;
|
||||
use serde_with::serde_as;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -12,7 +14,6 @@ use crate::payouts;
|
||||
use crate::{
|
||||
admin, enums as api_enums,
|
||||
payments::{self, BankCodeResponse},
|
||||
types::Percentage,
|
||||
};
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
@ -251,12 +252,28 @@ pub struct PaymentExperienceTypes {
|
||||
pub eligible_connectors: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)]
|
||||
pub struct CardNetworkTypes {
|
||||
/// The card network enabled
|
||||
#[schema(value_type = Option<CardNetwork>, example = "Visa")]
|
||||
pub card_network: api_enums::CardNetwork,
|
||||
|
||||
/// surcharge details for this card network
|
||||
#[schema(example = r#"
|
||||
{
|
||||
"surcharge": {
|
||||
"type": "rate",
|
||||
"value": {
|
||||
"percentage": 2.5
|
||||
}
|
||||
},
|
||||
"tax_on_surcharge": {
|
||||
"percentage": 1.5
|
||||
}
|
||||
}
|
||||
"#)]
|
||||
pub surcharge_details: Option<SurchargeDetailsResponse>,
|
||||
|
||||
/// The list of eligible connectors for a given card network
|
||||
#[schema(example = json!(["stripe", "adyen"]))]
|
||||
pub eligible_connectors: Vec<String>,
|
||||
@ -304,18 +321,31 @@ pub struct ResponsePaymentMethodTypes {
|
||||
}
|
||||
}
|
||||
"#)]
|
||||
pub surcharge_details: Option<SurchargeDetails>,
|
||||
pub surcharge_details: Option<SurchargeDetailsResponse>,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct SurchargeDetails {
|
||||
pub struct SurchargeDetailsResponse {
|
||||
/// surcharge value
|
||||
surcharge: Surcharge,
|
||||
pub surcharge: Surcharge,
|
||||
/// tax on surcharge value
|
||||
tax_on_surcharge: Option<Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>>,
|
||||
pub tax_on_surcharge: Option<Percentage<SURCHARGE_PERCENTAGE_PRECISION_LENGTH>>,
|
||||
/// surcharge amount for this payment
|
||||
pub surcharge_amount: i64,
|
||||
/// tax on surcharge amount for this payment
|
||||
pub tax_on_surcharge_amount: i64,
|
||||
/// sum of original amount,
|
||||
pub final_amount: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SurchargeMetadata {
|
||||
#[serde_as(as = "HashMap<_, _>")]
|
||||
pub surcharge_results: HashMap<String, SurchargeDetailsResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
|
||||
pub enum Surcharge {
|
||||
/// Fixed Surcharge value
|
||||
|
||||
@ -795,6 +795,168 @@ pub enum PaymentMethodData {
|
||||
GiftCard(Box<GiftCardData>),
|
||||
}
|
||||
|
||||
pub trait GetPaymentMethodType {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType;
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for CardRedirectData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::Knet {} => api_enums::PaymentMethodType::Knet,
|
||||
Self::Benefit {} => api_enums::PaymentMethodType::Benefit,
|
||||
Self::MomoAtm {} => api_enums::PaymentMethodType::MomoAtm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for WalletData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::AliPayQr(_) | Self::AliPayRedirect(_) => api_enums::PaymentMethodType::AliPay,
|
||||
Self::AliPayHkRedirect(_) => api_enums::PaymentMethodType::AliPayHk,
|
||||
Self::MomoRedirect(_) => api_enums::PaymentMethodType::Momo,
|
||||
Self::KakaoPayRedirect(_) => api_enums::PaymentMethodType::KakaoPay,
|
||||
Self::GoPayRedirect(_) => api_enums::PaymentMethodType::GoPay,
|
||||
Self::GcashRedirect(_) => api_enums::PaymentMethodType::Gcash,
|
||||
Self::ApplePay(_) | Self::ApplePayRedirect(_) | Self::ApplePayThirdPartySdk(_) => {
|
||||
api_enums::PaymentMethodType::ApplePay
|
||||
}
|
||||
Self::DanaRedirect {} => api_enums::PaymentMethodType::Dana,
|
||||
Self::GooglePay(_) | Self::GooglePayRedirect(_) | Self::GooglePayThirdPartySdk(_) => {
|
||||
api_enums::PaymentMethodType::GooglePay
|
||||
}
|
||||
Self::MbWayRedirect(_) => api_enums::PaymentMethodType::MbWay,
|
||||
Self::MobilePayRedirect(_) => api_enums::PaymentMethodType::MobilePay,
|
||||
Self::PaypalRedirect(_) | Self::PaypalSdk(_) => api_enums::PaymentMethodType::Paypal,
|
||||
Self::SamsungPay(_) => api_enums::PaymentMethodType::SamsungPay,
|
||||
Self::TwintRedirect {} => api_enums::PaymentMethodType::Twint,
|
||||
Self::VippsRedirect {} => api_enums::PaymentMethodType::Vipps,
|
||||
Self::TouchNGoRedirect(_) => api_enums::PaymentMethodType::TouchNGo,
|
||||
Self::WeChatPayRedirect(_) | Self::WeChatPayQr(_) => {
|
||||
api_enums::PaymentMethodType::WeChatPay
|
||||
}
|
||||
Self::CashappQr(_) => api_enums::PaymentMethodType::Cashapp,
|
||||
Self::SwishQr(_) => api_enums::PaymentMethodType::Swish,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for PayLaterData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna,
|
||||
Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna,
|
||||
Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm,
|
||||
Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay,
|
||||
Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright,
|
||||
Self::WalleyRedirect {} => api_enums::PaymentMethodType::Walley,
|
||||
Self::AlmaRedirect {} => api_enums::PaymentMethodType::Alma,
|
||||
Self::AtomeRedirect {} => api_enums::PaymentMethodType::Atome,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for BankRedirectData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::BancontactCard { .. } => api_enums::PaymentMethodType::BancontactCard,
|
||||
Self::Bizum {} => api_enums::PaymentMethodType::Bizum,
|
||||
Self::Blik { .. } => api_enums::PaymentMethodType::Blik,
|
||||
Self::Eps { .. } => api_enums::PaymentMethodType::Eps,
|
||||
Self::Giropay { .. } => api_enums::PaymentMethodType::Giropay,
|
||||
Self::Ideal { .. } => api_enums::PaymentMethodType::Ideal,
|
||||
Self::Interac { .. } => api_enums::PaymentMethodType::Interac,
|
||||
Self::OnlineBankingCzechRepublic { .. } => {
|
||||
api_enums::PaymentMethodType::OnlineBankingCzechRepublic
|
||||
}
|
||||
Self::OnlineBankingFinland { .. } => api_enums::PaymentMethodType::OnlineBankingFinland,
|
||||
Self::OnlineBankingPoland { .. } => api_enums::PaymentMethodType::OnlineBankingPoland,
|
||||
Self::OnlineBankingSlovakia { .. } => {
|
||||
api_enums::PaymentMethodType::OnlineBankingSlovakia
|
||||
}
|
||||
Self::OpenBankingUk { .. } => api_enums::PaymentMethodType::OpenBankingUk,
|
||||
Self::Przelewy24 { .. } => api_enums::PaymentMethodType::Przelewy24,
|
||||
Self::Sofort { .. } => api_enums::PaymentMethodType::Sofort,
|
||||
Self::Trustly { .. } => api_enums::PaymentMethodType::Trustly,
|
||||
Self::OnlineBankingFpx { .. } => api_enums::PaymentMethodType::OnlineBankingFpx,
|
||||
Self::OnlineBankingThailand { .. } => {
|
||||
api_enums::PaymentMethodType::OnlineBankingThailand
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for BankDebitData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::AchBankDebit { .. } => api_enums::PaymentMethodType::Ach,
|
||||
Self::SepaBankDebit { .. } => api_enums::PaymentMethodType::Sepa,
|
||||
Self::BecsBankDebit { .. } => api_enums::PaymentMethodType::Becs,
|
||||
Self::BacsBankDebit { .. } => api_enums::PaymentMethodType::Bacs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for BankTransferData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::AchBankTransfer { .. } => api_enums::PaymentMethodType::Ach,
|
||||
Self::SepaBankTransfer { .. } => api_enums::PaymentMethodType::Sepa,
|
||||
Self::BacsBankTransfer { .. } => api_enums::PaymentMethodType::Bacs,
|
||||
Self::MultibancoBankTransfer { .. } => api_enums::PaymentMethodType::Multibanco,
|
||||
Self::PermataBankTransfer { .. } => api_enums::PaymentMethodType::PermataBankTransfer,
|
||||
Self::BcaBankTransfer { .. } => api_enums::PaymentMethodType::BcaBankTransfer,
|
||||
Self::BniVaBankTransfer { .. } => api_enums::PaymentMethodType::BniVa,
|
||||
Self::BriVaBankTransfer { .. } => api_enums::PaymentMethodType::BriVa,
|
||||
Self::CimbVaBankTransfer { .. } => api_enums::PaymentMethodType::CimbVa,
|
||||
Self::DanamonVaBankTransfer { .. } => api_enums::PaymentMethodType::DanamonVa,
|
||||
Self::MandiriVaBankTransfer { .. } => api_enums::PaymentMethodType::MandiriVa,
|
||||
Self::Pix {} => api_enums::PaymentMethodType::Pix,
|
||||
Self::Pse {} => api_enums::PaymentMethodType::Pse,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for CryptoData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
api_enums::PaymentMethodType::CryptoCurrency
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodType for UpiData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
api_enums::PaymentMethodType::UpiCollect
|
||||
}
|
||||
}
|
||||
impl GetPaymentMethodType for VoucherData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::Boleto(_) => api_enums::PaymentMethodType::Boleto,
|
||||
Self::Efecty => api_enums::PaymentMethodType::Efecty,
|
||||
Self::PagoEfectivo => api_enums::PaymentMethodType::PagoEfectivo,
|
||||
Self::RedCompra => api_enums::PaymentMethodType::RedCompra,
|
||||
Self::RedPagos => api_enums::PaymentMethodType::RedPagos,
|
||||
Self::Alfamart(_) => api_enums::PaymentMethodType::Alfamart,
|
||||
Self::Indomaret(_) => api_enums::PaymentMethodType::Indomaret,
|
||||
Self::Oxxo => api_enums::PaymentMethodType::Oxxo,
|
||||
Self::SevenEleven(_) => api_enums::PaymentMethodType::SevenEleven,
|
||||
Self::Lawson(_) => api_enums::PaymentMethodType::Lawson,
|
||||
Self::MiniStop(_) => api_enums::PaymentMethodType::MiniStop,
|
||||
Self::FamilyMart(_) => api_enums::PaymentMethodType::FamilyMart,
|
||||
Self::Seicomart(_) => api_enums::PaymentMethodType::Seicomart,
|
||||
Self::PayEasy(_) => api_enums::PaymentMethodType::PayEasy,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl GetPaymentMethodType for GiftCardData {
|
||||
fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
|
||||
match self {
|
||||
Self::Givex(_) => api_enums::PaymentMethodType::Givex,
|
||||
Self::PaySafeCard {} => api_enums::PaymentMethodType::PaySafeCard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, ToSchema, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GiftCardData {
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
use common_utils::errors::{ApiModelsError, CustomResult};
|
||||
use error_stack::ResultExt;
|
||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)]
|
||||
pub struct Percentage<const PRECISION: u8> {
|
||||
// this value will range from 0 to 100, decimal length defined by precision macro
|
||||
/// Percentage value ranging between 0 and 100
|
||||
#[schema(example = 2.5)]
|
||||
percentage: f32,
|
||||
}
|
||||
|
||||
fn get_invalid_percentage_error_message(precision: u8) -> String {
|
||||
format!(
|
||||
"value should be between 0 to 100 and precise to only upto {} decimal digits",
|
||||
precision
|
||||
)
|
||||
}
|
||||
|
||||
impl<const PRECISION: u8> Percentage<PRECISION> {
|
||||
pub fn from_float(value: f32) -> CustomResult<Self, ApiModelsError> {
|
||||
if Self::is_valid_value(value) {
|
||||
Ok(Self { percentage: value })
|
||||
} else {
|
||||
Err(ApiModelsError::InvalidPercentageValue.into())
|
||||
.attach_printable(get_invalid_percentage_error_message(PRECISION))
|
||||
}
|
||||
}
|
||||
pub fn get_percentage(&self) -> f32 {
|
||||
self.percentage
|
||||
}
|
||||
fn is_valid_value(value: f32) -> bool {
|
||||
Self::is_valid_range(value) && Self::is_valid_precision_length(value)
|
||||
}
|
||||
fn is_valid_range(value: f32) -> bool {
|
||||
(0.0..=100.0).contains(&value)
|
||||
}
|
||||
fn is_valid_precision_length(value: f32) -> bool {
|
||||
let multiplier = f32::powf(10.0, PRECISION.into());
|
||||
let multiplied_value = value * multiplier;
|
||||
// if fraction part is 0, then the percentage value is valid
|
||||
multiplied_value.fract() == 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// custom serde deserialization function
|
||||
struct PercentageVisitor<const PRECISION: u8> {}
|
||||
impl<'de, const PRECISION: u8> Visitor<'de> for PercentageVisitor<PRECISION> {
|
||||
type Value = Percentage<PRECISION>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("Percentage object")
|
||||
}
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let mut percentage_value = None;
|
||||
while let Some(key) = map.next_key::<String>()? {
|
||||
if key.eq("percentage") {
|
||||
if percentage_value.is_some() {
|
||||
return Err(serde::de::Error::duplicate_field("percentage"));
|
||||
}
|
||||
percentage_value = Some(map.next_value::<f32>()?);
|
||||
} else {
|
||||
// Ignore unknown fields
|
||||
let _: serde::de::IgnoredAny = map.next_value()?;
|
||||
}
|
||||
}
|
||||
if let Some(value) = percentage_value {
|
||||
let str_value = value.to_string();
|
||||
Ok(Percentage::from_float(value).map_err(|_| {
|
||||
serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Other(&format!("percentage value `{}`", str_value)),
|
||||
&&*get_invalid_percentage_error_message(PRECISION),
|
||||
)
|
||||
})?)
|
||||
} else {
|
||||
Err(serde::de::Error::missing_field("percentage"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const PRECISION: u8> Deserialize<'de> for Percentage<PRECISION> {
|
||||
fn deserialize<D>(data: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
data.deserialize_map(PercentageVisitor::<PRECISION> {})
|
||||
}
|
||||
}
|
||||
@ -1,156 +0,0 @@
|
||||
#![allow(clippy::panic_in_result_fn)]
|
||||
use api_models::types::Percentage;
|
||||
use common_utils::errors::ApiModelsError;
|
||||
const PRECISION_2: u8 = 2;
|
||||
const PRECISION_0: u8 = 0;
|
||||
|
||||
#[test]
|
||||
fn invalid_range_more_than_100() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(100.01);
|
||||
assert!(percentage.is_err());
|
||||
if let Err(err) = percentage {
|
||||
assert_eq!(
|
||||
*err.current_context(),
|
||||
ApiModelsError::InvalidPercentageValue
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn invalid_range_less_than_0() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(-0.01);
|
||||
assert!(percentage.is_err());
|
||||
if let Err(err) = percentage {
|
||||
assert_eq!(
|
||||
*err.current_context(),
|
||||
ApiModelsError::InvalidPercentageValue
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn valid_range() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(2.22);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 2.22)
|
||||
}
|
||||
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(0.0);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 0.0)
|
||||
}
|
||||
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(100.0);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 100.0)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn valid_precision() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(2.2);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 2.2)
|
||||
}
|
||||
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(2.20000);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 2.2)
|
||||
}
|
||||
|
||||
let percentage = Percentage::<PRECISION_0>::from_float(2.0);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 2.0)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_precision() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let percentage = Percentage::<PRECISION_2>::from_float(2.221);
|
||||
assert!(percentage.is_err());
|
||||
if let Err(err) = percentage {
|
||||
assert_eq!(
|
||||
*err.current_context(),
|
||||
ApiModelsError::InvalidPercentageValue
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialization_test_ok() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let json_string = r#"
|
||||
{
|
||||
"percentage" : 12.4
|
||||
}
|
||||
"#;
|
||||
let percentage = serde_json::from_str::<Percentage<PRECISION_2>>(json_string);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 12.4)
|
||||
}
|
||||
|
||||
let json_string = r#"
|
||||
{
|
||||
"percentage" : 12.0
|
||||
}
|
||||
"#;
|
||||
let percentage = serde_json::from_str::<Percentage<PRECISION_0>>(json_string);
|
||||
assert!(percentage.is_ok());
|
||||
if let Ok(percentage) = percentage {
|
||||
assert_eq!(percentage.get_percentage(), 12.0)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialization_test_err() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// invalid percentage precision
|
||||
let json_string = r#"
|
||||
{
|
||||
"percentage" : 12.4
|
||||
}
|
||||
"#;
|
||||
let percentage = serde_json::from_str::<Percentage<PRECISION_0>>(json_string);
|
||||
assert!(percentage.is_err());
|
||||
if let Err(err) = percentage {
|
||||
assert_eq!(err.to_string(), "invalid value: percentage value `12.4`, expected value should be between 0 to 100 and precise to only upto 0 decimal digits at line 4 column 9".to_string())
|
||||
}
|
||||
|
||||
// invalid percentage value
|
||||
let json_string = r#"
|
||||
{
|
||||
"percentage" : 123.42
|
||||
}
|
||||
"#;
|
||||
let percentage = serde_json::from_str::<Percentage<PRECISION_2>>(json_string);
|
||||
assert!(percentage.is_err());
|
||||
if let Err(err) = percentage {
|
||||
assert_eq!(err.to_string(), "invalid value: percentage value `123.42`, expected value should be between 0 to 100 and precise to only upto 2 decimal digits at line 4 column 9".to_string())
|
||||
}
|
||||
|
||||
// missing percentage field
|
||||
let json_string = r#"
|
||||
{
|
||||
"percent": 22.0
|
||||
}
|
||||
"#;
|
||||
let percentage = serde_json::from_str::<Percentage<PRECISION_2>>(json_string);
|
||||
assert!(percentage.is_err());
|
||||
if let Err(err) = percentage {
|
||||
dbg!(err.to_string());
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"missing field `percentage` at line 4 column 9".to_string()
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user