finchers_core/input/
input.rs

1use error::HttpError;
2use failure::Fail;
3use http::{self, header, Request, StatusCode};
4use mime::{self, Mime};
5use std::cell::UnsafeCell;
6
7scoped_thread_local!(static CURRENT_INPUT: Input);
8
9/// The context which holds the received HTTP request.
10///
11/// The value is used throughout the processing in `Endpoint` and `Task`.
12#[derive(Debug)]
13pub struct Input {
14    request: Request<()>,
15    media_type: UnsafeCell<Option<Mime>>,
16}
17
18impl Input {
19    /// Create an instance of `Input` from components.
20    ///
21    /// Some fields remain uninitialized and their values are set when the corresponding
22    /// method will be called.
23    pub fn new(request: Request<()>) -> Input {
24        Input {
25            request,
26            media_type: UnsafeCell::new(None),
27        }
28    }
29
30    /// Set the reference to itself to the thread-local storage and execute given closure.
31    ///
32    /// Typically, this method is used in the implementation of `Task` which holds some closures.
33    pub fn enter_scope<F, R>(&self, f: F) -> R
34    where
35        F: FnOnce() -> R,
36    {
37        CURRENT_INPUT.set(self, f)
38    }
39
40    /// Execute a closure with the reference to the instance of `Input` from the thread-local storage.
41    ///
42    /// This method is only used in a closure passed to `enter_scope`.
43    /// Otherwise, it will be panic.
44    pub fn with<F, R>(f: F) -> R
45    where
46        F: FnOnce(&Input) -> R,
47    {
48        CURRENT_INPUT.with(|input| f(input))
49    }
50
51    /// Return a shared reference to the value of raw HTTP request without the message body.
52    pub fn request(&self) -> &Request<()> {
53        &self.request
54    }
55
56    /// Return the reference to the parsed media type in the request header.
57    ///
58    /// This method will perform parsing of the entry `Content-type` in the request header
59    /// if it has not been done yet.  If the value is invalid, it will return an `Err`.
60    pub fn media_type(&self) -> Result<Option<&Mime>, InvalidMediaType> {
61        // safety: this mutable borrow is used only in the block.
62        let media_type: &mut Option<Mime> = unsafe { &mut *self.media_type.get() };
63
64        if media_type.is_none() {
65            if let Some(raw) = self.request().headers().get(header::CONTENT_TYPE) {
66                let raw_str = raw.to_str().map_err(|cause| InvalidMediaType::DecodeToStr { cause })?;
67                let mime = raw_str
68                    .parse()
69                    .map_err(|cause| InvalidMediaType::ParseToMime { cause })?;
70                *media_type = Some(mime);
71            }
72        }
73
74        Ok((&*media_type).as_ref())
75    }
76}
77
78/// An error type which will be returned from `Input::media_type`.
79#[derive(Debug, Fail)]
80pub enum InvalidMediaType {
81    #[allow(missing_docs)]
82    #[fail(display = "Content-type is invalid: {}", cause)]
83    DecodeToStr { cause: http::header::ToStrError },
84
85    #[allow(missing_docs)]
86    #[fail(display = "Content-type is invalid: {}", cause)]
87    ParseToMime { cause: mime::FromStrError },
88}
89
90impl HttpError for InvalidMediaType {
91    fn status_code(&self) -> StatusCode {
92        StatusCode::BAD_REQUEST
93    }
94
95    fn as_fail(&self) -> Option<&Fail> {
96        Some(self)
97    }
98}