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