// Copyright 2022 Oxide Computer Company #![allow(dead_code)] //! Support code for generated clients. use std::ops::{Deref, DerefMut}; use bytes::Bytes; use futures_core::Stream; use reqwest::RequestBuilder; use serde::{de::DeserializeOwned, Serialize}; type InnerByteStream = std::pin::Pin> + Send + Sync>>; /// Untyped byte stream used for both success and error responses. pub struct ByteStream(InnerByteStream); impl ByteStream { /// Creates a new ByteStream /// /// Useful for generating test fixtures. pub fn new(inner: InnerByteStream) -> Self { Self(inner) } /// Consumes the [`ByteStream`] and return its inner [`Stream`]. pub fn into_inner(self) -> InnerByteStream { self.0 } } impl Deref for ByteStream { type Target = InnerByteStream; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for ByteStream { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } /// Typed value returned by generated client methods. /// /// This is used for successful responses and may appear in error responses /// generated from the server (see [`Error::ErrorResponse`]) 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 async fn upgrade( response: reqwest::Response, ) -> Result> { let status = response.status(); let headers = response.headers().clone(); if status == reqwest::StatusCode::SWITCHING_PROTOCOLS { let inner = response .upgrade() .await .map_err(Error::InvalidResponsePayload)?; Ok(Self { inner, status, headers, }) } else { Err(Error::UnexpectedResponse(response)) } } } impl ResponseValue { #[doc(hidden)] pub fn stream(response: reqwest::Response) -> Self { let status = response.status(); let headers = response.headers().clone(); Self { inner: ByteStream(Box::pin(response.bytes_stream())), 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 { /// Creates a [`ResponseValue`] from the inner type, status, and headers. /// /// Useful for generating test fixtures. 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 } /// Gets the status from this response. pub fn status(&self) -> reqwest::StatusCode { self.status } /// Gets the headers from this response. pub fn headers(&self) -> &reqwest::header::HeaderMap { &self.headers } /// Gets the parsed value of the Content-Length header, if present and /// valid. pub fn content_length(&self) -> Option { self.headers .get(reqwest::header::CONTENT_LENGTH)? .to_str() .ok()? .parse::() .ok() } #[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 ResponseValue { /// Consumes the `ResponseValue`, returning the wrapped [`Stream`]. pub fn into_inner_stream(self) -> InnerByteStream { self.into_inner().into_inner() } } 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. pub enum Error { /// The request did not conform to API requirements. InvalidRequest(String), /// A server error either due to 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::InvalidRequest(_) => None, Error::CommunicationError(e) => e.status(), Error::ErrorResponse(rv) => Some(rv.status()), Error::InvalidResponsePayload(e) => e.status(), Error::UnexpectedResponse(r) => Some(r.status()), } } /// Converts this error into one without a typed body. /// /// This is useful for unified error handling with APIs that distinguish /// various error response bodies. pub fn into_untyped(self) -> Error { match self { Error::InvalidRequest(s) => Error::InvalidRequest(s), 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 where ResponseValue: ErrorFormat, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::InvalidRequest(s) => { write!(f, "Invalid Request: {}", s) } Error::CommunicationError(e) => { write!(f, "Communication Error: {}", e) } Error::ErrorResponse(rve) => { write!(f, "Error Response: ")?; rve.fmt_info(f) } Error::InvalidResponsePayload(e) => { write!(f, "Invalid Response Payload: {}", e) } Error::UnexpectedResponse(r) => { write!(f, "Unexpected Response: {:?}", r) } } } } trait ErrorFormat { fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; } impl ErrorFormat for ResponseValue 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 { fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "status: {}; headers: {:?}; value: ", self.status, self.headers, ) } } impl std::fmt::Debug for Error where ResponseValue: ErrorFormat, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } } impl std::error::Error for Error where ResponseValue: ErrorFormat, { 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() } #[doc(hidden)] pub trait RequestBuilderExt { fn form_urlencoded( self, body: &T, ) -> Result>; } impl RequestBuilderExt for RequestBuilder { fn form_urlencoded( self, body: &T, ) -> Result> { Ok(self .header( reqwest::header::CONTENT_TYPE, reqwest::header::HeaderValue::from_static( "application/x-www-form-urlencoded", ), ) .body(serde_urlencoded::to_string(body).map_err(|_| { Error::InvalidRequest("failed to serialize body".to_string()) })?)) } }