1- use std:: { fmt, str:: FromStr } ;
1+ use std:: { fmt, marker :: PhantomData , str:: FromStr } ;
22
33use derive_more:: Display ;
44#[ cfg( feature = "schema" ) ]
@@ -7,85 +7,83 @@ use serde::{
77 Deserialize , Serialize ,
88 de:: { self , Deserializer , Unexpected , Visitor } ,
99} ;
10- use uuid:: Uuid ;
1110
12- use crate :: { Slug , ValidError , slug :: is_valid_slug } ;
11+ use crate :: ValidError ;
1312
1413#[ typeshare:: typeshare]
1514#[ derive( Debug , Display , Clone , PartialEq , Eq , PartialOrd , Ord , Hash , Serialize ) ]
16- #[ cfg_attr( feature = "schema" , derive( JsonSchema ) ) ]
17- pub struct ResourceId ( String ) ;
18-
19- pub enum ResourceIdKind {
20- Uuid ( Uuid ) ,
21- Slug ( Slug ) ,
15+ #[ serde( untagged) ]
16+ pub enum ResourceId < U , S > {
17+ Uuid ( U ) ,
18+ Slug ( S ) ,
2219}
2320
24- impl FromStr for ResourceId {
25- type Err = ValidError ;
21+ #[ cfg( feature = "schema" ) ]
22+ impl < U , S > JsonSchema for ResourceId < U , S >
23+ where
24+ U : JsonSchema ,
25+ S : JsonSchema ,
26+ {
27+ fn schema_name ( ) -> String {
28+ "ResourceId" . to_owned ( )
29+ }
2630
27- fn from_str ( value : & str ) -> Result < Self , Self :: Err > {
28- // A UUID is always a valid slug
29- // And a slug is always a valid resource ID
30- if is_valid_slug ( value) {
31- Ok ( Self ( value. into ( ) ) )
32- } else {
33- Err ( ValidError :: ResourceId ( value. into ( ) ) )
34- }
31+ fn schema_id ( ) -> std:: borrow:: Cow < ' static , str > {
32+ std:: borrow:: Cow :: Borrowed ( "bencher_valid::resource_id::ResourceId" )
33+ }
34+
35+ fn json_schema ( generator : & mut schemars:: SchemaGenerator ) -> schemars:: schema:: Schema {
36+ // Unfortunately, this seems to be required to have an untagged enum.
37+ // Otherwise, you get a runtime error: `can only flatten structs and maps (got a string)`
38+ // I believe this is a shortcoming of https://github.com/oxidecomputer/progenitor
39+ // For now, we just use the lowest common denominator's schema.
40+ S :: json_schema ( generator)
3541 }
3642}
3743
38- impl TryFrom < & ResourceId > for ResourceIdKind {
39- type Error = ValidError ;
44+ impl < U , S > FromStr for ResourceId < U , S >
45+ where
46+ U : FromStr ,
47+ S : FromStr ,
48+ {
49+ type Err = ValidError ;
4050
41- fn try_from ( resource_id : & ResourceId ) -> Result < Self , Self :: Error > {
42- if let Ok ( uuid) = Uuid :: from_str ( resource_id . as_ref ( ) ) {
51+ fn from_str ( name_id : & str ) -> Result < Self , Self :: Err > {
52+ if let Ok ( uuid) = U :: from_str ( name_id ) {
4353 Ok ( Self :: Uuid ( uuid) )
44- } else if let Ok ( slug) = Slug :: from_str ( resource_id . as_ref ( ) ) {
54+ } else if let Ok ( slug) = S :: from_str ( name_id ) {
4555 Ok ( Self :: Slug ( slug) )
4656 } else {
47- Err ( ValidError :: ResourceId ( resource_id . as_ref ( ) . into ( ) ) )
57+ Err ( ValidError :: ResourceId ( name_id . to_owned ( ) ) )
4858 }
4959 }
5060}
5161
52- impl From < Uuid > for ResourceId {
53- fn from ( uuid : Uuid ) -> Self {
54- Self ( uuid. to_string ( ) )
55- }
56- }
57-
58- impl From < Slug > for ResourceId {
59- fn from ( slug : Slug ) -> Self {
60- Self ( slug. into ( ) )
61- }
62- }
63-
64- impl AsRef < str > for ResourceId {
65- fn as_ref ( & self ) -> & str {
66- & self . 0
67- }
68- }
69-
70- impl From < ResourceId > for String {
71- fn from ( resource_id : ResourceId ) -> Self {
72- resource_id. 0
73- }
74- }
75-
76- impl < ' de > Deserialize < ' de > for ResourceId {
77- fn deserialize < D > ( deserializer : D ) -> Result < ResourceId , D :: Error >
62+ impl < ' de , U , S > Deserialize < ' de > for ResourceId < U , S >
63+ where
64+ U : FromStr ,
65+ S : FromStr ,
66+ {
67+ fn deserialize < D > ( deserializer : D ) -> Result < Self , D :: Error >
7868 where
7969 D : Deserializer < ' de > ,
8070 {
81- deserializer. deserialize_str ( ResourceIdVisitor )
71+ deserializer. deserialize_str ( ResourceIdVisitor {
72+ marker : PhantomData ,
73+ } )
8274 }
8375}
8476
85- struct ResourceIdVisitor ;
77+ struct ResourceIdVisitor < U , S > {
78+ marker : PhantomData < ( U , S ) > ,
79+ }
8680
87- impl Visitor < ' _ > for ResourceIdVisitor {
88- type Value = ResourceId ;
81+ impl < U , S > Visitor < ' _ > for ResourceIdVisitor < U , S >
82+ where
83+ U : FromStr ,
84+ S : FromStr ,
85+ {
86+ type Value = ResourceId < U , S > ;
8987
9088 fn expecting ( & self , formatter : & mut fmt:: Formatter ) -> fmt:: Result {
9189 formatter. write_str ( "a valid UUID or slug." )
@@ -99,6 +97,36 @@ impl Visitor<'_> for ResourceIdVisitor {
9997 }
10098}
10199
102- impl ResourceId {
103- pub const MAX_LEN : usize = crate :: MAX_LEN ;
100+ #[ cfg( test) ]
101+ mod tests {
102+ use super :: ResourceId ;
103+ use crate :: Slug ;
104+ use uuid:: Uuid ;
105+
106+ #[ test]
107+ fn test_resource_id_uuid ( ) {
108+ const UUID : & str = "123e4567-e89b-12d3-a456-426614174000" ;
109+ let resource_id: ResourceId < Uuid , Slug > = UUID . parse ( ) . unwrap ( ) ;
110+ assert_eq ! (
111+ resource_id,
112+ ResourceId :: Uuid ( Uuid :: parse_str( UUID ) . unwrap( ) )
113+ ) ;
114+
115+ let serialized = serde_json:: to_string ( & resource_id) . unwrap ( ) ;
116+ assert_eq ! ( serialized, format!( "\" {UUID}\" " ) ) ;
117+ let deserialized: ResourceId < Uuid , Slug > = serde_json:: from_str ( & serialized) . unwrap ( ) ;
118+ assert_eq ! ( deserialized, resource_id) ;
119+ }
120+
121+ #[ test]
122+ fn test_resource_id_slug ( ) {
123+ const SLUG : & str = "my-slug" ;
124+ let resource_id: ResourceId < Uuid , Slug > = SLUG . parse ( ) . unwrap ( ) ;
125+ assert_eq ! ( resource_id, ResourceId :: Slug ( SLUG . parse( ) . unwrap( ) ) ) ;
126+
127+ let serialized = serde_json:: to_string ( & resource_id) . unwrap ( ) ;
128+ assert_eq ! ( serialized, format!( "\" {SLUG}\" " ) ) ;
129+ let deserialized: ResourceId < Uuid , Slug > = serde_json:: from_str ( & serialized) . unwrap ( ) ;
130+ assert_eq ! ( deserialized, resource_id) ;
131+ }
104132}
0 commit comments