1use 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
51pub trait RequestContext {
53 type Method: AsRef<str>;
55 type Uri;
57
58 fn request_method(&self) -> &Self::Method;
60
61 fn original_uri(&self) -> &Self::Uri;
63
64 fn matched_route(&self) -> Cow<'_, str>;
66
67 fn request_path(&self) -> &str;
69
70 fn get_query_string(&self) -> Option<&str>;
72
73 fn get_header(&self, name: &str) -> Option<&str>;
75
76 fn client_ip(&self) -> Option<IpAddr>;
78
79 fn get_context(&self) -> Option<Arc<Context>>;
81
82 fn get_data<T: Clone + Send + Sync + 'static>(&self) -> Option<T>;
84
85 fn set_data<T: Clone + Send + Sync + 'static>(&mut self, value: T) -> Option<T>;
88
89 async fn read_body_bytes(&mut self) -> Result<Bytes, Error>;
91
92 #[inline]
94 fn path_segments(&self) -> Vec<&str> {
95 self.request_path().trim_matches('/').split('/').collect()
96 }
97
98 fn new_context(&self) -> Context {
100 #[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 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 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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[inline]
229 fn request_id(&self) -> Uuid {
230 self.get_context()
231 .map(|ctx| ctx.request_id())
232 .unwrap_or_default()
233 }
234
235 #[inline]
237 fn trace_id(&self) -> Uuid {
238 self.get_context()
239 .map(|ctx| ctx.trace_id())
240 .unwrap_or_default()
241 }
242
243 #[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 #[cfg(feature = "i18n")]
252 #[inline]
253 fn locale(&self) -> Option<LanguageIdentifier> {
254 self.get_context().and_then(|ctx| ctx.locale().cloned())
255 }
256
257 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 #[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 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 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}