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 {
10 futures::future::BoxFuture,
11 futures::prelude::*,
12 hyper::{Body, Request, Response},
13};
14
15pub mod mock;
16
17/// A trait for providing HTTP capabilities to the StateMachine.
18///
19/// This trait is a wrapper around Hyper, to provide a simple request->response style of API for
20/// the state machine to use.
21///
22/// In particular, it's meant to be easy to mock for tests.
23pub trait HttpRequest {
24 /// Make a request, and return an Response, as the header Parts and collect the entire collected
25 /// Body as a Vec of bytes.
26 fn request(&mut self, req: Request<Body>) -> BoxFuture<'_, Result<Response<Vec<u8>>, Error>>;
27}
28
29#[derive(Debug, thiserror::Error)]
30// Parentheses are needed for .source, but will trigger unused_parens, so a tuple is used.
31#[error("Http request failed: {}", match (.source, ()).0 {
32 Some(source) => format!("{source}"),
33 None => format!("kind: {:?}", .kind),
34})]
35pub struct Error {
36 kind: ErrorKind,
37 #[source]
38 source: Option<hyper::Error>,
39}
40
41#[derive(Debug, Eq, PartialEq)]
42enum ErrorKind {
43 User,
44 Transport,
45 Timeout,
46}
47
48impl Error {
49 /// Create a timeout error
50 ///
51 /// This is valid for use in tests as well as production implementations of the trait, if
52 /// application-layer timeouts are being implemented.
53 pub fn new_timeout() -> Self {
54 Self {
55 kind: ErrorKind::Timeout,
56 source: None,
57 }
58 }
59
60 /// Returns true if this error the result of the Hyper API being incorrectly used (a "user"
61 /// error in Hyper)
62 pub fn is_user(&self) -> bool {
63 self.kind == ErrorKind::User
64 }
65
66 /// Returns true if this error is the result of a timeout when trying to full-fill the request
67 ///
68 /// Note: Connect timeouts may be returned as io errors, not timeouts, depending on where in
69 /// the network / http client stack the timeout occurs in.
70 pub fn is_timeout(&self) -> bool {
71 self.kind == ErrorKind::Timeout
72 }
73}
74
75impl From<hyper::Error> for Error {
76 fn from(error: hyper::Error) -> Self {
77 let kind = if error.is_user() {
78 ErrorKind::User
79 } else {
80 ErrorKind::Transport
81 };
82 Error {
83 kind,
84 source: error.into(),
85 }
86 }
87}
88
89pub mod mock_errors {
90 use super::*;
91
92 pub fn make_user_error() -> Error {
93 Error {
94 kind: ErrorKind::User,
95 source: None,
96 }
97 }
98
99 pub fn make_transport_error() -> Error {
100 Error {
101 kind: ErrorKind::Transport,
102 source: None,
103 }
104 }
105}
106
107/// A stub HttpRequest that does nothing and returns an empty response immediately.
108pub struct StubHttpRequest;
109
110impl HttpRequest for StubHttpRequest {
111 fn request(&mut self, _req: Request<Body>) -> BoxFuture<'_, Result<Response<Vec<u8>>, Error>> {
112 future::ok(Response::default()).boxed()
113 }
114}