pinpayments/
ids.rs

1macro_rules! def_id_serde_impls {
2    ($struct_name:ident) => {
3        impl serde::Serialize for $struct_name {
4            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
5            where
6                S: serde::ser::Serializer,
7            {
8                self.as_str().serialize(serializer)
9            }
10        }
11
12        impl<'de> serde::Deserialize<'de> for $struct_name {
13            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
14            where
15                D: serde::de::Deserializer<'de>,
16            {
17                let s: String = serde::Deserialize::deserialize(deserializer)?;
18                s.parse::<Self>().map_err(::serde::de::Error::custom)
19            }
20        }
21    };
22    ($struct_name:ident, _) => {};
23}
24
25macro_rules! def_id {
26    ($struct_name:ident: String) => {
27        #[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
28        pub struct $struct_name(smol_str::SmolStr);
29
30        impl $struct_name {
31            /// Extracts a string slice containing the id.
32            #[inline(always)]
33            pub fn as_str(&self) -> &str {
34                self.0.as_str()
35            }
36        }
37
38        impl PartialEq<str> for $struct_name {
39            fn eq(&self, other: &str) -> bool {
40                self.as_str() == other
41            }
42        }
43
44        impl PartialEq<&str> for $struct_name {
45            fn eq(&self, other: &&str) -> bool {
46                self.as_str() == *other
47            }
48        }
49
50        impl PartialEq<String> for $struct_name {
51            fn eq(&self, other: &String) -> bool {
52                self.as_str() == other
53            }
54        }
55
56        impl PartialOrd for $struct_name {
57            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
58                Some(self.cmp(other))
59            }
60        }
61
62        impl Ord for $struct_name {
63            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
64                self.as_str().cmp(other.as_str())
65            }
66        }
67
68        impl AsRef<str> for $struct_name {
69            fn as_ref(&self) -> &str {
70                self.as_str()
71            }
72        }
73
74        impl std::ops::Deref for $struct_name {
75            type Target = str;
76
77            fn deref(&self) -> &str {
78                self.as_str()
79            }
80        }
81
82        impl std::fmt::Display for $struct_name {
83            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84                self.0.fmt(f)
85            }
86        }
87
88        impl std::str::FromStr for $struct_name {
89            type Err = ParseIdError;
90
91            fn from_str(s: &str) -> Result<Self, Self::Err> {
92                Ok($struct_name(s.into()))
93            }
94        }
95
96        impl serde::Serialize for $struct_name {
97            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
98                where S: serde::ser::Serializer
99            {
100                self.as_str().serialize(serializer)
101            }
102        }
103
104        impl<'de> serde::Deserialize<'de> for $struct_name {
105            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106                where D: serde::de::Deserializer<'de>
107            {
108                let s: String = serde::Deserialize::deserialize(deserializer)?;
109                s.parse::<Self>().map_err(::serde::de::Error::custom)
110            }
111        }
112    };
113    ($struct_name:ident, $prefix:literal $(| $alt_prefix:literal)* $(, { $generate_hint:tt })?) => {
114        /// An id for the corresponding object type.
115        ///
116        /// This type _typically_ will not allocate and
117        /// therefore is usually cheaply clonable.
118        #[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
119        pub struct $struct_name(smol_str::SmolStr);
120
121        impl $struct_name {
122            /// The prefix of the id type (e.g. `cus_` for a `CustomerId`).
123            #[inline(always)]
124            #[deprecated(note = "Please use prefixes or is_valid_prefix")]
125            pub fn prefix() -> &'static str {
126                $prefix
127            }
128
129            /// The valid prefixes of the id type (e.g. `ch_` for a `ChargeId`).
130            #[inline(always)]
131            pub fn prefixes() -> &'static [&'static str] {
132                &[$prefix$(, $alt_prefix)*]
133            }
134
135            /// Extracts a string slice containing the id.
136            #[inline(always)]
137            pub fn as_str(&self) -> &str {
138                self.0.as_str()
139            }
140
141            /// Check is provided prefix would be a valid prefix for id's of this type
142            pub fn is_valid_prefix(prefix: &str) -> bool {
143                prefix == $prefix $( || prefix == $alt_prefix )*
144            }
145        }
146
147        impl PartialEq<str> for $struct_name {
148            fn eq(&self, other: &str) -> bool {
149                self.as_str() == other
150            }
151        }
152
153        impl PartialEq<&str> for $struct_name {
154            fn eq(&self, other: &&str) -> bool {
155                self.as_str() == *other
156            }
157        }
158
159        impl PartialEq<String> for $struct_name {
160            fn eq(&self, other: &String) -> bool {
161                self.as_str() == other
162            }
163        }
164
165        impl PartialOrd for $struct_name {
166            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
167                Some(self.cmp(other))
168            }
169        }
170
171        impl Ord for $struct_name {
172            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
173                self.as_str().cmp(other.as_str())
174            }
175        }
176
177        impl AsRef<str> for $struct_name {
178            fn as_ref(&self) -> &str {
179                self.as_str()
180            }
181        }
182
183        impl std::ops::Deref for $struct_name {
184            type Target = str;
185
186            fn deref(&self) -> &str {
187                self.as_str()
188            }
189        }
190
191        impl std::fmt::Display for $struct_name {
192            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193                self.0.fmt(f)
194            }
195        }
196
197        impl std::str::FromStr for $struct_name {
198            type Err = ParseIdError;
199
200            fn from_str(s: &str) -> Result<Self, Self::Err> {
201                if !s.starts_with($prefix) $(
202                    && !s.starts_with($alt_prefix)
203                )* {
204                    // N.B. For debugging
205                    eprintln!("bad id is: {} (expected: {:?}) for {}", s, $prefix, stringify!($struct_name));
206
207                    Err(ParseIdError {
208                        typename: stringify!($struct_name),
209                        expected: stringify!(id to start with $prefix $(or $alt_prefix)*),
210                    })
211                } else {
212                    Ok($struct_name(s.into()))
213                }
214            }
215        }
216
217        def_id_serde_impls!($struct_name $(, $generate_hint )*);
218    };
219    (#[optional] enum $enum_name:ident { $( $variant_name:ident($($variant_type:tt)*) ),* $(,)* }) => {
220        #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
221        pub enum $enum_name {
222            None,
223            $( $variant_name($($variant_type)*), )*
224        }
225
226        impl $enum_name {
227            pub fn as_str(&self) -> &str {
228                match *self {
229                    $enum_name::None => "",
230                    $( $enum_name::$variant_name(ref id) => id.as_str(), )*
231                }
232            }
233        }
234
235        impl PartialEq<str> for $enum_name {
236            fn eq(&self, other: &str) -> bool {
237                self.as_str() == other
238            }
239        }
240
241        impl PartialEq<&str> for $enum_name {
242            fn eq(&self, other: &&str) -> bool {
243                self.as_str() == *other
244            }
245        }
246
247        impl PartialEq<String> for $enum_name {
248            fn eq(&self, other: &String) -> bool {
249                self.as_str() == other
250            }
251        }
252
253        impl AsRef<str> for $enum_name {
254            fn as_ref(&self) -> &str {
255                self.as_str()
256            }
257        }
258
259        impl crate::params::AsCursor for $enum_name {}
260
261        impl std::ops::Deref for $enum_name {
262            type Target = str;
263
264            fn deref(&self) -> &str {
265                self.as_str()
266            }
267        }
268
269        impl std::fmt::Display for $enum_name {
270            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
271                match *self {
272                    $enum_name::None => Ok(()),
273                    $( $enum_name::$variant_name(ref id) => id.fmt(f), )*
274                }
275            }
276        }
277
278        impl std::default::Default for $enum_name {
279            fn default() -> Self {
280                $enum_name::None
281            }
282        }
283
284        impl std::str::FromStr for $enum_name {
285            type Err = ParseIdError;
286
287            fn from_str(s: &str) -> Result<Self, Self::Err> {
288                let prefix = s.find('_')
289                    .map(|i| &s[0..=i])
290                    .ok_or_else(|| ParseIdError {
291                        typename: stringify!($enum_name),
292                        expected: "id to start with a prefix (as in 'prefix_')"
293                    })?;
294
295                match prefix {
296                    $(_ if $($variant_type)*::is_valid_prefix(prefix) => {
297                        Ok($enum_name::$variant_name(s.parse()?))
298                    })*
299                    _ => {
300                        Err(ParseIdError {
301                            typename: stringify!($enum_name),
302                            expected: "unknown id prefix",
303                        })
304                    }
305                }
306            }
307        }
308
309        impl serde::Serialize for $enum_name {
310            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
311                where S: serde::ser::Serializer
312            {
313                self.as_str().serialize(serializer)
314            }
315        }
316
317        impl<'de> serde::Deserialize<'de> for $enum_name {
318            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319                where D: serde::de::Deserializer<'de>
320            {
321                let s: String = serde::Deserialize::deserialize(deserializer)?;
322                s.parse::<Self>().map_err(::serde::de::Error::custom)
323            }
324        }
325
326        $(
327            impl From<$($variant_type)*> for $enum_name {
328                fn from(id: $($variant_type)*) -> Self {
329                    $enum_name::$variant_name(id)
330                }
331            }
332        )*
333    };
334    (enum $enum_name:ident { $( $(#[$test:meta])? $variant_name:ident($($variant_type:tt)*) ),+ $(,)? }) => {
335        #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
336        #[derive(SmartDefault)]
337        pub enum $enum_name {
338            $( $(#[$test])* $variant_name($($variant_type)*), )*
339        }
340
341        impl $enum_name {
342            pub fn as_str(&self) -> &str {
343                match *self {
344                    $( $enum_name::$variant_name(ref id) => id.as_str(), )*
345                }
346            }
347        }
348
349        impl PartialEq<str> for $enum_name {
350            fn eq(&self, other: &str) -> bool {
351                self.as_str() == other
352            }
353        }
354
355        impl PartialEq<&str> for $enum_name {
356            fn eq(&self, other: &&str) -> bool {
357                self.as_str() == *other
358            }
359        }
360
361        impl PartialEq<String> for $enum_name {
362            fn eq(&self, other: &String) -> bool {
363                self.as_str() == other
364            }
365        }
366
367        impl AsRef<str> for $enum_name {
368            fn as_ref(&self) -> &str {
369                self.as_str()
370            }
371        }
372
373        impl crate::params::AsCursor for $enum_name {}
374
375        impl std::ops::Deref for $enum_name {
376            type Target = str;
377
378            fn deref(&self) -> &str {
379                self.as_str()
380            }
381        }
382
383        impl std::fmt::Display for $enum_name {
384            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
385                match *self {
386                    $( $enum_name::$variant_name(ref id) => id.fmt(f), )*
387                }
388            }
389        }
390
391        impl std::str::FromStr for $enum_name {
392            type Err = ParseIdError;
393
394            fn from_str(s: &str) -> Result<Self, Self::Err> {
395                let prefix = s.find('_')
396                    .map(|i| &s[0..=i])
397                    .ok_or_else(|| ParseIdError {
398                        typename: stringify!($enum_name),
399                        expected: "id to start with a prefix (as in 'prefix_')"
400                    })?;
401
402                match prefix {
403                    $(_ if $($variant_type)*::is_valid_prefix(prefix) => {
404                        Ok($enum_name::$variant_name(s.parse()?))
405                    })*
406                    _ => {
407                        Err(ParseIdError {
408                            typename: stringify!($enum_name),
409                            expected: "unknown id prefix",
410                        })
411                    }
412                }
413            }
414        }
415
416        impl serde::Serialize for $enum_name {
417            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
418                where S: serde::ser::Serializer
419            {
420                self.as_str().serialize(serializer)
421            }
422        }
423
424        impl<'de> serde::Deserialize<'de> for $enum_name {
425            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426                where D: serde::de::Deserializer<'de>
427            {
428                let s: String = serde::Deserialize::deserialize(deserializer)?;
429                s.parse::<Self>().map_err(::serde::de::Error::custom)
430            }
431        }
432
433        $(
434            impl From<$($variant_type)*> for $enum_name {
435                fn from(id: $($variant_type)*) -> Self {
436                    $enum_name::$variant_name(id)
437                }
438            }
439        )*
440    };
441}
442
443#[derive(Clone, Debug)]
444pub struct ParseIdError {
445    typename: &'static str,
446    expected: &'static str,
447}
448
449impl std::fmt::Display for ParseIdError {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        write!(f, "invalid `{}`, expected {}", self.typename, self.expected)
452    }
453}
454
455impl std::error::Error for ParseIdError {
456    fn description(&self) -> &str {
457        "error parsing an id"
458    }
459}
460
461def_id!(AuthorisationId, "auth_");
462def_id!(ApplePayId, "apmd_");
463def_id!(BankAccountId, "ba_");
464def_id!(CardId, "card_");
465def_id!(ChargeId, "ch_"); 
466def_id!(CustomerId, "cus_");
467def_id!(DepositId, "dpo_");
468def_id!(DisputeId, "dis_");
469def_id!(EventId, "evt_");
470def_id!(FileId, "file_");
471def_id!(MerchantId, "mrch_");
472def_id!(PaymentSourceId, "ps_");
473def_id!(PlanId, "plan_");
474def_id!(RecipientId, "rp_");
475def_id!(RefundId, "rf_");
476def_id!(SessionId, "se_");
477def_id!(SubscriptionId, "sub_");
478def_id!(TransferId, "tfer_");
479def_id!(WebhookEndpointId, "whe_");
480def_id!(WebhookId, "whr_");
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn test_parse_customer() {
488        assert!("cus_123".parse::<CustomerId>().is_ok());
489        let bad_parse = "zzz_123".parse::<CustomerId>();
490        assert!(bad_parse.is_err());
491        if let Err(err) = bad_parse {
492            assert_eq!(
493                format!("{}", err),
494                "invalid `CustomerId`, expected id to start with \"cus_\""
495            );
496        }
497    }
498
499    #[test]
500    fn test_parse_charge() {
501        assert!("ch_123".parse::<ChargeId>().is_ok());
502        let bad_parse = "zz_123".parse::<ChargeId>();
503        assert!(bad_parse.is_err());
504        if let Err(err) = bad_parse {
505            assert_eq!(
506                format!("{}", err),
507                "invalid `ChargeId`, expected id to start with \"ch_\""
508            );
509        }
510    }
511
512    #[test]
513    fn test_parse_session() {
514        assert!("se_123".parse::<SessionId>().is_ok());
515        let bad_parse = "zz_123".parse::<SessionId>();
516        assert!(bad_parse.is_err());
517        if let Err(err) = bad_parse {
518            assert_eq!(
519                format!("{}", err),
520                "invalid `SessionId`, expected id to start with \"se_\""
521            );
522        }
523    }
524}