Skip to main content

lino_rest_api/
response.rs

1//! Explicit response values.
2//!
3//! A handler may return a plain value, which is sent as `200`, or one of these
4//! results when it needs to control the status code or the headers.
5
6use lino_objects_codec::LinoValue;
7
8use crate::headers::Headers;
9
10/// The body of a response: a value to encode, or nothing at all.
11///
12/// The distinction matters because `None` is a value Links Notation can encode,
13/// while a `204` carries no body whatsoever.
14#[derive(Debug, Clone, PartialEq)]
15pub enum Body {
16    /// No body, as `204` and `304` require.
17    Empty,
18    /// A value, encoded as the negotiated representation.
19    Value(LinoValue),
20    /// An already-encoded body, sent as it is.
21    Raw(String),
22}
23
24impl From<LinoValue> for Body {
25    fn from(value: LinoValue) -> Self {
26        Body::Value(value)
27    }
28}
29
30/// A handler result carrying a status code and headers alongside the value.
31#[derive(Debug, Clone, PartialEq)]
32pub struct LinoResult {
33    /// The body.
34    pub body: Body,
35    /// HTTP status code.
36    pub status: u16,
37    /// Response headers.
38    pub headers: Headers,
39    /// Whether to compute an entity tag for the representation.
40    pub etag: bool,
41    /// Whether to evaluate conditional request headers; [`None`] follows `etag`.
42    pub preconditions: Option<bool>,
43    /// Whether an `If-Match` is required on unsafe methods.
44    pub require_precondition: bool,
45    /// Media type to send, overriding the negotiated one.
46    pub media_type: Option<String>,
47}
48
49impl LinoResult {
50    /// A result with a status and a body, and nothing else set.
51    pub fn new(status: u16, body: Body) -> Self {
52        Self {
53            body,
54            status,
55            headers: Headers::new(),
56            etag: true,
57            preconditions: None,
58            require_precondition: false,
59            media_type: None,
60        }
61    }
62
63    /// The same result with one more header.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use lino_rest_api::response::ok;
69    /// use lino_rest_api::value::int;
70    ///
71    /// let result = ok(int(1)).with_header("X-Trace", "abc");
72    /// assert_eq!(result.headers.get("x-trace"), Some("abc"));
73    /// ```
74    pub fn with_header<N: Into<String>, V: Into<String>>(mut self, name: N, value: V) -> Self {
75        self.headers.insert(name, value);
76        self
77    }
78
79    /// The same result with several more headers.
80    pub fn with_headers(mut self, headers: Headers) -> Self {
81        for (name, value) in headers.iter() {
82            self.headers.insert(name.to_string(), value.to_string());
83        }
84        self
85    }
86
87    /// The same result without an entity tag, for bodies that are not worth
88    /// caching or that change on every read.
89    pub fn without_etag(mut self) -> Self {
90        self.etag = false;
91        self
92    }
93
94    /// The same result with conditional request evaluation turned on or off.
95    pub fn with_preconditions(mut self, preconditions: bool) -> Self {
96        self.preconditions = Some(preconditions);
97        self
98    }
99
100    /// The same result demanding an `If-Match` on unsafe methods.
101    pub fn requiring_precondition(mut self, require: bool) -> Self {
102        self.require_precondition = require;
103        self
104    }
105
106    /// The same result sent as a concrete media type.
107    pub fn with_media_type<S: Into<String>>(mut self, media_type: S) -> Self {
108        self.media_type = Some(media_type.into());
109        self
110    }
111}
112
113/// A `200 OK` carrying a value.
114pub fn ok(value: LinoValue) -> LinoResult {
115    LinoResult::new(200, Body::Value(value))
116}
117
118/// A `201 Created` carrying a value and a `Location`.
119pub fn created(value: LinoValue, location: &str) -> LinoResult {
120    LinoResult::new(201, Body::Value(value)).with_header("Location", location)
121}
122
123/// A `202 Accepted` carrying a value.
124pub fn accepted(value: LinoValue) -> LinoResult {
125    LinoResult::new(202, Body::Value(value))
126}
127
128/// A `204 No Content`.
129pub fn no_content() -> LinoResult {
130    LinoResult::new(204, Body::Empty)
131}
132
133/// A response with an explicit status code and a value.
134pub fn status(status_code: u16, value: LinoValue) -> LinoResult {
135    LinoResult::new(status_code, Body::Value(value))
136}
137
138/// A response with an explicit status code and no body.
139pub fn status_empty(status_code: u16) -> LinoResult {
140    LinoResult::new(status_code, Body::Empty)
141}
142
143/// A response whose body is already encoded, bypassing negotiation.
144///
145/// Used for representations that are defined by their own media type, such as
146/// the OpenAPI document of specification section 9.
147pub fn raw_response(body: &str, media_type: &str, status_code: u16) -> LinoResult {
148    LinoResult::new(status_code, Body::Raw(body.to_string())).with_media_type(media_type)
149}
150
151impl From<LinoValue> for LinoResult {
152    /// A plain value is a `200 OK` carrying it.
153    fn from(value: LinoValue) -> Self {
154        ok(value)
155    }
156}
157
158impl From<()> for LinoResult {
159    /// A handler that returns nothing sends a `204 No Content`.
160    fn from(_: ()) -> Self {
161        no_content()
162    }
163}