1use smart_default::SmartDefault;
2
3macro_rules! def_id_serde_impls {
4 ($struct_name:ident) => {
5 impl serde::Serialize for $struct_name {
6 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
7 where
8 S: serde::ser::Serializer,
9 {
10 self.as_str().serialize(serializer)
11 }
12 }
13
14 impl<'de> serde::Deserialize<'de> for $struct_name {
15 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
16 where
17 D: serde::de::Deserializer<'de>,
18 {
19 let s: String = serde::Deserialize::deserialize(deserializer)?;
20 s.parse::<Self>().map_err(::serde::de::Error::custom)
21 }
22 }
23 };
24 ($struct_name:ident, _) => {};
25}
26
27macro_rules! def_id {
28 ($struct_name:ident: String) => {
29 #[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
30 pub struct $struct_name(smol_str::SmolStr);
31
32 impl $struct_name {
33 #[inline(always)]
35 pub fn as_str(&self) -> &str {
36 self.0.as_str()
37 }
38 }
39
40 impl PartialEq<str> for $struct_name {
41 fn eq(&self, other: &str) -> bool {
42 self.as_str() == other
43 }
44 }
45
46 impl PartialEq<&str> for $struct_name {
47 fn eq(&self, other: &&str) -> bool {
48 self.as_str() == *other
49 }
50 }
51
52 impl PartialEq<String> for $struct_name {
53 fn eq(&self, other: &String) -> bool {
54 self.as_str() == other
55 }
56 }
57
58 impl PartialOrd for $struct_name {
59 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
60 Some(self.cmp(other))
61 }
62 }
63
64 impl Ord for $struct_name {
65 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
66 self.as_str().cmp(other.as_str())
67 }
68 }
69
70 impl AsRef<str> for $struct_name {
71 fn as_ref(&self) -> &str {
72 self.as_str()
73 }
74 }
75
76 impl crate::params::AsCursor for $struct_name {}
77
78 impl std::ops::Deref for $struct_name {
79 type Target = str;
80
81 fn deref(&self) -> &str {
82 self.as_str()
83 }
84 }
85
86 impl std::fmt::Display for $struct_name {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 self.0.fmt(f)
89 }
90 }
91
92 impl std::str::FromStr for $struct_name {
93 type Err = ParseIdError;
94
95 fn from_str(s: &str) -> Result<Self, Self::Err> {
96 Ok($struct_name(s.into()))
97 }
98 }
99
100 impl serde::Serialize for $struct_name {
101 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102 where S: serde::ser::Serializer
103 {
104 self.as_str().serialize(serializer)
105 }
106 }
107
108 impl<'de> serde::Deserialize<'de> for $struct_name {
109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110 where D: serde::de::Deserializer<'de>
111 {
112 let s: String = serde::Deserialize::deserialize(deserializer)?;
113 s.parse::<Self>().map_err(::serde::de::Error::custom)
114 }
115 }
116 };
117 ($struct_name:ident, $prefix:literal $(| $alt_prefix:literal)* $(, { $generate_hint:tt })?) => {
118 #[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
123 pub struct $struct_name(smol_str::SmolStr);
124
125 impl $struct_name {
126 #[inline(always)]
128 #[deprecated(note = "Please use prefixes or is_valid_prefix")]
129 pub fn prefix() -> &'static str {
130 $prefix
131 }
132
133 #[inline(always)]
135 pub fn prefixes() -> &'static [&'static str] {
136 &[$prefix$(, $alt_prefix)*]
137 }
138
139 #[inline(always)]
141 pub fn as_str(&self) -> &str {
142 self.0.as_str()
143 }
144
145 pub fn is_valid_prefix(prefix: &str) -> bool {
147 prefix == $prefix $( || prefix == $alt_prefix )*
148 }
149 }
150
151 impl PartialEq<str> for $struct_name {
152 fn eq(&self, other: &str) -> bool {
153 self.as_str() == other
154 }
155 }
156
157 impl PartialEq<&str> for $struct_name {
158 fn eq(&self, other: &&str) -> bool {
159 self.as_str() == *other
160 }
161 }
162
163 impl PartialEq<String> for $struct_name {
164 fn eq(&self, other: &String) -> bool {
165 self.as_str() == other
166 }
167 }
168
169 impl PartialOrd for $struct_name {
170 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
171 Some(self.cmp(other))
172 }
173 }
174
175 impl Ord for $struct_name {
176 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
177 self.as_str().cmp(other.as_str())
178 }
179 }
180
181 impl AsRef<str> for $struct_name {
182 fn as_ref(&self) -> &str {
183 self.as_str()
184 }
185 }
186
187 impl crate::params::AsCursor for $struct_name {}
188
189 impl std::ops::Deref for $struct_name {
190 type Target = str;
191
192 fn deref(&self) -> &str {
193 self.as_str()
194 }
195 }
196
197 impl std::fmt::Display for $struct_name {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 self.0.fmt(f)
200 }
201 }
202
203 impl std::str::FromStr for $struct_name {
204 type Err = ParseIdError;
205
206 fn from_str(s: &str) -> Result<Self, Self::Err> {
207 if !s.starts_with($prefix) $(
208 && !s.starts_with($alt_prefix)
209 )* {
210 eprintln!("bad id is: {} (expected: {:?}) for {}", s, $prefix, stringify!($struct_name));
212
213 Err(ParseIdError {
214 typename: stringify!($struct_name),
215 expected: stringify!(id to start with $prefix $(or $alt_prefix)*),
216 })
217 } else {
218 Ok($struct_name(s.into()))
219 }
220 }
221 }
222
223 def_id_serde_impls!($struct_name $(, $generate_hint )*);
224 };
225 (#[optional] enum $enum_name:ident { $( $variant_name:ident($($variant_type:tt)*) ),* $(,)* }) => {
226 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
227 pub enum $enum_name {
228 None,
229 $( $variant_name($($variant_type)*), )*
230 }
231
232 impl $enum_name {
233 pub fn as_str(&self) -> &str {
234 match *self {
235 $enum_name::None => "",
236 $( $enum_name::$variant_name(ref id) => id.as_str(), )*
237 }
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<&str> for $enum_name {
248 fn eq(&self, other: &&str) -> bool {
249 self.as_str() == *other
250 }
251 }
252
253 impl PartialEq<String> for $enum_name {
254 fn eq(&self, other: &String) -> bool {
255 self.as_str() == other
256 }
257 }
258
259 impl AsRef<str> for $enum_name {
260 fn as_ref(&self) -> &str {
261 self.as_str()
262 }
263 }
264
265 impl crate::params::AsCursor for $enum_name {}
266
267 impl std::ops::Deref for $enum_name {
268 type Target = str;
269
270 fn deref(&self) -> &str {
271 self.as_str()
272 }
273 }
274
275 impl std::fmt::Display for $enum_name {
276 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
277 match *self {
278 $enum_name::None => Ok(()),
279 $( $enum_name::$variant_name(ref id) => id.fmt(f), )*
280 }
281 }
282 }
283
284 impl std::default::Default for $enum_name {
285 fn default() -> Self {
286 $enum_name::None
287 }
288 }
289
290 impl std::str::FromStr for $enum_name {
291 type Err = ParseIdError;
292
293 fn from_str(s: &str) -> Result<Self, Self::Err> {
294 let prefix = s.find('_')
295 .map(|i| &s[0..=i])
296 .ok_or_else(|| ParseIdError {
297 typename: stringify!($enum_name),
298 expected: "id to start with a prefix (as in 'prefix_')"
299 })?;
300
301 match prefix {
302 $(_ if $($variant_type)*::is_valid_prefix(prefix) => {
303 Ok($enum_name::$variant_name(s.parse()?))
304 })*
305 _ => {
306 Err(ParseIdError {
307 typename: stringify!($enum_name),
308 expected: "unknown id prefix",
309 })
310 }
311 }
312 }
313 }
314
315 impl serde::Serialize for $enum_name {
316 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
317 where S: serde::ser::Serializer
318 {
319 self.as_str().serialize(serializer)
320 }
321 }
322
323 impl<'de> serde::Deserialize<'de> for $enum_name {
324 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
325 where D: serde::de::Deserializer<'de>
326 {
327 let s: String = serde::Deserialize::deserialize(deserializer)?;
328 s.parse::<Self>().map_err(::serde::de::Error::custom)
329 }
330 }
331
332 $(
333 impl From<$($variant_type)*> for $enum_name {
334 fn from(id: $($variant_type)*) -> Self {
335 $enum_name::$variant_name(id)
336 }
337 }
338 )*
339 };
340 (enum $enum_name:ident { $( $(#[$test:meta])? $variant_name:ident($($variant_type:tt)*) ),+ $(,)? }) => {
341 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
342 #[derive(SmartDefault)]
343 pub enum $enum_name {
344 $( $(#[$test])* $variant_name($($variant_type)*), )*
345 }
346
347 impl $enum_name {
348 pub fn as_str(&self) -> &str {
349 match *self {
350 $( $enum_name::$variant_name(ref id) => id.as_str(), )*
351 }
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<&str> for $enum_name {
362 fn eq(&self, other: &&str) -> bool {
363 self.as_str() == *other
364 }
365 }
366
367 impl PartialEq<String> for $enum_name {
368 fn eq(&self, other: &String) -> bool {
369 self.as_str() == other
370 }
371 }
372
373 impl AsRef<str> for $enum_name {
374 fn as_ref(&self) -> &str {
375 self.as_str()
376 }
377 }
378
379 impl crate::params::AsCursor for $enum_name {}
380
381 impl std::ops::Deref for $enum_name {
382 type Target = str;
383
384 fn deref(&self) -> &str {
385 self.as_str()
386 }
387 }
388
389 impl std::fmt::Display for $enum_name {
390 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
391 match *self {
392 $( $enum_name::$variant_name(ref id) => id.fmt(f), )*
393 }
394 }
395 }
396
397 impl std::str::FromStr for $enum_name {
398 type Err = ParseIdError;
399
400 fn from_str(s: &str) -> Result<Self, Self::Err> {
401 let prefix = s.find('_')
402 .map(|i| &s[0..=i])
403 .ok_or_else(|| ParseIdError {
404 typename: stringify!($enum_name),
405 expected: "id to start with a prefix (as in 'prefix_')"
406 })?;
407
408 match prefix {
409 $(_ if $($variant_type)*::is_valid_prefix(prefix) => {
410 Ok($enum_name::$variant_name(s.parse()?))
411 })*
412 _ => {
413 Err(ParseIdError {
414 typename: stringify!($enum_name),
415 expected: "unknown id prefix",
416 })
417 }
418 }
419 }
420 }
421
422 impl serde::Serialize for $enum_name {
423 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
424 where S: serde::ser::Serializer
425 {
426 self.as_str().serialize(serializer)
427 }
428 }
429
430 impl<'de> serde::Deserialize<'de> for $enum_name {
431 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
432 where D: serde::de::Deserializer<'de>
433 {
434 let s: String = serde::Deserialize::deserialize(deserializer)?;
435 s.parse::<Self>().map_err(::serde::de::Error::custom)
436 }
437 }
438
439 $(
440 impl From<$($variant_type)*> for $enum_name {
441 fn from(id: $($variant_type)*) -> Self {
442 $enum_name::$variant_name(id)
443 }
444 }
445 )*
446 };
447}
448
449#[derive(Clone, Debug)]
450pub struct ParseIdError {
451 typename: &'static str,
452 expected: &'static str,
453}
454
455impl std::fmt::Display for ParseIdError {
456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
457 write!(f, "invalid `{}`, expected {}", self.typename, self.expected)
458 }
459}
460
461impl std::error::Error for ParseIdError {
462 fn description(&self) -> &str {
463 "error parsing an id"
464 }
465}
466
467def_id!(AccountId, "acct_");
468def_id!(AlipayAccountId, "aliacc_");
469def_id!(ApplicationFeeId, "fee_");
470def_id!(ApplicationId, "ca_");
471def_id!(ApplicationFeeRefundId, "fr_");
472def_id!(BalanceTransactionId, "txn_");
473def_id!(BankAccountId, "ba_" | "card_");
474def_id!(BillingPortalSessionId, "bps_");
475def_id!(BillingPortalConfigurationId, "bpc_");
476def_id!(BankTokenId, "btok_");
477def_id!(
478 #[optional]
479 enum BalanceTransactionSourceId {
480 ApplicationFee(ApplicationFeeId),
481 Charge(ChargeId),
482 Dispute(DisputeId),
483 ApplicationFeeRefund(ApplicationFeeRefundId),
484 IssuingAuthorization(IssuingAuthorizationId),
485 IssuingDispute(IssuingDisputeId),
486 IssuingTransaction(IssuingTransactionId),
487 Payout(PayoutId),
488 Refund(RefundId),
489 Topup(TopupId),
490 Transfer(TransferId),
491 TransferReversal(TransferReversalId),
492 }
493);
494def_id!(CardId, "card_");
495def_id!(CardTokenId, "tok_");
496def_id!(ChargeId, "ch_" | "py_"); def_id!(CheckoutSessionId, "cs_");
498def_id!(CheckoutSessionItemId, "li_");
499def_id!(ConnectCollectionTransferId, "connct_");
500def_id!(ConnectTokenId, "ct_");
501def_id!(CouponId: String); def_id!(CreditNoteId, "cn_");
503def_id!(CreditNoteLineItemId, "cnli_");
504def_id!(CustomerBalanceTransactionId, "cbtxn_");
505def_id!(CustomerId, "cus_");
506def_id!(DiscountId, "di_");
507def_id!(DisputeId, "dp_" | "du_" | "pdp_");
508def_id!(EphemeralKeyId, "ephkey_");
509def_id!(EventId, "evt_");
510def_id!(FileId, "file_");
511def_id!(FileLinkId, "link_");
512def_id!(InvoiceId, "in_", { _ });
513def_id!(InvoiceItemId, "ii_");
514def_id!(InvoiceLineItemIdWebhook, "il_");
515
516def_id!(
517 enum InvoiceLineItemId {
518 #[default]
519 Item(InvoiceItemId),
520 Subscription(SubscriptionLineId),
521 InvoiceLineItemIdWebhook(InvoiceLineItemIdWebhook),
522 }
523);
524def_id!(IssuingAuthorizationId, "iauth_");
525def_id!(IssuingCardId, "ic_");
526def_id!(IssuingCardholderId, "ich_");
527def_id!(IssuingDisputeId, "idp_");
528def_id!(IssuingTransactionId, "ipi_");
529def_id!(IssuingTokenId: String);
530def_id!(OrderId, "or_");
531def_id!(OrderReturnId, "orret_");
532def_id!(MandateId, "mandate_");
533def_id!(PaymentMethodConfigurationId: String);
534def_id!(PaymentIntentId, "pi_");
535def_id!(PaymentLinkId, "plink_");
536def_id!(PaymentMethodId, "pm_" | "card_" | "src_" | "ba_");
537def_id!(
538 enum PaymentSourceId {
539 #[default]
540 Account(AccountId),
541 AlipayAccount(AlipayAccountId),
542 BankAccount(BankAccountId),
543 Card(CardId),
544 Source(SourceId),
545 }
546);
547def_id!(PayoutId, "po_");
548def_id!(
549 enum PayoutDestinationId {
550 #[default]
551 BankAccount(BankAccountId),
552 Card(CardId),
553 }
554);
555def_id!(PersonId, "person_");
556def_id!(PlanId: String); def_id!(PlatformTaxFeeId, "ptf");
558def_id!(PriceId: String); def_id!(ProductId: String); def_id!(PromotionCodeId, "promo_");
561def_id!(QuoteId, "qt_");
562def_id!(RecipientId: String); def_id!(RefundId, "re_" | "pyr_");
564def_id!(ReserveTransactionId, "rtx_");
565def_id!(ReviewId, "prv_");
566def_id!(ScheduledQueryRunId, "sqr_");
567def_id!(SetupAttemptId, "setatt_");
568def_id!(SetupIntentId, "seti_");
569def_id!(SkuId, "sku_");
570def_id!(ShippingRateId, "shr_");
571def_id!(SourceId, "src_");
572def_id!(SubscriptionId, "sub_");
573def_id!(SubscriptionItemId, "si_");
574def_id!(SubscriptionLineId, "sli_");
575def_id!(SubscriptionScheduleId, "sub_sched_");
576def_id!(TaxIdId, "txi_" | "atxi_");
577def_id!(TaxCalculationId: String);
578def_id!(TaxCalculationLineItemId: String);
579def_id!(TaxCodeId, "txcd_");
580def_id!(TaxDeductedAtSourceId, "itds");
581def_id!(TaxRateId, "txr_");
582def_id!(TerminalConfigurationId, "tmc_");
583def_id!(TerminalLocationId, "tml_");
584def_id!(TerminalReaderId, "tmr_");
585def_id!(TestHelpersTestClockId, "clock_");
586def_id!(
587 enum TokenId {
588 #[default]
589 Card(CardTokenId),
590 Bank(BankTokenId),
591 Connect(ConnectTokenId),
592 }
593);
594def_id!(TopupId, "tu_");
595def_id!(TransferId, "tr_");
596def_id!(TransferReversalId, "trr_");
597def_id!(UsageRecordId, "mbur_");
598def_id!(UsageRecordSummaryId, "urs_" | "sis_");
599def_id!(WebhookEndpointId, "we_");
600
601impl InvoiceId {
602 pub(crate) fn none() -> Self {
603 Self("".into())
604 }
605
606 pub fn is_none(&self) -> bool {
609 self.0.is_empty()
610 }
611}
612impl serde::Serialize for InvoiceId {
613 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
614 where
615 S: serde::ser::Serializer,
616 {
617 if self.0.is_empty() {
618 let val: Option<&str> = None;
619 val.serialize(serializer)
620 } else {
621 self.as_str().serialize(serializer)
622 }
623 }
624}
625impl<'de> serde::Deserialize<'de> for InvoiceId {
626 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
627 where
628 D: serde::de::Deserializer<'de>,
629 {
630 let s: Option<String> = serde::Deserialize::deserialize(deserializer)?;
631 match s {
632 None => Ok(InvoiceId::none()),
633 Some(s) => {
634 if s.is_empty() {
635 Ok(InvoiceId::none())
636 } else {
637 s.parse::<Self>().map_err(::serde::de::Error::custom)
638 }
639 }
640 }
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use std::fmt::{Debug, Display};
647 use std::str::FromStr;
648
649 use serde::de::DeserializeOwned;
650 use serde::{Deserialize, Serialize};
651 use serde_json::json;
652
653 use super::*;
654
655 fn assert_ser_de_roundtrip<T>(id: &str)
656 where
657 T: DeserializeOwned + Serialize + FromStr + Display + Debug,
658 <T as FromStr>::Err: Debug,
659 {
660 let parsed_id = T::from_str(id).expect("Could not parse id");
661 let ser = serde_json::to_string(&parsed_id).expect("Could not serialize id");
662 let deser: T = serde_json::from_str(&ser).expect("Could not deserialize id");
663 assert_eq!(deser.to_string(), id.to_string());
664 }
665
666 fn assert_deser_err<T: DeserializeOwned + Debug>(id: &str) {
667 let json_str = format!(r#""{}""#, id);
668 let deser: Result<T, _> = serde_json::from_str(&json_str);
669 assert!(deser.is_err(), "Expected error, got {:?}", deser);
670 }
671
672 #[test]
673 fn test_empty_invoice_id_default() {
674 #[derive(Deserialize)]
675 struct WithInvoiceId {
676 id: InvoiceId,
677 }
678
679 for body in [json!({"id": ""}), json!({})] {
680 let deser: WithInvoiceId = serde_json::from_value(body).expect("Could not deser");
681 assert_eq!(deser.id, InvoiceId::none());
682 }
683 }
684
685 #[test]
686 fn test_ser_de_roundtrip() {
687 for id in ["in_12345", "in_"] {
689 assert_ser_de_roundtrip::<InvoiceId>(id);
690 }
691
692 assert_ser_de_roundtrip::<PriceId>("price_abc");
694
695 for id in ["re_bcd", "pyr_123"] {
697 assert_ser_de_roundtrip::<RefundId>(id);
698 }
699
700 for id in ["anything", ""] {
702 assert_ser_de_roundtrip::<ProductId>(id);
703 }
704
705 for id in ["tok_123", "btok_456"] {
707 assert_ser_de_roundtrip::<TokenId>(id);
708 }
709 }
710
711 #[test]
712 fn test_deser_err() {
713 assert_deser_err::<InvoiceId>("in");
715
716 for id in ["sub", ""] {
718 assert_deser_err::<SubscriptionId>(id);
719 }
720
721 for id in ["abc_bcd", "pyr_123"] {
723 assert_deser_err::<PaymentMethodId>(id);
724 }
725
726 for id in ["tok_123", "btok_456"] {
728 assert_deser_err::<PaymentSourceId>(id);
729 }
730 }
731
732 #[test]
733 fn test_parse_customer() {
734 assert!("cus_123".parse::<CustomerId>().is_ok());
735 let bad_parse = "zzz_123".parse::<CustomerId>();
736 assert!(bad_parse.is_err());
737 if let Err(err) = bad_parse {
738 assert_eq!(
739 format!("{}", err),
740 "invalid `CustomerId`, expected id to start with \"cus_\""
741 );
742 }
743 }
744
745 #[test]
746 fn test_parse_charge() {
747 assert!("ch_123".parse::<ChargeId>().is_ok());
748 assert!("py_123".parse::<ChargeId>().is_ok());
749 let bad_parse = "zz_123".parse::<ChargeId>();
750 assert!(bad_parse.is_err());
751 if let Err(err) = bad_parse {
752 assert_eq!(
753 format!("{}", err),
754 "invalid `ChargeId`, expected id to start with \"ch_\" or \"py_\""
755 );
756 }
757 }
758}