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::*;
|
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)]
|
#[doc(hidden)]
|
||||||
pub fn code() -> &'static str {
|
pub fn code() -> &'static str {
|
||||||
include_str!("progenitor_client.rs")
|
include_str!("progenitor_client.rs")
|
||||||
|
|
|
@ -15,8 +15,23 @@ use reqwest::RequestBuilder;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
/// Represents an untyped byte stream for both success and error responses.
|
/// Represents an untyped byte stream for both success and error responses.
|
||||||
pub type ByteStream =
|
pub struct ByteStream(
|
||||||
Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>> + Send>>;
|
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.
|
/// Success value returned by generated client methods.
|
||||||
pub struct ResponseValue<T> {
|
pub struct ResponseValue<T> {
|
||||||
|
@ -52,7 +67,7 @@ impl ResponseValue<ByteStream> {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let headers = response.headers().clone();
|
let headers = response.headers().clone();
|
||||||
Self {
|
Self {
|
||||||
inner: Box::pin(response.bytes_stream()),
|
inner: ByteStream(Box::pin(response.bytes_stream())),
|
||||||
status,
|
status,
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
|
@ -75,6 +90,19 @@ impl ResponseValue<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ResponseValue<T> {
|
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.
|
/// Consumes the ResponseValue, returning the wrapped value.
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.inner
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::InvalidRequest(s) => {
|
Error::InvalidRequest(s) => {
|
||||||
|
@ -206,8 +237,9 @@ impl<E> std::fmt::Display for Error<E> {
|
||||||
Error::CommunicationError(e) => {
|
Error::CommunicationError(e) => {
|
||||||
write!(f, "Communication Error: {}", e)
|
write!(f, "Communication Error: {}", e)
|
||||||
}
|
}
|
||||||
Error::ErrorResponse(_) => {
|
Error::ErrorResponse(rve) => {
|
||||||
write!(f, "Error Response")
|
write!(f, "Error Response: ")?;
|
||||||
|
rve.fmt_info(f)
|
||||||
}
|
}
|
||||||
Error::InvalidResponsePayload(e) => {
|
Error::InvalidResponsePayload(e) => {
|
||||||
write!(f, "Invalid Response Payload: {}", 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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(self, f)
|
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)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
Error::CommunicationError(e) => Some(e),
|
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