refactor(id_type): use macros for defining ID types and implementing common traits (#5471)

This commit is contained in:
Sanchith Hegde
2024-07-30 20:21:29 +05:30
committed by GitHub
parent be9347b8d5
commit 1d4fb1d247
34 changed files with 321 additions and 384 deletions

View File

@ -1,104 +1,14 @@
use std::{borrow::Cow, fmt::Debug};
use diesel::{
backend::Backend,
deserialize::FromSql,
expression::AsExpression,
serialize::{Output, ToSql},
sql_types, Queryable,
};
use error_stack::{Result, ResultExt};
use serde::{Deserialize, Serialize};
use crate::{
consts::{MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH},
errors, generate_customer_id_of_default_length,
id_type::LengthId,
};
/// A type for customer_id that can be used for customer ids
#[derive(Clone, Serialize, Deserialize, Hash, PartialEq, Eq, AsExpression)]
#[diesel(sql_type = sql_types::Text)]
pub struct CustomerId(
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>,
crate::id_type!(
CustomerId,
"A type for customer_id that can be used for customer ids"
);
crate::impl_id_type_methods!(CustomerId, "customer_id");
impl Default for CustomerId {
fn default() -> Self {
generate_customer_id_of_default_length()
}
}
// This is to display the `CustomerId` as CustomerId(abcd)
crate::impl_debug_id_type!(CustomerId);
crate::impl_default_id_type!(CustomerId, "cus");
crate::impl_try_from_cow_str_id_type!(CustomerId, "customer_id");
/// This is to display the `CustomerId` as CustomerId(abcd)
impl Debug for CustomerId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("CustomerId").field(&self.0 .0 .0).finish()
}
}
impl<DB> Queryable<sql_types::Text, DB> for CustomerId
where
DB: Backend,
Self: FromSql<sql_types::Text, DB>,
{
type Row = Self;
fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
Ok(row)
}
}
impl CustomerId {
pub(crate) fn new(
merchant_ref_id: LengthId<
MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH,
MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH,
>,
) -> Self {
Self(merchant_ref_id)
}
/// Get the string representation of customer id
pub fn get_string_repr(&self) -> &str {
&self.0 .0 .0
}
/// Create a Customer id from string
pub fn from(input_string: Cow<'static, str>) -> Result<Self, errors::ValidationError> {
let merchant_ref_id = LengthId::from(input_string).change_context(
errors::ValidationError::IncorrectValueProvided {
field_name: "customer_id",
},
)?;
Ok(Self(merchant_ref_id))
}
}
impl masking::SerializableSecret for CustomerId {}
impl<DB> ToSql<sql_types::Text, DB> for CustomerId
where
DB: Backend,
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>:
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> FromSql<sql_types::Text, DB> for CustomerId
where
DB: Backend,
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>:
FromSql<sql_types::Text, DB>,
{
fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
LengthId::<
MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH,
MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH,
>::from_sql(value)
.map(Self)
}
}
crate::impl_serializable_secret_id_type!(CustomerId);
crate::impl_queryable_id_type!(CustomerId);
crate::impl_to_sql_from_sql_id_type!(CustomerId);

View File

@ -3,51 +3,29 @@
//! Ids for merchant account are derived from the merchant name
//! If there are any special characters, they are removed
use std::{
borrow::Cow,
fmt::{Debug, Display},
};
use diesel::{
backend::Backend,
deserialize::FromSql,
expression::AsExpression,
serialize::{Output, ToSql},
sql_types, Queryable,
};
use error_stack::{Result, ResultExt};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use std::fmt::Display;
use crate::{
consts::{MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH},
date_time, errors, generate_id_with_default_len, generate_ref_id_with_default_length,
date_time, generate_id_with_default_len,
id_type::{AlphaNumericId, LengthId},
new_type::MerchantName,
types::keymanager,
};
/// A type for merchant_id that can be used for merchant ids
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, AsExpression, Hash, ToSchema)]
#[diesel(sql_type = sql_types::Text)]
#[schema(value_type = String)]
pub struct MerchantId(
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>,
crate::id_type!(
MerchantId,
"A type for merchant_id that can be used for merchant ids"
);
crate::impl_id_type_methods!(MerchantId, "merchant_id");
/// This is to display the `MerchantId` as MerchantId(abcd)
impl Debug for MerchantId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MerchantId").field(&self.0 .0 .0).finish()
}
}
// This is to display the `MerchantId` as MerchantId(abcd)
crate::impl_debug_id_type!(MerchantId);
crate::impl_default_id_type!(MerchantId, "mer");
crate::impl_try_from_cow_str_id_type!(MerchantId, "merchant_id");
/// This should be temporary, we should not have direct impl of Display for merchant id
impl Display for MerchantId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.get_string_repr())
}
}
crate::impl_serializable_secret_id_type!(MerchantId);
crate::impl_queryable_id_type!(MerchantId);
crate::impl_to_sql_from_sql_id_type!(MerchantId);
#[cfg(feature = "metrics")]
/// This is implemented so that we can use merchant id directly as attribute in metrics
@ -58,41 +36,7 @@ impl From<MerchantId> for router_env::opentelemetry::Value {
}
}
impl<DB> Queryable<sql_types::Text, DB> for MerchantId
where
DB: Backend,
Self: FromSql<sql_types::Text, DB>,
{
type Row = Self;
fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
Ok(row)
}
}
impl Default for MerchantId {
fn default() -> Self {
Self(generate_ref_id_with_default_length("mer"))
}
}
impl MerchantId {
/// Get the string representation of merchant id
pub fn get_string_repr(&self) -> &str {
&self.0 .0 .0
}
/// Create a Merchant id from string
pub fn from(input_string: Cow<'static, str>) -> Result<Self, errors::ValidationError> {
let length_id = LengthId::from(input_string).change_context(
errors::ValidationError::IncorrectValueProvided {
field_name: "merchant_id",
},
)?;
Ok(Self(length_id))
}
/// Create a Merchant id from MerchantName
pub fn from_merchant_name(merchant_name: MerchantName) -> Self {
let merchant_name_string = merchant_name.into_inner();
@ -144,34 +88,6 @@ impl From<MerchantId> for keymanager::Identifier {
}
}
impl masking::SerializableSecret for MerchantId {}
impl<DB> ToSql<sql_types::Text, DB> for MerchantId
where
DB: Backend,
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>:
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> FromSql<sql_types::Text, DB> for MerchantId
where
DB: Backend,
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>:
FromSql<sql_types::Text, DB>,
{
fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
LengthId::<
MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH,
MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH,
>::from_sql(value)
.map(Self)
}
}
/// All the keys that can be formed from merchant id
impl MerchantId {
/// get step up enabled key

View File

@ -1,106 +1,14 @@
use std::{borrow::Cow, fmt::Debug};
use diesel::{
backend::Backend,
deserialize::FromSql,
expression::AsExpression,
serialize::{Output, ToSql},
sql_types, Queryable,
};
use error_stack::{Result, ResultExt};
use serde::{Deserialize, Serialize};
use crate::{
consts::{MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH},
errors, generate_organization_id_of_default_length,
id_type::LengthId,
};
/// A type for customer_id that can be used for customer ids
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, AsExpression, Hash)]
#[diesel(sql_type = sql_types::Text)]
pub struct OrganizationId(
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>,
crate::id_type!(
OrganizationId,
"A type for organization_id that can be used for organization ids"
);
crate::impl_id_type_methods!(OrganizationId, "organization_id");
impl Default for OrganizationId {
fn default() -> Self {
generate_organization_id_of_default_length()
}
}
// This is to display the `OrganizationId` as OrganizationId(abcd)
crate::impl_debug_id_type!(OrganizationId);
crate::impl_default_id_type!(OrganizationId, "org");
crate::impl_try_from_cow_str_id_type!(OrganizationId, "organization_id");
/// This is to display the `OrganizationId` as OrganizationId(abcd)
impl Debug for OrganizationId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("OrganizationId")
.field(&self.0 .0 .0)
.finish()
}
}
impl<DB> Queryable<sql_types::Text, DB> for OrganizationId
where
DB: Backend,
Self: FromSql<sql_types::Text, DB>,
{
type Row = Self;
fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
Ok(row)
}
}
impl OrganizationId {
pub(crate) fn new(
organization_id: LengthId<
MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH,
MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH,
>,
) -> Self {
Self(organization_id)
}
/// Get the string representation of customer id
pub fn get_string_repr(&self) -> &str {
&self.0 .0 .0
}
/// Create a Customer id from string
pub fn from(input_string: Cow<'static, str>) -> Result<Self, errors::ValidationError> {
let organization_id = LengthId::from(input_string).change_context(
errors::ValidationError::IncorrectValueProvided {
field_name: "customer_id",
},
)?;
Ok(Self(organization_id))
}
}
impl masking::SerializableSecret for OrganizationId {}
impl<DB> ToSql<sql_types::Text, DB> for OrganizationId
where
DB: Backend,
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>:
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> FromSql<sql_types::Text, DB> for OrganizationId
where
DB: Backend,
LengthId<MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH, MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH>:
FromSql<sql_types::Text, DB>,
{
fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
LengthId::<
MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH,
MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH,
>::from_sql(value)
.map(Self)
}
}
crate::impl_serializable_secret_id_type!(OrganizationId);
crate::impl_queryable_id_type!(OrganizationId);
crate::impl_to_sql_from_sql_id_type!(OrganizationId);

View File

@ -217,12 +217,12 @@ fn generate_ref_id_with_default_length<const MAX_LENGTH: u8, const MIN_LENGTH: u
/// Generate a customer id with default length, with prefix as `cus`
pub fn generate_customer_id_of_default_length() -> id_type::CustomerId {
id_type::CustomerId::new(generate_ref_id_with_default_length("cus"))
id_type::CustomerId::default()
}
/// Generate a organization id with default length, with prefix as `org`
pub fn generate_organization_id_of_default_length() -> id_type::OrganizationId {
id_type::OrganizationId::new(generate_ref_id_with_default_length("org"))
id_type::OrganizationId::default()
}
/// Generate a nanoid with the given prefix and a default length

View File

@ -1,9 +1,10 @@
#![allow(missing_docs)]
//! Utility macros
#[allow(missing_docs)]
#[macro_export]
macro_rules! newtype_impl {
($is_pub:vis, $name:ident, $ty_path:path) => {
impl std::ops::Deref for $name {
impl core::ops::Deref for $name {
type Target = $ty_path;
fn deref(&self) -> &Self::Target {
@ -11,7 +12,7 @@ macro_rules! newtype_impl {
}
}
impl std::ops::DerefMut for $name {
impl core::ops::DerefMut for $name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
@ -31,6 +32,7 @@ macro_rules! newtype_impl {
};
}
#[allow(missing_docs)]
#[macro_export]
macro_rules! newtype {
($is_pub:vis $name:ident = $ty_path:path) => {
@ -59,6 +61,7 @@ macro_rules! openapi_route {
}};
}
#[allow(missing_docs)]
#[macro_export]
macro_rules! fallback_reverse_lookup_not_found {
($a:expr,$b:expr) => {
@ -81,6 +84,8 @@ macro_rules! fallback_reverse_lookup_not_found {
};
}
/// Collects names of all optional fields that are `None`.
/// This is typically useful for constructing error messages including a list of all missing fields.
#[macro_export]
macro_rules! collect_missing_value_keys {
[$(($key:literal, $option:expr)),+] => {
@ -96,6 +101,8 @@ macro_rules! collect_missing_value_keys {
};
}
/// Implements the `ToSql` and `FromSql` traits on a type to allow it to be serialized/deserialized
/// to/from JSON data in the database.
#[macro_export]
macro_rules! impl_to_sql_from_sql_json {
($type:ty, $diesel_type:ty) => {
@ -135,3 +142,162 @@ macro_rules! impl_to_sql_from_sql_json {
$crate::impl_to_sql_from_sql_json!($type, diesel::sql_types::Jsonb);
};
}
mod id_type {
/// Defines an ID type.
#[macro_export]
macro_rules! id_type {
($type:ident, $doc:literal, $diesel_type:ty, $max_length:expr, $min_length:expr) => {
#[doc = $doc]
#[derive(
Clone,
Hash,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
diesel::expression::AsExpression,
)]
#[diesel(sql_type = $diesel_type)]
pub struct $type($crate::id_type::LengthId<$max_length, $min_length>);
};
($type:ident, $doc:literal) => {
$crate::id_type!(
$type,
$doc,
diesel::sql_types::Text,
{ $crate::consts::MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH },
{ $crate::consts::MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH }
);
};
}
/// Implements common methods on the specified ID type.
#[macro_export]
macro_rules! impl_id_type_methods {
($type:ty, $field_name:literal) => {
impl $type {
/// Get the string representation of the ID type.
pub fn get_string_repr(&self) -> &str {
&self.0 .0 .0
}
}
};
}
/// Implements the `Debug` trait on the specified ID type.
#[macro_export]
macro_rules! impl_debug_id_type {
($type:ty) => {
impl core::fmt::Debug for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple(stringify!($type))
.field(&self.0 .0 .0)
.finish()
}
}
};
}
/// Implements the `TryFrom<Cow<'static, str>>` trait on the specified ID type.
#[macro_export]
macro_rules! impl_try_from_cow_str_id_type {
($type:ty, $field_name:literal) => {
impl TryFrom<std::borrow::Cow<'static, str>> for $type {
type Error = error_stack::Report<$crate::errors::ValidationError>;
fn try_from(value: std::borrow::Cow<'static, str>) -> Result<Self, Self::Error> {
use error_stack::ResultExt;
let merchant_ref_id = $crate::id_type::LengthId::from(value).change_context(
$crate::errors::ValidationError::IncorrectValueProvided {
field_name: $field_name,
},
)?;
Ok(Self(merchant_ref_id))
}
}
};
}
/// Implements the `Default` trait on the specified ID type.
#[macro_export]
macro_rules! impl_default_id_type {
($type:ty, $prefix:literal) => {
impl Default for $type {
fn default() -> Self {
Self($crate::generate_ref_id_with_default_length($prefix))
}
}
};
}
/// Implements the `SerializableSecret` trait on the specified ID type.
#[macro_export]
macro_rules! impl_serializable_secret_id_type {
($type:ty) => {
impl masking::SerializableSecret for $type {}
};
}
/// Implements the `ToSql` and `FromSql` traits on the specified ID type.
#[macro_export]
macro_rules! impl_to_sql_from_sql_id_type {
($type:ty, $diesel_type:ty, $max_length:expr, $min_length:expr) => {
impl<DB> diesel::serialize::ToSql<$diesel_type, DB> for $type
where
DB: diesel::backend::Backend,
$crate::id_type::LengthId<$max_length, $min_length>:
diesel::serialize::ToSql<$diesel_type, 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_type, DB> for $type
where
DB: diesel::backend::Backend,
$crate::id_type::LengthId<$max_length, $min_length>:
diesel::deserialize::FromSql<$diesel_type, DB>,
{
fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
$crate::id_type::LengthId::<$max_length, $min_length>::from_sql(value).map(Self)
}
}
};
($type:ty) => {
$crate::impl_to_sql_from_sql_id_type!(
$type,
diesel::sql_types::Text,
{ $crate::consts::MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH },
{ $crate::consts::MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH }
);
};
}
/// Implements the `Queryable` trait on the specified ID type.
#[macro_export]
macro_rules! impl_queryable_id_type {
($type:ty, $diesel_type:ty) => {
impl<DB> diesel::Queryable<$diesel_type, DB> for $type
where
DB: diesel::backend::Backend,
Self: diesel::deserialize::FromSql<$diesel_type, DB>,
{
type Row = Self;
fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
Ok(row)
}
}
};
($type:ty) => {
$crate::impl_queryable_id_type!($type, diesel::sql_types::Text);
};
}
}