flowglad 0.1.1

(Unofficial) Rust SDK for FlowGlad - Open source billing infrastructure
Documentation
//! Strongly-typed ID wrappers
//!
//! This module provides newtype wrappers for all FlowGlad resource IDs.
//! Using newtypes prevents accidentally mixing up different types of IDs.

use serde::{Deserialize, Serialize};
use std::fmt;

/// Macro to define a strongly-typed ID wrapper
///
/// This creates a newtype wrapper around a String that:
/// - Implements Debug, Clone, PartialEq, Eq, Hash
/// - Serializes/deserializes transparently as a string
/// - Implements Display and AsRef<str> for convenience
macro_rules! define_id {
    ($(#[$meta:meta])* $name:ident) => {
        $(#[$meta])*
        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
        #[serde(transparent)]
        pub struct $name(String);

        impl $name {
            /// Create a new ID from a string
            pub fn new(id: impl Into<String>) -> Self {
                Self(id.into())
            }

            /// Get the ID as a string slice
            pub fn as_str(&self) -> &str {
                &self.0
            }

            /// Consume this ID and return the inner String
            pub fn into_inner(self) -> String {
                self.0
            }
        }

        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}", self.0)
            }
        }

        impl AsRef<str> for $name {
            fn as_ref(&self) -> &str {
                &self.0
            }
        }

        impl From<String> for $name {
            fn from(s: String) -> Self {
                Self(s)
            }
        }

        impl From<&str> for $name {
            fn from(s: &str) -> Self {
                Self(s.to_string())
            }
        }
    };
}

// Customer IDs
define_id!(
    /// A unique identifier for a Customer
    CustomerId
);

// Product IDs
define_id!(
    /// A unique identifier for a Product
    ProductId
);

define_id!(
    /// A unique identifier for a Price
    PriceId
);

define_id!(
    /// A unique identifier for a Pricing Model
    PricingModelId
);

// Subscription IDs
define_id!(
    /// A unique identifier for a Subscription
    SubscriptionId
);

define_id!(
    /// A unique identifier for a Subscription Item
    SubscriptionItemId
);

define_id!(
    /// A unique identifier for a Subscription Item Feature
    SubscriptionItemFeatureId
);

// Invoice IDs
define_id!(
    /// A unique identifier for an Invoice
    InvoiceId
);

define_id!(
    /// A unique identifier for an Invoice Line Item
    InvoiceLineItemId
);

// Payment IDs
define_id!(
    /// A unique identifier for a Payment
    PaymentId
);

define_id!(
    /// A unique identifier for a Payment Method
    PaymentMethodId
);

// Checkout IDs
define_id!(
    /// A unique identifier for a Checkout Session
    CheckoutSessionId
);

define_id!(
    /// A unique identifier for a Purchase
    PurchaseId
);

// Feature IDs
define_id!(
    /// A unique identifier for a Feature
    FeatureId
);

define_id!(
    /// A unique identifier for a Product Feature
    ProductFeatureId
);

// Discount IDs
define_id!(
    /// A unique identifier for a Discount
    DiscountId
);

// Usage IDs
define_id!(
    /// A unique identifier for a Usage Meter
    UsageMeterId
);

define_id!(
    /// A unique identifier for a Usage Event
    UsageEventId
);

// Webhook IDs
define_id!(
    /// A unique identifier for a Webhook
    WebhookId
);

// API Key IDs
define_id!(
    /// A unique identifier for an API Key
    ApiKeyId
);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_customer_id_creation() {
        let id = CustomerId::new("cus_123");
        assert_eq!(id.as_str(), "cus_123");
        assert_eq!(id.to_string(), "cus_123");
    }

    #[test]
    fn test_customer_id_from_string() {
        let id: CustomerId = "cus_456".into();
        assert_eq!(id.as_str(), "cus_456");
    }

    #[test]
    fn test_customer_id_equality() {
        let id1 = CustomerId::new("cus_123");
        let id2 = CustomerId::new("cus_123");
        let id3 = CustomerId::new("cus_456");

        assert_eq!(id1, id2);
        assert_ne!(id1, id3);
    }

    #[test]
    fn test_customer_id_serialization() {
        let id = CustomerId::new("cus_test");
        let json = serde_json::to_string(&id).unwrap();
        assert_eq!(json, r#""cus_test""#);

        let deserialized: CustomerId = serde_json::from_str(&json).unwrap();
        assert_eq!(id, deserialized);
    }

    #[test]
    fn test_different_id_types_are_distinct() {
        // This shouldn't compile, demonstrating type safety:
        // let customer_id = CustomerId::new("cus_123");
        // let product_id: ProductId = customer_id; // ERROR!

        // But we can create them from the same string:
        let customer_id = CustomerId::new("id_123");
        let product_id = ProductId::new("id_123");

        // They have the same string value but are different types
        assert_eq!(customer_id.as_str(), product_id.as_str());
    }
}