Skip to main content

zino_http/request/
mod.rs

1//! Request context and validation.
2
3use crate::{
4    helper,
5    response::{Rejection, Response, ResponseCode},
6};
7use bytes::Bytes;
8use multer::Multipart;
9use serde::de::DeserializeOwned;
10use std::{borrow::Cow, net::IpAddr, str::FromStr, sync::Arc, time::Instant};
11use zino_channel::{CloudEvent, Subscription};
12use zino_core::{
13    JsonValue, Map, SharedString, Uuid,
14    application::Agent,
15    error::Error,
16    extension::HeaderMapExt,
17    model::{ModelHooks, Query},
18    trace::{TraceContext, TraceState},
19    warn,
20};
21use zino_storage::NamedFile;
22
23#[cfg(feature = "auth")]
24use zino_auth::{AccessKeyId, Authentication, ParseSecurityTokenError, SecurityToken, SessionId};
25
26#[cfg(feature = "auth")]
27use zino_core::{datetime::DateTime, extension::JsonObjectExt, validation::Validation};
28
29#[cfg(feature = "cookie")]
30use cookie::{Cookie, SameSite};
31
32#[cfg(feature = "jwt")]
33use jwt_simple::algorithms::MACLike;
34#[cfg(feature = "jwt")]
35use zino_auth::JwtClaims;
36
37#[cfg(any(feature = "cookie", feature = "jwt"))]
38use std::time::Duration;
39
40#[cfg(feature = "i18n")]
41use fluent::FluentArgs;
42#[cfg(feature = "i18n")]
43use unic_langid::LanguageIdentifier;
44#[cfg(feature = "i18n")]
45use zino_core::i18n::{Intl, IntlError};
46
47mod context;
48
49pub use context::Context;
50
51/// Request context.
52pub trait RequestContext {
53    /// The method type.
54    type Method: AsRef<str>;
55    /// The uri type.
56    type Uri;
57
58    /// Returns the request method.
59    fn request_method(&self) -> &Self::Method;
60
61    /// Returns the original request URI regardless of nesting.
62    fn original_uri(&self) -> &Self::Uri;
63
64    /// Returns the route that matches the request.
65    fn matched_route(&self) -> Cow<'_, str>;
66
67    /// Returns the request path regardless of nesting.
68    fn request_path(&self) -> &str;
69
70    /// Gets the query string of the request.
71    fn get_query_string(&self) -> Option<&str>;
72
73    /// Gets an HTTP header value with the given name.
74    fn get_header(&self, name: &str) -> Option<&str>;
75
76    /// Returns the client's remote IP.
77    fn client_ip(&self) -> Option<IpAddr>;
78
79    /// Gets the request context.
80    fn get_context(&self) -> Option<Arc<Context>>;
81
82    /// Gets the request scoped data.
83    fn get_data<T: Clone + Send + Sync + 'static>(&self) -> Option<T>;
84
85    /// Sets the request scoped data and returns the old value
86    /// if an item of this type was already stored.
87    fn set_data<T: Clone + Send + Sync + 'static>(&mut self, value: T) -> Option<T>;
88
89    /// Reads the entire request body into `Bytes`.
90    async fn read_body_bytes(&mut self) -> Result<Bytes, Error>;
91
92    /// Returns the request path segments.
93    #[inline]
94    fn path_segments(&self) -> Vec<&str> {
95        self.request_path().trim_matches('/').split('/').collect()
96    }
97
98    /// Creates a new request context.
99    fn new_context(&self) -> Context {
100        // Emit metrics.
101        #[cfg(feature = "metrics")]
102        {
103            metrics::gauge!("zino_http_requests_in_flight").increment(1.0);
104            metrics::counter!(
105                "zino_http_requests_total",
106                "method" => self.request_method().as_ref().to_owned(),
107                "route" => self.matched_route().into_owned(),
108            )
109            .increment(1);
110        }
111
112        // Parse tracing headers.
113        let request_id = self
114            .get_header("x-request-id")
115            .and_then(|s| s.parse().ok())
116            .unwrap_or_else(Uuid::now_v7);
117        let trace_id = self
118            .get_trace_context()
119            .map_or_else(Uuid::now_v7, |t| Uuid::from_u128(t.trace_id()));
120        let session_id = self
121            .get_header("x-session-id")
122            .or_else(|| self.get_header("session_id"))
123            .and_then(|s| s.parse().ok());
124
125        // Generate new context.
126        let mut ctx = Context::new(request_id);
127        ctx.set_instance(self.request_path().to_owned());
128        ctx.set_trace_id(trace_id);
129        ctx.set_session_id(session_id);
130
131        // Set locale.
132        #[cfg(feature = "i18n")]
133        {
134            #[cfg(feature = "cookie")]
135            if let Some(cookie) = self.get_cookie("locale") {
136                if let Ok(locale) = cookie.value().parse() {
137                    ctx.set_locale(locale);
138                    return ctx;
139                }
140            }
141
142            if let Some(locale) = self
143                .get_header("accept-language")
144                .and_then(Intl::select_language)
145            {
146                ctx.set_locale(locale);
147            } else {
148                ctx.set_locale(Intl::default_locale().to_owned());
149            }
150        }
151        ctx
152    }
153
154    /// Returns the trace context by parsing the `traceparent` and `tracestate` header values.
155    #[inline]
156    fn get_trace_context(&self) -> Option<TraceContext> {
157        let traceparent = self.get_header("traceparent")?;
158        let mut trace_context = TraceContext::from_traceparent(traceparent)?;
159        if let Some(tracestate) = self.get_header("tracestate") {
160            *trace_context.trace_state_mut() = TraceState::from_tracestate(tracestate);
161        }
162        Some(trace_context)
163    }
164
165    /// Creates a new `TraceContext`.
166    fn new_trace_context(&self) -> TraceContext {
167        let mut trace_context = self
168            .get_trace_context()
169            .or_else(|| {
170                self.get_context()
171                    .map(|ctx| TraceContext::with_trace_id(ctx.trace_id()))
172            })
173            .map(|t| t.child())
174            .unwrap_or_default();
175        trace_context.record_trace_state();
176        trace_context
177    }
178
179    /// Creates a new cookie with the given name and value.
180    #[cfg(feature = "cookie")]
181    fn new_cookie(
182        &self,
183        name: SharedString,
184        value: SharedString,
185        max_age: Option<Duration>,
186    ) -> Cookie<'static> {
187        let mut cookie_builder = Cookie::build((name, value))
188            .http_only(true)
189            .secure(true)
190            .same_site(SameSite::Lax)
191            .path(self.request_path().to_owned());
192        if let Some(max_age) = max_age.and_then(|d| d.try_into().ok()) {
193            cookie_builder = cookie_builder.max_age(max_age);
194        }
195        cookie_builder.build()
196    }
197
198    /// Gets a cookie with the given name.
199    #[cfg(feature = "cookie")]
200    fn get_cookie(&self, name: &str) -> Option<Cookie<'_>> {
201        self.get_header("cookie")?.split(';').find_map(|cookie| {
202            if let Some((key, value)) = cookie.trim().split_once('=') {
203                let key = key.trim();
204                (key == name).then(|| Cookie::new(key, value.trim()))
205            } else {
206                None
207            }
208        })
209    }
210
211    /// Returns the start time.
212    #[inline]
213    fn start_time(&self) -> Instant {
214        self.get_context()
215            .map(|ctx| ctx.start_time())
216            .unwrap_or_else(Instant::now)
217    }
218
219    /// Returns the instance.
220    #[inline]
221    fn instance(&self) -> String {
222        self.get_context()
223            .map(|ctx| ctx.instance().to_owned())
224            .unwrap_or_else(|| self.request_path().to_owned())
225    }
226
227    /// Returns the request ID.
228    #[inline]
229    fn request_id(&self) -> Uuid {
230        self.get_context()
231            .map(|ctx| ctx.request_id())
232            .unwrap_or_default()
233    }
234
235    /// Returns the trace ID.
236    #[inline]
237    fn trace_id(&self) -> Uuid {
238        self.get_context()
239            .map(|ctx| ctx.trace_id())
240            .unwrap_or_default()
241    }
242
243    /// Returns the session ID.
244    #[inline]
245    fn session_id(&self) -> Option<String> {
246        self.get_context()
247            .and_then(|ctx| ctx.session_id().map(|s| s.to_owned()))
248    }
249
250    /// Returns the locale.
251    #[cfg(feature = "i18n")]
252    #[inline]
253    fn locale(&self) -> Option<LanguageIdentifier> {
254        self.get_context().and_then(|ctx| ctx.locale().cloned())
255    }
256
257    /// Gets the data type by parsing the `content-type` header.
258    ///
259    /// # Note
260    ///
261    /// Currently, we support the following values: `bytes` | `csv` | `form` | `json` | `multipart`
262    /// | `ndjson` | `text`.
263    fn data_type(&self) -> Option<&str> {
264        self.get_header("content-type")
265            .map(|content_type| {
266                if let Some((essence, _)) = content_type.split_once(';') {
267                    essence
268                } else {
269                    content_type
270                }
271            })
272            .map(helper::get_data_type)
273    }
274
275    /// Gets the route parameter by name.
276    /// The name should not include `:`, `*`, `{` or `}`.
277    ///
278    /// # Note
279    ///
280    /// Please note that it does not handle the percent-decoding.
281    /// You can use [`decode_param()`](Self::decode_param) or [`parse_param()`](Self::parse_param)
282    /// if you need percent-decoding.
283    fn get_param(&self, name: &str) -> Option<&str> {
284        const CAPTURES: [char; 4] = [':', '*', '{', '}'];
285        if let Some(index) = self
286            .matched_route()
287            .split('/')
288            .position(|segment| segment.trim_matches(CAPTURES.as_slice()) == name)
289        {
290            self.request_path().splitn(index + 2, '/').nth(index)
291        } else {
292            None
293        }
294    }
295
296    /// Decodes the UTF-8 percent-encoded route parameter by name.
297    fn decode_param(&self, name: &str) -> Result<Cow<'_, str>, Rejection> {
298        if let Some(value) = self.get_param(name) {
299            percent_encoding::percent_decode_str(value)
300                .decode_utf8()
301                .map_err(|err| Rejection::from_validation_entry(name.to_owned(), err).context(self))
302        } else {
303            Err(Rejection::from_validation_entry(
304                name.to_owned(),
305                warn!("param `{}` does not exist", name),
306            )
307            .context(self))
308        }
309    }
310
311    /// Parses the route parameter by name as an instance of type `T`.
312    /// The name should not include `:`, `*`, `{` or `}`.
313    fn parse_param<T: FromStr<Err: Into<Error>>>(&self, name: &str) -> Result<T, Rejection> {
314        if let Some(param) = self.get_param(name) {
315            percent_encoding::percent_decode_str(param)
316                .decode_utf8_lossy()
317                .parse::<T>()
318                .map_err(|err| Rejection::from_validation_entry(name.to_owned(), err).context(self))
319        } else {
320            Err(Rejection::from_validation_entry(
321                name.to_owned(),
322                warn!("param `{}` does not exist", name),
323            )
324            .context(self))
325        }
326    }
327
328    /// Gets the query value of the URI by name.
329    ///
330    /// # Note
331    ///
332    /// Please note that it does not handle the percent-decoding.
333    /// You can use [`decode_query()`](Self::decode_query) or [`parse_query()`](Self::parse_query)
334    /// if you need percent-decoding.
335    fn get_query(&self, name: &str) -> Option<&str> {
336        self.get_query_string()?.split('&').find_map(|param| {
337            if let Some((key, value)) = param.split_once('=') {
338                (key == name).then_some(value)
339            } else {
340                None
341            }
342        })
343    }
344
345    /// Decodes the UTF-8 percent-encoded query value of the URI by name.
346    fn decode_query(&self, name: &str) -> Result<Cow<'_, str>, Rejection> {
347        if let Some(value) = self.get_query(name) {
348            percent_encoding::percent_decode_str(value)
349                .decode_utf8()
350                .map_err(|err| Rejection::from_validation_entry(name.to_owned(), err).context(self))
351        } else {
352            Err(Rejection::from_validation_entry(
353                name.to_owned(),
354                warn!("query value `{}` does not exist", name),
355            )
356            .context(self))
357        }
358    }
359
360    /// Parses the query as an instance of type `T`.
361    /// Returns a default value of `T` when the query is empty.
362    /// If the query has a `timestamp` parameter, it will be used to prevent replay attacks.
363    fn parse_query<T: Default + DeserializeOwned>(&self) -> Result<T, Rejection> {
364        if let Some(query) = self.get_query_string() {
365            #[cfg(feature = "jwt")]
366            if let Some(timestamp) = self.get_query("timestamp").and_then(|s| s.parse().ok()) {
367                let duration = DateTime::from_timestamp(timestamp).span_between_now();
368                if duration > zino_auth::default_time_tolerance() {
369                    let err = warn!("timestamp `{}` can not be trusted", timestamp);
370                    let rejection = Rejection::from_validation_entry("timestamp", err);
371                    return Err(rejection.context(self));
372                }
373            }
374            serde_qs::from_str::<T>(query)
375                .map_err(|err| Rejection::from_validation_entry("query", err).context(self))
376        } else {
377            Ok(T::default())
378        }
379    }
380
381    /// Parses the request body as an instance of type `T`.
382    ///
383    /// # Note
384    ///
385    /// Currently, we have built-in support for the following `content-type` header values:
386    ///
387    /// - `application/json`
388    /// - `application/problem+json`
389    /// - `application/x-www-form-urlencoded`
390    async fn parse_body<T: DeserializeOwned>(&mut self) -> Result<T, Rejection> {
391        let data_type = self.data_type().unwrap_or("form");
392        if data_type.contains('/') {
393            let err = warn!(
394                "deserialization of the data type `{}` is unsupported",
395                data_type
396            );
397            let rejection = Rejection::from_validation_entry("data_type", err).context(self);
398            return Err(rejection);
399        }
400
401        let is_form = data_type == "form";
402        let bytes = self
403            .read_body_bytes()
404            .await
405            .map_err(|err| Rejection::from_validation_entry("body", err).context(self))?;
406        if is_form {
407            serde_qs::from_bytes(&bytes)
408                .map_err(|err| Rejection::from_validation_entry("body", err).context(self))
409        } else {
410            serde_json::from_slice(&bytes)
411                .map_err(|err| Rejection::from_validation_entry("body", err).context(self))
412        }
413    }
414
415    /// Parses the request body as a multipart, which is commonly used with file uploads.
416    async fn parse_multipart(&mut self) -> Result<Multipart<'_>, Rejection> {
417        let Some(content_type) = self.get_header("content-type") else {
418            return Err(Rejection::from_validation_entry(
419                "content_type",
420                warn!("invalid `content-type` header"),
421            )
422            .context(self));
423        };
424        match multer::parse_boundary(content_type) {
425            Ok(boundary) => {
426                let result = self.read_body_bytes().await.map_err(|err| err.to_string());
427                let stream = futures::stream::once(async { result });
428                Ok(Multipart::new(stream, boundary))
429            }
430            Err(err) => Err(Rejection::from_validation_entry("boundary", err).context(self)),
431        }
432    }
433
434    /// Parses the request body as a file.
435    async fn parse_file(&mut self) -> Result<NamedFile, Rejection> {
436        let multipart = self.parse_multipart().await?;
437        NamedFile::try_from_multipart(multipart)
438            .await
439            .map_err(|err| Rejection::from_validation_entry("body", err).context(self))
440    }
441
442    /// Parses the request body as a list of files.
443    async fn parse_files(&mut self) -> Result<Vec<NamedFile>, Rejection> {
444        let multipart = self.parse_multipart().await?;
445        NamedFile::try_collect_from_multipart(multipart)
446            .await
447            .map_err(|err| Rejection::from_validation_entry("body", err).context(self))
448    }
449
450    /// Parses the multipart form as an instance of `T` with the `name` and a list of files.
451    async fn parse_form<T: DeserializeOwned>(
452        &mut self,
453        name: &str,
454    ) -> Result<(Option<T>, Vec<NamedFile>), Rejection> {
455        let multipart = self.parse_multipart().await?;
456        helper::parse_form(multipart, name)
457            .await
458            .map_err(|err| Rejection::from_validation_entry("body", err).context(self))
459    }
460
461    /// Parses the `multipart/form-data` as an instance of type `T` and a list of files.
462    async fn parse_form_data<T: DeserializeOwned>(
463        &mut self,
464    ) -> Result<(T, Vec<NamedFile>), Rejection> {
465        let multipart = self.parse_multipart().await?;
466        helper::parse_form_data(multipart)
467            .await
468            .map_err(|err| Rejection::from_validation_entry("body", err).context(self))
469    }
470
471    /// Attempts to construct an instance of `Authentication` from an HTTP request.
472    /// The value is extracted from the query or the `authorization` header.
473    /// By default, the `Accept` header value is ignored and
474    /// the canonicalized resource is set to the request path.
475    #[cfg(feature = "auth")]
476    fn parse_authentication(&self) -> Result<Authentication, Rejection> {
477        let method = self.request_method();
478        let query = self.parse_query::<Map>().unwrap_or_default();
479        let mut authentication = Authentication::new(method.as_ref());
480        let mut validation = Validation::new();
481        if let Some(signature) = query.get_str("signature") {
482            authentication.set_signature(signature.to_owned());
483            if let Some(access_key_id) = query.parse_string("access_key_id") {
484                authentication.set_access_key_id(access_key_id);
485            } else {
486                validation.record("access_key_id", "should be nonempty");
487            }
488            if let Some(Ok(secs)) = query.parse_i64("expires") {
489                if DateTime::now().timestamp() <= secs {
490                    let expires = DateTime::from_timestamp(secs);
491                    authentication.set_expires(Some(expires));
492                } else {
493                    validation.record("expires", "valid period has expired");
494                }
495            } else {
496                validation.record("expires", "invalid timestamp");
497            }
498            if !validation.is_success() {
499                return Err(Rejection::bad_request(validation).context(self));
500            }
501        } else if let Some(authorization) = self.get_header("authorization") {
502            if let Some((service_name, token)) = authorization.split_once(' ') {
503                authentication.set_service_name(service_name);
504                if let Some((access_key_id, signature)) = token.split_once(':') {
505                    authentication.set_access_key_id(access_key_id);
506                    authentication.set_signature(signature.to_owned());
507                } else {
508                    validation.record("authorization", "invalid header value");
509                }
510            } else {
511                validation.record("authorization", "invalid service name");
512            }
513            if !validation.is_success() {
514                return Err(Rejection::bad_request(validation).context(self));
515            }
516        }
517        if let Some(content_md5) = self.get_header("content-md5") {
518            authentication.set_content_md5(content_md5.to_owned());
519        }
520        if let Some(date) = self.get_header("date") {
521            match DateTime::parse_utc_str(date) {
522                Ok(date) => {
523                    #[cfg(feature = "jwt")]
524                    if date.span_between_now() <= zino_auth::default_time_tolerance() {
525                        authentication.set_date_header("date", date);
526                    } else {
527                        validation.record("date", "untrusted date");
528                    }
529                    #[cfg(not(feature = "jwt"))]
530                    authentication.set_date_header("date", date);
531                }
532                Err(err) => {
533                    validation.record_fail("date", err);
534                    return Err(Rejection::bad_request(validation).context(self));
535                }
536            }
537        }
538        authentication.set_content_type(self.get_header("content-type").map(|s| s.to_owned()));
539        authentication.set_resource(self.request_path().to_owned(), None);
540        Ok(authentication)
541    }
542
543    /// Attempts to construct an instance of `AccessKeyId` from an HTTP request.
544    /// The value is extracted from the query parameter `access_key_id`
545    /// or the `authorization` header.
546    #[cfg(feature = "auth")]
547    fn parse_access_key_id(&self) -> Result<AccessKeyId, Rejection> {
548        if let Some(access_key_id) = self.get_query("access_key_id") {
549            Ok(access_key_id.into())
550        } else {
551            let mut validation = Validation::new();
552            if let Some(authorization) = self.get_header("authorization") {
553                if let Some((_, token)) = authorization.split_once(' ') {
554                    let access_key_id = if let Some((access_key_id, _)) = token.split_once(':') {
555                        access_key_id
556                    } else {
557                        token
558                    };
559                    return Ok(access_key_id.into());
560                } else {
561                    validation.record("authorization", "invalid service name");
562                }
563            } else {
564                validation.record("authorization", "invalid value to get the access key id");
565            }
566            Err(Rejection::bad_request(validation).context(self))
567        }
568    }
569
570    /// Attempts to construct an instance of `SecurityToken` from an HTTP request.
571    /// The value is extracted from the `x-security-token` header.
572    #[cfg(feature = "auth")]
573    fn parse_security_token(&self, key: &[u8]) -> Result<SecurityToken, Rejection> {
574        use ParseSecurityTokenError::*;
575        let query = self.parse_query::<Map>()?;
576        let mut validation = Validation::new();
577        if let Some(token) = self
578            .get_header("x-security-token")
579            .or_else(|| query.get_str("security_token"))
580        {
581            match SecurityToken::parse_with(token.to_owned(), key) {
582                Ok(security_token) => {
583                    if let Some(access_key_id) = query.get_str("access_key_id") {
584                        if security_token.access_key_id().as_str() != access_key_id {
585                            validation.record("access_key_id", "untrusted access key ID");
586                        }
587                    }
588                    if let Some(Ok(expires)) = query.parse_i64("expires") {
589                        if security_token.expires_at().timestamp() != expires {
590                            validation.record("expires", "untrusted timestamp");
591                        }
592                    }
593                    if validation.is_success() {
594                        return Ok(security_token);
595                    }
596                }
597                Err(err) => {
598                    let field = match err {
599                        DecodeError(_) | InvalidFormat => "security_token",
600                        ParseExpiresError(_) | ValidPeriodExpired(_) => "expires",
601                    };
602                    validation.record_fail(field, err);
603                }
604            }
605        } else {
606            validation.record("security_token", "should be nonempty");
607        }
608        Err(Rejection::bad_request(validation).context(self))
609    }
610
611    /// Attempts to construct an instance of `SessionId` from an HTTP request.
612    /// The value is extracted from the `x-session-id` or `session-id` header.
613    #[cfg(feature = "auth")]
614    fn parse_session_id(&self) -> Result<SessionId, Rejection> {
615        self.get_header("x-session-id")
616            .or_else(|| self.get_header("session-id"))
617            .ok_or_else(|| {
618                Rejection::from_validation_entry(
619                    "session_id",
620                    warn!("a `session-id` or `x-session-id` header is required"),
621                )
622                .context(self)
623            })
624            .and_then(|session_id| {
625                SessionId::parse(session_id).map_err(|err| {
626                    Rejection::from_validation_entry("session_id", err).context(self)
627                })
628            })
629    }
630
631    /// Attempts to construct an instance of `JwtClaims` from an HTTP request.
632    /// The value is extracted from the query parameter `access_token` or
633    /// the `authorization` header.
634    #[cfg(feature = "jwt")]
635    fn parse_jwt_claims<T, K>(&self, key: &K) -> Result<JwtClaims<T>, Rejection>
636    where
637        T: Default + serde::Serialize + DeserializeOwned,
638        K: MACLike,
639    {
640        let (param, mut token) = match self.get_query("access_token") {
641            Some(access_token) => ("access_token", access_token),
642            None => ("authorization", ""),
643        };
644        if let Some(authorization) = self.get_header("authorization") {
645            token = authorization
646                .strip_prefix("Bearer ")
647                .unwrap_or(authorization);
648        } else if cfg!(feature = "cookie") {
649            let value = self.get_header("cookie").and_then(|s| {
650                s.split(';').find_map(|cookie| {
651                    if let Some((key, value)) = cookie.split_once('=') {
652                        (key == "access_token").then_some(value)
653                    } else {
654                        None
655                    }
656                })
657            });
658            if let Some(access_token) = value {
659                token = access_token;
660            }
661        }
662        if token.is_empty() {
663            let mut validation = Validation::new();
664            validation.record(param, "JWT should be nonempty");
665            return Err(Rejection::bad_request(validation).context(self));
666        }
667
668        let mut options = zino_auth::default_verification_options();
669        options.reject_before = self
670            .get_query("timestamp")
671            .and_then(|s| s.parse().ok())
672            .map(|i| Duration::from_secs(i).into());
673        options.required_nonce = self.get_query("nonce").map(|s| s.to_owned());
674
675        match key.verify_token(token, Some(options)) {
676            Ok(claims) => Ok(claims.into()),
677            Err(err) => {
678                let rejection =
679                    Rejection::with_message("401 Unauthorized: invalid or expired token");
680                tracing::warn!("JWT verification failed: {err}");
681                Err(rejection.context(self))
682            }
683        }
684    }
685
686    /// Returns a `Response` or `Rejection` from a model query validation.
687    /// The data is extracted from [`parse_query()`](RequestContext::parse_query).
688    fn query_validation<S>(&self, query: &mut Query) -> Result<Response<S>, Rejection>
689    where
690        Self: Sized,
691        S: ResponseCode,
692    {
693        match self.parse_query() {
694            Ok(data) => {
695                let validation = query.read_map(&data);
696                if validation.is_success() {
697                    Ok(Response::with_context(S::OK, self))
698                } else {
699                    Err(Rejection::bad_request(validation).context(self))
700                }
701            }
702            Err(rejection) => Err(rejection),
703        }
704    }
705
706    /// Returns a `Response` or `Rejection` from a model validation.
707    /// The data is extracted from [`parse_body()`](RequestContext::parse_body).
708    async fn model_validation<M, S>(&mut self, model: &mut M) -> Result<Response<S>, Rejection>
709    where
710        Self: Sized,
711        M: ModelHooks,
712        S: ResponseCode,
713    {
714        let data_type = self.data_type().unwrap_or("form");
715        if data_type.contains('/') {
716            let err = warn!(
717                "deserialization of the data type `{}` is unsupported",
718                data_type
719            );
720            let rejection = Rejection::from_validation_entry("data_type", err).context(self);
721            return Err(rejection);
722        }
723        M::before_extract()
724            .await
725            .map_err(|err| Rejection::from_error(err).context(self))?;
726
727        let is_form = data_type == "form";
728        let bytes = self
729            .read_body_bytes()
730            .await
731            .map_err(|err| Rejection::from_validation_entry("body", err).context(self))?;
732        let extension = self.get_data::<M::Extension>();
733        if is_form {
734            let mut data = serde_qs::from_bytes(&bytes)
735                .map_err(|err| Rejection::from_validation_entry("body", err).context(self))?;
736            match M::before_validation(&mut data, extension.as_ref()).await {
737                Ok(()) => {
738                    let validation = model.read_map(&data);
739                    model
740                        .after_validation(&mut data)
741                        .await
742                        .map_err(|err| Rejection::from_error(err).context(self))?;
743                    if let Some(extension) = extension {
744                        model
745                            .after_extract(extension)
746                            .await
747                            .map_err(|err| Rejection::from_error(err).context(self))?;
748                    }
749                    if validation.is_success() {
750                        Ok(Response::with_context(S::OK, self))
751                    } else {
752                        Err(Rejection::bad_request(validation).context(self))
753                    }
754                }
755                Err(err) => Err(Rejection::from_error(err).context(self)),
756            }
757        } else {
758            let mut data = serde_json::from_slice(&bytes)
759                .map_err(|err| Rejection::from_validation_entry("body", err).context(self))?;
760            match M::before_validation(&mut data, extension.as_ref()).await {
761                Ok(()) => {
762                    let validation = model.read_map(&data);
763                    model
764                        .after_validation(&mut data)
765                        .await
766                        .map_err(|err| Rejection::from_error(err).context(self))?;
767                    if let Some(extension) = extension {
768                        model
769                            .after_extract(extension)
770                            .await
771                            .map_err(|err| Rejection::from_error(err).context(self))?;
772                    }
773                    if validation.is_success() {
774                        Ok(Response::with_context(S::OK, self))
775                    } else {
776                        Err(Rejection::bad_request(validation).context(self))
777                    }
778                }
779                Err(err) => Err(Rejection::from_error(err).context(self)),
780            }
781        }
782    }
783
784    /// Makes an HTTP request to the provided URL.
785    async fn fetch(&self, url: &str, options: Option<&Map>) -> Result<reqwest::Response, Error> {
786        let trace_context = self.new_trace_context();
787        Agent::request_builder(url, options)?
788            .header("traceparent", trace_context.traceparent())
789            .header("tracestate", trace_context.tracestate())
790            .send()
791            .await
792            .map_err(Error::from)
793    }
794
795    /// Makes an HTTP request to the provided URL and
796    /// deserializes the response body via JSON.
797    async fn fetch_json<T: DeserializeOwned>(
798        &self,
799        url: &str,
800        options: Option<&Map>,
801    ) -> Result<T, Error> {
802        let response = self.fetch(url, options).await?.error_for_status()?;
803        let data = if response.headers().has_json_content_type() {
804            response.json().await?
805        } else {
806            let text = response.text().await?;
807            serde_json::from_str(&text)?
808        };
809        Ok(data)
810    }
811
812    /// Translates the localization message.
813    #[cfg(feature = "i18n")]
814    fn translate(
815        &self,
816        message: &str,
817        args: Option<FluentArgs>,
818    ) -> Result<SharedString, IntlError> {
819        if let Some(locale) = self.locale() {
820            Intl::translate_with(message, args, &locale)
821        } else {
822            Intl::translate(message, args)
823        }
824    }
825
826    /// Constructs a new subscription instance.
827    fn subscription(&self) -> Subscription {
828        let mut subscription = self.parse_query::<Subscription>().unwrap_or_default();
829        if subscription.session_id().is_none()
830            && let Some(session_id) = self.session_id()
831        {
832            subscription.set_session_id(Some(session_id));
833        }
834        subscription
835    }
836
837    /// Constructs a new cloud event instance.
838    fn cloud_event(&self, event_type: SharedString, data: JsonValue) -> CloudEvent {
839        let id = self.request_id();
840        let source = self.instance();
841        let mut event = CloudEvent::new(id, source, event_type);
842        if let Some(session_id) = self.session_id() {
843            event.set_session_id(session_id);
844        }
845        event.set_data(data);
846        event
847    }
848}