Skip to main content

omaha_client/
http_request.rs

1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use {futures::future::BoxFuture, futures::prelude::*};
10
11pub use http::{Request, Response};
12pub type Body = http_body_util::Full<bytes::Bytes>;
13
14pub fn empty_body() -> Body {
15    Body::default()
16}
17
18pub fn body_from<T: Into<bytes::Bytes>>(data: T) -> Body {
19    Body::new(data.into())
20}
21
22pub trait IntoBody {
23    type Body: http_body::Body;
24    fn into_body(self) -> Self::Body;
25}
26
27impl<B: http_body::Body> IntoBody for http::Request<B> {
28    type Body = B;
29    fn into_body(self) -> B {
30        self.into_body()
31    }
32}
33
34impl<B: http_body::Body> IntoBody for http::Response<B> {
35    type Body = B;
36    fn into_body(self) -> B {
37        self.into_body()
38    }
39}
40
41impl<D: bytes::Buf> IntoBody for http_body_util::Full<D> {
42    type Body = Self;
43    fn into_body(self) -> Self {
44        self
45    }
46}
47
48impl<D: bytes::Buf> IntoBody for http_body_util::Empty<D> {
49    type Body = Self;
50    fn into_body(self) -> Self {
51        self
52    }
53}
54
55impl IntoBody for hyper::body::Incoming {
56    type Body = Self;
57    fn into_body(self) -> Self {
58        self
59    }
60}
61
62pub async fn to_bytes<I>(item: I) -> Result<bytes::Bytes, <I::Body as http_body::Body>::Error>
63where
64    I: IntoBody,
65{
66    use http_body_util::BodyExt as _;
67    item.into_body().collect().await.map(|buf| buf.to_bytes())
68}
69
70pub mod mock;
71
72/// A trait for providing HTTP capabilities to the StateMachine.
73///
74/// This trait is a wrapper around Hyper, to provide a simple request->response style of API for
75/// the state machine to use.
76///
77/// In particular, it's meant to be easy to mock for tests.
78pub trait HttpRequest {
79    /// Make a request, and return an Response, as the header Parts and collect the entire collected
80    /// Body as a Vec of bytes.
81    fn request(&mut self, req: Request<Body>) -> BoxFuture<'_, Result<Response<Vec<u8>>, Error>>;
82}
83
84#[derive(Debug, thiserror::Error)]
85// Parentheses are needed for .source, but will trigger unused_parens, so a tuple is used.
86#[error("Http request failed: {}", match (.source, ()).0 {
87    Some(source) => format!("{source}"),
88    None => format!("kind: {:?}", .kind),
89})]
90pub struct Error {
91    kind: ErrorKind,
92    #[source]
93    source: Option<Box<dyn std::error::Error + Send + Sync>>,
94}
95
96#[derive(Debug, Eq, PartialEq)]
97enum ErrorKind {
98    User,
99    Transport,
100    Timeout,
101}
102
103impl Error {
104    /// Create a timeout error
105    ///
106    /// This is valid for use in tests as well as production implementations of the trait, if
107    /// application-layer timeouts are being implemented.
108    pub fn new_timeout() -> Self {
109        Self { kind: ErrorKind::Timeout, source: None }
110    }
111
112    /// Returns true if this error the result of the Hyper API being incorrectly used (a "user"
113    /// error in Hyper)
114    pub fn is_user(&self) -> bool {
115        self.kind == ErrorKind::User
116    }
117
118    /// Returns true if this error is the result of a timeout when trying to fulfill the request.
119    ///
120    /// Note: Connect timeouts may be returned as transport or I/O errors, not timeouts, depending
121    /// on where in the network / HTTP client stack the timeout occurs.
122    pub fn is_timeout(&self) -> bool {
123        self.kind == ErrorKind::Timeout
124    }
125
126    /// Create a transport error wrapping an underlying source error.
127    pub fn new_transport(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
128        Self { kind: ErrorKind::Transport, source: Some(error.into()) }
129    }
130}
131
132impl From<hyper::Error> for Error {
133    fn from(error: hyper::Error) -> Self {
134        let kind = if error.is_user() { ErrorKind::User } else { ErrorKind::Transport };
135        Error { kind, source: Some(Box::new(error)) }
136    }
137}
138
139pub mod mock_errors {
140    use super::*;
141
142    pub fn make_user_error() -> Error {
143        Error { kind: ErrorKind::User, source: None }
144    }
145
146    pub fn make_transport_error() -> Error {
147        Error { kind: ErrorKind::Transport, source: None }
148    }
149}
150
151/// A stub HttpRequest that does nothing and returns an empty response immediately.
152pub struct StubHttpRequest;
153
154impl HttpRequest for StubHttpRequest {
155    fn request(&mut self, _req: Request<Body>) -> BoxFuture<'_, Result<Response<Vec<u8>>, Error>> {
156        future::ok(Response::default()).boxed()
157    }
158}