Skip to content

Commit 8972440

Browse files
committed
Add create_extensible_enum macro
1 parent 2c06035 commit 8972440

File tree

2 files changed

+180
-37
lines changed

2 files changed

+180
-37
lines changed

sdk/typespec/src/error/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ impl From<url::ParseError> for Error {
284284
}
285285
}
286286

287+
impl From<core::convert::Infallible> for Error {
288+
fn from(_: core::convert::Infallible) -> Self {
289+
panic!("no error")
290+
}
291+
}
292+
287293
impl Display for Error {
288294
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289295
match &self.context {

sdk/typespec/typespec_client_core/src/macros.rs

+174-37
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
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]
1126
macro_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)]
157255
mod 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

Comments
 (0)