1#[cfg(feature = "sqlx-postgres")]
22use sqlx::{
23 postgres::{PgTypeInfo, PgValueRef},
24 Postgres, Type,
25};
26use std::{convert::TryFrom, fmt, str::FromStr};
27
28#[derive(Debug, thiserror::Error)]
30#[error("failed to initialize {target_type} from \"{input}\": {error_detail}")]
31pub struct GeneralResourceError {
32 target_type: &'static str,
34 input: String,
36 error_detail: GeneralResourceErrorDetail,
38}
39
40#[derive(Debug, thiserror::Error)]
43pub enum GeneralResourceErrorDetail {
44 #[error("incorrect prefix, expected \"{0}\"")]
46 WrongPrefix(&'static str),
47 #[error("the unique part must be 8 or 17, not {0} characters long")]
49 IdLength(usize),
50 #[error("the unique part contains non ascii alphanumeric characters")]
52 NonAsciiAlphanumeric,
53}
54
55#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57enum UniquePart {
58 C8([u8; 8]),
59 C17([u8; 17]),
60}
61
62impl UniquePart {
63 fn as_slice(&self) -> &[u8] {
64 match self {
65 Self::C8(x) => x,
66 Self::C17(x) => x,
67 }
68 }
69}
70
71macro_rules! impl_resource_id {
72 ($type:ident, $prefix:literal, $doc:literal) => {
73 #[doc = $doc]
74 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
75 pub struct $type(UniquePart);
76
77 impl $type {
78 const PREFIX: &'static str = $prefix;
79 }
80
81 impl TryFrom<&str> for $type {
82 type Error = $crate::Error;
83
84 fn try_from(s: &str) -> Result<Self, Self::Error> {
85 if !s.starts_with(Self::PREFIX) {
86 return Err(GeneralResourceError::new(
87 short_type_name::<$type>(),
88 s,
89 GeneralResourceErrorDetail::WrongPrefix(Self::PREFIX),
90 )
91 .into());
92 }
93 if !s[Self::PREFIX.len()..]
94 .chars()
95 .all(|c| c.is_ascii_alphanumeric())
96 {
97 return Err(GeneralResourceError::new(
98 short_type_name::<$type>(),
99 s,
100 GeneralResourceErrorDetail::NonAsciiAlphanumeric,
101 )
102 .into());
103 }
104
105 let id = &s[Self::PREFIX.len()..];
106 if id.len() == 8 {
107 let mut arr = [0u8; 8];
108 arr.copy_from_slice(id.as_bytes());
109 Ok($type(UniquePart::C8(arr)))
110 } else if id.len() == 17 {
111 let mut arr = [0u8; 17];
112 arr.copy_from_slice(id.as_bytes());
113 Ok($type(UniquePart::C17(arr)))
114 } else {
115 Err(GeneralResourceError::new(
116 short_type_name::<$type>(),
117 s,
118 GeneralResourceErrorDetail::IdLength(id.len()),
119 )
120 .into())
121 }
122 }
123 }
124
125 impl TryFrom<String> for $type {
126 type Error = $crate::Error;
127
128 fn try_from(s: String) -> Result<Self, Self::Error> {
129 Self::try_from(s.as_str())
130 }
131 }
132
133 impl TryFrom<&String> for $type {
134 type Error = $crate::Error;
135
136 fn try_from(s: &String) -> Result<Self, Self::Error> {
137 Self::try_from(s.as_str())
138 }
139 }
140
141 impl FromStr for $type {
142 type Err = $crate::Error;
143
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 Self::try_from(s)
146 }
147 }
148
149 impl fmt::Display for $type {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(f, "{}", Self::PREFIX)?;
152 write!(
153 f,
154 "{}",
155 std::str::from_utf8(self.0.as_slice()).unwrap_or_default()
156 )
157 }
158 }
159
160 impl fmt::Debug for $type {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.debug_tuple(short_type_name::<Self>())
163 .field(&self.to_string())
164 .finish()
165 }
166 }
167
168 impl From<$type> for String {
169 fn from(value: $type) -> Self {
170 value.to_string()
171 }
172 }
173
174 #[cfg(feature = "sqlx-postgres")]
175 impl Type<Postgres> for $type {
176 fn type_info() -> PgTypeInfo {
177 <String as Type<Postgres>>::type_info()
178 }
179
180 fn compatible(ty: &PgTypeInfo) -> bool {
181 <String as Type<Postgres>>::compatible(ty)
182 }
183 }
184
185 #[cfg(feature = "sqlx-postgres")]
186 impl<'q> sqlx::encode::Encode<'q, Postgres> for $type {
187 fn encode_by_ref(
188 &self,
189 buf: &mut sqlx::postgres::PgArgumentBuffer,
190 ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
191 <String as sqlx::encode::Encode<Postgres>>::encode_by_ref(&self.to_string(), buf)
192 }
193 }
194
195 #[cfg(feature = "sqlx-postgres")]
196 impl<'r> sqlx::decode::Decode<'r, Postgres> for $type {
197 fn decode(
198 value: PgValueRef<'r>,
199 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
200 let s = <&str as sqlx::decode::Decode<Postgres>>::decode(value)?;
201 Ok($type::try_from(s).map_err(|e| Box::new(sqlx::Error::Decode(e.into())))?)
202 }
203 }
204
205 #[cfg(feature = "serde")]
206 impl serde::Serialize for $type {
207 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
208 where
209 S: serde::Serializer,
210 {
211 serializer.serialize_str(&self.to_string())
212 }
213 }
214
215 #[cfg(feature = "serde")]
216 impl<'de> serde::Deserialize<'de> for $type {
217 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
218 where
219 D: serde::Deserializer<'de>,
220 {
221 let s = String::deserialize(deserializer)?;
222 $type::try_from(s).map_err(serde::de::Error::custom)
223 }
224 }
225 };
226}
227
228fn short_type_name<T>() -> &'static str {
229 let name = std::any::type_name::<T>();
230 name.split("::").last().unwrap_or(name)
231}
232
233impl GeneralResourceError {
234 fn new(
235 target_type: &'static str,
236 input: impl Into<String>,
237 error_detail: GeneralResourceErrorDetail,
238 ) -> Self {
239 Self {
240 target_type,
241 input: input.into(),
242 error_detail,
243 }
244 }
245}
246
247impl_resource_id!(
248 AwsNetworkAclId,
249 "acl-",
250 "AWS Network ACL (Access Control List) ID"
251);
252impl_resource_id!(AwsAmiId, "ami-", "AWS AMI (Amazon Machine Image) ID");
253impl_resource_id!(AwsCustomerGatewayId, "cgw-", "AWS Customer Gateway ID");
254impl_resource_id!(AwsElasticIpId, "eipalloc-", "AWS Elastic IP ID");
255impl_resource_id!(
256 AwsEfsFileSystemId,
257 "fs-",
258 "AWS EFS (Elastic File System) ID"
259);
260impl_resource_id!(AwsEfsMountTargetId, "fsmt-", "AWS EFS Mount Target ID");
261impl_resource_id!(
262 AwsCloudFormationStackId,
263 "stack-",
264 "AWS CloudFormation Stack ID"
265);
266impl_resource_id!(
267 AwsElasticBeanstalkEnvironmentId,
268 "e-",
269 "AWS Elastic Beanstalk Environment ID"
270);
271impl_resource_id!(AwsInstanceId, "i-", "AWS EC2 Instance ID");
272impl_resource_id!(AwsInternetGatewayId, "igw-", "AWS Internet Gateway ID");
273impl_resource_id!(AwsKeyPairId, "key-", "AWS Key Pair ID");
274impl_resource_id!(AwsLoadBalancerId, "elbv2-", "AWS Elastic Load Balancer ID");
275impl_resource_id!(AwsNatGatewayId, "nat-", "AWS NAT Gateway ID");
276impl_resource_id!(AwsNetworkInterfaceId, "eni-", "AWS Network Interface ID");
277impl_resource_id!(AwsPlacementGroupId, "pg-", "AWS Placement Group ID");
278impl_resource_id!(AwsRdsInstanceId, "db-", "AWS RDS Instance ID");
279impl_resource_id!(AwsRedshiftClusterId, "redshift-", "AWS Redshift Cluster ID");
280impl_resource_id!(AwsRouteTableId, "rtb-", "AWS Route Table ID");
281impl_resource_id!(AwsSecurityGroupId, "sg-", "AWS Security Group ID");
282impl_resource_id!(AwsSnapshotId, "snap-", "AWS EBS Snapshot ID");
283impl_resource_id!(AwsSubnetId, "subnet-", "AWS VPC Subnet ID");
284impl_resource_id!(AwsTargetGroupId, "tg-", "AWS Target Group ID");
285impl_resource_id!(
286 AwsTransitGatewayAttachmentId,
287 "tgw-attach-",
288 "AWS Transit Gateway Attachment ID"
289);
290impl_resource_id!(AwsTransitGatewayId, "tgw-", "AWS Transit Gateway ID");
291impl_resource_id!(AwsVolumeId, "vol-", "AWS EBS Volume ID");
292impl_resource_id!(AwsVpcId, "vpc-", "AWS VPC (Virtual Private Cloud) ID");
293impl_resource_id!(AwsVpnConnectionId, "vpn-", "AWS VPN Connection ID");
294impl_resource_id!(AwsVpnGatewayId, "vgw-", "AWS VPN Gateway ID");
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 fn ami(s: &str) -> AwsAmiId {
301 AwsAmiId::try_from(s).unwrap()
302 }
303
304 #[test]
305 fn test_eq() {
306 assert_eq!(ami("ami-12345678"), ami("ami-12345678"));
307 assert_ne!(ami("ami-12345678"), ami("ami-abcdefgh"));
308 }
309
310 #[test]
311 fn test_fmt_display() {
312 assert_eq!(format!("{}", ami("ami-12345678")), "ami-12345678");
313 }
314
315 #[test]
316 fn test_fmt_debug() {
317 assert_eq!(
318 format!("{:?}", ami("ami-12345678")),
319 r#"AwsAmiId("ami-12345678")"#
320 );
321 }
322
323 #[test]
324 fn test_into_string() {
325 let s: String = ami("ami-12345678").into();
326 assert_eq!(s, "ami-12345678");
327 }
328
329 #[test]
330 fn test_tryfrom_str() {
331 assert!(AwsAmiId::try_from("ami-12345678").is_ok());
332 }
333
334 #[test]
335 fn test_tryfrom_string() {
336 assert!(AwsAmiId::try_from("ami-12345678".to_string()).is_ok());
337 }
338
339 #[test]
340 fn test_tryfrom_refstring() {
341 assert!(AwsAmiId::try_from(&"ami-12345678".to_string()).is_ok());
342 }
343
344 #[test]
345 fn test_fromstr() {
346 assert!("ami-12345678".parse::<AwsAmiId>().is_ok(),);
347 assert!("ami-12345678".to_string().parse::<AwsAmiId>().is_ok(),);
348 }
349
350 #[cfg(feature = "serde")]
351 #[test]
352 fn test_serialize() {
353 assert_eq!(
354 serde_json::to_string(&ami("ami-12345678")).unwrap(),
355 "\"ami-12345678\""
356 );
357 }
358
359 #[cfg(feature = "serde")]
360 #[test]
361 fn test_deserialize() {
362 assert_eq!(
363 serde_json::from_str::<AwsAmiId>("\"ami-12345678\"").unwrap(),
364 ami("ami-12345678"),
365 );
366 }
367
368 #[test]
369 fn test_wrong_prefix() {
370 let result = AwsAmiId::try_from("amx-12345678");
371 assert!(result.is_err());
372 assert_eq!(
373 result.unwrap_err().to_string(),
374 "failed to initialize AwsAmiId from \"amx-12345678\": incorrect prefix, expected \"ami-\""
375 );
376 }
377
378 #[test]
379 fn test_error_wrong_length() {
380 let result = AwsAmiId::try_from("ami-1234567");
381 assert!(result.is_err());
382 assert_eq!(
383 result.unwrap_err().to_string(),
384 "failed to initialize AwsAmiId from \"ami-1234567\": the unique part must be 8 or 17, not 7 characters long"
385 );
386
387 let result = AwsAmiId::try_from("ami-123456789012345678");
388 assert!(result.is_err());
389 assert_eq!(
390 result.unwrap_err().to_string(),
391 "failed to initialize AwsAmiId from \"ami-123456789012345678\": the unique part must be 8 or 17, not 18 characters long"
392 );
393 }
394
395 #[test]
396 fn test_error_non_alphanumeric() {
397 let result = AwsAmiId::try_from("ami-1234567!");
398 assert!(result.is_err());
399 assert_eq!(
400 result.unwrap_err().to_string(),
401 "failed to initialize AwsAmiId from \"ami-1234567!\": the unique part contains non ascii alphanumeric characters"
402 );
403 }
404
405 #[test]
406 fn test_valid_short_ids() {
407 assert_eq!(
408 AwsNetworkAclId::try_from("acl-1234abcd")
409 .unwrap()
410 .to_string(),
411 "acl-1234abcd"
412 );
413 assert_eq!(
414 AwsAmiId::try_from("ami-1234abcd").unwrap().to_string(),
415 "ami-1234abcd"
416 );
417 assert_eq!(
418 AwsCustomerGatewayId::try_from("cgw-1234abcd")
419 .unwrap()
420 .to_string(),
421 "cgw-1234abcd"
422 );
423 assert_eq!(
424 AwsElasticIpId::try_from("eipalloc-1234abcd")
425 .unwrap()
426 .to_string(),
427 "eipalloc-1234abcd"
428 );
429 assert_eq!(
430 AwsEfsFileSystemId::try_from("fs-1234abcd")
431 .unwrap()
432 .to_string(),
433 "fs-1234abcd"
434 );
435 assert_eq!(
436 AwsEfsMountTargetId::try_from("fsmt-1234abcd")
437 .unwrap()
438 .to_string(),
439 "fsmt-1234abcd"
440 );
441 assert_eq!(
442 AwsCloudFormationStackId::try_from("stack-1234abcd")
443 .unwrap()
444 .to_string(),
445 "stack-1234abcd"
446 );
447 assert_eq!(
448 AwsElasticBeanstalkEnvironmentId::try_from("e-1234abcd")
449 .unwrap()
450 .to_string(),
451 "e-1234abcd"
452 );
453 assert_eq!(
454 AwsInstanceId::try_from("i-1234abcd").unwrap().to_string(),
455 "i-1234abcd"
456 );
457 assert_eq!(
458 AwsInternetGatewayId::try_from("igw-1234abcd")
459 .unwrap()
460 .to_string(),
461 "igw-1234abcd"
462 );
463 assert_eq!(
464 AwsKeyPairId::try_from("key-1234abcd").unwrap().to_string(),
465 "key-1234abcd"
466 );
467 assert_eq!(
468 AwsLoadBalancerId::try_from("elbv2-1234abcd")
469 .unwrap()
470 .to_string(),
471 "elbv2-1234abcd"
472 );
473 assert_eq!(
474 AwsNatGatewayId::try_from("nat-1234abcd")
475 .unwrap()
476 .to_string(),
477 "nat-1234abcd"
478 );
479 assert_eq!(
480 AwsNetworkInterfaceId::try_from("eni-1234abcd")
481 .unwrap()
482 .to_string(),
483 "eni-1234abcd"
484 );
485 assert_eq!(
486 AwsPlacementGroupId::try_from("pg-1234abcd")
487 .unwrap()
488 .to_string(),
489 "pg-1234abcd"
490 );
491 assert_eq!(
492 AwsRdsInstanceId::try_from("db-1234abcd")
493 .unwrap()
494 .to_string(),
495 "db-1234abcd"
496 );
497 assert_eq!(
498 AwsRedshiftClusterId::try_from("redshift-1234abcd")
499 .unwrap()
500 .to_string(),
501 "redshift-1234abcd"
502 );
503 assert_eq!(
504 AwsRouteTableId::try_from("rtb-1234abcd")
505 .unwrap()
506 .to_string(),
507 "rtb-1234abcd"
508 );
509 assert_eq!(
510 AwsSecurityGroupId::try_from("sg-1234abcd")
511 .unwrap()
512 .to_string(),
513 "sg-1234abcd"
514 );
515 assert_eq!(
516 AwsSnapshotId::try_from("snap-1234abcd")
517 .unwrap()
518 .to_string(),
519 "snap-1234abcd"
520 );
521 assert_eq!(
522 AwsSubnetId::try_from("subnet-1234abcd")
523 .unwrap()
524 .to_string(),
525 "subnet-1234abcd"
526 );
527 assert_eq!(
528 AwsTargetGroupId::try_from("tg-1234abcd")
529 .unwrap()
530 .to_string(),
531 "tg-1234abcd"
532 );
533 assert_eq!(
534 AwsTransitGatewayAttachmentId::try_from("tgw-attach-1234abcd")
535 .unwrap()
536 .to_string(),
537 "tgw-attach-1234abcd"
538 );
539 assert_eq!(
540 AwsTransitGatewayId::try_from("tgw-1234abcd")
541 .unwrap()
542 .to_string(),
543 "tgw-1234abcd"
544 );
545 assert_eq!(
546 AwsVolumeId::try_from("vol-1234abcd").unwrap().to_string(),
547 "vol-1234abcd"
548 );
549 assert_eq!(
550 AwsVpcId::try_from("vpc-1234abcd").unwrap().to_string(),
551 "vpc-1234abcd"
552 );
553 assert_eq!(
554 AwsVpnConnectionId::try_from("vpn-1234abcd")
555 .unwrap()
556 .to_string(),
557 "vpn-1234abcd"
558 );
559 assert_eq!(
560 AwsVpnGatewayId::try_from("vgw-1234abcd")
561 .unwrap()
562 .to_string(),
563 "vgw-1234abcd"
564 );
565 }
566
567 #[test]
568 fn test_valid_long_ids() {
569 assert_eq!(
570 AwsNetworkAclId::try_from("acl-1a2b3c4d5e6f7j8h9")
571 .unwrap()
572 .to_string(),
573 "acl-1a2b3c4d5e6f7j8h9"
574 );
575 assert_eq!(
576 AwsAmiId::try_from("ami-1a2b3c4d5e6f7j8h9")
577 .unwrap()
578 .to_string(),
579 "ami-1a2b3c4d5e6f7j8h9"
580 );
581 assert_eq!(
582 AwsCustomerGatewayId::try_from("cgw-1a2b3c4d5e6f7j8h9")
583 .unwrap()
584 .to_string(),
585 "cgw-1a2b3c4d5e6f7j8h9"
586 );
587 assert_eq!(
588 AwsElasticIpId::try_from("eipalloc-1a2b3c4d5e6f7j8h9")
589 .unwrap()
590 .to_string(),
591 "eipalloc-1a2b3c4d5e6f7j8h9"
592 );
593 assert_eq!(
594 AwsEfsFileSystemId::try_from("fs-1a2b3c4d5e6f7j8h9")
595 .unwrap()
596 .to_string(),
597 "fs-1a2b3c4d5e6f7j8h9"
598 );
599 assert_eq!(
600 AwsEfsMountTargetId::try_from("fsmt-1a2b3c4d5e6f7j8h9")
601 .unwrap()
602 .to_string(),
603 "fsmt-1a2b3c4d5e6f7j8h9"
604 );
605 assert_eq!(
606 AwsCloudFormationStackId::try_from("stack-1a2b3c4d5e6f7j8h9")
607 .unwrap()
608 .to_string(),
609 "stack-1a2b3c4d5e6f7j8h9"
610 );
611 assert_eq!(
612 AwsElasticBeanstalkEnvironmentId::try_from("e-1a2b3c4d5e6f7j8h9")
613 .unwrap()
614 .to_string(),
615 "e-1a2b3c4d5e6f7j8h9"
616 );
617 assert_eq!(
618 AwsInstanceId::try_from("i-1a2b3c4d5e6f7j8h9")
619 .unwrap()
620 .to_string(),
621 "i-1a2b3c4d5e6f7j8h9"
622 );
623 assert_eq!(
624 AwsInternetGatewayId::try_from("igw-1a2b3c4d5e6f7j8h9")
625 .unwrap()
626 .to_string(),
627 "igw-1a2b3c4d5e6f7j8h9"
628 );
629 assert_eq!(
630 AwsKeyPairId::try_from("key-1a2b3c4d5e6f7j8h9")
631 .unwrap()
632 .to_string(),
633 "key-1a2b3c4d5e6f7j8h9"
634 );
635 assert_eq!(
636 AwsLoadBalancerId::try_from("elbv2-1a2b3c4d5e6f7j8h9")
637 .unwrap()
638 .to_string(),
639 "elbv2-1a2b3c4d5e6f7j8h9"
640 );
641 assert_eq!(
642 AwsNatGatewayId::try_from("nat-1a2b3c4d5e6f7j8h9")
643 .unwrap()
644 .to_string(),
645 "nat-1a2b3c4d5e6f7j8h9"
646 );
647 assert_eq!(
648 AwsNetworkInterfaceId::try_from("eni-1a2b3c4d5e6f7j8h9")
649 .unwrap()
650 .to_string(),
651 "eni-1a2b3c4d5e6f7j8h9"
652 );
653 assert_eq!(
654 AwsPlacementGroupId::try_from("pg-1a2b3c4d5e6f7j8h9")
655 .unwrap()
656 .to_string(),
657 "pg-1a2b3c4d5e6f7j8h9"
658 );
659 assert_eq!(
660 AwsRdsInstanceId::try_from("db-1a2b3c4d5e6f7j8h9")
661 .unwrap()
662 .to_string(),
663 "db-1a2b3c4d5e6f7j8h9"
664 );
665 assert_eq!(
666 AwsRedshiftClusterId::try_from("redshift-1a2b3c4d5e6f7j8h9")
667 .unwrap()
668 .to_string(),
669 "redshift-1a2b3c4d5e6f7j8h9"
670 );
671 assert_eq!(
672 AwsRouteTableId::try_from("rtb-1a2b3c4d5e6f7j8h9")
673 .unwrap()
674 .to_string(),
675 "rtb-1a2b3c4d5e6f7j8h9"
676 );
677 assert_eq!(
678 AwsSecurityGroupId::try_from("sg-1a2b3c4d5e6f7j8h9")
679 .unwrap()
680 .to_string(),
681 "sg-1a2b3c4d5e6f7j8h9"
682 );
683 assert_eq!(
684 AwsSnapshotId::try_from("snap-1a2b3c4d5e6f7j8h9")
685 .unwrap()
686 .to_string(),
687 "snap-1a2b3c4d5e6f7j8h9"
688 );
689 assert_eq!(
690 AwsSubnetId::try_from("subnet-1a2b3c4d5e6f7j8h9")
691 .unwrap()
692 .to_string(),
693 "subnet-1a2b3c4d5e6f7j8h9"
694 );
695 assert_eq!(
696 AwsTargetGroupId::try_from("tg-1a2b3c4d5e6f7j8h9")
697 .unwrap()
698 .to_string(),
699 "tg-1a2b3c4d5e6f7j8h9"
700 );
701 assert_eq!(
702 AwsTransitGatewayAttachmentId::try_from("tgw-attach-1a2b3c4d5e6f7j8h9")
703 .unwrap()
704 .to_string(),
705 "tgw-attach-1a2b3c4d5e6f7j8h9"
706 );
707 assert_eq!(
708 AwsTransitGatewayId::try_from("tgw-1a2b3c4d5e6f7j8h9")
709 .unwrap()
710 .to_string(),
711 "tgw-1a2b3c4d5e6f7j8h9"
712 );
713 assert_eq!(
714 AwsVolumeId::try_from("vol-1a2b3c4d5e6f7j8h9")
715 .unwrap()
716 .to_string(),
717 "vol-1a2b3c4d5e6f7j8h9"
718 );
719 assert_eq!(
720 AwsVpcId::try_from("vpc-1a2b3c4d5e6f7j8h9")
721 .unwrap()
722 .to_string(),
723 "vpc-1a2b3c4d5e6f7j8h9"
724 );
725 assert_eq!(
726 AwsVpnConnectionId::try_from("vpn-1a2b3c4d5e6f7j8h9")
727 .unwrap()
728 .to_string(),
729 "vpn-1a2b3c4d5e6f7j8h9"
730 );
731 assert_eq!(
732 AwsVpnGatewayId::try_from("vgw-1a2b3c4d5e6f7j8h9")
733 .unwrap()
734 .to_string(),
735 "vgw-1a2b3c4d5e6f7j8h9"
736 );
737 }
738}
739
740#[cfg(feature = "sqlx-postgres")]
741#[cfg(test)]
742mod sqlx_tests {
743 use super::*;
744 use sqlx::PgPool;
745
746 #[sqlx::test]
747 async fn serialize_varchar(pool: PgPool) -> sqlx::Result<()> {
748 let ami_str = "ami-12345678";
749 let ami: AwsAmiId = ami_str.parse().unwrap();
750 let serialized = sqlx::query_scalar!("SELECT $1::varchar", ami as _)
751 .fetch_one(&pool)
752 .await?
753 .unwrap();
754 assert_eq!(serialized, ami_str);
755 Ok(())
756 }
757
758 #[sqlx::test]
759 async fn serialize_text(pool: PgPool) -> sqlx::Result<()> {
760 let ami_str = "ami-12345678";
761 let ami: AwsAmiId = ami_str.parse().unwrap();
762 let serialized = sqlx::query_scalar!("SELECT $1::text", ami as _)
763 .fetch_one(&pool)
764 .await?
765 .unwrap();
766 assert_eq!(serialized, ami_str);
767 Ok(())
768 }
769
770 #[sqlx::test]
771 async fn deserialize_varchar(pool: PgPool) -> sqlx::Result<()> {
772 let ami: AwsAmiId = "ami-12345678".parse().unwrap();
773 let deserialized =
774 sqlx::query_scalar!(r#"SELECT 'ami-12345678'::varchar as "val: AwsAmiId""#)
775 .fetch_one(&pool)
776 .await?
777 .unwrap();
778 assert_eq!(deserialized, ami);
779 Ok(())
780 }
781
782 #[sqlx::test]
783 async fn deserialize_text(pool: PgPool) -> sqlx::Result<()> {
784 let ami: AwsAmiId = "ami-12345678".parse().unwrap();
785 let deserialized = sqlx::query_scalar!(r#"SELECT 'ami-12345678' as "val: AwsAmiId""#)
786 .fetch_one(&pool)
787 .await?
788 .unwrap();
789 assert_eq!(deserialized, ami);
790 Ok(())
791 }
792}