// Copyright 2021 Oxide Computer Company //! Support code for generated clients. use std::ops::{Deref, DerefMut}; use serde::de::DeserializeOwned; /// Success value returned by generated client methods. pub struct ResponseValue { inner: T, status: reqwest::StatusCode, headers: reqwest::header::HeaderMap, // TODO cookies? } impl ResponseValue { #[doc(hidden)] pub async fn from_response( response: reqwest::Response, ) -> Result> { let status = response.status(); let headers = response.headers().clone(); let inner = response .json() .await .map_err(Error::InvalidResponsePayload)?; Ok(Self { inner, status, headers, }) } } impl ResponseValue<()> { #[doc(hidden)] pub fn empty(response: reqwest::Response) -> Self { let status = response.status(); let headers = response.headers().clone(); // TODO is there anything we want to do to confirm that there is no // content? Self { inner: (), status, headers, } } } impl ResponseValue { /// Consumes the ResponseValue, returning the wrapped value. pub fn into_inner(self) -> T { self.inner } /// Get the status from this response. pub fn status(&self) -> reqwest::StatusCode { self.status } /// Get the headers from this response. pub fn headers(&self) -> &reqwest::header::HeaderMap { &self.headers } #[doc(hidden)] pub fn map( self, f: F, ) -> Result, E> where F: FnOnce(T) -> U, { let Self { inner, status, headers, } = self; Ok(ResponseValue { inner: f(inner), status, headers, }) } } impl Deref for ResponseValue { type Target = T; fn deref(&self) -> &Self::Target { &self.inner } } impl DerefMut for ResponseValue { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } impl std::fmt::Debug for ResponseValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.inner.fmt(f) } } /// Error produced by generated client methods. /// /// The type parameter may be a struct if there's a single expected error type /// or an enum if there are multiple valid error types. It can be the unit type /// if there are no structured returns expected. #[derive(Debug)] pub enum Error { /// A server error either with the data, or with the connection. CommunicationError(reqwest::Error), /// A documented, expected error response. ErrorResponse(ResponseValue), /// An expected response code whose deserialization failed. // TODO we have stuff from the response; should we include it? InvalidResponsePayload(reqwest::Error), /// A response not listed in the API description. This may represent a /// success or failure response; check `status().is_success()`. UnexpectedResponse(reqwest::Response), } impl Error { /// Returns the status code, if the error was generated from a response. pub fn status(&self) -> Option { match self { Error::CommunicationError(e) => e.status(), Error::ErrorResponse(rv) => Some(rv.status()), Error::InvalidResponsePayload(e) => e.status(), Error::UnexpectedResponse(r) => Some(r.status()), } } /// Convert this error into one without a typed body for unified error /// handling with APIs that distinguish various error response bodies. pub fn into_untyped(self) -> Error { match self { Error::CommunicationError(e) => Error::CommunicationError(e), Error::ErrorResponse(ResponseValue { inner: _, status, headers, }) => Error::ErrorResponse(ResponseValue { inner: (), status, headers, }), Error::InvalidResponsePayload(e) => { Error::InvalidResponsePayload(e) } Error::UnexpectedResponse(r) => Error::UnexpectedResponse(r), } } } impl From for Error { fn from(e: reqwest::Error) -> Self { Self::CommunicationError(e) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::CommunicationError(e) => { write!(f, "Communication Error {}", e) } Error::ErrorResponse(rv) => { write!(f, "Error Response {:?}", rv) } Error::InvalidResponsePayload(e) => { write!(f, "Invalid Response Payload {}", e) } Error::UnexpectedResponse(r) => { write!(f, "Unexpected Response {:?}", r) } } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::CommunicationError(e) => Some(e), Error::InvalidResponsePayload(e) => Some(e), _ => None, } } } const PATH_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS .add(b' ') .add(b'"') .add(b'#') .add(b'<') .add(b'>') .add(b'?') .add(b'`') .add(b'{') .add(b'}'); #[doc(hidden)] pub fn encode_path(pc: &str) -> String { percent_encoding::utf8_percent_encode(pc, PATH_SET).to_string() }