azure_core/
macros.rs

1/// Creates setter methods
2///
3/// The methods created are of the form `$name` that takes an argument of type `$typ`
4/// and sets the field $name to result of calling `$transform` with the value of the argument.
5///
6/// In other words. The following macro call:
7/// ```
8/// # #[macro_use] extern crate azure_core;
9/// struct MyStruct<'a> { foo: Option<&'a str> };
10/// impl <'a> MyStruct<'a> {
11///     setters! { foo: &'a str => Some(foo), }
12/// }
13/// ```
14/// Roughly expands to:
15/// ```
16/// struct MyStruct<'a> { foo: Option<&'a str> };
17/// impl <'a> MyStruct<'a> {
18///     fn foo(self, foo: &'a str) -> Self {
19///         Self {
20///             foo: Some(foo),
21///             ..self
22///         }
23///     }
24/// }
25/// ```
26#[macro_export]
27macro_rules! setters {
28    (@single $(#[$meta:meta])* $name:ident : $typ:ty => $transform:expr) => {
29        #[allow(clippy::redundant_field_names)]
30        #[allow(clippy::needless_update)]
31        #[allow(missing_docs)]
32        #[must_use]
33        $(#[$meta])*
34        pub fn $name<P: ::std::convert::Into<$typ>>(self, $name: P) -> Self {
35            let $name: $typ = $name.into();
36            Self  {
37                $name: $transform,
38                ..self
39            }
40        }
41    };
42    // Terminal condition
43    (@recurse) => {};
44    // Recurse without transform
45    (@recurse $(#[$meta:meta])* $name:ident : $typ:ty, $($tokens:tt)*) => {
46        $crate::setters! { @recurse $(#[$meta])* $name: $typ => $name, $($tokens)* }
47    };
48    // Recurse with transform
49    (@recurse $(#[$meta:meta])* $name:ident : $typ:ty => $transform:expr, $($tokens:tt)*) => {
50        $crate::setters! { @single $(#[$meta])* $name : $typ => $transform }
51        $crate::setters! { @recurse $($tokens)* }
52    };
53    ($($tokens:tt)*) => {
54        $crate::setters! { @recurse $($tokens)* }
55    }
56}
57
58/// Helper for constructing operations
59///
60/// For the following code:
61/// ```
62/// # #[derive(Clone, Debug)]
63/// # pub struct DatabaseClient;
64/// # pub struct CreateCollectionResponse;
65/// azure_core::operation! {
66///    CreateCollection,
67///    client: DatabaseClient,
68///    collection_name: String,
69///    ?consistency_level: u32
70/// }
71/// ```
72///
73/// The following code will be generated
74///
75/// ```
76/// # use azure_core::setters;
77/// # use azure_core::Context;
78/// # #[derive(Clone, Debug)]
79/// # pub struct DatabaseClient;
80/// # pub struct CreateCollectionResponse;
81/// #[derive(Debug, Clone)]
82/// pub struct CreateCollectionBuilder {
83///     client: DatabaseClient,
84///     collection_name: String,
85///     consistency_level: Option<u32>,
86///     context: Context,
87/// }
88///
89/// impl CreateCollectionBuilder {
90///     pub(crate) fn new(
91///         client: DatabaseClient,
92///         collection_name: String,
93///     ) -> Self {
94///         Self {
95///             client,
96///             collection_name,
97///             consistency_level: None,
98///             context: Context::new(),
99///         }
100///     }
101///
102///     setters! {
103///         consistency_level: u32 => Some(consistency_level),
104///         context: Context => context,
105///     }
106/// }
107///
108/// impl std::future::IntoFuture for CreateCollectionBuilder {
109///     type IntoFuture = CreateCollection;
110///     type Output = <CreateCollection as std::future::Future>::Output;
111///     fn into_future(self) -> Self::IntoFuture {
112///         Self::into_future(self)
113///     }
114/// }
115///
116/// /// The future returned by calling `into_future` on the builder.
117/// pub type CreateCollection =
118///     futures::future::BoxFuture<'static, azure_core::Result<CreateCollectionResponse>>;
119/// ```
120///
121/// Additionally, `#[stream]` can be used before the operation name to generate code appropriate for list operations
122/// and `#[skip]` can be used at the end of the list of options for options where we should not generate a setter.
123#[macro_export]
124macro_rules! operation {
125    // Construct the builder.
126    (@builder
127        $(#[$outer:meta])*
128        // The name of the operation and any generic params along with their constraints
129        $name:ident<$($generic:ident: $first_constraint:ident $(+ $constraint:ident)* ),* $(+ $lt:lifetime)?>,
130        // The client
131        client: $client:ty,
132        // The required fields that will be used in the constructor
133        @required
134        $($required:ident: $rtype:ty,)*
135        // The optional fields that will have generated setters
136        @optional
137        $($optional:ident: $otype:ty,)*
138        // The optional fields which won't have generated setters
139        @nosetter
140        $($nosetter:ident: $nstype:ty),*
141        ) => {
142        azure_core::__private::paste! {
143        #[derive(Debug, Clone)]
144        $(#[$outer])*
145        pub struct [<$name Builder>]<$($generic)*> {
146            client: $client,
147            $($required: $rtype,)*
148            $($optional: Option<$otype>,)*
149            $($nosetter: Option<$nstype>,)*
150            context: azure_core::Context,
151        }
152
153        /// Setters for the various options for this builder
154        impl <$($generic: $first_constraint $(+ $constraint)* )*>[<$name Builder>]<$($generic),*> {
155            pub(crate) fn new(
156                client: $client,
157                $($required: $rtype,)*
158            ) -> Self {
159                Self {
160                    client,
161                    $($required,)*
162                    $($optional: None,)*
163                    $($nosetter: None,)*
164                    context: azure_core::Context::new(),
165                }
166            }
167
168            $crate::setters! {
169                $($optional: $otype => Some($optional),)*
170                context: azure_core::Context => context,
171            }
172        }
173        }
174    };
175    // `operation! { #[stream] ListUsers, client: UserClient, ?consistency_level: ConsistencyLevel }`
176    (#[stream] $(#[$outer:meta])* $name:ident,
177        client: $client:ty,
178        $($required:ident: $rtype:ty,)*
179        $(?$optional:ident: $otype:ty),*) => {
180            $crate::operation!{
181                @builder
182                $(#[$outer])*
183                $name<>,
184                client: $client,
185                @required
186                $($required: $rtype,)*
187                @optional
188                $($optional: $otype,)*
189                @nosetter
190            }
191    };
192    (#[stream] $(#[$outer:meta])*
193        $name:ident,
194        client: $client:ty,
195        $($required:ident: $rtype:ty,)*
196        $(?$optional:ident: $otype:ty,)*
197        $(#[skip]$nosetter:ident: $nstype:ty),*
198    ) => {
199            $crate::operation!{
200                @builder
201                $(#[$outer])*
202                $name<>,
203                client: $client,
204                @required
205                $($required: $rtype,)*
206                @optional
207                $($optional: $otype,)*
208                @nosetter
209                $($nosetter: $nstype),*
210            }
211    };
212    // Construct a builder and the `Future` related code
213    ($(#[$outer:meta])* $name:ident<$($generic:ident: $first_constraint:ident $(+ $constraint:ident)* ),* $(+ $lt:lifetime)?>,
214        client: $client:ty,
215        @required
216        $($required:ident: $rtype:ty,)*
217        @optional
218        $($optional:ident: $otype:ty,)*
219        @nosetter
220        $($nosetter:ident: $nstype:ty),*
221        ) => {
222        $crate::operation! {
223            @builder
224            $(#[$outer])*
225            $name<$($generic: $first_constraint $(+ $constraint)*),* $(+ $lt)*>,
226            client: $client,
227            @required
228            $($required: $rtype,)*
229            @optional
230            $($optional: $otype,)*
231            @nosetter
232            $($nosetter: $nstype),*
233        }
234        $crate::future!($name);
235        azure_core::__private::paste! {
236        impl <$($generic: $first_constraint $(+ $constraint)*)* $(+ $lt)*> std::future::IntoFuture for [<$name Builder>]<$($generic),*> {
237            type IntoFuture = $name;
238            type Output = <$name as std::future::Future>::Output;
239            fn into_future(self) -> Self::IntoFuture {
240                Self::into_future(self)
241            }
242        }
243        }
244    };
245    // `operation! { CreateUser, client: UserClient, ?consistency_level: ConsistencyLevel }`
246    ($(#[$outer:meta])* $name:ident,
247        client: $client:ty,
248        $($required:ident: $rtype:ty,)*
249        $(?$optional:ident: $otype:ty),*) => {
250            $crate::operation!{
251                $(#[$outer])*
252                $name<>,
253                client: $client,
254                @required
255                $($required: $rtype,)*
256                @optional
257                $($optional: $otype,)*
258                @nosetter
259            }
260    };
261    // `operation! { CreateDocument<D: Serialize>, client: UserClient, ?consistency_level: ConsistencyLevel, ??other_field: bool }`
262    ($(#[$outer:meta])* $name:ident<$($generic:ident: $first_constraint:ident $(+ $constraint:ident)*),* $(+ $lt:lifetime)?>,
263        client: $client:ty,
264        $($required:ident: $rtype:ty,)*
265        $(?$optional:ident: $otype:ty,)*
266        $(#[skip] $nosetter:ident: $nstype:ty),*) => {
267            $crate::operation!{
268                $(#[$outer])*
269                $name<$($generic: $first_constraint $(+ $constraint)*),* $(+ $lt)*>,
270                client: $client,
271                @required
272                $($required: $rtype,)*
273                @optional
274                $($optional: $otype,)*
275                @nosetter
276                $($nosetter: $nstype),*
277            }
278    }
279}
280
281/// Declare a `Future` with the given name
282///
283/// `Future::Output` will be set to `azure_core::Result<$NAMEResponse>`.
284/// The `Future` will be `Send` for all targets but `wasm32`.
285#[macro_export]
286macro_rules! future {
287    ($name:ident) => {
288        $crate::future!($name<>);
289    };
290    ($name:ident<$($generic:ident)?>) => {
291        azure_core::__private::paste! {
292        #[cfg(target_arch = "wasm32")]
293        pub type $name<$($generic)*> =
294            std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = azure_core::Result<[<$name Response>]<$($generic)*>>> + 'static>>;
295        #[cfg(not(target_arch = "wasm32"))]
296        pub type $name<$($generic)*> =
297            futures::future::BoxFuture<'static, azure_core::Result<[<$name Response>]<$($generic)*>>>;
298        }
299    };
300}
301
302/// The following macro invocation:
303/// ```
304/// # #[macro_use] extern crate azure_core;
305/// request_header!(
306///     /// Builds a client request id header
307///     ClientRequestId, CLIENT_REQUEST_ID,
308/// );
309/// ```
310/// Turns into a Header value used to construct requests.
311#[macro_export]
312macro_rules! request_header {
313    ($(#[$outer:meta])* $name:ident, $header:ident) => {
314        $crate::request_header!($name, $header,);
315    };
316    ($(#[$outer:meta])* $name:ident, $header:ident, $(($variant:ident, $value:expr)), *) => {
317        $crate::request_option!($(#[$outer])* $name);
318        impl $name {
319            $(
320                pub const $variant: $name = $name::from_static($value);
321            )*
322        }
323        impl $crate::headers::Header for $name {
324            fn name(&self) -> $crate::headers::HeaderName {
325                $crate::headers::$header
326            }
327
328            fn value(&self) -> $crate::headers::HeaderValue {
329                $crate::headers::HeaderValue::from_cow(self.0.clone())
330            }
331        }
332    };
333}
334
335/// The following macro invocation:
336/// ```
337/// # #[macro_use] extern crate azure_core;
338/// request_query!(Prefix, "prefix");
339/// ```
340/// Turns into a request query option used to construct requests
341#[macro_export]
342macro_rules! request_query {
343    ($(#[$outer:meta])* $name:ident, $option:expr) => {
344        $crate::request_option!($(#[$outer])* $name);
345        impl $crate::AppendToUrlQuery for $name {
346            fn append_to_url_query(&self, url: &mut $crate::Url) {
347                url.query_pairs_mut().append_pair($option, &self.0);
348            }
349        }
350    };
351}
352
353/// The following macro invocation:
354/// ```
355/// # #[macro_use] extern crate azure_core;
356/// request_option!(Prefix);
357/// ```
358/// Turns into a request option useable either as a header or as a query string.
359#[macro_export]
360macro_rules! request_option {
361    ($(#[$outer:meta])* $name:ident) => {
362        #[derive(Debug, Clone)]
363        $(#[$outer])*
364        pub struct $name(std::borrow::Cow<'static, str>);
365
366        impl $name {
367            pub fn new<S>(s: S) -> Self
368            where
369                S: Into<std::borrow::Cow<'static, str>>,
370            {
371                Self(s.into())
372            }
373
374            pub const fn from_static(s: &'static str) -> Self {
375                Self(std::borrow::Cow::Borrowed(s))
376            }
377        }
378
379        impl<S> From<S> for $name
380        where
381            S: Into<std::borrow::Cow<'static, str>>,
382        {
383            fn from(s: S) -> Self {
384                Self::new(s)
385            }
386        }
387    };
388}
389
390/// The following macro invocation:
391/// ```
392/// # #[macro_use] extern crate azure_core;
393/// create_enum!(Words, (Pollo, "Pollo"), (Bianco, "Bianco"), (Giallo, "Giallo"));
394/// ```
395/// Turns into a struct where each variant can be turned into and construct from the corresponding string.
396#[macro_export]
397macro_rules! create_enum {
398    ($name:ident, $(($variant:ident, $value:expr)), *) => (
399        #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)]
400        pub enum $name {
401            $(
402                $variant,
403            )*
404        }
405
406        impl ::std::convert::From<$name> for &'static str {
407            fn from(e: $name) -> Self {
408                match e {
409                    $(
410                        $name::$variant => $value,
411                    )*
412                }
413            }
414        }
415
416        impl $crate::parsing::FromStringOptional<$name> for $name {
417            fn from_str_optional(s : &str) -> $crate::error::Result<$name> {
418                s.parse::<$name>()
419            }
420        }
421
422        impl ::std::str::FromStr for $name {
423            type Err = $crate::error::Error;
424
425            fn from_str(s: &str) -> $crate::error::Result<$name> {
426                match s {
427                    $(
428                        $value => Ok($name::$variant),
429                    )*
430                    _ => Err($crate::error::Error::with_message($crate::error::ErrorKind::DataConversion, || format!("unknown variant of {} found: \"{}\"",
431                        stringify!($name),
432                         s
433                    )))
434                }
435            }
436        }
437
438        impl<'de> serde::Deserialize<'de> for $name {
439            fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
440            where
441                D: serde::Deserializer<'de>,
442            {
443                let s = String::deserialize(deserializer)?;
444
445                match s.as_ref() {
446                    $(
447                        $value => Ok(Self::$variant),
448                    )*
449                    _ => Err(serde::de::Error::custom("unsupported value")),
450                }
451            }
452        }
453
454        impl serde::Serialize for $name {
455            fn serialize<S>(&self, s: S) -> ::core::result::Result<S::Ok, S::Error>
456            where S: serde::Serializer {
457                return s.serialize_str(&self.to_string())
458            }
459        }
460
461        impl ::std::convert::AsRef<str> for $name {
462            fn as_ref(&self) -> &str {
463                 match *self {
464                    $(
465                        $name::$variant => $value,
466                    )*
467                }
468            }
469        }
470
471        impl ::std::fmt::Display for $name {
472            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
473                match *self {
474                    $(
475                        $name::$variant => write!(f, "{}", $value),
476                    )*
477                }
478            }
479        }
480    )
481}
482
483// once Rust's `lazy_cell` feature lands, this should be replaced with that.
484// ref: https://github.com/rust-lang/rust/issues/109736
485
486#[macro_export]
487macro_rules! static_url {
488    ( $(#[$outer:meta])* $name:ident, $value:expr) => {
489        $(#[$outer])*
490        pub static $name: once_cell::sync::Lazy<$crate::Url> = once_cell::sync::Lazy::new(|| {
491            $crate::Url::parse($value).expect("hardcoded URL must parse")
492        });
493    };
494}
495
496#[cfg(test)]
497mod test {
498    create_enum!(Colors, (Black, "Black"), (White, "White"), (Red, "Red"));
499    create_enum!(ColorsMonochrome, (Black, "Black"), (White, "White"));
500
501    struct Options {
502        a: Option<String>,
503        b: u32,
504    }
505
506    #[allow(dead_code)]
507    impl Options {
508        setters! {
509            a: String => Some(a),
510            b: u32 => b,
511        }
512    }
513
514    impl Default for Options {
515        fn default() -> Self {
516            Options { a: None, b: 1 }
517        }
518    }
519
520    #[test]
521    fn test_color_parse_1() {
522        let color = "Black".parse::<Colors>().unwrap();
523        assert_eq!(Colors::Black, color);
524    }
525
526    #[test]
527    fn test_color_parse_2() {
528        let color = "White".parse::<ColorsMonochrome>().unwrap();
529        assert_eq!(ColorsMonochrome::White, color);
530    }
531
532    #[test]
533    fn test_color_parse_err_1() {
534        "Red".parse::<ColorsMonochrome>().unwrap_err();
535    }
536
537    #[test]
538    fn test_setters() {
539        let options = Options::default().a("test".to_owned());
540
541        assert_eq!(Some("test".to_owned()), options.a);
542        assert_eq!(1, options.b);
543    }
544}