mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
refactor(payment_intent_v2): payment intent fields refactoring (#5880)
Co-authored-by: hrithikesh026 <hrithikesh.vm@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -147,5 +147,10 @@ pub const ROLE_ID_INTERNAL_VIEW_ONLY_USER: &str = "internal_view_only";
|
||||
/// Role ID for Internal Admin
|
||||
pub const ROLE_ID_INTERNAL_ADMIN: &str = "internal_admin";
|
||||
|
||||
/// Max length allowed for Description
|
||||
pub const MAX_DESCRIPTION_LENGTH: u16 = 255;
|
||||
|
||||
/// Max length allowed for Statement Descriptor
|
||||
pub const MAX_STATEMENT_DESCRIPTOR_LENGTH: u16 = 255;
|
||||
/// Payout flow identifier used for performing GSM operations
|
||||
pub const PAYOUT_FLOW_STR: &str = "payout_flow";
|
||||
|
||||
@ -11,9 +11,8 @@ mod payment;
|
||||
mod profile;
|
||||
mod routing;
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
mod global_id;
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
mod payment_methods;
|
||||
|
||||
pub use customer::CustomerId;
|
||||
use diesel::{
|
||||
@ -23,12 +22,12 @@ use diesel::{
|
||||
serialize::{Output, ToSql},
|
||||
sql_types,
|
||||
};
|
||||
#[cfg(feature = "v2")]
|
||||
pub use global_id::{payment::GlobalPaymentId, payment_methods::GlobalPaymentMethodId, CellId};
|
||||
pub use merchant::MerchantId;
|
||||
pub use merchant_connector_account::MerchantConnectorAccountId;
|
||||
pub use organization::OrganizationId;
|
||||
pub use payment::PaymentId;
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
pub use payment_methods::GlobalPaymentMethodId;
|
||||
pub use profile::ProfileId;
|
||||
pub use routing::RoutingId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -155,6 +154,7 @@ impl<const MAX_LENGTH: u8, const MIN_LENGTH: u8> LengthId<MAX_LENGTH, MIN_LENGTH
|
||||
Self(alphanumeric_id)
|
||||
}
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
/// Create a new LengthId from aplhanumeric id
|
||||
pub(crate) fn from_alphanumeric_id(
|
||||
alphanumeric_id: AlphaNumericId,
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
#![allow(unused)]
|
||||
pub mod payment;
|
||||
pub mod payment_methods;
|
||||
|
||||
use diesel::{backend::Backend, deserialize::FromSql, serialize::ToSql, sql_types};
|
||||
use error_stack::ResultExt;
|
||||
@ -34,8 +36,9 @@ impl GlobalEntity {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cell identifier for an instance / deployment of application
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct CellId(LengthId<CELL_IDENTIFIER_LENGTH, CELL_IDENTIFIER_LENGTH>);
|
||||
pub struct CellId(LengthId<CELL_IDENTIFIER_LENGTH, CELL_IDENTIFIER_LENGTH>);
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum CellIdError {
|
||||
@ -107,7 +110,9 @@ impl GlobalId {
|
||||
Self(LengthId::new_unchecked(alphanumeric_id))
|
||||
}
|
||||
|
||||
pub fn from_string(input_string: String) -> Result<Self, GlobalIdError> {
|
||||
pub(crate) fn from_string(
|
||||
input_string: std::borrow::Cow<'static, str>,
|
||||
) -> Result<Self, GlobalIdError> {
|
||||
let length_id = LengthId::from(input_string.into())?;
|
||||
let input_string = &length_id.0 .0;
|
||||
let (cell_id, remaining) = input_string
|
||||
@ -118,6 +123,10 @@ impl GlobalId {
|
||||
|
||||
Ok(Self(length_id))
|
||||
}
|
||||
|
||||
pub(crate) fn get_string_repr(&self) -> &str {
|
||||
&self.0 .0 .0
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> ToSql<sql_types::Text, DB> for GlobalId
|
||||
@ -154,7 +163,7 @@ impl<'de> serde::Deserialize<'de> for GlobalId {
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let deserialized_string = String::deserialize(deserializer)?;
|
||||
Self::from_string(deserialized_string).map_err(serde::de::Error::custom)
|
||||
Self::from_string(deserialized_string.into()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,7 +196,7 @@ mod global_id_tests {
|
||||
#[test]
|
||||
fn test_global_id_from_string() {
|
||||
let input_string = "12345_cus_abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
let global_id = GlobalId::from_string(input_string.to_string()).unwrap();
|
||||
let global_id = GlobalId::from_string(input_string.into()).unwrap();
|
||||
assert_eq!(global_id.0 .0 .0, input_string);
|
||||
}
|
||||
|
||||
|
||||
63
crates/common_utils/src/id_type/global_id/payment.rs
Normal file
63
crates/common_utils/src/id_type/global_id/payment.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use crate::errors;
|
||||
|
||||
/// A global id that can be used to identify a payment
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
diesel::expression::AsExpression,
|
||||
)]
|
||||
#[diesel(sql_type = diesel::sql_types::Text)]
|
||||
pub struct GlobalPaymentId(super::GlobalId);
|
||||
|
||||
// Database related implementations so that this field can be used directly in the database tables
|
||||
crate::impl_queryable_id_type!(GlobalPaymentId);
|
||||
|
||||
impl GlobalPaymentId {
|
||||
/// Get string representation of the id
|
||||
pub fn get_string_repr(&self) -> &str {
|
||||
self.0.get_string_repr()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor the macro to include this id use case as well
|
||||
impl TryFrom<std::borrow::Cow<'static, str>> for GlobalPaymentId {
|
||||
type Error = error_stack::Report<errors::ValidationError>;
|
||||
fn try_from(value: std::borrow::Cow<'static, str>) -> Result<Self, Self::Error> {
|
||||
use error_stack::ResultExt;
|
||||
let merchant_ref_id = super::GlobalId::from_string(value).change_context(
|
||||
errors::ValidationError::IncorrectValueProvided {
|
||||
field_name: "payment_id",
|
||||
},
|
||||
)?;
|
||||
Ok(Self(merchant_ref_id))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor the macro to include this id use case as well
|
||||
impl<DB> diesel::serialize::ToSql<diesel::sql_types::Text, DB> for GlobalPaymentId
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
super::GlobalId: diesel::serialize::ToSql<diesel::sql_types::Text, DB>,
|
||||
{
|
||||
fn to_sql<'b>(
|
||||
&'b self,
|
||||
out: &mut diesel::serialize::Output<'b, '_, DB>,
|
||||
) -> diesel::serialize::Result {
|
||||
self.0.to_sql(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> diesel::deserialize::FromSql<diesel::sql_types::Text, DB> for GlobalPaymentId
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
super::GlobalId: diesel::deserialize::FromSql<diesel::sql_types::Text, DB>,
|
||||
{
|
||||
fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
|
||||
super::GlobalId::from_sql(value).map(Self)
|
||||
}
|
||||
}
|
||||
@ -27,9 +27,6 @@ pub enum GlobalPaymentMethodIdError {
|
||||
}
|
||||
|
||||
impl GlobalPaymentMethodId {
|
||||
fn get_global_id(&self) -> &GlobalId {
|
||||
&self.0
|
||||
}
|
||||
/// Create a new GlobalPaymentMethodId from celll id information
|
||||
pub fn generate(cell_id: &str) -> error_stack::Result<Self, errors::ValidationError> {
|
||||
let cell_id = CellId::from_string(cell_id.to_string())?;
|
||||
@ -37,18 +34,18 @@ impl GlobalPaymentMethodId {
|
||||
Ok(Self(global_id))
|
||||
}
|
||||
|
||||
pub fn get_string_repr(&self) -> String {
|
||||
todo!()
|
||||
pub fn get_string_repr(&self) -> &str {
|
||||
self.0.get_string_repr()
|
||||
}
|
||||
|
||||
pub fn generate_from_string(value: String) -> CustomResult<Self, GlobalPaymentMethodIdError> {
|
||||
let id = GlobalId::from_string(value)
|
||||
let id = GlobalId::from_string(value.into())
|
||||
.change_context(GlobalPaymentMethodIdError::ConstructionError)?;
|
||||
Ok(Self(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> diesel::Queryable<diesel::sql_types::Text, DB> for GlobalPaymentMethodId
|
||||
impl<DB> diesel::Queryable<sql_types::Text, DB> for GlobalPaymentMethodId
|
||||
where
|
||||
DB: diesel::backend::Backend,
|
||||
Self: diesel::deserialize::FromSql<diesel::sql_types::Text, DB>,
|
||||
@ -68,8 +65,7 @@ where
|
||||
&'b self,
|
||||
out: &mut diesel::serialize::Output<'b, '_, DB>,
|
||||
) -> diesel::serialize::Result {
|
||||
let id = self.get_global_id();
|
||||
id.to_sql(out)
|
||||
self.0.to_sql(out)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
errors::{CustomResult, ValidationError},
|
||||
generate_id_with_default_len,
|
||||
id_type::{global_id, AlphaNumericId, LengthId},
|
||||
id_type::{AlphaNumericId, LengthId},
|
||||
};
|
||||
|
||||
crate::id_type!(
|
||||
@ -15,23 +15,10 @@ crate::impl_debug_id_type!(PaymentId);
|
||||
crate::impl_default_id_type!(PaymentId, "pay");
|
||||
crate::impl_try_from_cow_str_id_type!(PaymentId, "payment_id");
|
||||
|
||||
// Database related implementations so that this field can be used directly in the database tables
|
||||
crate::impl_queryable_id_type!(PaymentId);
|
||||
crate::impl_to_sql_from_sql_id_type!(PaymentId);
|
||||
|
||||
/// A global id that can be used to identify a payment
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
diesel::expression::AsExpression,
|
||||
)]
|
||||
#[diesel(sql_type = diesel::sql_types::Text)]
|
||||
pub struct PaymentGlobalId(global_id::GlobalId);
|
||||
|
||||
impl PaymentId {
|
||||
/// Get the hash key to be stored in redis
|
||||
pub fn get_hash_key_for_kv_store(&self) -> String {
|
||||
|
||||
@ -5,6 +5,7 @@ pub mod keymanager;
|
||||
pub mod authentication;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::Display,
|
||||
ops::{Add, Sub},
|
||||
primitive::i64,
|
||||
@ -28,13 +29,16 @@ use rust_decimal::{
|
||||
};
|
||||
use semver::Version;
|
||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
|
||||
use thiserror::Error;
|
||||
use time::PrimitiveDateTime;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{
|
||||
consts,
|
||||
consts::{self, MAX_DESCRIPTION_LENGTH, MAX_STATEMENT_DESCRIPTOR_LENGTH},
|
||||
errors::{CustomResult, ParsingError, PercentageError, ValidationError},
|
||||
fp_utils::when,
|
||||
};
|
||||
|
||||
/// Represents Percentage Value between 0 and 100 both inclusive
|
||||
#[derive(Clone, Default, Debug, PartialEq, Serialize)]
|
||||
pub struct Percentage<const PRECISION: u8> {
|
||||
@ -583,6 +587,251 @@ impl StringMajorUnit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
serde::Deserialize,
|
||||
AsExpression,
|
||||
serde::Serialize,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
ToSchema,
|
||||
PartialOrd,
|
||||
)]
|
||||
#[diesel(sql_type = sql_types::Text)]
|
||||
/// This domain type can be used for any url
|
||||
pub struct Url(url::Url);
|
||||
|
||||
impl<DB> ToSql<sql_types::Text, DB> for Url
|
||||
where
|
||||
DB: Backend,
|
||||
str: ToSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
|
||||
let url_string = self.0.as_str();
|
||||
url_string.to_sql(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> FromSql<sql_types::Text, DB> for Url
|
||||
where
|
||||
DB: Backend,
|
||||
String: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn from_sql(value: DB::RawValue<'_>) -> deserialize::Result<Self> {
|
||||
let val = String::from_sql(value)?;
|
||||
let url = url::Url::parse(&val)?;
|
||||
Ok(Self(url))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
pub use client_secret_type::ClientSecret;
|
||||
#[cfg(feature = "v2")]
|
||||
mod client_secret_type {
|
||||
use std::fmt;
|
||||
|
||||
use masking::PeekInterface;
|
||||
|
||||
use super::*;
|
||||
use crate::id_type;
|
||||
|
||||
/// A domain type that can be used to represent a client secret
|
||||
/// Client secret is generated for a payment and is used to authenticate the client side api calls
|
||||
#[derive(Debug, PartialEq, Clone, AsExpression)]
|
||||
#[diesel(sql_type = sql_types::Text)]
|
||||
pub struct ClientSecret {
|
||||
/// The payment id of the payment
|
||||
pub payment_id: id_type::GlobalPaymentId,
|
||||
/// The secret string
|
||||
pub secret: masking::Secret<String>,
|
||||
}
|
||||
|
||||
impl ClientSecret {
|
||||
pub(crate) fn get_string_repr(&self) -> String {
|
||||
format!(
|
||||
"{}_secret_{}",
|
||||
self.payment_id.get_string_repr(),
|
||||
self.secret.peek()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientSecret {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ClientSecretVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ClientSecretVisitor {
|
||||
type Value = ClientSecret;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a string in the format '{payment_id}_secret_{secret}'")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<ClientSecret, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let (payment_id, secret) = value.rsplit_once("_secret_").ok_or_else(|| {
|
||||
E::invalid_value(
|
||||
serde::de::Unexpected::Str(value),
|
||||
&"a string with '_secret_'",
|
||||
)
|
||||
})?;
|
||||
|
||||
let payment_id =
|
||||
id_type::GlobalPaymentId::try_from(Cow::Owned(payment_id.to_owned()))
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(ClientSecret {
|
||||
payment_id,
|
||||
secret: masking::Secret::new(secret.to_owned()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(ClientSecretVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ClientSecret {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.get_string_repr().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql<sql_types::Text, diesel::pg::Pg> for ClientSecret
|
||||
where
|
||||
String: ToSql<sql_types::Text, diesel::pg::Pg>,
|
||||
{
|
||||
fn to_sql<'b>(
|
||||
&'b self,
|
||||
out: &mut Output<'b, '_, diesel::pg::Pg>,
|
||||
) -> diesel::serialize::Result {
|
||||
let string_repr = self.get_string_repr();
|
||||
<String as ToSql<sql_types::Text, diesel::pg::Pg>>::to_sql(
|
||||
&string_repr,
|
||||
&mut out.reborrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> FromSql<sql_types::Text, DB> for ClientSecret
|
||||
where
|
||||
DB: Backend,
|
||||
String: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn from_sql(value: DB::RawValue<'_>) -> deserialize::Result<Self> {
|
||||
let string_repr = String::from_sql(value)?;
|
||||
Ok(serde_json::from_str(&string_repr)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> Queryable<sql_types::Text, DB> for ClientSecret
|
||||
where
|
||||
DB: Backend,
|
||||
Self: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
type Row = Self;
|
||||
|
||||
fn build(row: Self::Row) -> deserialize::Result<Self> {
|
||||
Ok(row)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod client_secret_tests {
|
||||
#![allow(clippy::expect_used)]
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use serde_json;
|
||||
|
||||
use super::*;
|
||||
use crate::id_type::GlobalPaymentId;
|
||||
|
||||
#[test]
|
||||
fn test_serialize_client_secret() {
|
||||
let global_payment_id = "12345_pay_1a961ed9093c48b09781bf8ab17ba6bd";
|
||||
let secret = "fc34taHLw1ekPgNh92qr".to_string();
|
||||
|
||||
let expected_client_secret_string = format!("\"{global_payment_id}_secret_{secret}\"");
|
||||
|
||||
let client_secret1 = ClientSecret {
|
||||
payment_id: GlobalPaymentId::try_from(Cow::Borrowed(global_payment_id)).unwrap(),
|
||||
secret: masking::Secret::new(secret),
|
||||
};
|
||||
|
||||
let parsed_client_secret =
|
||||
serde_json::to_string(&client_secret1).expect("Failed to serialize client_secret1");
|
||||
|
||||
assert_eq!(expected_client_secret_string, parsed_client_secret);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_client_secret() {
|
||||
// This is a valid global id
|
||||
let global_payment_id_str = "12345_pay_1a961ed9093c48b09781bf8ab17ba6bd";
|
||||
let secret = "fc34taHLw1ekPgNh92qr".to_string();
|
||||
|
||||
let valid_payment_global_id =
|
||||
GlobalPaymentId::try_from(Cow::Borrowed(global_payment_id_str))
|
||||
.expect("Failed to create valid global payment id");
|
||||
|
||||
// This is an invalid global id because of the cell id being in invalid length
|
||||
let invalid_global_payment_id = "123_pay_1a961ed9093c48b09781bf8ab17ba6bd";
|
||||
|
||||
// Create a client secret string which is valid
|
||||
let valid_client_secret = format!(r#""{global_payment_id_str}_secret_{secret}""#);
|
||||
|
||||
dbg!(&valid_client_secret);
|
||||
|
||||
// Create a client secret string which is invalid
|
||||
let invalid_client_secret_because_of_invalid_payment_id =
|
||||
format!(r#""{invalid_global_payment_id}_secret_{secret}""#);
|
||||
|
||||
// Create a client secret string which is invalid because of invalid secret
|
||||
let invalid_client_secret_because_of_invalid_secret =
|
||||
format!(r#""{invalid_global_payment_id}""#);
|
||||
|
||||
let valid_client_secret = serde_json::from_str::<ClientSecret>(&valid_client_secret)
|
||||
.expect("Failed to deserialize client_secret_str1");
|
||||
|
||||
let invalid_deser1 = serde_json::from_str::<ClientSecret>(
|
||||
&invalid_client_secret_because_of_invalid_payment_id,
|
||||
);
|
||||
|
||||
dbg!(&invalid_deser1);
|
||||
|
||||
let invalid_deser2 = serde_json::from_str::<ClientSecret>(
|
||||
&invalid_client_secret_because_of_invalid_secret,
|
||||
);
|
||||
|
||||
dbg!(&invalid_deser2);
|
||||
|
||||
assert_eq!(valid_client_secret.payment_id, valid_payment_global_id);
|
||||
|
||||
assert_eq!(valid_client_secret.secret.peek(), &secret);
|
||||
|
||||
assert_eq!(
|
||||
invalid_deser1.err().unwrap().to_string(),
|
||||
"Incorrect value provided for field: payment_id at line 1 column 70"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
invalid_deser2.err().unwrap().to_string(),
|
||||
"invalid value: string \"123_pay_1a961ed9093c48b09781bf8ab17ba6bd\", expected a string with '_secret_' at line 1 column 42"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing a range of time for filtering, including a mandatory start time and an optional end time.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, ToSchema,
|
||||
@ -725,31 +974,107 @@ pub struct ChargeRefunds {
|
||||
|
||||
crate::impl_to_sql_from_sql_json!(ChargeRefunds);
|
||||
|
||||
/// Domain type for description
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Queryable, serde::Deserialize, serde::Serialize, AsExpression,
|
||||
)]
|
||||
/// A common type of domain type that can be used for fields that contain a string with restriction of length
|
||||
#[derive(Debug, Clone, Serialize, Hash, PartialEq, Eq, AsExpression)]
|
||||
#[diesel(sql_type = sql_types::Text)]
|
||||
pub struct Description(String);
|
||||
pub(crate) struct LengthString<const MAX_LENGTH: u16, const MIN_LENGTH: u8>(String);
|
||||
|
||||
/// Error generated from violation of constraints for MerchantReferenceId
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub(crate) enum LengthStringError {
|
||||
#[error("the maximum allowed length for this field is {0}")]
|
||||
/// Maximum length of string violated
|
||||
MaxLengthViolated(u16),
|
||||
|
||||
#[error("the minimum required length for this field is {0}")]
|
||||
/// Minimum length of string violated
|
||||
MinLengthViolated(u8),
|
||||
}
|
||||
|
||||
impl<const MAX_LENGTH: u16, const MIN_LENGTH: u8> LengthString<MAX_LENGTH, MIN_LENGTH> {
|
||||
/// Generates new [MerchantReferenceId] from the given input string
|
||||
pub fn from(input_string: Cow<'static, str>) -> Result<Self, LengthStringError> {
|
||||
let trimmed_input_string = input_string.trim().to_string();
|
||||
let length_of_input_string = u16::try_from(trimmed_input_string.len())
|
||||
.map_err(|_| LengthStringError::MaxLengthViolated(MAX_LENGTH))?;
|
||||
|
||||
when(length_of_input_string > MAX_LENGTH, || {
|
||||
Err(LengthStringError::MaxLengthViolated(MAX_LENGTH))
|
||||
})?;
|
||||
|
||||
when(length_of_input_string < u16::from(MIN_LENGTH), || {
|
||||
Err(LengthStringError::MinLengthViolated(MIN_LENGTH))
|
||||
})?;
|
||||
|
||||
Ok(Self(trimmed_input_string))
|
||||
}
|
||||
|
||||
pub(crate) fn new_unchecked(input_string: String) -> Self {
|
||||
Self(input_string)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const MAX_LENGTH: u16, const MIN_LENGTH: u8> Deserialize<'de>
|
||||
for LengthString<MAX_LENGTH, MIN_LENGTH>
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let deserialized_string = String::deserialize(deserializer)?;
|
||||
Self::from(deserialized_string.into()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> FromSql<sql_types::Text, DB> for LengthString<MAX_DESCRIPTION_LENGTH, 1>
|
||||
where
|
||||
DB: Backend,
|
||||
String: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
|
||||
let val = String::from_sql(bytes)?;
|
||||
Ok(Self(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, const MAX_LENGTH: u16, const MIN_LENGTH: u8> ToSql<sql_types::Text, DB>
|
||||
for LengthString<MAX_LENGTH, MIN_LENGTH>
|
||||
where
|
||||
DB: Backend,
|
||||
String: ToSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
|
||||
self.0.to_sql(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> Queryable<sql_types::Text, DB> for LengthString<MAX_DESCRIPTION_LENGTH, 1>
|
||||
where
|
||||
DB: Backend,
|
||||
Self: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
type Row = Self;
|
||||
fn build(row: Self::Row) -> deserialize::Result<Self> {
|
||||
Ok(row)
|
||||
}
|
||||
}
|
||||
|
||||
/// Domain type for description
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, AsExpression)]
|
||||
#[diesel(sql_type = sql_types::Text)]
|
||||
pub struct Description(LengthString<MAX_DESCRIPTION_LENGTH, 1>);
|
||||
|
||||
impl Description {
|
||||
/// Create a new Description Domain type
|
||||
pub fn new(value: String) -> Self {
|
||||
Self(value)
|
||||
/// Create a new Description Domain type without any length check from a static str
|
||||
pub fn from_str_unchecked(input_str: &'static str) -> Self {
|
||||
Self(LengthString::new_unchecked(input_str.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Description> for String {
|
||||
fn from(description: Description) -> Self {
|
||||
description.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Description {
|
||||
fn from(description: String) -> Self {
|
||||
Self(description)
|
||||
}
|
||||
}
|
||||
/// Domain type for Statement Descriptor
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, AsExpression)]
|
||||
#[diesel(sql_type = sql_types::Text)]
|
||||
pub struct StatementDescriptor(LengthString<MAX_STATEMENT_DESCRIPTOR_LENGTH, 1>);
|
||||
|
||||
impl<DB> Queryable<sql_types::Text, DB> for Description
|
||||
where
|
||||
@ -766,18 +1091,51 @@ where
|
||||
impl<DB> FromSql<sql_types::Text, DB> for Description
|
||||
where
|
||||
DB: Backend,
|
||||
String: FromSql<sql_types::Text, DB>,
|
||||
LengthString<MAX_DESCRIPTION_LENGTH, 1>: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
|
||||
let val = String::from_sql(bytes)?;
|
||||
Ok(Self::from(val))
|
||||
let val = LengthString::<MAX_DESCRIPTION_LENGTH, 1>::from_sql(bytes)?;
|
||||
Ok(Self(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> ToSql<sql_types::Text, DB> for Description
|
||||
where
|
||||
DB: Backend,
|
||||
String: ToSql<sql_types::Text, DB>,
|
||||
LengthString<MAX_DESCRIPTION_LENGTH, 1>: ToSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
|
||||
self.0.to_sql(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> Queryable<sql_types::Text, DB> for StatementDescriptor
|
||||
where
|
||||
DB: Backend,
|
||||
Self: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
type Row = Self;
|
||||
|
||||
fn build(row: Self::Row) -> deserialize::Result<Self> {
|
||||
Ok(row)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> FromSql<sql_types::Text, DB> for StatementDescriptor
|
||||
where
|
||||
DB: Backend,
|
||||
LengthString<MAX_DESCRIPTION_LENGTH, 1>: FromSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
|
||||
let val = LengthString::<MAX_DESCRIPTION_LENGTH, 1>::from_sql(bytes)?;
|
||||
Ok(Self(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> ToSql<sql_types::Text, DB> for StatementDescriptor
|
||||
where
|
||||
DB: Backend,
|
||||
LengthString<MAX_DESCRIPTION_LENGTH, 1>: ToSql<sql_types::Text, DB>,
|
||||
{
|
||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
|
||||
self.0.to_sql(out)
|
||||
|
||||
Reference in New Issue
Block a user