Skip to content

Try out an operation macro #878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ serde_json = "1.0"
url = "2.2"
uuid = { version = "1.0" }
pin-project = "1.0.10"
paste = "1.0"

# Add dependency to getrandom to enable WASM support
[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down
6 changes: 6 additions & 0 deletions sdk/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,9 @@ where
}
}
}

#[doc(hidden)]
/// Used by macros as an implementation detail
pub mod __private {
pub use paste::paste;
}
228 changes: 223 additions & 5 deletions sdk/core/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ macro_rules! setters {
#[allow(clippy::redundant_field_names)]
#[allow(clippy::needless_update)]
// TODO: Declare using idiomatic with_$name when https://github.com/Azure/azure-sdk-for-rust/issues/292 is resolved.
pub fn $name<_T: ::std::convert::Into<$typ>>(self, $name: _T) -> Self {
pub fn $name<P: ::std::convert::Into<$typ>>(self, $name: P) -> Self {
let $name: $typ = $name.into();
Self {
$name: $transform,
Expand All @@ -41,15 +41,233 @@ macro_rules! setters {
(@recurse) => {};
// Recurse without transform
(@recurse $name:ident : $typ:ty, $($tokens:tt)*) => {
setters! { @recurse $name: $typ => $name, $($tokens)* }
$crate::setters! { @recurse $name: $typ => $name, $($tokens)* }
};
// Recurse with transform
(@recurse $name:ident : $typ:ty => $transform:expr, $($tokens:tt)*) => {
setters! { @single $name : $typ => $transform }
setters! { @recurse $($tokens)* }
$crate::setters! { @single $name : $typ => $transform }
$crate::setters! { @recurse $($tokens)* }
};
($($tokens:tt)*) => {
setters! { @recurse $($tokens)* }
$crate::setters! { @recurse $($tokens)* }
}
}

/// Helper for constructing operations
///
/// For the following code:
/// ```
/// # #[derive(Clone, Debug)]
/// # struct DatabaseClient;
/// # struct CreateCollectionResponse;
/// azure_core::operation! {
/// CreateCollection,
/// client: DatabaseClient,
/// collection_name: String,
/// ?consistency_level: u32
/// }
/// ```
///
/// The following code will be generated
///
/// ```
/// # use azure_core::setters;
/// # use azure_core::Context;
/// # #[derive(Clone, Debug)]
/// # struct DatabaseClient;
/// # struct CreateCollectionResponse;
/// #[derive(Debug, Clone)]
/// pub struct CreateCollectionBuilder {
/// client: DatabaseClient,
/// collection_name: String,
/// consistency_level: Option<u32>,
/// context: Context,
/// }
///
/// impl CreateCollectionBuilder {
/// pub(crate) fn new(
/// client: DatabaseClient,
/// collection_name: String,
/// ) -> Self {
/// Self {
/// client,
/// collection_name,
/// consistency_level: None,
/// context: Context::new(),
/// }
/// }
///
/// setters! {
/// consistency_level: u32 => Some(consistency_level),
/// context: Context => context,
/// }
/// }
///
/// #[cfg(feature = "into_future")]
/// impl std::future::IntoFuture for CreateCollectionBuilder {
/// type IntoFuture = CreateCollection;
/// type Output = <CreateCollection as std::future::Future>::Output;
/// fn into_future(self) -> Self::IntoFuture {
/// Self::into_future(self)
/// }
/// }
///
/// /// The future returned by calling `into_future` on the builder.
/// pub type CreateCollection =
/// futures::future::BoxFuture<'static, azure_core::Result<CreateCollectionResponse>>;
/// ```
///
/// Additionally, `#[stream]` can be used before the operation name to generate code appropriate for list operations
/// and `#[skip]` can be used at the end of the list of options for options where we should not generate a setter.
#[macro_export]
macro_rules! operation {
// Construct the builder.
(@builder
// The name of the operation and any generic params along with their constraints
$name:ident<$($generic:ident: $first_constraint:ident $(+ $constraint:ident)* ),*>,
// The client
client: $client:ty,
// The required fields that will be used in the constructor
@required
$($required:ident: $rtype:ty,)*
// The optional fields that will have generated setters
@optional
$($optional:ident: $otype:ty,)*
// The optional fields which won't have generated setters
@nosetter
$($nosetter:ident: $nstype:ty),*
) => {
azure_core::__private::paste! {
#[derive(Debug, Clone)]
pub struct [<$name Builder>]<$($generic)*> {
client: $client,
$($required: $rtype,)*
$($optional: Option<$otype>,)*
$($nosetter: Option<$nstype>,)*
context: azure_core::Context,
}

/// Setters for the various options for this builder
impl <$($generic: $first_constraint $(+ $constraint)* )*>[<$name Builder>]<$($generic),*> {
pub(crate) fn new(
client: $client,
$($required: $rtype,)*
) -> Self {
Self {
client,
$($required,)*
$($optional: None,)*
$($nosetter: None,)*
context: azure_core::Context::new(),
}
}

$crate::setters! {
$($optional: $otype => Some($optional),)*
context: azure_core::Context => context,
}
}
}
};
// Construct a builder and the `Future` related code
($name:ident<$($generic:ident: $first_constraint:ident $(+ $constraint:ident)* ),*>,
client: $client:ty,
@required
$($required:ident: $rtype:ty,)*
@optional
$($optional:ident: $otype:ty,)*
@nosetter
$($nosetter:ident: $nstype:ty),*
) => {
$crate::operation! {
@builder $name<$($generic: $first_constraint $(+ $constraint)*),*>,
client: $client,
@required
$($required: $rtype,)*
@optional
$($optional: $otype,)*
@nosetter
$($nosetter: $nstype),*
}
azure_core::__private::paste! {
/// The future returned by calling `into_future` on the builder.
pub type $name =
futures::future::BoxFuture<'static, azure_core::Result<[<$name Response>]>>;
#[cfg(feature = "into_future")]
impl std::future::IntoFuture for [<$name Builder>] {
type IntoFuture = $name;
type Output = <$name as std::future::Future>::Output;
fn into_future(self) -> Self::IntoFuture {
Self::into_future(self)
}
}
}
};
// `operation! { CreateUser, client: UserClient, ?consistency_level: ConsistencyLevel }`
($name:ident,
client: $client:ty,
$($required:ident: $rtype:ty,)*
$(?$optional:ident: $otype:ty),*) => {
$crate::operation!{
$name<>,
client: $client,
@required
$($required: $rtype,)*
@optional
$($optional: $otype,)*
@nosetter
}
};
// `operation! { #[stream] ListUsers, client: UserClient, ?consistency_level: ConsistencyLevel }`
(#[stream] $name:ident,
client: $client:ty,
$($required:ident: $rtype:ty,)*
$(?$optional:ident: $otype:ty),*) => {
$crate::operation!{
@builder
$name<>,
client: $client,
@required
$($required: $rtype,)*
@optional
$($optional: $otype,)*
@nosetter
}
};
(#[stream] $name:ident,
client: $client:ty,
$($required:ident: $rtype:ty,)*
$(?$optional:ident: $otype:ty,)*
$(#[skip]$nosetter:ident: $nstype:ty),*
) => {
$crate::operation!{
@builder
$name<>,
client: $client,
@required
$($required: $rtype,)*
@optional
$($optional: $otype,)*
@nosetter
$($nosetter: $nstype),*
}
};
// `operation! { CreateDocument<D: Serialize>, client: UserClient, ?consistency_level: ConsistencyLevel, ??other_field: bool }`
($name:ident<$($generic:ident: $first_constraint:ident $(+ $constraint:ident)*),*>,
client: $client:ty,
$($required:ident: $rtype:ty,)*
$(?$optional:ident: $otype:ty,)*
$(#[skip] $nosetter:ident: $nstype:ty),*) => {
$crate::operation!{
$name<$($generic: $first_constraint $(+ $constraint)*),*>,
client: $client,
@required
$($required: $rtype,)*
@optional
$($optional: $otype,)*
@nosetter
$($nosetter: $nstype),*
}
}
}

Expand Down
67 changes: 37 additions & 30 deletions sdk/core/src/mock/player_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::mock_transaction::MockTransaction;
use crate::error::{Error, ErrorKind};
use crate::policies::{Policy, PolicyResult};
use crate::{Context, Request, TransportOptions};
use std::collections::HashMap;
use std::sync::Arc;

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -70,45 +71,51 @@ impl Policy for MockTransportPlayerPolicy {
.headers()
.iter()
.filter(|(h, _)| !SKIPPED_HEADERS.contains(&h.as_str()))
.collect::<Vec<_>>();
.collect::<HashMap<_, _>>();

let expected_headers = expected_request
.headers()
.iter()
.filter(|(h, _)| !SKIPPED_HEADERS.contains(&h.as_str()))
.collect::<Vec<_>>();
.collect::<HashMap<_, _>>();
let more_headers = if expected_headers.len() > actual_headers.len() {
expected_headers.iter()
} else {
actual_headers.iter()
};

// In order to accept a request, we make sure that:
// 1. There are no extra headers (in both the received and read request).
// 2. Each header has the same value.
if actual_headers.len() != expected_headers.len() {
return Err(Error::with_message(ErrorKind::MockFramework, || {
format!(
"different number of headers in request. Actual: {0}, Expected: {1}",
actual_headers.len(),
expected_headers.len(),
)
}));
}

for (actual_header_key, actual_header_value) in actual_headers.iter() {
let (_, expected_header_value) = expected_headers
.iter()
.find(|(h, _)| actual_header_key.as_str() == h.as_str())
.ok_or_else(|| Error::with_message(ErrorKind::MockFramework, ||
format!("received request have header '{0}' but it was not present in the read request",
actual_header_key.as_str(),
)))?;

if actual_header_value != expected_header_value {
return Err(Error::with_message(ErrorKind::MockFramework, || {
format!(
"request header '{0}' value is different. Actual: {1}, Expected: {2}",
actual_header_key.as_str().to_owned(),
actual_header_value.as_str().to_owned(),
expected_header_value.as_str().to_owned(),
)
}));
for (name, _) in more_headers {
match (expected_headers.get(name), actual_headers.get(name)) {
(Some(_), None) => {
return Err(Error::with_message(ErrorKind::MockFramework, || {
format!(
"actual request does not have header '{0}' but it was expected",
name.as_str(),
)
}));
}
(None, Some(_)) => {
return Err(Error::with_message(ErrorKind::MockFramework, || {
format!(
"actual request has header '{0}' but it was not expected",
name.as_str(),
)
}));
}
(Some(exp), Some(act)) if exp != act => {
return Err(Error::with_message(ErrorKind::MockFramework, || {
format!(
"request header '{}' is different. Actual: {}, Expected: {}",
name.as_str(),
act.as_str(),
exp.as_str()
)
}));
}
_ => {}
}
}

Expand Down
6 changes: 6 additions & 0 deletions sdk/core/src/request_options/if_modified_since.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ impl Header for IfModifiedSince {
self.0.to_rfc2822().into()
}
}

impl From<DateTime<Utc>> for IfModifiedSince {
fn from(time: DateTime<Utc>) -> Self {
Self::new(time)
}
}
12 changes: 12 additions & 0 deletions sdk/core/src/request_options/max_item_count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ impl Header for MaxItemCount {
format!("{}", count).into()
}
}

impl From<i32> for MaxItemCount {
fn from(count: i32) -> Self {
Self::new(count)
}
}

impl Default for MaxItemCount {
fn default() -> Self {
MaxItemCount::new(-1)
}
}
Loading