11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33
4- /// The following macro invocation:
4+ /// Creates an enum with a fixed set of variants.
5+ ///
6+ /// This macro creates an enum where each variant can be turned into and constructed from the corresponding string.
7+ /// The [`std::str::FromStr`] implementation will return a [`typespec::error::Error`] if not supported (case-sensitive).
8+ ///
9+ /// # Examples
10+ ///
511/// ```
612/// # #[macro_use] extern crate typespec_client_core;
7- /// create_enum!(Words, (Chicken, "Chicken"), (White, "White"), (Yellow, "Yellow"));
13+ /// create_enum!(
14+ /// #[doc = "Example words"]
15+ /// Words,
16+ /// #[doc = "Poultry"]
17+ /// (Chicken, "Chicken"),
18+ /// (White, "White"),
19+ /// (Yellow, "Yellow")
20+ /// );
21+ ///
22+ /// let word = Words::Chicken;
23+ /// assert_eq!(word.to_string(), String::from("Chicken"));
824/// ```
9- /// Turns into a struct where each variant can be turned into and construct from the corresponding string.
1025#[ macro_export]
1126macro_rules! create_enum {
12- ( $( #[ $type_doc : meta] ) * $name: ident, $( $( #[ $val_doc : meta] ) * ( $variant: ident, $value: expr) ) , * ) => (
13- $( #[ $type_doc ] ) *
27+ ( $( #[ $type_meta : meta] ) * $name: ident, $( $( #[ $value_meta : meta] ) * ( $variant: ident, $value: expr) ) , * ) => (
28+ $( #[ $type_meta ] ) *
1429 #[ derive( Debug , PartialEq , Eq , PartialOrd , Clone , Copy ) ]
1530 pub enum $name {
1631 $(
17- $( #[ $val_doc ] ) *
32+ $( #[ $value_meta ] ) *
1833 $variant,
1934 ) *
2035 }
@@ -29,12 +44,6 @@ macro_rules! create_enum {
2944 }
3045 }
3146
32- impl $crate:: parsing:: FromStringOptional <$name> for $name {
33- fn from_str_optional( s : & str ) -> $crate:: error:: Result <$name> {
34- s. parse:: <$name>( )
35- }
36- }
37-
3847 impl :: std:: str :: FromStr for $name {
3948 type Err = $crate:: error:: Error ;
4049
@@ -51,19 +60,37 @@ macro_rules! create_enum {
5160 }
5261 }
5362
63+ impl :: std:: convert:: AsRef <str > for $name {
64+ fn as_ref( & self ) -> & str {
65+ match self {
66+ $(
67+ $name:: $variant => $value,
68+ ) *
69+ }
70+ }
71+ }
72+
73+ impl :: std:: fmt:: Display for $name {
74+ fn fmt( & self , f: & mut :: std:: fmt:: Formatter <' _>) -> :: std:: fmt:: Result {
75+ match self {
76+ $(
77+ $name:: $variant => write!( f, "{}" , $value) ,
78+ ) *
79+ }
80+ }
81+ }
82+
83+ create_enum!( @intern $name) ;
84+ ) ;
85+
86+ ( @intern $name: ident) => (
5487 impl <' de> serde:: Deserialize <' de> for $name {
5588 fn deserialize<D >( deserializer: D ) -> :: core:: result:: Result <Self , D :: Error >
5689 where
5790 D : serde:: Deserializer <' de>,
5891 {
5992 let s = String :: deserialize( deserializer) ?;
60-
61- match s. as_ref( ) {
62- $(
63- $value => Ok ( Self :: $variant) ,
64- ) *
65- _ => Err ( serde:: de:: Error :: custom( "unsupported value" ) ) ,
66- }
93+ s. parse( ) . map_err( serde:: de:: Error :: custom)
6794 }
6895 }
6996
@@ -74,26 +101,97 @@ macro_rules! create_enum {
74101 }
75102 }
76103
104+ impl $crate:: parsing:: FromStringOptional <$name> for $name {
105+ fn from_str_optional( s : & str ) -> $crate:: error:: Result <$name> {
106+ s. parse:: <$name>( ) . map_err( :: core:: convert:: Into :: into)
107+ }
108+ }
109+ ) ;
110+ }
111+
112+ /// Creates an enum with a set of variants including `UnknownValue` which holds any unsupported string from which it was created.
113+ ///
114+ /// This macro creates an enum where each variant can be turned into and constructed from the corresponding string.
115+ /// The [`std::str::FromStr`] implementation will not return an error but instead store the string in `UnknownValue(String)`.
116+ ///
117+ /// # Examples
118+ ///
119+ /// ```
120+ /// # #[macro_use] extern crate typespec_client_core;
121+ /// create_extensible_enum!(
122+ /// #[doc = "Example words"]
123+ /// Words,
124+ /// #[doc = "Poultry"]
125+ /// (Chicken, "Chicken"),
126+ /// (White, "White"),
127+ /// (Yellow, "Yellow")
128+ /// );
129+ ///
130+ /// let word: Words = "Turkey".parse().unwrap();
131+ /// assert_eq!(word.to_string(), String::from("Turkey"));
132+ /// ```
133+ #[ macro_export]
134+ macro_rules! create_extensible_enum {
135+ ( $( #[ $type_meta: meta] ) * $name: ident, $( $( #[ $value_meta: meta] ) * ( $variant: ident, $value: expr) ) , * ) => (
136+ $( #[ $type_meta] ) *
137+ #[ derive( Debug , PartialEq , Eq , PartialOrd , Clone ) ]
138+ pub enum $name {
139+ $(
140+ $( #[ $value_meta] ) *
141+ $variant,
142+ ) *
143+ /// Any other value not defined in `$name`.
144+ UnknownValue ( String ) ,
145+ }
146+
147+ impl <' a> :: std:: convert:: From <& ' a $name> for & ' a str {
148+ fn from( e: & ' a $name) -> Self {
149+ match e {
150+ $(
151+ $name:: $variant => $value,
152+ ) *
153+ $name:: UnknownValue ( s) => s. as_ref( ) ,
154+ }
155+ }
156+ }
157+
158+ impl :: std:: str :: FromStr for $name {
159+ type Err = :: std:: convert:: Infallible ;
160+
161+ fn from_str( s: & str ) -> :: core:: result:: Result <Self , <Self as :: std:: str :: FromStr >:: Err > {
162+ Ok ( match s {
163+ $(
164+ $value => $name:: $variant,
165+ ) *
166+ _ => $name:: UnknownValue ( s. to_string( ) ) ,
167+ } )
168+ }
169+ }
170+
77171 impl :: std:: convert:: AsRef <str > for $name {
78172 fn as_ref( & self ) -> & str {
79- match * self {
173+ match self {
80174 $(
81175 $name:: $variant => $value,
82176 ) *
177+ $name:: UnknownValue ( s) => s. as_str( ) ,
83178 }
84179 }
85180 }
86181
87182 impl :: std:: fmt:: Display for $name {
88183 fn fmt( & self , f: & mut :: std:: fmt:: Formatter <' _>) -> :: std:: fmt:: Result {
89- match * self {
184+ match self {
90185 $(
91- $name:: $variant => write! ( f , "{}" , $value) ,
186+ $name:: $variant => f . write_str ( $value) ,
92187 ) *
188+ $name:: UnknownValue ( s) => f. write_str( s. as_str( ) ) ,
93189 }
94190 }
95191 }
96- )
192+
193+ create_enum!( @intern $name) ;
194+ ) ;
97195}
98196
99197/// Creates setter methods
@@ -155,19 +253,22 @@ macro_rules! setters {
155253
156254#[ cfg( test) ]
157255mod test {
256+ use serde:: { Deserialize , Serialize } ;
257+
158258 create_enum ! ( Colors , ( Black , "Black" ) , ( White , "White" ) , ( Red , "Red" ) ) ;
159259 create_enum ! ( ColorsMonochrome , ( Black , "Black" ) , ( White , "White" ) ) ;
160260
161- create_enum ! (
162- #[ doc = "Defines operation states" ]
163- OperationState ,
164- #[ doc = "The operation hasn't started" ]
165- ( NotStarted , "notStarted" ) ,
166- #[ doc = "The operation is in progress" ]
167- ( InProgress , "inProgress" ) ,
168- #[ doc = "The operation has completed" ]
169- ( Completed , "completed" )
170- ) ;
261+ // cspell:ignore metasyntactic
262+ create_extensible_enum ! ( Metasyntactic , ( Foo , "foo" ) , ( Bar , "bar" ) ) ;
263+
264+ #[ derive( Debug , Default , Deserialize , Serialize ) ]
265+ #[ serde( default ) ]
266+ struct TestData {
267+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
268+ color : Option < Colors > ,
269+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
270+ meta : Option < Metasyntactic > ,
271+ }
171272
172273 struct Options {
173274 a : Option < String > ,
@@ -189,27 +290,63 @@ mod test {
189290 }
190291
191292 #[ test]
192- fn test_color_parse_1 ( ) {
293+ fn color_parse_1 ( ) {
193294 let color = "Black" . parse :: < Colors > ( ) . unwrap ( ) ;
194295 assert_eq ! ( Colors :: Black , color) ;
195296 }
196297
197298 #[ test]
198- fn test_color_parse_2 ( ) {
299+ fn color_parse_2 ( ) {
199300 let color = "White" . parse :: < ColorsMonochrome > ( ) . unwrap ( ) ;
200301 assert_eq ! ( ColorsMonochrome :: White , color) ;
201302 }
202303
203304 #[ test]
204- fn test_color_parse_err_1 ( ) {
305+ fn color_parse_err_1 ( ) {
205306 "Red" . parse :: < ColorsMonochrome > ( ) . unwrap_err ( ) ;
206307 }
207308
208309 #[ test]
209- fn test_setters ( ) {
310+ fn setters ( ) {
210311 let options = Options :: default ( ) . a ( "test" . to_owned ( ) ) ;
211312
212313 assert_eq ! ( Some ( "test" . to_owned( ) ) , options. a) ;
213314 assert_eq ! ( 1 , options. b) ;
214315 }
316+
317+ #[ test]
318+ fn deserialize_enum ( ) {
319+ let data: TestData = serde_json:: from_str ( r#"{"color": "Black"}"# ) . unwrap ( ) ;
320+ assert_eq ! ( Some ( Colors :: Black ) , data. color) ;
321+ }
322+
323+ #[ test]
324+ fn deserialize_extensible_enum ( ) {
325+ // Variant values are case-sensitive.
326+ let data: TestData = serde_json:: from_str ( r#"{"meta": "Foo"}"# ) . unwrap ( ) ;
327+ assert_eq ! (
328+ Some ( Metasyntactic :: UnknownValue ( String :: from( "Foo" ) ) ) ,
329+ data. meta
330+ ) ;
331+ }
332+
333+ #[ test]
334+ fn serialize_enum ( ) {
335+ let data = TestData {
336+ color : Some ( Colors :: Red ) ,
337+ ..Default :: default ( )
338+ } ;
339+ let json = serde_json:: to_string ( & data) . unwrap ( ) ;
340+ assert_eq ! ( String :: from( r#"{"color":"Red"}"# ) , json) ;
341+ }
342+
343+ #[ test]
344+ fn serialize_extensible_enum ( ) {
345+ let data = TestData {
346+ meta : Some ( Metasyntactic :: Foo ) ,
347+ ..Default :: default ( )
348+ } ;
349+ let json = serde_json:: to_string ( & data) . unwrap ( ) ;
350+ assert_eq ! ( String :: from( r#"{"meta":"foo"}"# ) , json) ;
351+ }
215352}
0 commit comments