improve panic/unwrap message for user-typed errors (#110)
This commit is contained in:
parent
03e2cfad3c
commit
236efadeee
|
@ -4,6 +4,10 @@ mod progenitor_client;
|
|||
|
||||
pub use crate::progenitor_client::*;
|
||||
|
||||
// For stand-alone crates, rather than adding a dependency on
|
||||
// progenitor-client, we simply dump the code right in. This means we don't
|
||||
// need to determine the provenance of progenitor (crates.io, github, etc.)
|
||||
// when generating the stand-alone crate.
|
||||
#[doc(hidden)]
|
||||
pub fn code() -> &'static str {
|
||||
include_str!("progenitor_client.rs")
|
||||
|
|
|
@ -15,8 +15,23 @@ use reqwest::RequestBuilder;
|
|||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
/// Represents an untyped byte stream for both success and error responses.
|
||||
pub type ByteStream =
|
||||
Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>> + Send>>;
|
||||
pub struct ByteStream(
|
||||
Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>> + Send>>,
|
||||
);
|
||||
|
||||
impl Deref for ByteStream {
|
||||
type Target = Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>> + Send>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ByteStream {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Success value returned by generated client methods.
|
||||
pub struct ResponseValue<T> {
|
||||
|
@ -52,7 +67,7 @@ impl ResponseValue<ByteStream> {
|
|||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
Self {
|
||||
inner: Box::pin(response.bytes_stream()),
|
||||
inner: ByteStream(Box::pin(response.bytes_stream())),
|
||||
status,
|
||||
headers,
|
||||
}
|
||||
|
@ -75,6 +90,19 @@ impl ResponseValue<()> {
|
|||
}
|
||||
|
||||
impl<T> ResponseValue<T> {
|
||||
/// Create an instance for testing
|
||||
pub fn new(
|
||||
inner: T,
|
||||
status: reqwest::StatusCode,
|
||||
headers: reqwest::header::HeaderMap,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
status,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the ResponseValue, returning the wrapped value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
|
@ -197,7 +225,10 @@ impl<E> From<reqwest::Error> for Error<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E> std::fmt::Display for Error<E> {
|
||||
impl<E> std::fmt::Display for Error<E>
|
||||
where
|
||||
ResponseValue<E>: ErrorFormat,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::InvalidRequest(s) => {
|
||||
|
@ -206,8 +237,9 @@ impl<E> std::fmt::Display for Error<E> {
|
|||
Error::CommunicationError(e) => {
|
||||
write!(f, "Communication Error: {}", e)
|
||||
}
|
||||
Error::ErrorResponse(_) => {
|
||||
write!(f, "Error Response")
|
||||
Error::ErrorResponse(rve) => {
|
||||
write!(f, "Error Response: ")?;
|
||||
rve.fmt_info(f)
|
||||
}
|
||||
Error::InvalidResponsePayload(e) => {
|
||||
write!(f, "Invalid Response Payload: {}", e)
|
||||
|
@ -218,12 +250,46 @@ impl<E> std::fmt::Display for Error<E> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<E> std::fmt::Debug for Error<E> {
|
||||
|
||||
trait ErrorFormat {
|
||||
fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
|
||||
}
|
||||
|
||||
impl<E> ErrorFormat for ResponseValue<E>
|
||||
where
|
||||
E: std::fmt::Debug,
|
||||
{
|
||||
fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"status: {}; headers: {:?}; value: {:?}",
|
||||
self.status, self.headers, self.inner,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorFormat for ResponseValue<ByteStream> {
|
||||
fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"status: {}; headers: {:?}; value: <stream>",
|
||||
self.status, self.headers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> std::fmt::Debug for Error<E>
|
||||
where
|
||||
ResponseValue<E>: ErrorFormat,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
impl<E> std::error::Error for Error<E> {
|
||||
impl<E> std::error::Error for Error<E>
|
||||
where
|
||||
ResponseValue<E>: ErrorFormat,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Error::CommunicationError(e) => Some(e),
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 Oxide Computer Company
|
||||
|
||||
// Validate that we get useful output from a user-typed error.
|
||||
#[test]
|
||||
#[should_panic = "Error Response: \
|
||||
status: 403 Forbidden; \
|
||||
headers: {}; \
|
||||
value: MyErr { msg: \"things went bad\" }"]
|
||||
fn test_error() {
|
||||
#[derive(Debug)]
|
||||
struct MyErr {
|
||||
#[allow(dead_code)]
|
||||
msg: String,
|
||||
}
|
||||
|
||||
let mine = MyErr {
|
||||
msg: "things went bad".to_string(),
|
||||
};
|
||||
let e = progenitor_client::Error::ErrorResponse(
|
||||
progenitor_client::ResponseValue::new(
|
||||
mine,
|
||||
reqwest::StatusCode::FORBIDDEN,
|
||||
reqwest::header::HeaderMap::default(),
|
||||
),
|
||||
);
|
||||
|
||||
(Err(e) as Result<(), progenitor_client::Error<MyErr>>).unwrap();
|
||||
}
|
Loading…
Reference in New Issue