Skip to content

Commit 85d13b3

Browse files
authored
Standardize on json & xml helpers (#1505)
1 parent 838f181 commit 85d13b3

File tree

137 files changed

+772
-902
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+772
-902
lines changed

sdk/core/src/date/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ pub fn diff(first: OffsetDateTime, second: OffsetDateTime) -> Duration {
137137
#[cfg(test)]
138138
mod tests {
139139
use super::*;
140+
use crate::from_json;
140141
use serde::{Deserialize, Serialize};
141-
use serde_json;
142142
use time::macros::datetime;
143143

144144
#[derive(Serialize, Deserialize)]
@@ -207,7 +207,7 @@ mod tests {
207207
"created_time": "2021-07-01T10:45:02Z"
208208
}"#;
209209

210-
let state: ExampleState = serde_json::from_str(json_state).unwrap();
210+
let state: ExampleState = from_json(json_state)?;
211211

212212
assert_eq!(parse_rfc3339("2021-07-01T10:45:02Z")?, state.created_time);
213213
assert_eq!(state.deleted_time, None);
@@ -222,12 +222,12 @@ mod tests {
222222
"deleted_time": "2022-03-28T11:05:31Z"
223223
}"#;
224224

225-
let state: ExampleState = serde_json::from_str(json_state).unwrap();
225+
let state: ExampleState = from_json(json_state)?;
226226

227227
assert_eq!(parse_rfc3339("2021-07-01T10:45:02Z")?, state.created_time);
228228
assert_eq!(
229229
state.deleted_time,
230-
Some(parse_rfc3339("2022-03-28T11:05:31Z").unwrap())
230+
Some(parse_rfc3339("2022-03-28T11:05:31Z")?)
231231
);
232232

233233
Ok(())

sdk/core/src/error/http_error.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use crate::{headers, Response, StatusCode};
1+
use crate::{from_json, headers, Response, StatusCode};
22
use bytes::Bytes;
3+
use serde::Deserialize;
34
use std::collections::HashMap;
45

56
/// An unsuccessful HTTP response
@@ -103,24 +104,31 @@ fn get_error_code_from_header(headers: &HashMap<String, String>) -> Option<Strin
103104
headers.get(headers::ERROR_CODE.as_str()).cloned()
104105
}
105106

107+
#[derive(Deserialize)]
108+
struct NestedError {
109+
message: Option<String>,
110+
code: Option<String>,
111+
}
112+
113+
#[derive(Deserialize)]
114+
struct ErrorBody {
115+
error: Option<NestedError>,
116+
message: Option<String>,
117+
code: Option<String>,
118+
}
119+
106120
/// Gets the error code if it's present in the body
107121
///
108122
/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
109123
pub(crate) fn get_error_code_from_body(body: &[u8]) -> Option<String> {
110-
let json = serde_json::from_slice::<serde_json::Value>(body).ok()?;
111-
let nested = || json.get("error")?.get("code")?.as_str();
112-
let top_level = || json.get("code")?.as_str();
113-
let code = nested().or_else(top_level);
114-
code.map(ToOwned::to_owned)
124+
let decoded: ErrorBody = from_json(body).ok()?;
125+
decoded.error.and_then(|e| e.code).or(decoded.code)
115126
}
116127

117128
/// Gets the error message if it's present in the body
118129
///
119130
/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
120131
pub(crate) fn get_error_message_from_body(body: &[u8]) -> Option<String> {
121-
let json = serde_json::from_slice::<serde_json::Value>(body).ok()?;
122-
let nested = || json.get("error")?.get("message")?.as_str();
123-
let top_level = || json.get("message")?.as_str();
124-
let code = nested().or_else(top_level);
125-
code.map(ToOwned::to_owned)
132+
let decoded: ErrorBody = from_json(body).ok()?;
133+
decoded.error.and_then(|e| e.message).or(decoded.message)
126134
}

sdk/core/src/http_client/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use self::reqwest::new_reqwest_client;
1010
use crate::error::ErrorKind;
1111
use async_trait::async_trait;
1212
use bytes::Bytes;
13-
use serde::Serialize;
13+
use serde::{de::DeserializeOwned, Serialize};
1414
use std::sync::Arc;
1515

1616
/// Construct a new `HttpClient`
@@ -61,3 +61,12 @@ where
6161
{
6262
Ok(Bytes::from(serde_json::to_vec(value)?))
6363
}
64+
65+
/// Reads the XML from bytes.
66+
pub fn from_json<S, T>(body: S) -> crate::Result<T>
67+
where
68+
S: AsRef<[u8]>,
69+
T: DeserializeOwned,
70+
{
71+
serde_json::from_slice(body.as_ref()).map_err(|e| e.into())
72+
}

sdk/core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub use context::Context;
5555
pub use error::{Error, Result};
5656
#[doc(inline)]
5757
pub use headers::Header;
58-
pub use http_client::{new_http_client, to_json, HttpClient};
58+
pub use http_client::{from_json, new_http_client, to_json, HttpClient};
5959
pub use models::*;
6060
pub use options::*;
6161
pub use pageable::*;

sdk/core/src/lro.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub fn get_retry_after(headers: &Headers) -> Duration {
5959

6060
pub mod location {
6161
use crate::{
62+
from_json,
6263
headers::{Headers, AZURE_ASYNCOPERATION, LOCATION, OPERATION_LOCATION},
6364
lro::LroStatus,
6465
Url,
@@ -80,15 +81,18 @@ pub mod location {
8081
}
8182

8283
pub fn get_provisioning_state(body: &[u8]) -> Option<LroStatus> {
83-
let body: serde_json::Value = serde_json::from_slice(body).ok()?;
84-
let provisioning_state = body["status"].as_str()?;
85-
Some(LroStatus::from(provisioning_state))
84+
#[derive(serde::Deserialize)]
85+
struct Body {
86+
status: String,
87+
}
88+
let body: Body = from_json(body).ok()?;
89+
Some(LroStatus::from(body.status.as_str()))
8690
}
8791
}
8892

8993
pub mod body_content {
90-
use crate::{lro::LroStatus, StatusCode};
91-
use serde::Serialize;
94+
use crate::{from_json, lro::LroStatus, to_json, StatusCode};
95+
use serde::{Deserialize, Serialize};
9296

9397
/// Extract the provisioning state based on the status code and response body
9498
///
@@ -115,13 +119,22 @@ pub mod body_content {
115119
}
116120
}
117121

122+
#[derive(Deserialize)]
123+
#[serde(rename_all = "snake_case")]
124+
struct Properties {
125+
provisioning_state: String,
126+
}
127+
128+
#[derive(Deserialize)]
129+
struct Body {
130+
properties: Properties,
131+
}
132+
118133
fn get_provisioning_state_from_body<S>(body: &S) -> Option<LroStatus>
119134
where
120135
S: Serialize,
121136
{
122-
let body: serde_json::Value =
123-
serde_json::from_str(&serde_json::to_string(body).ok()?).ok()?;
124-
let provisioning_state = body["properties"]["provisioningState"].as_str()?;
125-
Some(LroStatus::from(provisioning_state))
137+
let body: Body = from_json(to_json(&body).ok()?).ok()?;
138+
Some(LroStatus::from(body.properties.provisioning_state.as_str()))
126139
}
127140
}

sdk/core/src/request.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
use crate::SeekableStream;
33
use crate::{
44
headers::{AsHeaders, Headers},
5-
Method, Url,
5+
to_json, Method, Url,
66
};
77
use bytes::Bytes;
8+
use serde::Serialize;
89
use std::fmt::Debug;
910

1011
/// An HTTP Body.
@@ -116,6 +117,14 @@ impl Request {
116117
&self.body
117118
}
118119

120+
pub fn set_json<T>(&mut self, data: &T) -> crate::Result<()>
121+
where
122+
T: ?Sized + Serialize,
123+
{
124+
self.set_body(to_json(data)?);
125+
Ok(())
126+
}
127+
119128
pub fn set_body(&mut self, body: impl Into<Body>) {
120129
self.body = body.into();
121130
}

sdk/core/src/response.rs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use crate::error::{ErrorKind, ResultExt};
2-
use crate::headers::Headers;
3-
use crate::StatusCode;
1+
use crate::{
2+
error::{ErrorKind, ResultExt},
3+
from_json,
4+
headers::Headers,
5+
StatusCode,
6+
};
47
use bytes::Bytes;
58
use futures::{Stream, StreamExt};
6-
use std::fmt::Debug;
7-
use std::pin::Pin;
9+
use serde::de::DeserializeOwned;
10+
use std::{fmt::Debug, pin::Pin};
811

912
#[cfg(not(target_arch = "wasm32"))]
1013
pub(crate) type PinnedStream = Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
@@ -46,6 +49,21 @@ impl Response {
4649
pub fn into_body(self) -> ResponseBody {
4750
self.body
4851
}
52+
53+
pub async fn json<T>(self) -> crate::Result<T>
54+
where
55+
T: DeserializeOwned,
56+
{
57+
self.into_body().json().await
58+
}
59+
60+
#[cfg(feature = "xml")]
61+
pub async fn xml<T>(self) -> crate::Result<T>
62+
where
63+
T: DeserializeOwned,
64+
{
65+
self.into_body().xml().await
66+
}
4967
}
5068

5169
impl std::fmt::Debug for Response {
@@ -66,6 +84,12 @@ pub struct CollectedResponse {
6684
body: Bytes,
6785
}
6886

87+
impl AsRef<[u8]> for CollectedResponse {
88+
fn as_ref(&self) -> &[u8] {
89+
self.body.as_ref()
90+
}
91+
}
92+
6993
impl CollectedResponse {
7094
/// Create a new instance
7195
pub fn new(status: StatusCode, headers: Headers, body: Bytes) -> Self {
@@ -97,6 +121,21 @@ impl CollectedResponse {
97121
let body = body.collect().await?;
98122
Ok(Self::new(status, headers, body))
99123
}
124+
125+
pub fn json<T>(&self) -> crate::Result<T>
126+
where
127+
T: DeserializeOwned,
128+
{
129+
from_json(&self.body)
130+
}
131+
132+
#[cfg(feature = "xml")]
133+
pub async fn xml<T>(&self) -> crate::Result<T>
134+
where
135+
T: DeserializeOwned,
136+
{
137+
crate::xml::read_xml(&self.body)
138+
}
100139
}
101140

102141
/// A response body stream
@@ -130,6 +169,23 @@ impl ResponseBody {
130169
)
131170
.map(ToOwned::to_owned)
132171
}
172+
173+
pub async fn json<T>(self) -> crate::Result<T>
174+
where
175+
T: DeserializeOwned,
176+
{
177+
let body = self.collect().await?;
178+
from_json(body)
179+
}
180+
181+
#[cfg(feature = "xml")]
182+
pub async fn xml<T>(self) -> crate::Result<T>
183+
where
184+
T: DeserializeOwned,
185+
{
186+
let body = self.collect().await?;
187+
crate::xml::read_xml(&body)
188+
}
133189
}
134190

135191
impl Stream for ResponseBody {

sdk/core/src/util.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ where
3030
#[cfg(test)]
3131
mod tests {
3232
use super::*;
33+
use crate::from_json;
3334
use serde::Serialize;
3435

3536
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
@@ -54,15 +55,15 @@ mod tests {
5455
#[test]
5556
fn deserialize_empty() -> crate::Result<()> {
5657
let bytes = br#"{}"#;
57-
let site_config: SiteConfig = serde_json::from_slice(bytes)?;
58+
let site_config: SiteConfig = from_json(bytes)?;
5859
assert_eq!(Vec::<NameValuePair>::default(), site_config.app_settings);
5960
Ok(())
6061
}
6162

6263
#[test]
6364
fn deserialize_null() -> crate::Result<()> {
6465
let bytes = br#"{ "appSettings": null }"#;
65-
let site_config: SiteConfig = serde_json::from_slice(bytes)?;
66+
let site_config: SiteConfig = from_json(bytes)?;
6667
assert_eq!(Vec::<NameValuePair>::default(), site_config.app_settings);
6768
Ok(())
6869
}

sdk/core/src/xml.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ mod test {
8080
}
8181

8282
#[test]
83-
fn reading_xml() {
83+
fn reading_xml() -> crate::Result<()> {
8484
#[derive(Deserialize, PartialEq, Debug)]
8585
#[serde(rename = "Foo")]
8686
struct Test {
@@ -90,15 +90,16 @@ mod test {
9090
x: "Hello, world!".into(),
9191
};
9292
let xml = br#"<?xml version="1.0" encoding="utf-8"?><Foo><x>Hello, world!</x></Foo>"#;
93-
assert_eq!(test, read_xml(xml).unwrap());
93+
assert_eq!(test, read_xml(xml)?);
9494

9595
let error = read_xml::<Test>(&xml[..xml.len() - 2]).unwrap_err();
9696
assert!(format!("{error}").contains("reading_xml::Test"));
9797

9898
let xml = r#"<?xml version="1.0" encoding="utf-8"?><Foo><x>Hello, world!</x></Foo>"#;
99-
assert_eq!(test, read_xml_str(xml).unwrap());
99+
assert_eq!(test, read_xml_str(xml)?);
100100

101101
let error = read_xml_str::<Test>(&xml[..xml.len() - 2]).unwrap_err();
102102
assert!(format!("{error}").contains("reading_xml::Test"));
103+
Ok(())
103104
}
104105
}

sdk/data_cosmos/src/operations/create_collection.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
use crate::headers::from_headers::*;
2-
use crate::prelude::*;
3-
use crate::resources::collection::{IndexingPolicy, PartitionKey};
4-
use azure_core::headers::{etag_from_headers, session_token_from_headers};
5-
use azure_core::Response as HttpResponse;
1+
use crate::{
2+
headers::from_headers::*,
3+
prelude::*,
4+
resources::collection::{IndexingPolicy, PartitionKey},
5+
};
6+
use azure_core::{
7+
headers::{etag_from_headers, session_token_from_headers},
8+
Response as HttpResponse,
9+
};
610
use time::OffsetDateTime;
711

812
operation! {
@@ -40,7 +44,7 @@ impl CreateCollectionBuilder {
4044
partition_key: &self.partition_key,
4145
};
4246

43-
request.set_body(serde_json::to_vec(&collection)?);
47+
request.set_json(&collection)?;
4448

4549
let response = self
4650
.client
@@ -73,10 +77,8 @@ pub struct CreateCollectionResponse {
7377
impl CreateCollectionResponse {
7478
pub async fn try_from(response: HttpResponse) -> azure_core::Result<Self> {
7579
let (_status_code, headers, body) = response.deconstruct();
76-
let body = body.collect().await?;
77-
7880
Ok(Self {
79-
collection: serde_json::from_slice(&body)?,
81+
collection: body.json().await?,
8082
charge: request_charge_from_headers(&headers)?,
8183
activity_id: activity_id_from_headers(&headers)?,
8284
etag: etag_from_headers(&headers)?,

0 commit comments

Comments
 (0)