Skip to content

Commit dd6b852

Browse files
authored
Add pipeline to storage_account_client (#811)
* Add pipeline to storage_account_client * Add Authorization policy * Update tests * Fix clippy warnings * Switch to url crate * PR feedback * Fix mock tests
1 parent 461035b commit dd6b852

File tree

26 files changed

+498
-183
lines changed

26 files changed

+498
-183
lines changed

sdk/core/src/headers/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ impl Headers {
6161
}
6262

6363
/// Get a header value given a specific header name
64-
pub fn get(&self, key: &HeaderName) -> Option<&HeaderValue> {
65-
self.0.get(key)
64+
pub fn get<T: Into<HeaderName>>(&self, key: T) -> Option<&HeaderValue> {
65+
self.0.get(&key.into())
6666
}
6767

6868
/// Insert a header name/value pair

sdk/core/src/http_client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl HttpClient for reqwest::Client {
9898
}
9999

100100
async fn execute_request2(&self, request: &crate::Request) -> Result<crate::Response> {
101-
let url = url::Url::parse(&request.uri().to_string())?;
101+
let url = request.url().clone();
102102
let mut reqwest_request = self.request(request.method(), url);
103103
for (name, value) in request.headers().iter() {
104104
reqwest_request = reqwest_request.header(name, value);

sdk/core/src/mock/mock_request.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use crate::{Body, Request};
2-
use http::{Method, Uri};
2+
use http::Method;
33
use serde::de::Visitor;
44
use serde::ser::{Serialize, SerializeStruct, Serializer};
55
use serde::{Deserialize, Deserializer};
66
use std::collections::HashMap;
77
use std::str::FromStr;
8+
use url::Url;
89

910
const FIELDS: &[&str] = &["uri", "method", "headers", "body"];
1011

@@ -83,8 +84,11 @@ impl<'de> Visitor<'de> for RequestVisitor {
8384
hm.insert(k.to_owned().into(), v.into());
8485
}
8586

87+
// `url` cannot be relative
88+
let url = Url::parse("http://example.com").unwrap();
89+
let url = url.join(uri.1).expect("expected a valid uri");
8690
Ok(Self::Value {
87-
uri: Uri::from_str(uri.1).expect("expected a valid uri"),
91+
url,
8892
method: Method::from_str(method.1).expect("expected a valid HTTP method"),
8993
headers: hm.into(),
9094
body: bytes::Bytes::from(body).into(),
@@ -107,14 +111,7 @@ impl Serialize for Request {
107111
}
108112

109113
let mut state = serializer.serialize_struct("Request", 4)?;
110-
state.serialize_field(
111-
FIELDS[0],
112-
&self
113-
.uri
114-
.path_and_query()
115-
.map(|p| p.to_string())
116-
.unwrap_or_else(String::new),
117-
)?;
114+
state.serialize_field(FIELDS[0], &self.path_and_query())?;
118115
state.serialize_field(FIELDS[1], &self.method.to_string())?;
119116
state.serialize_field(FIELDS[2], &hm)?;
120117
state.serialize_field(

sdk/core/src/mock/player_policy.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,8 @@ impl Policy for MockTransportPlayerPolicy {
5151
let expected_request: Request = serde_json::from_str(&expected_request)?;
5252
let expected_response = serde_json::from_str::<MockResponse>(&expected_response)?;
5353

54-
let expected_uri = expected_request.uri().to_string();
55-
let actual_uri = request
56-
.uri()
57-
.path_and_query()
58-
.map(|p| p.to_string())
59-
.unwrap_or_else(String::new);
54+
let expected_uri = expected_request.path_and_query();
55+
let actual_uri = request.path_and_query();
6056
if expected_uri != actual_uri {
6157
return Err(Error::with_message(ErrorKind::MockFramework, || {
6258
format!(

sdk/core/src/policies/transport.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ impl Policy for TransportPolicy {
3434

3535
let response = { self.transport_options.http_client.execute_request2(request) };
3636

37-
Ok(response.await?)
37+
response.await
3838
}
3939
}

sdk/core/src/request.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use crate::error::{ErrorKind, Result, ResultExt};
21
use crate::headers::{AsHeaders, Headers};
32
use crate::SeekableStream;
43
use bytes::Bytes;
5-
use http::{Method, Uri};
4+
use http::Method;
65
use std::fmt::Debug;
7-
use std::str::FromStr;
6+
use url::Url;
87

98
/// An HTTP Body.
109
#[derive(Debug, Clone)]
@@ -36,25 +35,38 @@ impl From<Box<dyn SeekableStream>> for Body {
3635
/// body. Policies are expected to enrich the request by mutating it.
3736
#[derive(Debug, Clone)]
3837
pub struct Request {
39-
pub(crate) uri: Uri,
38+
pub(crate) url: Url,
4039
pub(crate) method: Method,
4140
pub(crate) headers: Headers,
4241
pub(crate) body: Body,
4342
}
4443

4544
impl Request {
4645
/// Create a new request with an empty body and no headers
47-
pub fn new(uri: Uri, method: Method) -> Self {
46+
pub fn new(url: Url, method: Method) -> Self {
4847
Self {
49-
uri,
48+
url,
5049
method,
5150
headers: Headers::new(),
5251
body: Body::Bytes(bytes::Bytes::new()),
5352
}
5453
}
5554

56-
pub fn uri(&self) -> &Uri {
57-
&self.uri
55+
pub fn url(&self) -> &Url {
56+
&self.url
57+
}
58+
59+
pub fn url_mut(&mut self) -> &mut Url {
60+
&mut self.url
61+
}
62+
63+
pub fn path_and_query(&self) -> String {
64+
let mut result = self.url.path().to_owned();
65+
if let Some(query) = self.url.query() {
66+
result.push('?');
67+
result.push_str(query)
68+
}
69+
result
5870
}
5971

6072
pub fn method(&self) -> Method {
@@ -82,11 +94,6 @@ impl Request {
8294
pub fn set_body(&mut self, body: impl Into<Body>) {
8395
self.body = body.into();
8496
}
85-
86-
/// Parse a `Uri` from a `str`
87-
pub fn parse_uri(uri: &str) -> Result<Uri> {
88-
Uri::from_str(uri).map_kind(ErrorKind::DataConversion)
89-
}
9097
}
9198

9299
/// Temporary hack to convert preexisting requests into the new format. It
@@ -95,7 +102,7 @@ impl From<http::Request<bytes::Bytes>> for Request {
95102
fn from(request: http::Request<bytes::Bytes>) -> Self {
96103
let (parts, body) = request.into_parts();
97104
Self {
98-
uri: parts.uri,
105+
url: Url::parse(&parts.uri.to_string()).unwrap(),
99106
method: parts.method,
100107
headers: parts.headers.into(),
101108
body: Body::Bytes(body),

sdk/data_cosmos/src/authorization_policy.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ use url::form_urlencoded;
1414
const AZURE_VERSION: &str = "2018-12-31";
1515
const VERSION: &str = "1.0";
1616

17-
/// The `AuthorizationPolicy` takes care to authenticate your calls to Azure CosmosDB. Currently it
18-
/// supports two type of authorization: one at service level and another at resource level (see
19-
/// [AuthorizationToken] for more info). The policy must be added just before the transport policy
17+
/// The `AuthorizationPolicy` takes care to authenticate your calls to Azure CosmosDB.
18+
///
19+
/// Currently it supports two type of authorization: one at service level and another at resource level (see
20+
/// [`AuthorizationToken`] for more info). The policy must be added just before the transport policy
2021
/// because it needs to inspect the values that are about to be sent to the transport and inject
2122
/// the proper authorization token.
22-
/// The `AuthorizationPolicy` is the only owner of the passed credentials so if you want to
23-
/// authenticate the same operation with different credentials all you have to do is to swap the
23+
///
24+
/// The `AuthorizationPolicy` is the only owner of the passed credentials, so if you want to
25+
/// authenticate the same operation with different credentials, all you have to do is to swap the
2426
/// `AuthorizationPolicy`.
25-
/// This struct is `Debug` but secrets are encrypted by `AuthorizationToken` so there is no risk of
27+
///
28+
/// This struct implements `Debug` but secrets are encrypted by `AuthorizationToken` so there is no risk of
2629
/// leaks in debug logs (secrets are stored in cleartext in memory: dumps are still leaky).
2730
#[derive(Debug, Clone, PartialEq, Eq)]
2831
pub struct AuthorizationPolicy {
@@ -54,11 +57,11 @@ impl Policy for AuthorizationPolicy {
5457

5558
let time_nonce = TimeNonce::new();
5659

57-
let uri_path = &request.uri().path_and_query().unwrap().to_string()[1..];
60+
let uri_path = request.path_and_query();
5861
trace!("uri_path used by AuthorizationPolicy == {:#?}", uri_path);
5962

6063
let auth = {
61-
let resource_link = generate_resource_link(uri_path);
64+
let resource_link = generate_resource_link(&uri_path);
6265
trace!("resource_link == {}", resource_link);
6366
generate_authorization(
6467
&self.authorization_token,
@@ -100,9 +103,7 @@ impl Policy for AuthorizationPolicy {
100103
/// 2. Find if the uri **is** the ending string (without the leading slash). If so return an empty
101104
/// string. This covers the exception of the rule above.
102105
/// 3. Return the received uri unchanged.
103-
// TODO: will become private as soon as client will be migrated
104-
// to pipeline arch.
105-
pub(crate) fn generate_resource_link(uri: &str) -> &str {
106+
fn generate_resource_link(uri: &str) -> &str {
106107
static ENDING_STRINGS: &[&str] = &[
107108
"/dbs",
108109
"/colls",
@@ -139,12 +140,11 @@ pub(crate) fn generate_resource_link(uri: &str) -> &str {
139140
}
140141
}
141142

142-
/// The CosmosDB authorization can either be "primary" (ie one of the two service-level tokens) or
143-
/// "resource" (ie a single database). In the first case the signature must be constructed by
143+
/// The CosmosDB authorization can either be "primary" (i.e., one of the two service-level tokens) or
144+
/// "resource" (i.e., a single database). In the first case the signature must be constructed by
144145
/// signing the HTTP method, resource type, resource link (the relative URI) and the current time.
145146
/// In the second case, the signature is just the resource key.
146-
// TODO: make it private after pipeline migration
147-
pub(crate) fn generate_authorization(
147+
fn generate_authorization(
148148
auth_token: &AuthorizationToken,
149149
http_method: &http::Method,
150150
resource_type: &ResourceType,

sdk/identity/src/client_credentials_flow/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use azure_core::{
4747
use http::Method;
4848
use login_response::LoginResponse;
4949
use std::sync::Arc;
50-
use url::form_urlencoded;
50+
use url::{form_urlencoded, Url};
5151

5252
/// Perform the client credentials flow
5353
#[allow(clippy::manual_async_fn)]
@@ -66,7 +66,7 @@ pub async fn perform(
6666
.append_pair("grant_type", "client_credentials")
6767
.finish();
6868

69-
let url = Request::parse_uri(&format!(
69+
let url = Url::parse(&format!(
7070
"https://login.microsoftonline.com/{}/oauth2/v2.0/token",
7171
tenant_id
7272
))

sdk/identity/src/device_code_flow/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use http::Method;
1717
use oauth2::ClientId;
1818
use serde::Deserialize;
1919
use std::{borrow::Cow, sync::Arc, time::Duration};
20-
use url::form_urlencoded;
20+
use url::{form_urlencoded, Url};
2121

2222
/// Start the device authorization grant flow.
2323
/// The user has only 15 minutes to sign in (the usual value for expires_in).
@@ -171,7 +171,7 @@ async fn post_form(
171171
url: &str,
172172
form_body: String,
173173
) -> Result<Response> {
174-
let url = Request::parse_uri(url)?;
174+
let url = Url::parse(url)?;
175175
let mut req = Request::new(url, Method::POST);
176176
req.headers_mut().insert(
177177
headers::CONTENT_TYPE,

sdk/identity/src/refresh_token.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use oauth2::{AccessToken, ClientId, ClientSecret};
1010
use serde::Deserialize;
1111
use std::fmt;
1212
use std::sync::Arc;
13-
use url::form_urlencoded;
13+
use url::{form_urlencoded, Url};
1414

1515
/// Exchange a refresh token for a new access token and refresh token
1616
#[allow(clippy::manual_async_fn)]
@@ -34,7 +34,7 @@ pub async fn exchange(
3434
let encoded = encoded.append_pair("refresh_token", refresh_token.secret());
3535
let encoded = encoded.finish();
3636

37-
let url = Request::parse_uri(&format!(
37+
let url = Url::parse(&format!(
3838
"https://login.microsoftonline.com/{}/oauth2/v2.0/token",
3939
tenant_id
4040
))?;

sdk/identity/src/token_credentials/imds_managed_identity_credentials.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ impl TokenCredential for ImdsManagedIdentityCredential {
112112
"error parsing url for MSI endpoint",
113113
)?;
114114

115-
let url = Request::parse_uri(url.as_str())?;
116115
let mut req = Request::new(url, Method::GET);
117116

118117
req.headers_mut().insert("Metadata", "true");

sdk/storage/examples/account00.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ async fn main() -> Result<()> {
1515
StorageAccountClient::new_access_key(http_client.clone(), &account, &master_key)
1616
.as_storage_client();
1717

18-
let response = storage_client.get_account_information().execute().await?;
18+
let response = storage_client
19+
.get_account_information()
20+
.into_future()
21+
.await?;
1922
println!("{:?}", response);
2023

2124
Ok(())

sdk/storage/src/account/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod operations;
12
pub mod requests;
23
pub mod responses;
34

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use crate::core::prelude::*;
2+
use azure_core::error::Result;
3+
use azure_core::headers::{
4+
account_kind_from_headers, date_from_headers, request_id_from_headers, sku_name_from_headers,
5+
};
6+
7+
use azure_core::{Context, RequestId};
8+
use chrono::{DateTime, Utc};
9+
use http::HeaderMap;
10+
11+
#[derive(Debug, Clone)]
12+
pub struct GetAccountInformationBuilder {
13+
storage_client: StorageClient,
14+
context: Context,
15+
}
16+
17+
impl GetAccountInformationBuilder {
18+
pub(crate) fn new(storage_client: StorageClient) -> Self {
19+
Self {
20+
storage_client,
21+
context: Context::new(),
22+
}
23+
}
24+
25+
setters! {
26+
context: Context => context,
27+
}
28+
29+
pub fn into_future(mut self) -> GetAccountInformation {
30+
Box::pin(async move {
31+
let mut request = self
32+
.storage_client
33+
.storage_account_client()
34+
.blob_storage_request("", http::Method::GET);
35+
36+
for (k, v) in [("restype", "account"), ("comp", "properties")].iter() {
37+
request.url_mut().query_pairs_mut().append_pair(k, v);
38+
}
39+
40+
let response = self
41+
.storage_client
42+
.storage_account_client()
43+
.pipeline()
44+
.send(&mut self.context, &mut request)
45+
.await?;
46+
47+
GetAccountInformationResponse::try_from(response.headers())
48+
})
49+
}
50+
}
51+
52+
/// The future returned by calling `into_future` on the builder.
53+
pub type GetAccountInformation =
54+
futures::future::BoxFuture<'static, azure_core::error::Result<GetAccountInformationResponse>>;
55+
56+
#[cfg(feature = "into_future")]
57+
impl std::future::IntoFuture for GetAccountInformationBuilder {
58+
type IntoFuture = GetAccountInformation;
59+
type Output = <GetAccountInformation as std::future::Future>::Output;
60+
fn into_future(self) -> Self::IntoFuture {
61+
Self::into_future(self)
62+
}
63+
}
64+
65+
#[derive(Debug, Clone)]
66+
pub struct GetAccountInformationResponse {
67+
pub request_id: RequestId,
68+
pub date: DateTime<Utc>,
69+
pub sku_name: String,
70+
pub account_kind: String,
71+
}
72+
73+
impl GetAccountInformationResponse {
74+
pub(crate) fn try_from(headers: &HeaderMap) -> Result<GetAccountInformationResponse> {
75+
let request_id = request_id_from_headers(headers)?;
76+
let date = date_from_headers(headers)?;
77+
let sku_name = sku_name_from_headers(headers)?;
78+
let account_kind = account_kind_from_headers(headers)?;
79+
80+
Ok(GetAccountInformationResponse {
81+
request_id,
82+
date,
83+
sku_name,
84+
account_kind,
85+
})
86+
}
87+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod get_account_information;
2+
3+
pub use get_account_information::*;

0 commit comments

Comments
 (0)