zino_http/response/
mod.rs

1//! Constructing responses and rejections.
2
3use crate::{
4    helper,
5    request::RequestContext,
6    timing::{ServerTiming, TimingMetric},
7};
8use bytes::Bytes;
9use etag::EntityTag;
10use serde::Serialize;
11use smallvec::SmallVec;
12use std::{
13    marker::PhantomData,
14    time::{Duration, Instant},
15};
16use zino_core::{
17    JsonValue, SharedString, Uuid, error::Error, extension::JsonValueExt, trace::TraceContext,
18    validation::Validation,
19};
20use zino_storage::NamedFile;
21
22#[cfg(feature = "cookie")]
23use cookie::Cookie;
24
25mod rejection;
26mod response_code;
27mod webhook;
28
29pub use rejection::{ExtractRejection, Rejection};
30pub use response_code::ResponseCode;
31pub use webhook::WebHook;
32
33/// An HTTP status code for http v0.2.
34#[cfg(feature = "http02")]
35pub type StatusCode = http02::StatusCode;
36
37/// An HTTP status code.
38#[cfg(not(feature = "http02"))]
39pub type StatusCode = http::StatusCode;
40
41/// A function pointer of transforming the response data.
42pub type DataTransformer = fn(data: &JsonValue) -> Result<Bytes, Error>;
43
44/// An HTTP response.
45#[derive(Debug, Clone, Serialize)]
46#[serde(rename_all = "snake_case")]
47pub struct Response<S: ResponseCode> {
48    /// A URI reference that identifies the problem type.
49    #[serde(rename = "type")]
50    #[serde(skip_serializing_if = "Option::is_none")]
51    type_uri: Option<SharedString>,
52    /// A short, human-readable summary of the problem type.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    title: Option<SharedString>,
55    /// Status code.
56    #[serde(rename = "status")]
57    status_code: u16,
58    /// Error code.
59    #[serde(rename = "error")]
60    #[serde(skip_serializing_if = "Option::is_none")]
61    error_code: Option<S::ErrorCode>,
62    /// Business code.
63    #[serde(rename = "code")]
64    #[serde(skip_serializing_if = "Option::is_none")]
65    business_code: Option<S::BusinessCode>,
66    /// A human-readable explanation specific to this occurrence of the problem.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    detail: Option<SharedString>,
69    /// A URI reference that identifies the specific occurrence of the problem.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    instance: Option<SharedString>,
72    /// Indicates the response is successful or not.
73    success: bool,
74    /// A context-specific descriptive message for successful response.
75    #[serde(skip_serializing_if = "Option::is_none")]
76    message: Option<SharedString>,
77    /// Start time.
78    #[serde(skip)]
79    start_time: Instant,
80    /// Request ID.
81    #[serde(skip_serializing_if = "Uuid::is_nil")]
82    request_id: Uuid,
83    /// JSON data.
84    #[serde(rename = "data")]
85    #[serde(skip_serializing_if = "JsonValue::is_null")]
86    json_data: JsonValue,
87    /// Bytes data.
88    #[serde(skip)]
89    bytes_data: Bytes,
90    /// Transformer of the response data.
91    #[serde(skip)]
92    data_transformer: Option<DataTransformer>,
93    /// Content type.
94    #[serde(skip)]
95    content_type: Option<SharedString>,
96    /// Trace context.
97    #[serde(skip)]
98    trace_context: Option<TraceContext>,
99    /// Server timing.
100    #[serde(skip)]
101    server_timing: ServerTiming,
102    /// Custom headers.
103    #[serde(skip)]
104    headers: SmallVec<[(SharedString, String); 8]>,
105    /// Phantom type of response code.
106    #[serde(skip)]
107    phantom: PhantomData<S>,
108}
109
110impl<S: ResponseCode> Response<S> {
111    /// Creates a new instance.
112    pub fn new(code: S) -> Self {
113        let success = code.is_success();
114        let message = code.message();
115        let mut res = Self {
116            type_uri: code.type_uri(),
117            title: code.title(),
118            status_code: code.status_code(),
119            error_code: code.error_code(),
120            business_code: code.business_code(),
121            detail: None,
122            instance: None,
123            success,
124            message: None,
125            start_time: Instant::now(),
126            request_id: Uuid::nil(),
127            json_data: JsonValue::Null,
128            bytes_data: Bytes::new(),
129            data_transformer: None,
130            content_type: None,
131            trace_context: None,
132            server_timing: ServerTiming::new(),
133            headers: SmallVec::new(),
134            phantom: PhantomData,
135        };
136        if success {
137            res.message = message;
138        } else {
139            res.detail = message;
140        }
141        res
142    }
143
144    /// Creates a new instance with the request context.
145    pub fn with_context<Ctx: RequestContext>(code: S, ctx: &Ctx) -> Self {
146        let success = code.is_success();
147        let message = code.message();
148        let mut res = Self {
149            type_uri: code.type_uri(),
150            title: code.title(),
151            status_code: code.status_code(),
152            error_code: code.error_code(),
153            business_code: code.business_code(),
154            detail: None,
155            instance: (!success).then(|| ctx.instance().into()),
156            success,
157            message: None,
158            start_time: ctx.start_time(),
159            request_id: ctx.request_id(),
160            json_data: JsonValue::Null,
161            bytes_data: Bytes::new(),
162            data_transformer: None,
163            content_type: None,
164            trace_context: None,
165            server_timing: ServerTiming::new(),
166            headers: SmallVec::new(),
167            phantom: PhantomData,
168        };
169        if success {
170            res.message = message;
171        } else {
172            res.detail = message;
173        }
174        res.trace_context = Some(ctx.new_trace_context());
175        res
176    }
177
178    /// Provides the request context for the response.
179    pub fn context<Ctx: RequestContext>(mut self, ctx: &Ctx) -> Self {
180        self.instance = (!self.is_success()).then(|| ctx.instance().into());
181        self.start_time = ctx.start_time();
182        self.request_id = ctx.request_id();
183        self.trace_context = Some(ctx.new_trace_context());
184        self
185    }
186
187    /// Renders a template with the data and sets it as the reponse.
188    #[cfg(feature = "view")]
189    pub fn render<T: Serialize>(mut self, template_name: &str, data: T) -> Self {
190        let result = serde_json::to_value(data)
191            .map_err(|err| err.into())
192            .and_then(|mut value| {
193                if let Some(data) = value.as_object_mut() {
194                    let mut map = zino_core::Map::new();
195                    map.append(data);
196                    crate::view::render(template_name, map)
197                } else {
198                    Err(zino_core::warn!("invalid template data"))
199                }
200            });
201        match result {
202            Ok(content) => {
203                self.json_data = content.into();
204                self.bytes_data = Bytes::new();
205                self.content_type = Some("text/html; charset=utf-8".into());
206            }
207            Err(err) => {
208                let code = S::INTERNAL_SERVER_ERROR;
209                self.type_uri = code.type_uri();
210                self.title = code.title();
211                self.status_code = code.status_code();
212                self.error_code = code.error_code();
213                self.business_code = code.business_code();
214                self.success = false;
215                self.detail = Some(err.to_string().into());
216                self.message = None;
217                self.json_data = JsonValue::Null;
218                self.bytes_data = Bytes::new();
219            }
220        }
221        self
222    }
223
224    /// Sets the response code.
225    pub fn set_code(&mut self, code: S) {
226        let success = code.is_success();
227        let message = code.message();
228        self.type_uri = code.type_uri();
229        self.title = code.title();
230        self.status_code = code.status_code();
231        self.error_code = code.error_code();
232        self.business_code = code.business_code();
233        self.success = success;
234        if success {
235            self.detail = None;
236            self.message = message;
237        } else {
238            self.detail = message;
239            self.message = None;
240        }
241    }
242
243    /// Sets the status code.
244    #[inline]
245    pub fn set_status_code(&mut self, status_code: impl Into<u16>) {
246        self.status_code = status_code.into();
247    }
248
249    /// Sets the error code.
250    #[inline]
251    pub fn set_error_code(&mut self, error_code: impl Into<S::ErrorCode>) {
252        self.error_code = Some(error_code.into());
253    }
254
255    /// Sets the bussiness code.
256    #[inline]
257    pub fn set_business_code(&mut self, business_code: impl Into<S::BusinessCode>) {
258        self.business_code = Some(business_code.into());
259    }
260
261    /// Sets a URI reference that identifies the specific occurrence of the problem.
262    #[inline]
263    pub fn set_instance(&mut self, instance: impl Into<SharedString>) {
264        self.instance = Some(instance.into());
265    }
266
267    /// Sets the message. If the response is not successful,
268    /// it should be a human-readable explanation specific to this occurrence of the problem.
269    pub fn set_message(&mut self, message: impl Into<SharedString>) {
270        fn inner<S: ResponseCode>(res: &mut Response<S>, message: SharedString) {
271            if res.is_success() {
272                res.detail = None;
273                res.message = Some(message);
274            } else {
275                res.detail = Some(message);
276                res.message = None;
277            }
278        }
279        inner::<S>(self, message.into())
280    }
281
282    /// Sets the error message.
283    pub fn set_error_message(&mut self, error: impl Into<Error>) {
284        fn inner<S: ResponseCode>(res: &mut Response<S>, error: Error) {
285            let message = error.to_string().into();
286            if res.is_success() {
287                res.detail = None;
288                res.message = Some(message);
289            } else {
290                res.detail = Some(message);
291                res.message = None;
292            }
293        }
294        inner::<S>(self, error.into())
295    }
296
297    /// Sets the response data.
298    #[inline]
299    pub fn set_data<T: Serialize>(&mut self, data: &T) {
300        match serde_json::to_value(data) {
301            Ok(value) => {
302                self.json_data = value;
303                self.bytes_data = Bytes::new();
304            }
305            Err(err) => self.set_error_message(err),
306        }
307    }
308
309    /// Sets the JSON data.
310    #[inline]
311    pub fn set_json_data(&mut self, data: impl Into<JsonValue>) {
312        self.json_data = data.into();
313        self.bytes_data = Bytes::new();
314    }
315
316    /// Sets the bytes data.
317    #[inline]
318    pub fn set_bytes_data(&mut self, data: impl Into<Bytes>) {
319        self.json_data = JsonValue::Null;
320        self.bytes_data = data.into();
321    }
322
323    /// Sets the response data for the validation.
324    #[inline]
325    pub fn set_validation_data(&mut self, validation: Validation) {
326        self.json_data = validation.into_map().into();
327        self.bytes_data = Bytes::new();
328    }
329
330    /// Sets a transformer for the response data.
331    #[inline]
332    pub fn set_data_transformer(&mut self, transformer: DataTransformer) {
333        self.data_transformer = Some(transformer);
334    }
335
336    /// Sets the content type.
337    ///
338    /// # Note
339    ///
340    /// Currently, we have built-in support for the following values:
341    ///
342    /// - `application/json`
343    /// - `application/jsonlines`
344    /// - `application/octet-stream`
345    /// - `application/problem+json`
346    /// - `application/x-www-form-urlencoded`
347    /// - `text/csv`
348    /// - `text/html`
349    /// - `text/plain`
350    #[inline]
351    pub fn set_content_type(&mut self, content_type: impl Into<SharedString>) {
352        self.content_type = Some(content_type.into());
353    }
354
355    /// Sets the form data as the response body.
356    #[inline]
357    pub fn set_form_response(&mut self, data: impl Into<JsonValue>) {
358        fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
359            res.set_json_data(data);
360            res.set_content_type("application/x-www-form-urlencoded");
361            res.set_data_transformer(|data| {
362                let mut bytes = Vec::new();
363                serde_qs::to_writer(&data, &mut bytes)?;
364                Ok(bytes.into())
365            });
366        }
367        inner::<S>(self, data.into())
368    }
369
370    /// Sets the JSON data as the response body.
371    #[inline]
372    pub fn set_json_response(&mut self, data: impl Into<JsonValue>) {
373        fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
374            res.set_json_data(data);
375            res.set_data_transformer(|data| Ok(serde_json::to_vec(&data)?.into()));
376        }
377        inner::<S>(self, data.into())
378    }
379
380    /// Sets the JSON Lines data as the response body.
381    #[inline]
382    pub fn set_jsonlines_response(&mut self, data: impl Into<JsonValue>) {
383        fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
384            res.set_json_data(data);
385            res.set_content_type("application/jsonlines; charset=utf-8");
386            res.set_data_transformer(|data| Ok(data.to_jsonlines(Vec::new())?.into()));
387        }
388        inner::<S>(self, data.into())
389    }
390
391    /// Sets the CSV data as the response body.
392    #[inline]
393    pub fn set_csv_response(&mut self, data: impl Into<JsonValue>) {
394        fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
395            res.set_json_data(data);
396            res.set_content_type("text/csv; charset=utf-8");
397            res.set_data_transformer(|data| Ok(data.to_csv(Vec::new())?.into()));
398        }
399        inner::<S>(self, data.into())
400    }
401
402    /// Sets the plain text as the response body.
403    #[inline]
404    pub fn set_text_response(&mut self, data: impl Into<String>) {
405        self.set_json_data(data.into());
406        self.set_content_type("text/plain; charset=utf-8");
407    }
408
409    /// Sets the bytes data as the response body.
410    #[inline]
411    pub fn set_bytes_response(&mut self, data: impl Into<Bytes>) {
412        self.set_bytes_data(data);
413        self.set_content_type("application/octet-stream");
414    }
415
416    /// Sets the request ID.
417    #[inline]
418    pub(crate) fn set_request_id(&mut self, request_id: Uuid) {
419        self.request_id = request_id;
420    }
421
422    /// Sets the trace context from headers.
423    #[inline]
424    pub(crate) fn set_trace_context(&mut self, trace_context: Option<TraceContext>) {
425        self.trace_context = trace_context;
426    }
427
428    /// Sets the start time.
429    #[inline]
430    pub(crate) fn set_start_time(&mut self, start_time: Instant) {
431        self.start_time = start_time;
432    }
433
434    /// Sends a cookie to the user agent.
435    #[cfg(feature = "cookie")]
436    #[inline]
437    pub fn set_cookie(&mut self, cookie: &Cookie<'_>) {
438        self.insert_header("set-cookie", cookie.to_string());
439    }
440
441    /// Records a server timing metric entry.
442    pub fn record_server_timing(
443        &mut self,
444        name: impl Into<SharedString>,
445        description: impl Into<Option<SharedString>>,
446        duration: impl Into<Option<Duration>>,
447    ) {
448        fn inner<S: ResponseCode>(
449            res: &mut Response<S>,
450            name: SharedString,
451            description: Option<SharedString>,
452            duration: Option<Duration>,
453        ) {
454            let metric = TimingMetric::new(name, description, duration);
455            res.server_timing.push(metric);
456        }
457        inner::<S>(self, name.into(), description.into(), duration.into())
458    }
459
460    /// Inserts a custom header.
461    #[inline]
462    pub fn insert_header(&mut self, name: impl Into<SharedString>, value: impl ToString) {
463        self.headers.push((name.into(), value.to_string()));
464    }
465
466    /// Gets a custome header with the given name.
467    #[inline]
468    pub fn get_header(&self, name: &str) -> Option<&str> {
469        self.headers
470            .iter()
471            .find_map(|(key, value)| (key == name).then_some(value.as_str()))
472    }
473
474    /// Returns the status code as `u16`.
475    #[inline]
476    pub fn status_code(&self) -> u16 {
477        self.status_code
478    }
479
480    /// Returns the error code.
481    #[inline]
482    pub fn error_code(&self) -> Option<&S::ErrorCode> {
483        self.error_code.as_ref()
484    }
485
486    /// Returns the business code.
487    #[inline]
488    pub fn business_code(&self) -> Option<&S::BusinessCode> {
489        self.business_code.as_ref()
490    }
491
492    /// Returns `true` if the response is successful or `false` otherwise.
493    #[inline]
494    pub fn is_success(&self) -> bool {
495        self.success
496    }
497
498    /// Returns `true` if the response has a request context.
499    #[inline]
500    pub fn has_context(&self) -> bool {
501        self.trace_context.is_some() && !self.request_id.is_nil()
502    }
503
504    /// Returns the message.
505    #[inline]
506    pub fn message(&self) -> Option<&str> {
507        self.detail
508            .as_ref()
509            .or(self.message.as_ref())
510            .map(|s| s.as_ref())
511    }
512
513    /// Returns the request ID.
514    #[inline]
515    pub fn request_id(&self) -> Uuid {
516        self.request_id
517    }
518
519    /// Returns the trace ID.
520    #[inline]
521    pub fn trace_id(&self) -> Uuid {
522        if let Some(ref trace_context) = self.trace_context {
523            Uuid::from_u128(trace_context.trace_id())
524        } else {
525            Uuid::nil()
526        }
527    }
528
529    /// Returns the content type.
530    #[inline]
531    pub fn content_type(&self) -> &str {
532        self.content_type.as_deref().unwrap_or_else(|| {
533            if !self.bytes_data.is_empty() {
534                "application/octet-stream"
535            } else if self.is_success() {
536                "application/json; charset=utf-8"
537            } else {
538                "application/problem+json; charset=utf-8"
539            }
540        })
541    }
542
543    /// Returns the custom headers.
544    #[inline]
545    pub fn headers(&self) -> &[(SharedString, String)] {
546        &self.headers
547    }
548
549    /// Returns the trace context in the form `(traceparent, tracestate)`.
550    pub fn trace_context(&self) -> (String, String) {
551        if let Some(ref trace_context) = self.trace_context {
552            (trace_context.traceparent(), trace_context.tracestate())
553        } else {
554            let mut trace_context = TraceContext::new();
555            trace_context.record_trace_state();
556            (trace_context.traceparent(), trace_context.tracestate())
557        }
558    }
559
560    /// Returns the server timing.
561    #[inline]
562    pub fn server_timing(&self) -> String {
563        self.server_timing.to_string()
564    }
565
566    /// Reads the response into a byte buffer.
567    pub fn read_bytes(&mut self) -> Result<Bytes, Error> {
568        let has_bytes_data = !self.bytes_data.is_empty();
569        let has_json_data = !self.json_data.is_null();
570        let bytes_opt = if has_bytes_data {
571            Some(self.bytes_data.clone())
572        } else if has_json_data {
573            if let Some(transformer) = self.data_transformer.as_ref() {
574                Some(transformer(&self.json_data)?)
575            } else {
576                None
577            }
578        } else {
579            None
580        };
581        if let Some(bytes) = bytes_opt {
582            let etag = EntityTag::from_data(&bytes);
583            self.insert_header("x-etag", etag);
584            return Ok(bytes);
585        }
586
587        let content_type = self.content_type();
588        let (bytes, etag_opt) = if crate::helper::check_json_content_type(content_type) {
589            let (capacity, etag_opt) = if has_json_data {
590                let data = serde_json::to_vec(&self.json_data)?;
591                let etag = EntityTag::from_data(&data);
592                (data.len() + 128, Some(etag))
593            } else {
594                (128, None)
595            };
596            let mut bytes = Vec::with_capacity(capacity);
597            serde_json::to_writer(&mut bytes, &self)?;
598            (bytes, etag_opt)
599        } else if has_json_data {
600            let value = &self.json_data;
601            let bytes = if content_type.starts_with("text/csv") {
602                value.to_csv(Vec::new())?
603            } else if content_type.starts_with("application/jsonlines") {
604                value.to_jsonlines(Vec::new())?
605            } else if let JsonValue::String(s) = value {
606                s.as_bytes().to_vec()
607            } else {
608                value.to_string().into_bytes()
609            };
610            (bytes, None)
611        } else {
612            (Vec::new(), None)
613        };
614        let etag = etag_opt.unwrap_or_else(|| EntityTag::from_data(&bytes));
615        self.insert_header("x-etag", etag);
616        Ok(bytes.into())
617    }
618
619    /// Gets the response time.
620    ///
621    /// # Note
622    ///
623    /// It should only be called when the response will finish.
624    pub fn response_time(&self) -> Duration {
625        let start_time = self.start_time;
626        #[cfg(feature = "metrics")]
627        {
628            let labels = [("status_code", self.status_code().to_string())];
629            metrics::gauge!("zino_http_requests_in_flight").decrement(1.0);
630            metrics::counter!("zino_http_responses_total", &labels).increment(1);
631            metrics::histogram!("zino_http_requests_duration_seconds", &labels,)
632                .record(start_time.elapsed().as_secs_f64());
633        }
634        start_time.elapsed()
635    }
636
637    /// Sends a file to the client.
638    pub fn send_file(&mut self, file: NamedFile) {
639        let mut displayed_inline = false;
640        if let Some(content_type) = file.content_type() {
641            displayed_inline = helper::displayed_inline(content_type);
642            self.set_content_type(content_type.to_string());
643        }
644        if !displayed_inline {
645            if let Some(file_name) = file.file_name() {
646                self.insert_header(
647                    "content-disposition",
648                    format!(r#"attachment; filename="{file_name}""#),
649                );
650            }
651        }
652        self.insert_header("etag", file.etag());
653        self.set_bytes_data(Bytes::from(file));
654    }
655
656    /// Consumes `self` and returns the custom headers.
657    pub fn finalize(mut self) -> impl Iterator<Item = (SharedString, String)> {
658        let request_id = self.request_id();
659        if !request_id.is_nil() {
660            self.insert_header("x-request-id", request_id.to_string());
661        }
662
663        let (traceparent, tracestate) = self.trace_context();
664        self.insert_header("traceparent", traceparent);
665        self.insert_header("tracestate", tracestate);
666
667        let duration = self.response_time();
668        self.record_server_timing("total", None, Some(duration));
669        self.insert_header("server-timing", self.server_timing());
670
671        self.headers.into_iter()
672    }
673}
674
675impl Response<StatusCode> {
676    /// Constructs a new response with status `200 OK`.
677    #[inline]
678    pub fn ok() -> Self {
679        Response::new(StatusCode::OK)
680    }
681
682    /// Constructs a new response with status `201 Created`.
683    #[inline]
684    pub fn created() -> Self {
685        Response::new(StatusCode::CREATED)
686    }
687
688    /// Constructs a new response with status `400 Bad Request`.
689    #[inline]
690    pub fn bad_request() -> Self {
691        Response::new(StatusCode::BAD_REQUEST)
692    }
693
694    /// Constructs a new response with status `404 Not Found`.
695    #[inline]
696    pub fn not_found() -> Self {
697        Response::new(StatusCode::NOT_FOUND)
698    }
699
700    /// Constructs a new response with status `500 Internal Server Error`.
701    #[inline]
702    pub fn internal_server_error() -> Self {
703        Response::new(StatusCode::INTERNAL_SERVER_ERROR)
704    }
705}
706
707impl<S: ResponseCode> Default for Response<S> {
708    #[inline]
709    fn default() -> Self {
710        Self::new(S::OK)
711    }
712}
713
714impl<S: ResponseCode> From<Validation> for Response<S> {
715    fn from(validation: Validation) -> Self {
716        if validation.is_success() {
717            Self::new(S::OK)
718        } else {
719            let mut res = Self::new(S::BAD_REQUEST);
720            res.set_validation_data(validation);
721            res
722        }
723    }
724}