1use crate::{
4 helper,
5 request::RequestContext,
6 timing::{ServerTiming, TimingMetric},
7};
8use bytes::Bytes;
9use etag::EntityTag;
10use http::{HeaderMap, HeaderName};
11use serde::Serialize;
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 = "inertia")]
24use crate::inertia::InertiaPage;
25
26#[cfg(feature = "cookie")]
27use cookie::Cookie;
28
29mod rejection;
30mod response_code;
31mod webhook;
32
33pub use rejection::{ExtractRejection, Rejection};
34pub use response_code::ResponseCode;
35pub use webhook::WebHook;
36
37#[cfg(feature = "http02")]
39pub type StatusCode = http02::StatusCode;
40
41#[cfg(not(feature = "http02"))]
43pub type StatusCode = http::StatusCode;
44
45pub type DataTransformer = fn(data: &JsonValue) -> Result<Bytes, Error>;
47
48#[derive(Debug, Clone, Serialize)]
50#[serde(rename_all = "snake_case")]
51pub struct Response<S: ResponseCode> {
52 #[serde(rename = "type")]
54 #[serde(skip_serializing_if = "Option::is_none")]
55 type_uri: Option<SharedString>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 title: Option<SharedString>,
59 #[serde(rename = "status")]
61 status_code: u16,
62 #[serde(rename = "error")]
64 #[serde(skip_serializing_if = "Option::is_none")]
65 error_code: Option<S::ErrorCode>,
66 #[serde(rename = "code")]
68 #[serde(skip_serializing_if = "Option::is_none")]
69 business_code: Option<S::BusinessCode>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 detail: Option<SharedString>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 instance: Option<SharedString>,
76 success: bool,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 message: Option<SharedString>,
81 #[serde(skip)]
83 start_time: Instant,
84 #[serde(skip_serializing_if = "Uuid::is_nil")]
86 request_id: Uuid,
87 #[serde(rename = "data")]
89 #[serde(skip_serializing_if = "JsonValue::is_null")]
90 json_data: JsonValue,
91 #[serde(skip)]
93 bytes_data: Bytes,
94 #[serde(skip)]
96 data_transformer: Option<DataTransformer>,
97 #[serde(skip)]
99 content_type: Option<SharedString>,
100 #[serde(skip)]
102 trace_context: Option<TraceContext>,
103 #[serde(skip)]
105 server_timing: ServerTiming,
106 #[serde(skip)]
108 headers: HeaderMap<String>,
109 #[serde(skip)]
111 phantom: PhantomData<S>,
112}
113
114impl<S: ResponseCode> Response<S> {
115 pub fn new(code: S) -> Self {
117 let success = code.is_success();
118 let message = code.message();
119 let mut res = Self {
120 type_uri: code.type_uri(),
121 title: code.title(),
122 status_code: code.status_code(),
123 error_code: code.error_code(),
124 business_code: code.business_code(),
125 detail: None,
126 instance: None,
127 success,
128 message: None,
129 start_time: Instant::now(),
130 request_id: Uuid::nil(),
131 json_data: JsonValue::Null,
132 bytes_data: Bytes::new(),
133 data_transformer: None,
134 content_type: None,
135 trace_context: None,
136 server_timing: ServerTiming::new(),
137 headers: HeaderMap::default(),
138 phantom: PhantomData,
139 };
140 if success {
141 res.message = message;
142 } else {
143 res.detail = message;
144 }
145 res
146 }
147
148 pub fn with_context<Ctx: RequestContext>(code: S, ctx: &Ctx) -> Self {
150 let success = code.is_success();
151 let message = code.message();
152 let mut res = Self {
153 type_uri: code.type_uri(),
154 title: code.title(),
155 status_code: code.status_code(),
156 error_code: code.error_code(),
157 business_code: code.business_code(),
158 detail: None,
159 instance: (!success).then(|| ctx.instance().into()),
160 success,
161 message: None,
162 start_time: ctx.start_time(),
163 request_id: ctx.request_id(),
164 json_data: JsonValue::Null,
165 bytes_data: Bytes::new(),
166 data_transformer: None,
167 content_type: None,
168 trace_context: None,
169 server_timing: ServerTiming::new(),
170 headers: HeaderMap::default(),
171 phantom: PhantomData,
172 };
173 if success {
174 res.message = message;
175 } else {
176 res.detail = message;
177 }
178 res.trace_context = Some(ctx.new_trace_context());
179 res
180 }
181
182 pub fn context<Ctx: RequestContext>(mut self, ctx: &Ctx) -> Self {
184 self.instance = (!self.is_success()).then(|| ctx.instance().into());
185 self.start_time = ctx.start_time();
186 self.request_id = ctx.request_id();
187 self.trace_context = Some(ctx.new_trace_context());
188 self
189 }
190
191 #[cfg(feature = "view")]
193 pub fn render<T: Serialize>(mut self, template_name: &str, data: T) -> Self {
194 let result = serde_json::to_value(data)
195 .map_err(|err| err.into())
196 .and_then(|mut value| {
197 if let Some(data) = value.as_object_mut() {
198 let mut map = zino_core::Map::new();
199 map.append(data);
200 crate::view::render(template_name, map)
201 } else {
202 Err(zino_core::warn!("invalid template data"))
203 }
204 });
205 match result {
206 Ok(content) => {
207 self.json_data = content.into();
208 self.bytes_data = Bytes::new();
209 self.content_type = Some("text/html; charset=utf-8".into());
210 }
211 Err(err) => {
212 let code = S::INTERNAL_SERVER_ERROR;
213 self.type_uri = code.type_uri();
214 self.title = code.title();
215 self.status_code = code.status_code();
216 self.error_code = code.error_code();
217 self.business_code = code.business_code();
218 self.success = false;
219 self.detail = Some(err.to_string().into());
220 self.message = None;
221 self.json_data = JsonValue::Null;
222 self.bytes_data = Bytes::new();
223 }
224 }
225 self
226 }
227
228 pub fn set_code(&mut self, code: S) {
230 let success = code.is_success();
231 let message = code.message();
232 self.type_uri = code.type_uri();
233 self.title = code.title();
234 self.status_code = code.status_code();
235 self.error_code = code.error_code();
236 self.business_code = code.business_code();
237 self.success = success;
238 if success {
239 self.detail = None;
240 self.message = message;
241 } else {
242 self.detail = message;
243 self.message = None;
244 }
245 }
246
247 #[inline]
249 pub fn set_status_code(&mut self, status_code: impl Into<u16>) {
250 self.status_code = status_code.into();
251 }
252
253 #[inline]
255 pub fn set_error_code(&mut self, error_code: impl Into<S::ErrorCode>) {
256 self.error_code = Some(error_code.into());
257 }
258
259 #[inline]
261 pub fn set_business_code(&mut self, business_code: impl Into<S::BusinessCode>) {
262 self.business_code = Some(business_code.into());
263 }
264
265 #[inline]
267 pub fn set_instance(&mut self, instance: impl Into<SharedString>) {
268 self.instance = Some(instance.into());
269 }
270
271 pub fn set_message(&mut self, message: impl Into<SharedString>) {
274 fn inner<S: ResponseCode>(res: &mut Response<S>, message: SharedString) {
275 if res.is_success() {
276 res.detail = None;
277 res.message = Some(message);
278 } else {
279 res.detail = Some(message);
280 res.message = None;
281 }
282 }
283 inner::<S>(self, message.into())
284 }
285
286 pub fn set_error_message(&mut self, error: impl Into<Error>) {
288 fn inner<S: ResponseCode>(res: &mut Response<S>, error: Error) {
289 let message = error.to_string().into();
290 if res.is_success() {
291 res.detail = None;
292 res.message = Some(message);
293 } else {
294 res.detail = Some(message);
295 res.message = None;
296 }
297 }
298 inner::<S>(self, error.into())
299 }
300
301 #[inline]
303 pub fn set_data<T: Serialize>(&mut self, data: &T) {
304 match serde_json::to_value(data) {
305 Ok(value) => {
306 self.json_data = value;
307 self.bytes_data = Bytes::new();
308 }
309 Err(err) => self.set_error_message(err),
310 }
311 }
312
313 #[inline]
315 pub fn set_json_data(&mut self, data: impl Into<JsonValue>) {
316 self.json_data = data.into();
317 self.bytes_data = Bytes::new();
318 }
319
320 #[inline]
322 pub fn set_bytes_data(&mut self, data: impl Into<Bytes>) {
323 self.json_data = JsonValue::Null;
324 self.bytes_data = data.into();
325 }
326
327 #[inline]
329 pub fn set_validation_data(&mut self, validation: Validation) {
330 self.json_data = validation.into_map().into();
331 self.bytes_data = Bytes::new();
332 }
333
334 #[inline]
336 pub fn set_data_transformer(&mut self, transformer: DataTransformer) {
337 self.data_transformer = Some(transformer);
338 }
339
340 #[inline]
355 pub fn set_content_type(&mut self, content_type: impl Into<SharedString>) {
356 self.content_type = Some(content_type.into());
357 }
358
359 #[inline]
361 pub fn set_form_response(&mut self, data: impl Into<JsonValue>) {
362 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
363 res.set_json_data(data);
364 res.set_content_type("application/x-www-form-urlencoded");
365 res.set_data_transformer(|data| {
366 let mut bytes = Vec::new();
367 serde_qs::to_writer(&data, &mut bytes)?;
368 Ok(bytes.into())
369 });
370 }
371 inner::<S>(self, data.into())
372 }
373
374 #[inline]
376 pub fn set_json_response(&mut self, data: impl Into<JsonValue>) {
377 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
378 res.set_json_data(data);
379 res.set_data_transformer(|data| Ok(serde_json::to_vec(&data)?.into()));
380 }
381 inner::<S>(self, data.into())
382 }
383
384 #[inline]
386 pub fn set_jsonlines_response(&mut self, data: impl Into<JsonValue>) {
387 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
388 res.set_json_data(data);
389 res.set_content_type("application/jsonlines; charset=utf-8");
390 res.set_data_transformer(|data| Ok(data.to_jsonlines(Vec::new())?.into()));
391 }
392 inner::<S>(self, data.into())
393 }
394
395 #[inline]
397 pub fn set_csv_response(&mut self, data: impl Into<JsonValue>) {
398 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
399 res.set_json_data(data);
400 res.set_content_type("text/csv; charset=utf-8");
401 res.set_data_transformer(|data| Ok(data.to_csv(Vec::new())?.into()));
402 }
403 inner::<S>(self, data.into())
404 }
405
406 #[inline]
408 pub fn set_text_response(&mut self, data: impl Into<String>) {
409 self.set_json_data(data.into());
410 self.set_content_type("text/plain; charset=utf-8");
411 }
412
413 #[inline]
415 pub fn set_bytes_response(&mut self, data: impl Into<Bytes>) {
416 self.set_bytes_data(data);
417 self.set_content_type("application/octet-stream");
418 }
419
420 #[inline]
422 pub(crate) fn set_request_id(&mut self, request_id: Uuid) {
423 self.request_id = request_id;
424 }
425
426 #[inline]
428 pub(crate) fn set_trace_context(&mut self, trace_context: Option<TraceContext>) {
429 self.trace_context = trace_context;
430 }
431
432 #[inline]
434 pub(crate) fn set_start_time(&mut self, start_time: Instant) {
435 self.start_time = start_time;
436 }
437
438 #[cfg(feature = "cookie")]
440 #[inline]
441 pub fn set_cookie(&mut self, cookie: &Cookie<'_>) {
442 self.insert_header("set-cookie", cookie.to_string());
443 }
444
445 pub fn record_server_timing(
447 &mut self,
448 name: impl Into<SharedString>,
449 description: impl Into<Option<SharedString>>,
450 duration: impl Into<Option<Duration>>,
451 ) {
452 fn inner<S: ResponseCode>(
453 res: &mut Response<S>,
454 name: SharedString,
455 description: Option<SharedString>,
456 duration: Option<Duration>,
457 ) {
458 let metric = TimingMetric::new(name, description, duration);
459 res.server_timing.push(metric);
460 }
461 inner::<S>(self, name.into(), description.into(), duration.into())
462 }
463
464 #[inline]
466 pub fn insert_header(&mut self, name: &'static str, value: impl ToString) {
467 self.headers
468 .insert(HeaderName::from_static(name), value.to_string());
469 }
470
471 #[inline]
473 pub fn get_header(&self, name: &str) -> Option<&str> {
474 self.headers
475 .iter()
476 .find_map(|(key, value)| (key == name).then_some(value.as_str()))
477 }
478
479 #[inline]
481 pub fn status_code(&self) -> u16 {
482 self.status_code
483 }
484
485 #[inline]
487 pub fn error_code(&self) -> Option<&S::ErrorCode> {
488 self.error_code.as_ref()
489 }
490
491 #[inline]
493 pub fn business_code(&self) -> Option<&S::BusinessCode> {
494 self.business_code.as_ref()
495 }
496
497 #[inline]
499 pub fn is_success(&self) -> bool {
500 self.success
501 }
502
503 #[inline]
505 pub fn has_context(&self) -> bool {
506 self.trace_context.is_some() && !self.request_id.is_nil()
507 }
508
509 #[inline]
511 pub fn message(&self) -> Option<&str> {
512 self.detail
513 .as_ref()
514 .or(self.message.as_ref())
515 .map(|s| s.as_ref())
516 }
517
518 #[inline]
520 pub fn request_id(&self) -> Uuid {
521 self.request_id
522 }
523
524 #[inline]
526 pub fn trace_id(&self) -> Uuid {
527 if let Some(ref trace_context) = self.trace_context {
528 Uuid::from_u128(trace_context.trace_id())
529 } else {
530 Uuid::nil()
531 }
532 }
533
534 #[inline]
536 pub fn content_type(&self) -> &str {
537 self.content_type.as_deref().unwrap_or_else(|| {
538 if !self.bytes_data.is_empty() {
539 "application/octet-stream"
540 } else if self.is_success() {
541 "application/json; charset=utf-8"
542 } else {
543 "application/problem+json; charset=utf-8"
544 }
545 })
546 }
547
548 #[inline]
550 pub fn headers(&self) -> &HeaderMap<String> {
551 &self.headers
552 }
553
554 #[inline]
556 pub fn headers_mut(&mut self) -> &mut HeaderMap<String> {
557 &mut self.headers
558 }
559
560 pub fn trace_context(&self) -> (String, String) {
562 if let Some(ref trace_context) = self.trace_context {
563 (trace_context.traceparent(), trace_context.tracestate())
564 } else {
565 let mut trace_context = TraceContext::new();
566 trace_context.record_trace_state();
567 (trace_context.traceparent(), trace_context.tracestate())
568 }
569 }
570
571 #[inline]
573 pub fn server_timing(&self) -> String {
574 self.server_timing.to_string()
575 }
576
577 pub fn read_bytes(&mut self) -> Result<Bytes, Error> {
579 let has_bytes_data = !self.bytes_data.is_empty();
580 let has_json_data = !self.json_data.is_null();
581 let bytes_opt = if has_bytes_data {
582 Some(mem::take(&mut self.bytes_data))
583 } else if has_json_data {
584 if let Some(transformer) = self.data_transformer.as_ref() {
585 Some(transformer(&self.json_data)?)
586 } else {
587 None
588 }
589 } else {
590 None
591 };
592 if let Some(bytes) = bytes_opt {
593 let etag = EntityTag::from_data(&bytes);
594 self.insert_header("x-etag", etag);
595 return Ok(bytes);
596 }
597
598 let content_type = self.content_type();
599 let (bytes, etag_opt) = if crate::helper::check_json_content_type(content_type) {
600 let (capacity, etag_opt) = if has_json_data {
601 let data = serde_json::to_vec(&self.json_data)?;
602 let etag = EntityTag::from_data(&data);
603 (data.len() + 128, Some(etag))
604 } else {
605 (128, None)
606 };
607 let mut bytes = Vec::with_capacity(capacity);
608 serde_json::to_writer(&mut bytes, &self)?;
609 (bytes, etag_opt)
610 } else if has_json_data {
611 let bytes = if content_type.starts_with("text/csv") {
612 self.json_data.to_csv(Vec::new())?
613 } else if content_type.starts_with("application/jsonlines") {
614 self.json_data.to_jsonlines(Vec::new())?
615 } else {
616 let text = if let JsonValue::String(s) = &mut self.json_data {
617 mem::take(s)
618 } else {
619 self.json_data.to_string()
620 };
621 text.into_bytes()
622 };
623 (bytes, None)
624 } else {
625 (Vec::new(), None)
626 };
627 let etag = etag_opt.unwrap_or_else(|| EntityTag::from_data(&bytes));
628 self.insert_header("x-etag", etag);
629 Ok(bytes.into())
630 }
631
632 pub fn response_time(&self) -> Duration {
638 let start_time = self.start_time;
639 #[cfg(feature = "metrics")]
640 {
641 let labels = [("status_code", self.status_code().to_string())];
642 metrics::gauge!("zino_http_requests_in_flight").decrement(1.0);
643 metrics::counter!("zino_http_responses_total", &labels).increment(1);
644 metrics::histogram!("zino_http_requests_duration_seconds", &labels,)
645 .record(start_time.elapsed().as_secs_f64());
646 }
647 start_time.elapsed()
648 }
649
650 pub fn send_file(&mut self, file: NamedFile) {
652 let mut displayed_inline = false;
653 if let Some(content_type) = file.content_type() {
654 displayed_inline = helper::displayed_inline(content_type);
655 self.set_content_type(content_type.to_string());
656 }
657 if !displayed_inline {
658 if let Some(file_name) = file.file_name() {
659 self.insert_header(
660 "content-disposition",
661 format!(r#"attachment; filename="{file_name}""#),
662 );
663 }
664 }
665 self.insert_header("etag", file.etag());
666 self.set_bytes_data(Bytes::from(file));
667 }
668
669 #[cfg(feature = "inertia")]
671 pub fn send_inertia_page(&mut self, mut page: InertiaPage) {
672 if page.version().is_empty() {
673 page.set_version(zino_core::datetime::DateTime::current_timestamp().to_string());
674 }
675 self.insert_header("vary", "x-inertia");
676 self.insert_header("x-insertia", true);
677 if let Some(url) = page.redirect_url() {
678 self.insert_header("x-inertia-location", url);
679 } else {
680 self.set_json_response(page.into_json_response());
681 }
682 }
683
684 pub fn emit<Ctx: RequestContext>(mut self, ctx: &Ctx) -> Self {
686 if ctx
687 .get_header("prefer")
688 .is_some_and(|s| s.split(';').any(|p| p.trim() == "return=data-only"))
689 {
690 self.set_data_transformer(|data| Ok(serde_json::to_vec(&data)?.into()));
691 }
692 #[cfg(feature = "inertia")]
693 if self.get_header("x-inertia").is_none()
694 && ctx.get_header("x-inertia-partial-component").is_some()
695 {
696 match InertiaPage::partial_reload(ctx) {
697 Ok(mut page) => {
698 if let JsonValue::Object(data) = &mut self.json_data {
699 page.append_props(data);
700 }
701 self.send_inertia_page(page);
702 }
703 Err(err) => self.set_error_message(err),
704 }
705 }
706 self
707 }
708
709 pub fn finalize(mut self) -> HeaderMap<String> {
711 let request_id = self.request_id();
712 if !request_id.is_nil() {
713 self.insert_header("x-request-id", request_id.to_string());
714 }
715
716 let (traceparent, tracestate) = self.trace_context();
717 self.insert_header("traceparent", traceparent);
718 self.insert_header("tracestate", tracestate);
719
720 let duration = self.response_time();
721 self.record_server_timing("total", None, Some(duration));
722 self.insert_header("server-timing", self.server_timing());
723 self.headers
724 }
725}
726
727impl Response<StatusCode> {
728 #[inline]
730 pub fn ok() -> Self {
731 Response::new(StatusCode::OK)
732 }
733
734 #[inline]
736 pub fn created() -> Self {
737 Response::new(StatusCode::CREATED)
738 }
739
740 #[inline]
742 pub fn bad_request() -> Self {
743 Response::new(StatusCode::BAD_REQUEST)
744 }
745
746 #[inline]
748 pub fn unauthorized() -> Self {
749 Response::new(StatusCode::UNAUTHORIZED)
750 }
751
752 #[inline]
754 pub fn forbidden() -> Self {
755 Response::new(StatusCode::FORBIDDEN)
756 }
757
758 #[inline]
760 pub fn not_found() -> Self {
761 Response::new(StatusCode::NOT_FOUND)
762 }
763
764 #[inline]
766 pub fn method_not_allowed() -> Self {
767 Response::new(StatusCode::METHOD_NOT_ALLOWED)
768 }
769
770 #[inline]
772 pub fn conflict() -> Self {
773 Response::new(StatusCode::CONFLICT)
774 }
775
776 #[inline]
778 pub fn internal_server_error() -> Self {
779 Response::new(StatusCode::INTERNAL_SERVER_ERROR)
780 }
781
782 #[inline]
784 pub fn service_unavailable() -> Self {
785 Response::new(StatusCode::SERVICE_UNAVAILABLE)
786 }
787}
788
789impl<S: ResponseCode> Default for Response<S> {
790 #[inline]
791 fn default() -> Self {
792 Self::new(S::OK)
793 }
794}
795
796impl<S: ResponseCode> From<Validation> for Response<S> {
797 fn from(validation: Validation) -> Self {
798 if validation.is_success() {
799 Self::new(S::OK)
800 } else {
801 let mut res = Self::new(S::BAD_REQUEST);
802 res.set_validation_data(validation);
803 res
804 }
805 }
806}