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}