Skip to content

add Method & StatusCode to azure_core #849

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

Closed
wants to merge 2 commits into from
Closed
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
3 changes: 2 additions & 1 deletion sdk/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ bytes = "1.0"
chrono = "0.4"
dyn-clone = "1.0"
futures = "0.3"
http = "0.2"
http-types = "2.12"

log = "0.4"
rand = "0.8"
reqwest = { version = "0.11", features = [
Expand Down
16 changes: 9 additions & 7 deletions sdk/core/src/error/http_error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{headers, Response};
use crate::{headers, response, Response, INVALID_BODY};
use bytes::Bytes;
use std::collections::HashMap;

Expand All @@ -16,19 +16,21 @@ impl HttpError {
///
/// This does not check whether the response was a success and should only be used with unsuccessful responses.
pub async fn new(response: Response) -> Self {
let status = response.status();
let mut error_code = get_error_code_from_header(&response);
let mut headers = HashMap::new();
let (status, headers, body) = response.deconstruct();
let mut error_headers = HashMap::new();

for (name, value) in response.headers().iter() {
headers.insert(name.as_str().to_string(), value.as_str().to_string());
for (name, value) in headers.iter() {
error_headers.insert(name.as_str().to_string(), value.as_str().to_string());
}

let body = response.into_body().await;
let body = response::collect_pinned_stream(body)
.await
.unwrap_or_else(|_| Bytes::from_static(INVALID_BODY));
error_code = error_code.or_else(|| get_error_code_from_body(&body));
HttpError {
status: status.as_u16(),
headers,
headers: error_headers,
error_code,
body,
}
Expand Down
34 changes: 0 additions & 34 deletions sdk/core/src/error/hyperium_http.rs

This file was deleted.

1 change: 0 additions & 1 deletion sdk/core/src/error/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::borrow::Cow;
use std::fmt::{Debug, Display};
mod http_error;
mod hyperium_http;
mod macros;
pub use http_error::HttpError;

Expand Down
18 changes: 1 addition & 17 deletions sdk/core/src/headers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
mod utilities;

use crate::error::{Error, ErrorKind, ResultExt};
use std::{collections::HashMap, fmt::Debug, str::FromStr};
use std::{fmt::Debug, str::FromStr};
pub use utilities::*;

/// A trait for converting a type into request headers
Expand Down Expand Up @@ -162,22 +162,6 @@ impl From<std::collections::HashMap<HeaderName, HeaderValue>> for Headers {
}
}

impl From<&http::HeaderMap> for Headers {
fn from(map: &http::HeaderMap) -> Self {
let map = map
.into_iter()
.map(|(k, v)| {
let key = k.as_str().to_owned();
let value = std::str::from_utf8(v.as_bytes())
.expect("non-UTF8 header value")
.to_owned();
(key.into(), value.into())
})
.collect::<HashMap<HeaderName, HeaderValue>>();
Self(map)
}
}

/// A header name
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct HeaderName(std::borrow::Cow<'static, str>);
Expand Down
61 changes: 7 additions & 54 deletions sdk/core/src/http_client.rs → sdk/core/src/http_client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
#[cfg(any(feature = "enable_reqwest", feature = "enable_reqwest_rustls"))]
#[cfg(not(target_arch = "wasm32"))]
mod reqwest;

#[cfg(any(feature = "enable_reqwest", feature = "enable_reqwest_rustls"))]
#[cfg(not(target_arch = "wasm32"))]
pub use self::reqwest::*;
#[allow(unused_imports)]
use crate::error::{Error, ErrorKind, ResultExt};
#[allow(unused_imports)]
Expand All @@ -10,13 +17,6 @@ use bytes::Bytes;
use futures::TryStreamExt;
use serde::Serialize;

/// Construct a new `HttpClient` with the `reqwest` backend.
#[cfg(any(feature = "enable_reqwest", feature = "enable_reqwest_rustls"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn new_http_client() -> std::sync::Arc<dyn HttpClient> {
std::sync::Arc::new(reqwest::Client::new())
}

/// An HTTP client which can send requests.
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
Expand Down Expand Up @@ -45,53 +45,6 @@ pub trait HttpClient: Send + Sync + std::fmt::Debug {
}
}

#[cfg(any(feature = "enable_reqwest", feature = "enable_reqwest_rustls"))]
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl HttpClient for reqwest::Client {
async fn execute_request(&self, request: &crate::Request) -> crate::Result<crate::Response> {
let url = request.url().clone();
let mut reqwest_request = self.request(request.method().clone(), url);
for (name, value) in request.headers().iter() {
reqwest_request = reqwest_request.header(name.as_str(), value.as_str());
}

let body = request.body().clone();

let reqwest_request = match body {
Body::Bytes(bytes) => reqwest_request
.body(bytes)
.build()
.context(ErrorKind::Other, "failed to build request")?,
Body::SeekableStream(mut seekable_stream) => {
seekable_stream.reset().await.unwrap(); // TODO: remove unwrap when `HttpError` has been removed

reqwest_request
.body(reqwest::Body::wrap_stream(seekable_stream))
.build()
.context(ErrorKind::Other, "failed to build request")?
}
};

let reqwest_response = self
.execute(reqwest_request)
.await
.context(ErrorKind::Io, "failed to execute request")?;

let status = reqwest_response.status();
let headers = Headers::from(reqwest_response.headers());
let body: PinnedStream = Box::pin(reqwest_response.bytes_stream().map_err(|error| {
Error::full(
ErrorKind::Io,
error,
"error converting `reqwest` request into a byte stream",
)
}));

Ok(crate::Response::new(status, headers, body))
}
}

/// Serialize a type to json.
pub fn to_json<T>(value: &T) -> crate::Result<Bytes>
where
Expand Down
84 changes: 84 additions & 0 deletions sdk/core/src/http_client/reqwest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use super::*;
use std::{collections::HashMap, str::FromStr};

/// Construct a new `HttpClient` with the `reqwest` backend.
pub fn new_http_client() -> std::sync::Arc<dyn HttpClient> {
std::sync::Arc::new(::reqwest::Client::new())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::sync::Arc::new(::reqwest::Client::new())
std::sync::Arc::new(reqwest::Client::new())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put these in a module named reqwest, so needed that prefix. Could rename the module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... or you could rename the 3rd party crate. use ::request as hyperium_request.

}

#[async_trait]
impl HttpClient for ::reqwest::Client {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl HttpClient for ::reqwest::Client {
impl HttpClient for reqwest::Client {

async fn execute_request(&self, request: &crate::Request) -> crate::Result<crate::Response> {
let url = request.url().clone();
let mut req = self.request(try_from_method(request.method())?, url);
for (name, value) in request.headers().iter() {
req = req.header(name.as_str(), value.as_str());
}

let body = request.body().clone();

let reqwest_request = match body {
Body::Bytes(bytes) => req
.body(bytes)
.build()
.context(ErrorKind::Other, "failed to build request")?,
Body::SeekableStream(mut seekable_stream) => {
seekable_stream.reset().await.unwrap(); // TODO: remove unwrap when `HttpError` has been removed
req.body(::reqwest::Body::wrap_stream(seekable_stream))
.build()
.context(ErrorKind::Other, "failed to build request")?
}
};

let rsp = self
.execute(reqwest_request)
.await
.context(ErrorKind::Io, "failed to execute request")?;

let status = rsp.status();
let headers = to_headers(rsp.headers())?;
let body: PinnedStream = Box::pin(rsp.bytes_stream().map_err(|error| {
Error::full(
ErrorKind::Io,
error,
"error converting `reqwest` request into a byte stream",
)
}));

Ok(crate::Response::new(
try_from_status(status)?,
headers,
body,
))
}
}

fn to_headers(map: &::reqwest::header::HeaderMap) -> crate::Result<crate::headers::Headers> {
let map = map
.iter()
.map(|(k, v)| {
let key = k.as_str();
let value = std::str::from_utf8(v.as_bytes()).expect("non-UTF8 header value");
(
crate::headers::HeaderName::from(key.to_owned()),
crate::headers::HeaderValue::from(value.to_owned()),
)
})
.collect::<HashMap<_, _>>();
Ok(crate::headers::Headers::from(map))
}

fn try_from_method(method: &crate::Method) -> crate::Result<::reqwest::Method> {
::reqwest::Method::from_str(method.as_ref()).map_kind(ErrorKind::DataConversion)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
::reqwest::Method::from_str(method.as_ref()).map_kind(ErrorKind::DataConversion)
reqwest::Method::from_str(method.as_ref()).map_kind(ErrorKind::DataConversion)

This should never happen. I wonder if we should have a wrapper error that says something like: if you ever see this, please report a bug.

}

fn try_from_status(status: ::reqwest::StatusCode) -> crate::Result<crate::StatusCode> {
let status = status.as_u16();
http_types::StatusCode::try_from(status)
.map_err(|_| {
Error::with_message(ErrorKind::DataConversion, || {
format!("invalid status code {status}")
})
})
.map(crate::StatusCode)
Comment on lines +77 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use crate::StatusCode's TryFrom<u16> impl

}
8 changes: 8 additions & 0 deletions sdk/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod constants;
mod context;
pub mod error;
mod http_client;
mod method;
mod models;
mod options;
mod pageable;
Expand All @@ -25,6 +26,7 @@ mod request;
mod request_options;
mod response;
mod seekable_stream;
mod status_code;

pub mod auth;
pub mod headers;
Expand All @@ -47,6 +49,7 @@ pub use headers::Header;
#[cfg(not(target_arch = "wasm32"))]
pub use http_client::new_http_client;
pub use http_client::{to_json, HttpClient};
pub use method::Method;
pub use models::*;
pub use options::*;
pub use pageable::*;
Expand All @@ -56,7 +59,12 @@ pub use request::*;
pub use response::*;
pub use seekable_stream::*;
pub use sleep::sleep;
pub use status_code::StatusCode;

// re-export packages
pub use url;

// re-export important types at crate level
pub use url::Url;

/// A unique identifier for a request.
Expand Down
46 changes: 46 additions & 0 deletions sdk/core/src/method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use crate::error::{Error, ErrorKind};
use std::{ops::Deref, str::FromStr};

#[derive(PartialEq, Eq, Clone, Debug)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not Copy?

pub struct Method(pub(crate) http_types::Method);

impl Method {
pub fn as_str(&self) -> &str {
self.as_ref()
}
}

impl Deref for Method {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This puts http_types into the public API. Do we want that?

type Target = http_types::Method;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Method {
pub const CONNECT: Method = Method(http_types::Method::Connect);
pub const DELETE: Method = Method(http_types::Method::Delete);
pub const GET: Method = Method(http_types::Method::Get);
pub const HEAD: Method = Method(http_types::Method::Head);
pub const MERGE: Method = Method(http_types::Method::Merge);
pub const OPTIONS: Method = Method(http_types::Method::Options);
pub const PATCH: Method = Method(http_types::Method::Patch);
pub const POST: Method = Method(http_types::Method::Post);
pub const PUT: Method = Method(http_types::Method::Put);
pub const TRACE: Method = Method(http_types::Method::Trace);
}

impl Default for Method {
fn default() -> Method {
Method::GET
}
}

impl FromStr for Method {
type Err = Error;
fn from_str(s: &str) -> crate::Result<Self> {
Ok(Method(http_types::Method::from_str(s).map_err(
|error| Error::full(ErrorKind::DataConversion, error, "Method::from_str failed"),
)?))
}
}
2 changes: 1 addition & 1 deletion sdk/core/src/mock/mock_request.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::Method;
use crate::{Body, Request};
use http::Method;
use serde::de::Visitor;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde::{Deserialize, Deserializer};
Expand Down
Loading