1use crate::{
42 helper,
43 request::RequestContext,
44 timing::{ServerTiming, TimingMetric},
45};
46use bytes::Bytes;
47use etag::EntityTag;
48use http::{HeaderMap, HeaderName};
49use serde::Serialize;
50use std::{
51 marker::PhantomData,
52 mem,
53 time::{Duration, Instant},
54};
55use zino_core::{
56 JsonValue, SharedString, Uuid, application::ApplicationCode, error::Error,
57 extension::JsonValueExt, trace::TraceContext, validation::Validation,
58};
59use zino_storage::NamedFile;
60
61#[cfg(feature = "inertia")]
62use crate::inertia::InertiaPage;
63
64#[cfg(feature = "cookie")]
65use cookie::{Cookie, SameSite};
66
67mod rejection;
68mod response_code;
69mod webhook;
70
71pub use rejection::{ExtractRejection, Rejection};
72pub use response_code::ResponseCode;
73pub use webhook::WebHook;
74
75#[cfg(feature = "http02")]
77pub type StatusCode = http02::StatusCode;
78
79#[cfg(not(feature = "http02"))]
81pub type StatusCode = http::StatusCode;
82
83pub type DataTransformer = fn(data: &JsonValue) -> Result<Bytes, Error>;
85
86#[derive(Debug, Clone, Serialize)]
88#[serde(rename_all = "snake_case")]
89pub struct Response<S: ResponseCode> {
90 #[serde(rename = "type")]
92 #[serde(skip_serializing_if = "Option::is_none")]
93 type_uri: Option<SharedString>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 title: Option<SharedString>,
97 #[serde(rename = "status")]
99 status_code: u16,
100 #[serde(rename = "code")]
102 #[serde(skip_serializing_if = "Option::is_none")]
103 app_code: Option<i32>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 detail: Option<SharedString>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 instance: Option<SharedString>,
110 success: bool,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 message: Option<SharedString>,
115 #[serde(skip)]
117 start_time: Instant,
118 #[serde(skip_serializing_if = "Uuid::is_nil")]
120 request_id: Uuid,
121 #[serde(rename = "data")]
123 #[serde(skip_serializing_if = "JsonValue::is_null")]
124 json_data: JsonValue,
125 #[serde(skip)]
127 bytes_data: Bytes,
128 #[serde(skip)]
130 data_transformer: Option<DataTransformer>,
131 #[serde(skip)]
133 content_type: Option<SharedString>,
134 #[serde(skip)]
136 trace_context: Option<TraceContext>,
137 #[serde(skip)]
139 server_timing: ServerTiming,
140 #[serde(skip)]
142 headers: HeaderMap<String>,
143 #[serde(skip)]
145 phantom: PhantomData<S>,
146}
147
148impl<S: ResponseCode> Response<S> {
149 pub fn new(code: S) -> Self {
151 let success = code.is_success();
152 let message = code.message();
153 let mut res = Self {
154 type_uri: code.type_uri(),
155 title: code.title(),
156 status_code: code.status_code(),
157 app_code: None,
158 detail: None,
159 instance: None,
160 success,
161 message: None,
162 start_time: Instant::now(),
163 request_id: Uuid::nil(),
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
179 }
180
181 pub fn with_context<Ctx: RequestContext>(code: S, ctx: &Ctx) -> Self {
183 let success = code.is_success();
184 let message = code.message();
185 let mut res = Self {
186 type_uri: code.type_uri(),
187 title: code.title(),
188 status_code: code.status_code(),
189 app_code: None,
190 detail: None,
191 instance: (!success).then(|| ctx.instance().into()),
192 success,
193 message: None,
194 start_time: ctx.start_time(),
195 request_id: ctx.request_id(),
196 json_data: JsonValue::Null,
197 bytes_data: Bytes::new(),
198 data_transformer: None,
199 content_type: None,
200 trace_context: None,
201 server_timing: ServerTiming::new(),
202 headers: HeaderMap::default(),
203 phantom: PhantomData,
204 };
205 if success {
206 res.message = message;
207 } else {
208 res.detail = message;
209 }
210 res.trace_context = Some(ctx.new_trace_context());
211 res
212 }
213
214 pub fn context<Ctx: RequestContext>(mut self, ctx: &Ctx) -> Self {
216 self.instance = (!self.is_success()).then(|| ctx.instance().into());
217 self.start_time = ctx.start_time();
218 self.request_id = ctx.request_id();
219 self.trace_context = Some(ctx.new_trace_context());
220 self
221 }
222
223 #[cfg(feature = "view")]
225 pub fn render<T: Serialize>(mut self, template_name: &str, data: T) -> Self {
226 let result = serde_json::to_value(data)
227 .map_err(|err| err.into())
228 .and_then(|value| {
229 if let JsonValue::Object(map) = value {
230 crate::view::render(template_name, map)
231 } else {
232 Err(zino_core::warn!("invalid template data"))
233 }
234 });
235 match result {
236 Ok(content) => {
237 self.json_data = content.into();
238 self.bytes_data = Bytes::new();
239 self.content_type = Some("text/html; charset=utf-8".into());
240 }
241 Err(err) => {
242 let code = S::INTERNAL_SERVER_ERROR;
243 self.type_uri = code.type_uri();
244 self.title = code.title();
245 self.status_code = code.status_code();
246 self.success = false;
247 self.detail = Some(err.to_string().into());
248 self.json_data = JsonValue::Null;
249 self.bytes_data = Bytes::new();
250 }
251 }
252 self
253 }
254
255 pub fn set_code(&mut self, code: S) {
257 let success = code.is_success();
258 let message = code.message();
259 self.type_uri = code.type_uri();
260 self.title = code.title();
261 self.status_code = code.status_code();
262 self.success = success;
263 if success {
264 self.detail = None;
265 self.message = message;
266 } else {
267 self.detail = message;
268 self.message = None;
269 }
270 }
271
272 #[inline]
274 pub fn set_status_code(&mut self, status_code: impl Into<u16>) {
275 self.status_code = status_code.into();
276 }
277
278 #[inline]
280 pub fn set_app_code<C: ApplicationCode>(&mut self, app_code: &C) {
281 self.app_code = Some(app_code.code());
282 self.message = Some(app_code.message());
283 }
284
285 #[inline]
287 pub fn set_instance(&mut self, instance: impl Into<SharedString>) {
288 self.instance = Some(instance.into());
289 }
290
291 pub fn set_message(&mut self, message: impl Into<SharedString>) {
294 fn inner<S: ResponseCode>(res: &mut Response<S>, message: SharedString) {
295 if res.is_success() {
296 res.detail = None;
297 res.message = Some(message);
298 } else {
299 res.detail = Some(message);
300 res.message = None;
301 }
302 }
303 inner::<S>(self, message.into())
304 }
305
306 pub fn set_error_message(&mut self, error: impl Into<Error>) {
308 fn inner<S: ResponseCode>(res: &mut Response<S>, error: Error) {
309 let message = error.to_string().into();
310 if res.is_success() {
311 res.detail = None;
312 res.message = Some(message);
313 } else {
314 res.detail = Some(message);
315 res.message = None;
316 }
317 }
318 inner::<S>(self, error.into())
319 }
320
321 #[inline]
323 pub fn set_data<T: Serialize>(&mut self, data: &T) {
324 match serde_json::to_value(data) {
325 Ok(value) => {
326 self.json_data = value;
327 self.bytes_data = Bytes::new();
328 }
329 Err(err) => self.set_error_message(err),
330 }
331 }
332
333 #[inline]
335 pub fn set_json_data(&mut self, data: impl Into<JsonValue>) {
336 self.json_data = data.into();
337 self.bytes_data = Bytes::new();
338 }
339
340 #[inline]
342 pub fn set_bytes_data(&mut self, data: impl Into<Bytes>) {
343 self.json_data = JsonValue::Null;
344 self.bytes_data = data.into();
345 }
346
347 #[inline]
349 pub fn set_validation_data(&mut self, validation: Validation) {
350 self.json_data = validation.into_map().into();
351 self.bytes_data = Bytes::new();
352 }
353
354 #[inline]
356 pub fn set_data_transformer(&mut self, transformer: DataTransformer) {
357 self.data_transformer = Some(transformer);
358 }
359
360 #[inline]
375 pub fn set_content_type(&mut self, content_type: impl Into<SharedString>) {
376 self.content_type = Some(content_type.into());
377 }
378
379 #[inline]
381 pub fn set_form_response(&mut self, data: impl Into<JsonValue>) {
382 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
383 res.set_json_data(data);
384 res.set_content_type("application/x-www-form-urlencoded");
385 res.set_data_transformer(|data| {
386 let mut bytes = Vec::new();
387 serde_qs::to_writer(&data, &mut bytes)?;
388 Ok(bytes.into())
389 });
390 }
391 inner::<S>(self, data.into())
392 }
393
394 #[inline]
396 pub fn set_json_response(&mut self, data: impl Into<JsonValue>) {
397 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
398 res.set_json_data(data);
399 res.set_data_transformer(|data| Ok(serde_json::to_vec(&data)?.into()));
400 }
401 inner::<S>(self, data.into())
402 }
403
404 #[inline]
406 pub fn set_jsonlines_response(&mut self, data: impl Into<JsonValue>) {
407 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
408 res.set_json_data(data);
409 res.set_content_type("application/jsonlines; charset=utf-8");
410 res.set_data_transformer(|data| Ok(data.to_jsonlines(Vec::new())?.into()));
411 }
412 inner::<S>(self, data.into())
413 }
414
415 #[inline]
417 pub fn set_csv_response(&mut self, data: impl Into<JsonValue>) {
418 fn inner<S: ResponseCode>(res: &mut Response<S>, data: JsonValue) {
419 res.set_json_data(data);
420 res.set_content_type("text/csv; charset=utf-8");
421 res.set_data_transformer(|data| Ok(data.to_csv(Vec::new())?.into()));
422 }
423 inner::<S>(self, data.into())
424 }
425
426 #[inline]
428 pub fn set_text_response(&mut self, data: impl Into<String>) {
429 self.set_json_data(data.into());
430 self.set_content_type("text/plain; charset=utf-8");
431 }
432
433 #[inline]
435 pub fn set_bytes_response(&mut self, data: impl Into<Bytes>) {
436 self.set_bytes_data(data);
437 self.set_content_type("application/octet-stream");
438 }
439
440 #[inline]
442 pub(crate) fn set_request_id(&mut self, request_id: Uuid) {
443 self.request_id = request_id;
444 }
445
446 #[inline]
448 pub(crate) fn set_trace_context(&mut self, trace_context: Option<TraceContext>) {
449 self.trace_context = trace_context;
450 }
451
452 #[inline]
454 pub(crate) fn set_start_time(&mut self, start_time: Instant) {
455 self.start_time = start_time;
456 }
457
458 #[cfg(feature = "cookie")]
460 #[inline]
461 pub fn set_cookie(&mut self, cookie: &Cookie<'_>) {
462 self.insert_header("set-cookie", cookie.to_string());
463 }
464
465 #[cfg(feature = "cookie")]
467 #[inline]
468 pub fn clear_cookie(&mut self, name: impl Into<SharedString>) {
469 let cookie = Cookie::build((name, ""))
470 .path("/")
471 .http_only(true)
472 .secure(true)
473 .same_site(SameSite::Lax)
474 .removal()
475 .build();
476 self.insert_header("set-cookie", cookie.to_string());
477 }
478
479 pub fn record_server_timing(
481 &mut self,
482 name: impl Into<SharedString>,
483 description: impl Into<Option<SharedString>>,
484 duration: impl Into<Option<Duration>>,
485 ) {
486 fn inner<S: ResponseCode>(
487 res: &mut Response<S>,
488 name: SharedString,
489 description: Option<SharedString>,
490 duration: Option<Duration>,
491 ) {
492 let metric = TimingMetric::new(name, description, duration);
493 res.server_timing.push(metric);
494 }
495 inner::<S>(self, name.into(), description.into(), duration.into())
496 }
497
498 #[inline]
500 pub fn insert_header(&mut self, name: &'static str, value: impl ToString) {
501 self.headers
502 .insert(HeaderName::from_static(name), value.to_string());
503 }
504
505 #[inline]
507 pub fn get_header(&self, name: &str) -> Option<&str> {
508 self.headers
509 .iter()
510 .find_map(|(key, value)| (key == name).then_some(value.as_str()))
511 }
512
513 #[inline]
515 pub fn status_code(&self) -> u16 {
516 self.status_code
517 }
518
519 #[inline]
521 pub fn app_code(&self) -> Option<i32> {
522 self.app_code
523 }
524
525 #[inline]
527 pub fn is_success(&self) -> bool {
528 self.success
529 }
530
531 #[inline]
533 pub fn has_context(&self) -> bool {
534 self.trace_context.is_some() && !self.request_id.is_nil()
535 }
536
537 #[inline]
539 pub fn message(&self) -> Option<&str> {
540 self.detail
541 .as_ref()
542 .or(self.message.as_ref())
543 .map(|s| s.as_ref())
544 }
545
546 #[inline]
548 pub fn request_id(&self) -> Uuid {
549 self.request_id
550 }
551
552 #[inline]
554 pub fn trace_id(&self) -> Uuid {
555 if let Some(ref trace_context) = self.trace_context {
556 Uuid::from_u128(trace_context.trace_id())
557 } else {
558 Uuid::nil()
559 }
560 }
561
562 #[inline]
564 pub fn content_type(&self) -> &str {
565 self.content_type.as_deref().unwrap_or_else(|| {
566 if !self.bytes_data.is_empty() {
567 "application/octet-stream"
568 } else if self.is_success() {
569 "application/json; charset=utf-8"
570 } else {
571 "application/problem+json; charset=utf-8"
572 }
573 })
574 }
575
576 #[inline]
578 pub fn headers(&self) -> &HeaderMap<String> {
579 &self.headers
580 }
581
582 #[inline]
584 pub fn headers_mut(&mut self) -> &mut HeaderMap<String> {
585 &mut self.headers
586 }
587
588 pub fn trace_context(&self) -> (String, String) {
590 if let Some(ref trace_context) = self.trace_context {
591 (trace_context.traceparent(), trace_context.tracestate())
592 } else {
593 let mut trace_context = TraceContext::new();
594 trace_context.record_trace_state();
595 (trace_context.traceparent(), trace_context.tracestate())
596 }
597 }
598
599 #[inline]
601 pub fn server_timing(&self) -> String {
602 self.server_timing.to_string()
603 }
604
605 pub fn read_bytes(&mut self) -> Result<Bytes, Error> {
607 let has_bytes_data = !self.bytes_data.is_empty();
608 let has_json_data = !self.json_data.is_null();
609 let bytes_opt = if has_bytes_data {
610 Some(mem::take(&mut self.bytes_data))
611 } else if has_json_data {
612 if let Some(transformer) = self.data_transformer.as_ref() {
613 Some(transformer(&self.json_data)?)
614 } else {
615 None
616 }
617 } else {
618 None
619 };
620 if let Some(bytes) = bytes_opt {
621 let etag = EntityTag::from_data(&bytes);
622 self.insert_header("x-etag", etag);
623 return Ok(bytes);
624 }
625
626 let content_type = self.content_type();
627 let (bytes, etag_opt) = if crate::helper::check_json_content_type(content_type) {
628 let (capacity, etag_opt) = if has_json_data {
629 let data = serde_json::to_vec(&self.json_data)?;
630 let etag = EntityTag::from_data(&data);
631 (data.len() + 128, Some(etag))
632 } else {
633 (128, None)
634 };
635 let mut bytes = Vec::with_capacity(capacity);
636 serde_json::to_writer(&mut bytes, &self)?;
637 (bytes, etag_opt)
638 } else if has_json_data {
639 let bytes = if content_type.starts_with("text/csv") {
640 self.json_data.to_csv(Vec::new())?
641 } else if content_type.starts_with("application/jsonlines") {
642 self.json_data.to_jsonlines(Vec::new())?
643 } else {
644 let text = if let JsonValue::String(s) = &mut self.json_data {
645 mem::take(s)
646 } else {
647 self.json_data.to_string()
648 };
649 text.into_bytes()
650 };
651 (bytes, None)
652 } else {
653 (Vec::new(), None)
654 };
655 let etag = etag_opt.unwrap_or_else(|| EntityTag::from_data(&bytes));
656 self.insert_header("x-etag", etag);
657 Ok(bytes.into())
658 }
659
660 pub fn response_time(&self) -> Duration {
666 let start_time = self.start_time;
667 #[cfg(feature = "metrics")]
668 {
669 let labels = [("status_code", self.status_code().to_string())];
670 metrics::gauge!("zino_http_requests_in_flight").decrement(1.0);
671 metrics::counter!("zino_http_responses_total", &labels).increment(1);
672 metrics::histogram!("zino_http_requests_duration_seconds", &labels,)
673 .record(start_time.elapsed().as_secs_f64());
674 }
675 start_time.elapsed()
676 }
677
678 pub fn send_file(&mut self, file: NamedFile) {
680 let mut displayed_inline = false;
681 if let Some(content_type) = file.content_type() {
682 displayed_inline = helper::displayed_inline(content_type);
683 self.set_content_type(content_type.to_string());
684 }
685 if !displayed_inline && let Some(file_name) = file.file_name() {
686 self.insert_header(
687 "content-disposition",
688 format!(r#"attachment; filename="{file_name}""#),
689 );
690 }
691 self.insert_header("etag", file.etag());
692 self.set_bytes_data(Bytes::from(file));
693 }
694
695 #[cfg(feature = "inertia")]
697 pub fn send_inertia_page(&mut self, mut page: InertiaPage) {
698 if page.version().is_empty() {
699 page.set_version(zino_core::datetime::DateTime::current_timestamp().to_string());
700 }
701 self.insert_header("vary", "x-inertia");
702 self.insert_header("x-inertia", true);
703 if let Some(url) = page.redirect_url() {
704 self.insert_header("x-inertia-location", url);
705 } else {
706 self.set_json_response(page.into_json_response());
707 }
708 }
709
710 pub fn emit<Ctx: RequestContext>(mut self, ctx: &Ctx) -> Self {
712 if ctx
713 .get_header("prefer")
714 .is_some_and(|s| s.split(';').any(|p| p.trim() == "return=data-only"))
715 {
716 self.set_data_transformer(|data| Ok(serde_json::to_vec(&data)?.into()));
717 }
718 #[cfg(feature = "inertia")]
719 if self.get_header("x-inertia").is_none()
720 && ctx.get_header("x-inertia-partial-component").is_some()
721 {
722 match InertiaPage::partial_reload(ctx) {
723 Ok(mut page) => {
724 if let JsonValue::Object(data) = &mut self.json_data {
725 page.append_props(data);
726 }
727 self.send_inertia_page(page);
728 }
729 Err(err) => self.set_error_message(err),
730 }
731 }
732 self
733 }
734
735 pub fn finalize(mut self) -> HeaderMap<String> {
737 let request_id = self.request_id();
738 if !request_id.is_nil() {
739 self.insert_header("x-request-id", request_id.to_string());
740 }
741
742 let (traceparent, tracestate) = self.trace_context();
743 self.insert_header("traceparent", traceparent);
744 self.insert_header("tracestate", tracestate);
745
746 let duration = self.response_time();
747 self.record_server_timing("total", None, Some(duration));
748 self.insert_header("server-timing", self.server_timing());
749 self.headers
750 }
751}
752
753impl Response<StatusCode> {
754 #[inline]
756 pub fn ok() -> Self {
757 Response::new(StatusCode::OK)
758 }
759
760 #[inline]
762 pub fn created() -> Self {
763 Response::new(StatusCode::CREATED)
764 }
765
766 #[inline]
768 pub fn redirect(uri: &str) -> Self {
769 let mut res = Response::new(StatusCode::SEE_OTHER);
770 res.insert_header("location", uri);
771 res
772 }
773
774 #[inline]
776 pub fn temporary_redirect(uri: &str) -> Self {
777 let mut res = Response::new(StatusCode::TEMPORARY_REDIRECT);
778 res.insert_header("location", uri);
779 res
780 }
781
782 #[inline]
784 pub fn permanent_redirect(uri: &str) -> Self {
785 let mut res = Response::new(StatusCode::PERMANENT_REDIRECT);
786 res.insert_header("location", uri);
787 res
788 }
789
790 #[inline]
792 pub fn bad_request() -> Self {
793 Response::new(StatusCode::BAD_REQUEST)
794 }
795
796 #[inline]
798 pub fn unauthorized() -> Self {
799 Response::new(StatusCode::UNAUTHORIZED)
800 }
801
802 #[inline]
804 pub fn forbidden() -> Self {
805 Response::new(StatusCode::FORBIDDEN)
806 }
807
808 #[inline]
810 pub fn not_found() -> Self {
811 Response::new(StatusCode::NOT_FOUND)
812 }
813
814 #[inline]
816 pub fn method_not_allowed() -> Self {
817 Response::new(StatusCode::METHOD_NOT_ALLOWED)
818 }
819
820 #[inline]
822 pub fn conflict() -> Self {
823 Response::new(StatusCode::CONFLICT)
824 }
825
826 #[inline]
828 pub fn internal_server_error() -> Self {
829 Response::new(StatusCode::INTERNAL_SERVER_ERROR)
830 }
831
832 #[inline]
834 pub fn service_unavailable() -> Self {
835 Response::new(StatusCode::SERVICE_UNAVAILABLE)
836 }
837}
838
839impl<S: ResponseCode> Default for Response<S> {
840 #[inline]
841 fn default() -> Self {
842 Self::new(S::OK)
843 }
844}
845
846impl<S: ResponseCode> From<Validation> for Response<S> {
847 fn from(validation: Validation) -> Self {
848 if validation.is_success() {
849 Self::new(S::OK)
850 } else {
851 let mut res = Self::new(S::BAD_REQUEST);
852 res.set_validation_data(validation);
853 res
854 }
855 }
856}