stateset_primitives/
id.rs1use core::fmt;
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11macro_rules! define_id {
23 (
24 $(#[$meta:meta])*
25 $name:ident
26 ) => {
27 $(#[$meta])*
28 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
29 #[serde(transparent)]
30 #[must_use]
31 pub struct $name(Uuid);
32
33 impl $name {
34 #[inline]
36 #[cfg(feature = "std")]
37 pub fn new() -> Self {
38 Self(Uuid::new_v4())
39 }
40
41 #[inline]
43 pub const fn nil() -> Self {
44 Self(Uuid::nil())
45 }
46
47 #[inline]
49 pub const fn from_uuid(id: Uuid) -> Self {
50 Self(id)
51 }
52
53 #[inline]
55 pub const fn as_uuid(&self) -> &Uuid {
56 &self.0
57 }
58
59 #[inline]
61 pub const fn into_uuid(self) -> Uuid {
62 self.0
63 }
64
65 #[inline]
67 pub const fn is_nil(&self) -> bool {
68 self.0.is_nil()
69 }
70 }
71
72 #[cfg(feature = "std")]
73 impl Default for $name {
74 #[inline]
75 fn default() -> Self {
76 Self::new()
77 }
78 }
79
80 impl fmt::Debug for $name {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "{}({})", stringify!($name), self.0)
83 }
84 }
85
86 impl fmt::Display for $name {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 self.0.fmt(f)
89 }
90 }
91
92 impl core::str::FromStr for $name {
93 type Err = uuid::Error;
94
95 #[inline]
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 Uuid::parse_str(s).map(Self)
98 }
99 }
100
101 impl From<Uuid> for $name {
102 #[inline]
103 fn from(id: Uuid) -> Self {
104 Self(id)
105 }
106 }
107
108 impl From<$name> for Uuid {
109 #[inline]
110 fn from(id: $name) -> Self {
111 id.0
112 }
113 }
114
115 impl AsRef<Uuid> for $name {
116 #[inline]
117 fn as_ref(&self) -> &Uuid {
118 &self.0
119 }
120 }
121
122 #[cfg(feature = "sqlx-postgres")]
123 impl sqlx::Type<sqlx::Postgres> for $name {
124 fn type_info() -> sqlx::postgres::PgTypeInfo {
125 <Uuid as sqlx::Type<sqlx::Postgres>>::type_info()
126 }
127 }
128
129 #[cfg(feature = "sqlx-postgres")]
130 impl sqlx::postgres::PgHasArrayType for $name {
131 fn array_type_info() -> sqlx::postgres::PgTypeInfo {
132 <Uuid as sqlx::postgres::PgHasArrayType>::array_type_info()
133 }
134 }
135
136 #[cfg(feature = "sqlx-postgres")]
137 impl<'q> sqlx::Encode<'q, sqlx::Postgres> for $name {
138 fn encode_by_ref(
139 &self,
140 buf: &mut sqlx::postgres::PgArgumentBuffer,
141 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
142 <Uuid as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&self.0, buf)
143 }
144 }
145
146 #[cfg(feature = "sqlx-postgres")]
147 impl<'r> sqlx::Decode<'r, sqlx::Postgres> for $name {
148 fn decode(value: sqlx::postgres::PgValueRef<'r>)
149 -> Result<Self, sqlx::error::BoxDynError> {
150 <Uuid as sqlx::Decode<'r, sqlx::Postgres>>::decode(value).map(Self)
151 }
152 }
153
154 #[cfg(any(test, feature = "arbitrary"))]
155 impl proptest::arbitrary::Arbitrary for $name {
156 type Parameters = ();
157 type Strategy = proptest::strategy::MapInto<
158 proptest::arbitrary::StrategyFor<[u8; 16]>,
159 Self,
160 >;
161
162 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
163 use proptest::strategy::Strategy;
164 proptest::arbitrary::any::<[u8; 16]>().prop_map_into()
165 }
166 }
167
168 #[cfg(any(test, feature = "arbitrary"))]
169 impl From<[u8; 16]> for $name {
170 fn from(bytes: [u8; 16]) -> Self {
171 Self(Uuid::from_bytes(bytes))
172 }
173 }
174
175 #[cfg(feature = "rusqlite")]
176 impl rusqlite::types::ToSql for $name {
177 #[inline]
178 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
179 Ok(rusqlite::types::ToSqlOutput::Owned(
181 rusqlite::types::Value::Text(self.0.to_string()),
182 ))
183 }
184 }
185
186 #[cfg(feature = "rusqlite")]
187 impl rusqlite::types::FromSql for $name {
188 #[inline]
189 fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
190 let text = value.as_str()?;
191 Uuid::parse_str(text)
192 .map(Self)
193 .map_err(|e| rusqlite::types::FromSqlError::Other(Box::new(e)))
194 }
195 }
196 };
197}
198
199define_id! {
204 OrderId
206}
207
208define_id! {
209 CustomerId
211}
212
213define_id! {
214 ProductId
216}
217
218define_id! {
219 InvoiceId
221}
222
223define_id! {
224 ShipmentId
226}
227
228define_id! {
229 ReturnId
231}
232
233define_id! {
234 WarehouseId
236}
237
238define_id! {
239 PaymentId
241}
242
243define_id! {
244 InventoryItemId
246}
247
248define_id! {
249 SubscriptionId
251}
252
253define_id! {
254 CartId
256}
257
258define_id! {
259 FulfillmentId
261}
262
263define_id! {
264 OrderItemId
266}
267
268define_id! {
269 PurchaseOrderId
271}
272
273define_id! {
274 PromotionId
276}
277
278define_id! {
279 WarrantyId
281}
282
283define_id! {
284 CreditId
286}
287
288define_id! {
289 AgentId
291}
292
293define_id! {
294 GiftCardId
296}
297
298define_id! {
299 StoreCreditId
301}
302
303define_id! {
304 SegmentId
306}
307
308define_id! {
309 ShippingZoneId
311}
312
313define_id! {
314 ShippingMethodId
316}
317
318define_id! {
319 ReviewId
321}
322
323define_id! {
324 WishlistId
326}
327
328define_id! {
329 LoyaltyProgramId
331}
332
333define_id! {
334 RewardId
336}
337
338define_id! {
339 GiftCardTransactionId
341}
342
343define_id! {
344 StoreCreditTransactionId
346}
347
348define_id! {
349 LoyaltyTransactionId
351}
352
353define_id! {
354 FraudRuleId
356}
357
358define_id! {
359 SearchConfigId
361}
362
363define_id! {
364 LoyaltyAccountId
366}
367
368define_id! {
369 ChannelId
371}
372
373define_id! {
374 CompanyId
376}
377
378define_id! {
379 ContactId
381}
382
383define_id! {
384 CompanyAddressId
386}
387
388define_id! {
389 TransferOrderId
391}
392
393define_id! {
394 TransferOrderItemId
396}
397
398define_id! {
399 UnitClassId
401}
402
403define_id! {
404 UnitOfMeasureId
406}
407
408define_id! {
409 UnitConversionRuleId
411}
412
413define_id! {
414 ProductionBatchId
416}
417
418define_id! {
419 SupplierSkuId
421}
422
423define_id! {
424 VendorReturnId
426}
427
428define_id! {
429 VendorReturnItemId
431}
432
433define_id! {
434 VendorCreditId
436}
437
438define_id! {
439 VendorCreditApplicationId
441}
442
443define_id! {
444 PaymentObligationId
446}
447
448define_id! {
449 PriceLevelId
451}
452
453define_id! {
454 PrepaymentId
456}
457
458define_id! {
459 PrepaymentApplicationId
461}
462
463define_id! {
464 PriceScheduleId
466}
467
468define_id! {
469 ActivityLogId
471}
472
473define_id! {
474 IntegrationMappingId
476}
477
478define_id! {
479 InboundShipmentId
481}
482
483define_id! {
484 InboundShipmentItemId
486}
487
488define_id! {
489 PurgatoryOrderId
491}
492
493define_id! {
494 PurgatoryLineItemId
496}
497
498define_id! {
499 PrintStationId
501}
502
503define_id! {
504 PrintJobId
506}
507
508define_id! {
509 EdiDocumentId
511}
512
513define_id! {
514 IntegrationFieldMappingId
516}
517
518define_id! {
519 TopologySnapshotId
521}
522
523define_id! {
524 StockSnapshotId
526}
527
528define_id! {
529 StockSnapshotLineId
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn id_types_are_distinct() {
539 let order_id = OrderId::from_uuid(Uuid::from_bytes([1; 16]));
540 let customer_id = CustomerId::from_uuid(Uuid::from_bytes([2; 16]));
541
542 let _: Uuid = order_id.into();
544 let _: Uuid = customer_id.into();
545
546 }
549
550 #[test]
551 fn roundtrip_display_parse() {
552 let id = OrderId::from_uuid(Uuid::from_bytes([3; 16]));
553 let s = id.to_string();
554 let parsed: OrderId = s.parse().unwrap();
555 assert_eq!(id, parsed);
556 }
557
558 #[test]
559 fn serde_roundtrip() {
560 let id = ProductId::from_uuid(Uuid::from_bytes([4; 16]));
561 let json = serde_json::to_string(&id).unwrap();
562 let parsed: ProductId = serde_json::from_str(&json).unwrap();
563 assert_eq!(id, parsed);
564
565 let uuid_json = serde_json::to_string(id.as_uuid()).unwrap();
567 assert_eq!(json, uuid_json);
568 }
569
570 #[test]
571 fn nil_id() {
572 let id = OrderId::nil();
573 assert!(id.is_nil());
574 assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000000");
575 }
576
577 #[test]
578 fn debug_includes_type_name() {
579 let id = CustomerId::nil();
580 let debug = format!("{:?}", id);
581 assert!(debug.starts_with("CustomerId("));
582 }
583
584 #[test]
585 fn from_uuid_roundtrip() {
586 let uuid = Uuid::from_bytes([5; 16]);
587 let order_id = OrderId::from(uuid);
588 assert_eq!(Uuid::from(order_id), uuid);
589 }
590
591 mod proptests {
592 use super::*;
593 use proptest::prelude::*;
594
595 proptest! {
596 #[test]
597 fn order_id_display_parse_roundtrip(id: OrderId) {
598 let s = id.to_string();
599 let parsed: OrderId = s.parse().unwrap();
600 prop_assert_eq!(id, parsed);
601 }
602
603 #[test]
604 fn customer_id_display_parse_roundtrip(id: CustomerId) {
605 let s = id.to_string();
606 let parsed: CustomerId = s.parse().unwrap();
607 prop_assert_eq!(id, parsed);
608 }
609
610 #[test]
611 fn product_id_serde_roundtrip(id: ProductId) {
612 let json = serde_json::to_string(&id).unwrap();
613 let parsed: ProductId = serde_json::from_str(&json).unwrap();
614 prop_assert_eq!(id, parsed);
615 }
616 }
617 }
618}