Skip to content

Commit f1dad48

Browse files
authored
convert non-success status codes to errors in the cosmos pipeline
1 parent 189d374 commit f1dad48

File tree

4 files changed

+84
-11
lines changed

4 files changed

+84
-11
lines changed

sdk/cosmos/azure_data_cosmos/src/pipeline/mod.rs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ mod signature_target;
77
use std::sync::Arc;
88

99
pub use authorization_policy::AuthorizationPolicy;
10-
use azure_core::http::{
11-
request::{options::ContentType, Request},
12-
response::Response,
13-
ClientOptions, Context, Method, PagerState, RawResponse,
10+
use azure_core::{
11+
error::HttpError,
12+
http::{
13+
request::{options::ContentType, Request},
14+
response::Response,
15+
ClientOptions, Context, Method, PagerState, RawResponse,
16+
},
1417
};
1518
use futures::TryStreamExt;
1619
use serde::de::DeserializeOwned;
@@ -64,7 +67,7 @@ impl CosmosPipeline {
6467
resource_link: ResourceLink,
6568
) -> azure_core::Result<RawResponse> {
6669
let ctx = ctx.with_value(resource_link);
67-
self.pipeline.send(&ctx, request).await
70+
self.pipeline.send(&ctx, request).await?.success().await
6871
}
6972

7073
pub async fn send<T>(
@@ -75,7 +78,7 @@ impl CosmosPipeline {
7578
) -> azure_core::Result<Response<T>> {
7679
self.send_raw(ctx, request, resource_link)
7780
.await
78-
.map(|r| r.into())
81+
.map(Into::into)
7982
}
8083

8184
pub fn send_query_request<T: DeserializeOwned + Send>(
@@ -105,7 +108,7 @@ impl CosmosPipeline {
105108
req.insert_header(constants::CONTINUATION, continuation);
106109
}
107110

108-
let resp = pipeline.send(&ctx, &mut req).await?;
111+
let resp = pipeline.send(&ctx, &mut req).await?.success().await?;
109112
let page = FeedPage::<T>::from_response(resp).await?;
110113

111114
Ok(page.into())
@@ -191,3 +194,39 @@ pub(crate) fn create_base_query_request(url: Url, query: &Query) -> azure_core::
191194
request.set_json(query)?;
192195
Ok(request)
193196
}
197+
198+
trait ResponseExt {
199+
async fn success(self) -> azure_core::Result<Self>
200+
where
201+
Self: Sized;
202+
}
203+
204+
impl<T, F> ResponseExt for Response<T, F> {
205+
async fn success(self) -> azure_core::Result<Self> {
206+
if self.status().is_success() {
207+
Ok(self)
208+
} else {
209+
RawResponse::from(self).success().await.map(Into::into)
210+
}
211+
}
212+
}
213+
214+
impl ResponseExt for RawResponse {
215+
async fn success(self) -> azure_core::Result<Self> {
216+
if self.status().is_success() {
217+
Ok(self)
218+
} else {
219+
let http_error = HttpError::new(self).await;
220+
let status = http_error.status();
221+
let error_kind = azure_core::error::ErrorKind::http_response(
222+
status,
223+
http_error.error_code().map(ToOwned::to_owned),
224+
);
225+
Err(azure_core::Error::full(
226+
error_kind,
227+
http_error,
228+
format!("Request failed with status code: {}", status),
229+
))
230+
}
231+
}
232+
}

sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,12 @@ impl TestAccount {
147147
// We COULD choose not to delete them and instead validate that they were deleted, but this is what I've gone with for now.
148148
for id in ids {
149149
println!("Deleting left-over database: {}", &id);
150-
cosmos_client.database_client(&id).delete(None).await?;
150+
cosmos_client
151+
.database_client(&id)
152+
.delete(None)
153+
.await?
154+
.success()
155+
.await?;
151156
}
152157
Ok(())
153158
}

sdk/cosmos/azure_data_cosmos/tests/framework/test_data/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ pub async fn create_database(
6666
) -> azure_core::Result<DatabaseClient> {
6767
// The TestAccount has a unique context_id that includes the test name.
6868
let db_name = account.unique_db("TestData");
69-
let response = match cosmos_client.create_database(&db_name, None).await {
69+
let response = match cosmos_client
70+
.create_database(&db_name, None)
71+
.await?
72+
.success()
73+
.await
74+
{
7075
// The database creation was successful.
7176
Ok(props) => props,
7277
Err(e) if e.http_status() == Some(StatusCode::Conflict) => {

sdk/typespec/typespec_client_core/src/http/response.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33

44
//! HTTP responses.
55
6-
use crate::http::{headers::Headers, DeserializeWith, Format, JsonFormat, StatusCode};
6+
use crate::{
7+
error::HttpError,
8+
http::{headers::Headers, DeserializeWith, Format, JsonFormat, StatusCode},
9+
};
710
use bytes::Bytes;
811
use futures::{Stream, StreamExt};
912
use serde::de::DeserializeOwned;
1013
use std::{fmt, marker::PhantomData, pin::Pin};
11-
use typespec::error::{ErrorKind, ResultExt};
14+
use typespec::{
15+
error::{ErrorKind, ResultExt},
16+
Error,
17+
};
1218

1319
#[cfg(not(target_arch = "wasm32"))]
1420
pub type PinnedStream = Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send>>;
@@ -77,6 +83,24 @@ pub struct Response<T, F = JsonFormat> {
7783
}
7884

7985
impl<T, F> Response<T, F> {
86+
pub async fn success(self) -> crate::Result<Self> {
87+
let status = self.status();
88+
if status.is_success() {
89+
Ok(self)
90+
} else {
91+
let http_error = HttpError::new(self.raw).await;
92+
let error_kind = ErrorKind::http_response(
93+
status,
94+
http_error.error_code().map(std::borrow::ToOwned::to_owned),
95+
);
96+
Err(Error::full(
97+
error_kind,
98+
http_error,
99+
format!("server returned an error response: {status}"),
100+
))
101+
}
102+
}
103+
80104
/// Get the status code from the response.
81105
pub fn status(&self) -> StatusCode {
82106
self.raw.status()

0 commit comments

Comments
 (0)