Skip to main content

sparkscan_client/
client.rs

1// Copyright 2025 Oxide Computer Company
2
3#![allow(dead_code)]
4
5//! Support code for generated clients.
6
7use std::ops::{Deref, DerefMut};
8
9use bytes::Bytes;
10use futures_core::Stream;
11use reqwest::RequestBuilder;
12use serde::{de::DeserializeOwned, ser::SerializeStruct, Serialize};
13
14#[cfg(not(target_arch = "wasm32"))]
15type InnerByteStream = std::pin::Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>> + Send + Sync>>;
16
17#[cfg(target_arch = "wasm32")]
18type InnerByteStream = std::pin::Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>>>>;
19
20/// Untyped byte stream used for both success and error responses.
21pub struct ByteStream(InnerByteStream);
22
23impl ByteStream {
24    /// Creates a new ByteStream
25    ///
26    /// Useful for generating test fixtures.
27    pub fn new(inner: InnerByteStream) -> Self {
28        Self(inner)
29    }
30
31    /// Consumes the [`ByteStream`] and return its inner [`Stream`].
32    pub fn into_inner(self) -> InnerByteStream {
33        self.0
34    }
35}
36
37impl Deref for ByteStream {
38    type Target = InnerByteStream;
39
40    fn deref(&self) -> &Self::Target {
41        &self.0
42    }
43}
44
45impl DerefMut for ByteStream {
46    fn deref_mut(&mut self) -> &mut Self::Target {
47        &mut self.0
48    }
49}
50
51/// Interface for which an implementation is generated for all clients.
52pub trait ClientInfo<Inner> {
53    /// Get the version of this API.
54    ///
55    /// This string is pulled directly from the source OpenAPI document and may
56    /// be in any format the API selects.
57    fn api_version() -> &'static str;
58
59    /// Get the base URL to which requests are made.
60    fn baseurl(&self) -> &str;
61
62    cfg_if::cfg_if! {
63        if #[cfg(feature = "middleware")] {
64            /// Get the internal `reqwest_middleware::ClientWithMiddleware` used to make requests.
65            fn client(&self) -> &reqwest_middleware::ClientWithMiddleware;
66        } else {
67            /// Get the internal `reqwest::Client` used to make requests.
68            fn client(&self) -> &reqwest::Client;
69        }
70    }
71
72    /// Get the inner value of type `T` if one is specified.
73    fn inner(&self) -> &Inner;
74}
75
76impl<T, Inner> ClientInfo<Inner> for &T
77where
78    T: ClientInfo<Inner>,
79{
80    fn api_version() -> &'static str {
81        T::api_version()
82    }
83
84    fn baseurl(&self) -> &str {
85        (*self).baseurl()
86    }
87
88    cfg_if::cfg_if! {
89        if #[cfg(feature = "middleware")] {
90            fn client(&self) -> &reqwest_middleware::ClientWithMiddleware {
91                (*self).client()
92            }
93        } else {
94            fn client(&self) -> &reqwest::Client {
95                (*self).client()
96            }
97        }
98    }
99
100    fn inner(&self) -> &Inner {
101        (*self).inner()
102    }
103}
104
105/// Information about an operation, consumed by hook implementations.
106pub struct OperationInfo {
107    /// The corresponding operationId from the source OpenAPI document.
108    pub operation_id: &'static str,
109}
110
111/// Interface for changing the behavior of generated clients. All clients
112/// implement this for `&Client`; to override the default behavior, implement
113/// some or all of the interfaces for the `Client` type (without the
114/// reference). This mechanism relies on so-called "auto-ref specialization".
115#[allow(async_fn_in_trait, unused)]
116pub trait ClientHooks<Inner = ()>
117where
118    Self: ClientInfo<Inner>,
119{
120    /// Runs prior to the execution of the request. This may be used to modify
121    /// the request before it is transmitted.
122    async fn pre<E>(
123        &self,
124        request: &mut reqwest::Request,
125        info: &OperationInfo,
126    ) -> std::result::Result<(), Error<E>> {
127        Ok(())
128    }
129
130    /// Runs after completion of the request.
131    async fn post<E>(
132        &self,
133        result: &reqwest::Result<reqwest::Response>,
134        info: &OperationInfo,
135    ) -> std::result::Result<(), Error<E>> {
136        Ok(())
137    }
138
139    /// Execute the request.
140    async fn exec(
141        &self,
142        request: reqwest::Request,
143        info: &OperationInfo,
144    ) -> reqwest::Result<reqwest::Response> {
145        cfg_if::cfg_if! {
146            if #[cfg(feature = "middleware")] {
147                match self.client().execute(request).await {
148                    Ok(response) => Ok(response),
149                    Err(reqwest_middleware::Error::Reqwest(req_err)) => Err(req_err),
150                    // TODO: Simplifiy
151                    Err(reqwest_middleware::Error::Middleware(_err)) => {
152                        // Convert middleware error to a reqwest error by attempting a request that will fail
153                        let client = reqwest::Client::new();
154                        match client.get("http://127.0.0.1:0").send().await {
155                            Err(e) => Err(e),
156                            Ok(_) => unreachable!(),
157                        }
158                    }
159                }
160            } else {
161                self.client().execute(request).await
162            }
163        }
164    }
165}
166
167/// Typed value returned by generated client methods.
168///
169/// This is used for successful responses and may appear in error responses
170/// generated from the server (see [`Error::ErrorResponse`])
171pub struct ResponseValue<T> {
172    inner: T,
173    status: reqwest::StatusCode,
174    headers: reqwest::header::HeaderMap,
175    // TODO cookies?
176}
177
178impl<T: DeserializeOwned> ResponseValue<T> {
179    #[doc(hidden)]
180    pub async fn from_response<E>(response: reqwest::Response) -> Result<Self, Error<E>> {
181        let status = response.status();
182        let headers = response.headers().clone();
183        let full = response.bytes().await.map_err(Error::ResponseBodyError)?;
184        let inner =
185            serde_json::from_slice(&full).map_err(|e| Error::InvalidResponsePayload(full, e))?;
186
187        Ok(Self {
188            inner,
189            status,
190            headers,
191        })
192    }
193}
194
195#[cfg(not(target_arch = "wasm32"))]
196impl ResponseValue<reqwest::Upgraded> {
197    #[doc(hidden)]
198    pub async fn upgrade<E: std::fmt::Debug>(
199        response: reqwest::Response,
200    ) -> Result<Self, Error<E>> {
201        let status = response.status();
202        let headers = response.headers().clone();
203        if status == reqwest::StatusCode::SWITCHING_PROTOCOLS {
204            let inner = response.upgrade().await.map_err(Error::InvalidUpgrade)?;
205
206            Ok(Self {
207                inner,
208                status,
209                headers,
210            })
211        } else {
212            Err(Error::UnexpectedResponse(response))
213        }
214    }
215}
216
217impl ResponseValue<ByteStream> {
218    #[doc(hidden)]
219    pub fn stream(response: reqwest::Response) -> Self {
220        let status = response.status();
221        let headers = response.headers().clone();
222        Self {
223            inner: ByteStream(Box::pin(response.bytes_stream())),
224            status,
225            headers,
226        }
227    }
228}
229
230impl ResponseValue<()> {
231    #[doc(hidden)]
232    pub fn empty(response: reqwest::Response) -> Self {
233        let status = response.status();
234        let headers = response.headers().clone();
235        // TODO is there anything we want to do to confirm that there is no
236        // content?
237        Self {
238            inner: (),
239            status,
240            headers,
241        }
242    }
243}
244
245impl<T> ResponseValue<T> {
246    /// Creates a [`ResponseValue`] from the inner type, status, and headers.
247    ///
248    /// Useful for generating test fixtures.
249    pub fn new(inner: T, status: reqwest::StatusCode, headers: reqwest::header::HeaderMap) -> Self {
250        Self {
251            inner,
252            status,
253            headers,
254        }
255    }
256
257    /// Consumes the ResponseValue, returning the wrapped value.
258    pub fn into_inner(self) -> T {
259        self.inner
260    }
261
262    /// Gets the status from this response.
263    pub fn status(&self) -> reqwest::StatusCode {
264        self.status
265    }
266
267    /// Gets the headers from this response.
268    pub fn headers(&self) -> &reqwest::header::HeaderMap {
269        &self.headers
270    }
271
272    /// Gets the parsed value of the Content-Length header, if present and
273    /// valid.
274    pub fn content_length(&self) -> Option<u64> {
275        self.headers
276            .get(reqwest::header::CONTENT_LENGTH)?
277            .to_str()
278            .ok()?
279            .parse::<u64>()
280            .ok()
281    }
282
283    #[doc(hidden)]
284    pub fn map<U: std::fmt::Debug, F, E>(self, f: F) -> Result<ResponseValue<U>, E>
285    where
286        F: FnOnce(T) -> U,
287    {
288        let Self {
289            inner,
290            status,
291            headers,
292        } = self;
293
294        Ok(ResponseValue {
295            inner: f(inner),
296            status,
297            headers,
298        })
299    }
300}
301
302impl ResponseValue<ByteStream> {
303    /// Consumes the `ResponseValue`, returning the wrapped [`Stream`].
304    pub fn into_inner_stream(self) -> InnerByteStream {
305        self.into_inner().into_inner()
306    }
307}
308
309impl<T> Deref for ResponseValue<T> {
310    type Target = T;
311
312    fn deref(&self) -> &Self::Target {
313        &self.inner
314    }
315}
316
317impl<T> DerefMut for ResponseValue<T> {
318    fn deref_mut(&mut self) -> &mut Self::Target {
319        &mut self.inner
320    }
321}
322
323impl<T> AsRef<T> for ResponseValue<T> {
324    fn as_ref(&self) -> &T {
325        &self.inner
326    }
327}
328
329impl<T: std::fmt::Debug> std::fmt::Debug for ResponseValue<T> {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        self.inner.fmt(f)
332    }
333}
334
335/// Error produced by generated client methods.
336///
337/// The type parameter may be a struct if there's a single expected error type
338/// or an enum if there are multiple valid error types. It can be the unit type
339/// if there are no structured returns expected.
340pub enum Error<E = ()> {
341    /// The request did not conform to API requirements.
342    InvalidRequest(String),
343
344    /// A server error either due to the data, or with the connection.
345    CommunicationError(reqwest::Error),
346
347    /// An expected response when upgrading connection.
348    InvalidUpgrade(reqwest::Error),
349
350    /// A documented, expected error response.
351    ErrorResponse(ResponseValue<E>),
352
353    /// Encountered an error reading the body for an expected response.
354    ResponseBodyError(reqwest::Error),
355
356    /// An expected response code whose deserialization failed.
357    InvalidResponsePayload(Bytes, serde_json::Error),
358
359    /// A response not listed in the API description. This may represent a
360    /// success or failure response; check `status().is_success()`.
361    UnexpectedResponse(reqwest::Response),
362
363    /// A custom error from a consumer-defined hook.
364    Custom(String),
365}
366
367impl<E> Error<E> {
368    /// Returns the status code, if the error was generated from a response.
369    pub fn status(&self) -> Option<reqwest::StatusCode> {
370        match self {
371            Error::InvalidRequest(_) => None,
372            Error::Custom(_) => None,
373            Error::CommunicationError(e) => e.status(),
374            Error::ErrorResponse(rv) => Some(rv.status()),
375            Error::InvalidUpgrade(e) => e.status(),
376            Error::ResponseBodyError(e) => e.status(),
377            Error::InvalidResponsePayload(_, _) => None,
378            Error::UnexpectedResponse(r) => Some(r.status()),
379        }
380    }
381
382    /// Converts this error into one without a typed body.
383    ///
384    /// This is useful for unified error handling with APIs that distinguish
385    /// various error response bodies.
386    pub fn into_untyped(self) -> Error {
387        match self {
388            Error::InvalidRequest(s) => Error::InvalidRequest(s),
389            Error::Custom(s) => Error::Custom(s),
390            Error::CommunicationError(e) => Error::CommunicationError(e),
391            Error::ErrorResponse(ResponseValue {
392                inner: _,
393                status,
394                headers,
395            }) => Error::ErrorResponse(ResponseValue {
396                inner: (),
397                status,
398                headers,
399            }),
400            Error::InvalidUpgrade(e) => Error::InvalidUpgrade(e),
401            Error::ResponseBodyError(e) => Error::ResponseBodyError(e),
402            Error::InvalidResponsePayload(b, e) => Error::InvalidResponsePayload(b, e),
403            Error::UnexpectedResponse(r) => Error::UnexpectedResponse(r),
404        }
405    }
406}
407
408impl<E> From<std::convert::Infallible> for Error<E> {
409    fn from(x: std::convert::Infallible) -> Self {
410        match x {}
411    }
412}
413
414impl<E> From<reqwest::Error> for Error<E> {
415    fn from(e: reqwest::Error) -> Self {
416        Self::CommunicationError(e)
417    }
418}
419
420impl<E> From<reqwest::header::InvalidHeaderValue> for Error<E> {
421    fn from(e: reqwest::header::InvalidHeaderValue) -> Self {
422        Self::InvalidRequest(e.to_string())
423    }
424}
425
426impl<E> std::fmt::Display for Error<E>
427where
428    ResponseValue<E>: ErrorFormat,
429{
430    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431        match self {
432            Error::InvalidRequest(s) => {
433                write!(f, "Invalid Request: {}", s)?;
434            }
435            Error::CommunicationError(e) => {
436                write!(f, "Communication Error: {}", e)?;
437            }
438            Error::ErrorResponse(rve) => {
439                write!(f, "Error Response: ")?;
440                rve.fmt_info(f)?;
441            }
442            Error::InvalidUpgrade(e) => {
443                write!(f, "Invalid Response Upgrade: {}", e)?;
444            }
445            Error::ResponseBodyError(e) => {
446                write!(f, "Invalid Response Body Bytes: {}", e)?;
447            }
448            Error::InvalidResponsePayload(b, e) => {
449                write!(f, "Invalid Response Payload ({:?}): {}", b, e)?;
450            }
451            Error::UnexpectedResponse(r) => {
452                write!(f, "Unexpected Response: {:?}", r)?;
453            }
454            Error::Custom(s) => {
455                write!(f, "Error: {}", s)?;
456            }
457        }
458
459        if f.alternate() {
460            use std::error::Error as _;
461
462            let mut src = self.source().and_then(|e| e.source());
463            while let Some(s) = src {
464                write!(f, ": {s}")?;
465                src = s.source();
466            }
467        }
468        Ok(())
469    }
470}
471
472trait ErrorFormat {
473    fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
474}
475
476impl<E> ErrorFormat for ResponseValue<E>
477where
478    E: std::fmt::Debug,
479{
480    fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481        write!(
482            f,
483            "status: {}; headers: {:?}; value: {:?}",
484            self.status, self.headers, self.inner,
485        )
486    }
487}
488
489impl ErrorFormat for ResponseValue<ByteStream> {
490    fn fmt_info(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491        write!(
492            f,
493            "status: {}; headers: {:?}; value: <stream>",
494            self.status, self.headers,
495        )
496    }
497}
498
499impl<E> std::fmt::Debug for Error<E>
500where
501    ResponseValue<E>: ErrorFormat,
502{
503    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
504        std::fmt::Display::fmt(self, f)
505    }
506}
507impl<E> std::error::Error for Error<E>
508where
509    ResponseValue<E>: ErrorFormat,
510{
511    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
512        match self {
513            Error::CommunicationError(e) => Some(e),
514            Error::InvalidUpgrade(e) => Some(e),
515            Error::ResponseBodyError(e) => Some(e),
516            Error::InvalidResponsePayload(_b, e) => Some(e),
517            _ => None,
518        }
519    }
520}
521
522// See https://url.spec.whatwg.org/#url-path-segment-string
523const PATH_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
524    .add(b' ')
525    .add(b'"')
526    .add(b'#')
527    .add(b'<')
528    .add(b'>')
529    .add(b'?')
530    .add(b'`')
531    .add(b'{')
532    .add(b'}')
533    .add(b'/')
534    .add(b'%');
535
536#[doc(hidden)]
537/// Percent encode input string.
538pub fn encode_path(pc: &str) -> String {
539    percent_encoding::utf8_percent_encode(pc, PATH_SET).to_string()
540}
541
542#[doc(hidden)]
543pub trait RequestBuilderExt<E> {
544    fn form_urlencoded<T: Serialize + ?Sized>(self, body: &T) -> Result<RequestBuilder, Error<E>>;
545}
546
547impl<E> RequestBuilderExt<E> for RequestBuilder {
548    fn form_urlencoded<T: Serialize + ?Sized>(self, body: &T) -> Result<Self, Error<E>> {
549        Ok(self
550            .header(
551                reqwest::header::CONTENT_TYPE,
552                reqwest::header::HeaderValue::from_static("application/x-www-form-urlencoded"),
553            )
554            .body(
555                serde_urlencoded::to_string(body)
556                    .map_err(|_| Error::InvalidRequest("failed to serialize body".to_string()))?,
557            ))
558    }
559}
560
561#[doc(hidden)]
562pub struct QueryParam<'a, T> {
563    name: &'a str,
564    value: &'a T,
565}
566
567impl<'a, T> QueryParam<'a, T> {
568    #[doc(hidden)]
569    pub fn new(name: &'a str, value: &'a T) -> Self {
570        Self { name, value }
571    }
572}
573impl<T> Serialize for QueryParam<'_, T>
574where
575    T: Serialize,
576{
577    fn serialize<S>(&self, inner: S) -> Result<S::Ok, S::Error>
578    where
579        S: serde::Serializer,
580    {
581        let serializer = QuerySerializer {
582            inner,
583            name: self.name,
584        };
585        self.value.serialize(serializer)
586    }
587}
588
589pub(crate) struct QuerySerializer<'a, S> {
590    inner: S,
591    name: &'a str,
592}
593
594macro_rules! serialize_scalar {
595    ($f:ident, $t:ty) => {
596        fn $f(self, v: $t) -> Result<Self::Ok, Self::Error> {
597            [(self.name, v)].serialize(self.inner)
598        }
599    };
600}
601
602impl<'a, S> serde::Serializer for QuerySerializer<'a, S>
603where
604    S: serde::Serializer,
605{
606    type Ok = S::Ok;
607    type Error = S::Error;
608    type SerializeSeq = QuerySeq<'a, S::SerializeSeq>;
609    type SerializeTuple = S::SerializeTuple;
610    type SerializeTupleStruct = S::SerializeTupleStruct;
611    type SerializeTupleVariant = S::SerializeTupleVariant;
612    type SerializeMap = S::SerializeMap;
613    type SerializeStruct = S::SerializeStruct;
614    type SerializeStructVariant = S::SerializeStructVariant;
615
616    serialize_scalar!(serialize_bool, bool);
617    serialize_scalar!(serialize_i8, i8);
618    serialize_scalar!(serialize_i16, i16);
619    serialize_scalar!(serialize_i32, i32);
620    serialize_scalar!(serialize_i64, i64);
621    serialize_scalar!(serialize_u8, u8);
622    serialize_scalar!(serialize_u16, u16);
623    serialize_scalar!(serialize_u32, u32);
624    serialize_scalar!(serialize_u64, u64);
625    serialize_scalar!(serialize_f32, f32);
626    serialize_scalar!(serialize_f64, f64);
627    serialize_scalar!(serialize_char, char);
628    serialize_scalar!(serialize_str, &str);
629
630    fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
631        self.inner.serialize_bytes(v)
632    }
633
634    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
635        self.inner.serialize_none()
636    }
637
638    fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
639    where
640        T: ?Sized + Serialize,
641    {
642        // Serialize the value through self which will proxy into the inner
643        // Serializer as appropriate.
644        value.serialize(self)
645    }
646
647    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
648        self.inner.serialize_unit()
649    }
650
651    fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
652        self.inner.serialize_unit_struct(name)
653    }
654
655    fn serialize_unit_variant(
656        self,
657        _name: &'static str,
658        _variant_index: u32,
659        variant: &'static str,
660    ) -> Result<Self::Ok, Self::Error> {
661        // A query parameter with a list of enumerated values will produce an
662        // enum with unit variants. We treat these as scalar values, ignoring
663        // the unit variant wrapper.
664        variant.serialize(self)
665    }
666
667    fn serialize_newtype_struct<T>(
668        self,
669        name: &'static str,
670        value: &T,
671    ) -> Result<Self::Ok, Self::Error>
672    where
673        T: ?Sized + Serialize,
674    {
675        self.inner.serialize_newtype_struct(name, value)
676    }
677
678    fn serialize_newtype_variant<T>(
679        self,
680        name: &'static str,
681        _variant_index: u32,
682        variant: &'static str,
683        value: &T,
684    ) -> Result<Self::Ok, Self::Error>
685    where
686        T: ?Sized + Serialize,
687    {
688        // As with serde_json, we treat a newtype variant like a struct with a
689        // single field. This may seem a little weird, but if an OpenAPI
690        // document were to specify a query parameter whose schema was a oneOf
691        // whose elements were objects with a single field, the user would end
692        // up with an enum like this as a parameter.
693        let mut map = self.inner.serialize_struct(name, 1)?;
694        map.serialize_field(variant, value)?;
695        map.end()
696    }
697
698    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
699        let Self { inner, name, .. } = self;
700        Ok(QuerySeq {
701            inner: inner.serialize_seq(len)?,
702            name,
703        })
704    }
705
706    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
707        self.inner.serialize_tuple(len)
708    }
709
710    fn serialize_tuple_struct(
711        self,
712        name: &'static str,
713        len: usize,
714    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
715        self.inner.serialize_tuple_struct(name, len)
716    }
717
718    fn serialize_tuple_variant(
719        self,
720        name: &'static str,
721        variant_index: u32,
722        variant: &'static str,
723        len: usize,
724    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
725        self.inner
726            .serialize_tuple_variant(name, variant_index, variant, len)
727    }
728
729    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
730        self.inner.serialize_map(len)
731    }
732
733    fn serialize_struct(
734        self,
735        name: &'static str,
736        len: usize,
737    ) -> Result<Self::SerializeStruct, Self::Error> {
738        self.inner.serialize_struct(name, len)
739    }
740
741    fn serialize_struct_variant(
742        self,
743        name: &'static str,
744        variant_index: u32,
745        variant: &'static str,
746        len: usize,
747    ) -> Result<Self::SerializeStructVariant, Self::Error> {
748        self.inner
749            .serialize_struct_variant(name, variant_index, variant, len)
750    }
751}
752
753#[doc(hidden)]
754pub struct QuerySeq<'a, S> {
755    inner: S,
756    name: &'a str,
757}
758
759impl<S> serde::ser::SerializeSeq for QuerySeq<'_, S>
760where
761    S: serde::ser::SerializeSeq,
762{
763    type Ok = S::Ok;
764
765    type Error = S::Error;
766
767    fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
768    where
769        T: ?Sized + Serialize,
770    {
771        let v = (self.name, value);
772        self.inner.serialize_element(&v)
773    }
774
775    fn end(self) -> Result<Self::Ok, Self::Error> {
776        self.inner.end()
777    }
778}