1use crate::AuthContext;
4use crate::session::SessionRequest;
5use crate::types::RequestExtensions;
6use crate::utils::LockUnpoisoned;
7use crate::utils::json::JsValue;
8use crate::wire::{SessionView, UserView};
9use crate::{AuthError, AuthRequest, AuthResult, AuthSchema, Headers, HttpMethod};
10use serde::{Serialize, de::DeserializeOwned};
11use std::collections::HashMap;
12use std::marker::PhantomData;
13use std::sync::{Arc, Mutex};
14
15#[derive(Clone, Debug)]
17pub struct EndpointDefinition {
18 pub name: &'static str,
19 pub operation_id: &'static str,
20 pub path: Option<String>,
21 pub method: HttpMethod,
22}
23
24#[derive(Clone, Debug)]
26pub struct ServerEndpoint<T> {
27 plugin: &'static str,
28 name: &'static str,
29 body: Option<JsValue>,
30 query: Option<JsValue>,
31 params: Option<JsValue>,
32 output: PhantomData<fn() -> T>,
33}
34
35impl<T> ServerEndpoint<T> {
36 #[must_use]
37 pub const fn new(plugin: &'static str, name: &'static str) -> Self {
38 Self {
39 plugin,
40 name,
41 body: None,
42 query: None,
43 params: None,
44 output: PhantomData,
45 }
46 }
47
48 pub fn with_body(mut self, body: &impl Serialize) -> AuthResult<Self> {
51 self.body = Some(crate::utils::json::parse_value(
52 &crate::utils::json::to_string(body)?,
53 )?);
54 Ok(self)
55 }
56
57 pub fn with_query(mut self, query: &impl Serialize) -> AuthResult<Self> {
60 self.query = Some(crate::utils::json::parse_value(
61 &crate::utils::json::to_string(query)?,
62 )?);
63 Ok(self)
64 }
65
66 #[must_use]
67 pub fn with_body_value(mut self, body: JsValue) -> Self {
68 self.body = Some(body);
69 self
70 }
71
72 #[must_use]
73 pub fn with_query_value(mut self, query: JsValue) -> Self {
74 self.query = Some(query);
75 self
76 }
77
78 pub fn with_params(mut self, params: &impl Serialize) -> AuthResult<Self> {
83 self.params = Some(crate::utils::json::parse_value(
84 &crate::utils::json::to_string(params)?,
85 )?);
86 Ok(self)
87 }
88
89 #[must_use]
90 pub fn with_params_value(mut self, params: JsValue) -> Self {
91 self.params = Some(params);
92 self
93 }
94
95 #[must_use]
96 pub const fn params(&self) -> Option<&JsValue> {
97 self.params.as_ref()
98 }
99
100 #[must_use]
101 pub const fn plugin(&self) -> &'static str {
102 self.plugin
103 }
104
105 #[must_use]
106 pub const fn name(&self) -> &'static str {
107 self.name
108 }
109
110 #[must_use]
111 pub fn input(self) -> (Option<JsValue>, Option<JsValue>) {
112 (self.body, self.query)
113 }
114}
115
116#[derive(Clone, Debug, Default)]
118pub struct EndpointOptions {
119 pub headers: Option<HashMap<String, String>>,
120 pub request: Option<AuthRequest>,
121 pub method: Option<HttpMethod>,
122}
123
124#[derive(Clone, Debug, Default)]
125struct EndpointState {
126 session: Option<SessionView>,
127 principal: Option<(UserView, SessionView)>,
128 response_headers: Headers,
129 session_observation: Option<(UserView, SessionView)>,
130}
131
132struct VerifiedEndpointUser<S: AuthSchema> {
133 user: S::User,
134 config: Arc<crate::AuthConfig>,
135 database: Arc<dyn crate::AuthStore<S>>,
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub enum EndpointPhase {
141 Raw,
142 Middleware,
143 Handler,
144}
145
146#[derive(Clone, Debug)]
152pub struct EndpointCall {
153 operation_id: String,
154 path: Option<String>,
155 method: Option<HttpMethod>,
156 body: Option<JsValue>,
157 query: Option<JsValue>,
158 params: Option<JsValue>,
159 headers: Option<HashMap<String, String>>,
160 request: Option<AuthRequest>,
161 default_method: HttpMethod,
162 phase: EndpointPhase,
163 extensions: RequestExtensions,
164 state: Arc<Mutex<EndpointState>>,
165}
166
167impl EndpointCall {
168 #[must_use]
169 pub fn new(
170 definition: &EndpointDefinition,
171 body: Option<JsValue>,
172 query: Option<JsValue>,
173 options: EndpointOptions,
174 ) -> Self {
175 Self {
176 operation_id: definition.operation_id.into(),
177 path: definition.path.clone(),
178 method: options.method,
179 body,
180 query,
181 params: None,
182 headers: options.headers.map(|headers| {
183 headers
184 .into_iter()
185 .map(|(name, value)| (name.to_ascii_lowercase(), value))
186 .collect()
187 }),
188 request: options.request.map(fresh_request),
189 default_method: definition.method.clone(),
190 phase: EndpointPhase::Raw,
191 extensions: RequestExtensions::default(),
192 state: Arc::new(Mutex::new(EndpointState::default())),
193 }
194 }
195
196 #[must_use]
199 pub fn from_http_request(request: &AuthRequest, route: &crate::AuthRoute) -> Self {
200 let body = request
201 .extensions()
202 .get::<crate::types::ParsedRequestBody>()
203 .and_then(|body| match body.as_ref() {
204 crate::types::ParsedRequestBody::Value(value) => Some(value.clone()),
205 crate::types::ParsedRequestBody::Opaque(_) => None,
206 });
207 let query = JsValue::Object(
208 request
209 .query
210 .keys()
211 .map(|name| {
212 let values = request.query_values(name).unwrap_or_default();
213 let value = if values.len() > 1 {
214 JsValue::Array(values.iter().cloned().map(JsValue::String).collect())
215 } else {
216 JsValue::String(request.query.get(name).cloned().unwrap_or_default())
217 };
218 (name.clone(), value)
219 })
220 .collect(),
221 );
222 let definition = EndpointDefinition {
223 name: "http",
224 operation_id: "http",
225 path: Some(route.context_path.as_ref().unwrap_or(&route.path).clone()),
226 method: route.method.clone(),
227 };
228 let mut call = Self::new(
229 &definition,
230 body,
231 Some(query),
232 EndpointOptions {
233 headers: Some(request.headers.clone()),
234 request: Some(
235 crate::hooks::current_request_hook_context()
236 .map_or_else(|| request.clone(), |context| context.request),
237 ),
238 method: Some(request.method.clone()),
239 },
240 );
241 call.operation_id.clone_from(&route.operation_id);
242 call
243 }
244
245 #[must_use]
246 pub fn operation_id(&self) -> &str {
247 &self.operation_id
248 }
249
250 #[must_use]
251 pub fn path(&self) -> Option<&str> {
252 self.path.as_deref()
253 }
254
255 #[must_use]
256 pub const fn method(&self) -> Option<&HttpMethod> {
257 self.method.as_ref()
258 }
259
260 #[must_use]
261 pub const fn body(&self) -> Option<&JsValue> {
262 self.body.as_ref()
263 }
264
265 #[must_use]
266 pub const fn query(&self) -> Option<&JsValue> {
267 self.query.as_ref()
268 }
269
270 #[must_use]
272 pub fn with_params_value(mut self, params: JsValue) -> Self {
273 self.params = Some(params);
274 self
275 }
276
277 #[must_use]
278 pub const fn params(&self) -> Option<&JsValue> {
279 self.params.as_ref()
280 }
281
282 #[must_use]
283 pub const fn headers(&self) -> Option<&HashMap<String, String>> {
284 self.headers.as_ref()
285 }
286
287 pub fn replace_headers(&mut self, headers: HashMap<String, String>) {
290 self.headers = Some(headers);
291 }
292
293 #[must_use]
294 pub const fn request(&self) -> Option<&AuthRequest> {
295 self.request.as_ref()
296 }
297
298 #[must_use]
299 pub const fn phase(&self) -> EndpointPhase {
300 self.phase
301 }
302
303 #[must_use]
304 pub fn has_body(&self) -> bool {
305 self.phase != EndpointPhase::Raw || self.body.is_some()
306 }
307
308 #[must_use]
309 pub fn has_query(&self) -> bool {
310 self.phase != EndpointPhase::Raw || self.query.is_some()
311 }
312
313 #[must_use]
314 pub fn has_method(&self) -> bool {
315 self.phase != EndpointPhase::Raw || self.method.is_some()
316 }
317
318 #[must_use]
320 pub fn middleware_context(&self) -> Self {
321 let mut context = self.clone();
322 context.phase = EndpointPhase::Middleware;
323 if context.path.as_ref().is_none_or(String::is_empty) {
324 context.path = Some("/".into());
325 }
326 context
327 }
328
329 #[must_use]
331 pub fn handler_context(&self, body: Option<JsValue>, query: Option<JsValue>) -> Self {
332 let mut context = self.clone();
333 context.phase = EndpointPhase::Handler;
334 if context.path.as_ref().is_none_or(String::is_empty) {
335 context.path = Some("virtual:".into());
336 }
337 if context.method.is_none() {
338 context.method = Some(context.default_method.clone());
339 }
340 context.body = body;
341 context.query = query;
342 context
343 }
344
345 pub(crate) fn session_read_context(&self) -> Self {
347 let mut context = self.clone();
348 context.phase = EndpointPhase::Handler;
349 if context.path.as_ref().is_none_or(String::is_empty) {
350 context.path = Some("virtual:".into());
351 }
352 context.method = Some(HttpMethod::Get);
353 let mut query = indexmap::IndexMap::new();
354 for name in ["disableCookieCache", "disableRefresh"] {
355 if self
356 .query
357 .as_ref()
358 .and_then(|value| value.get(name))
359 .is_some()
360 {
361 _ = query.insert(name.into(), JsValue::Bool(self.session_query_truthy(name)));
362 }
363 }
364 context.query = Some(JsValue::Object(query));
365 context
366 }
367
368 #[must_use]
369 pub const fn extensions(&self) -> &RequestExtensions {
370 &self.extensions
371 }
372
373 #[must_use]
374 pub const fn default_method(&self) -> &HttpMethod {
375 &self.default_method
376 }
377
378 #[must_use]
379 pub fn virtual_session(&self) -> Option<SessionView> {
380 self.state.lock_unpoisoned().session.clone()
381 }
382
383 pub fn establish_session<S: AuthSchema>(
386 &self,
387 user: S::User,
388 user_view: UserView,
389 session: SessionView,
390 context: &AuthContext<S>,
391 ) {
392 self.extensions.insert(VerifiedEndpointUser::<S> {
393 user,
394 config: context.config.clone(),
395 database: context.database.clone(),
396 });
397 let mut state = self.state.lock_unpoisoned();
398 state.principal = Some((user_view, session.clone()));
399 state.session = Some(session);
400 }
401
402 #[must_use]
404 pub fn session(&self) -> Option<(UserView, SessionView)> {
405 self.state.lock_unpoisoned().principal.clone()
406 }
407
408 pub(crate) fn clear_authenticated_session(&self) {
409 let mut state = self.state.lock_unpoisoned();
410 state.session = None;
411 state.principal = None;
412 }
413
414 pub fn record_authenticated_session(&self, user: UserView, session: SessionView) {
415 self.state.lock_unpoisoned().principal = Some((user, session));
416 }
417
418 #[must_use]
419 pub fn authenticated_user<S: AuthSchema>(&self, context: &AuthContext<S>) -> Option<S::User> {
420 self.extensions
421 .get::<VerifiedEndpointUser<S>>()
422 .filter(|user| {
423 Arc::ptr_eq(&user.config, &context.config)
424 && Arc::ptr_eq(&user.database, &context.database)
425 })
426 .map(|user| user.user.clone())
427 }
428
429 pub fn set_response_header(&self, name: impl Into<String>, value: impl Into<String>) {
430 _ = self
431 .state
432 .lock_unpoisoned()
433 .response_headers
434 .insert(name, value);
435 }
436
437 pub fn queue_response_header(&self, name: impl Into<String>, value: impl Into<String>) {
438 self.state
439 .lock_unpoisoned()
440 .response_headers
441 .append(name, value);
442 }
443
444 pub fn take_response_headers(&self) -> Headers {
445 std::mem::take(&mut self.state.lock_unpoisoned().response_headers)
446 }
447
448 pub fn set_session_hook_snapshot(&self, user: UserView, session: SessionView) {
449 self.state.lock_unpoisoned().session_observation = Some((user, session));
450 }
451
452 #[must_use]
453 pub fn session_hook_snapshot(&self) -> Option<(UserView, SessionView)> {
454 self.state.lock_unpoisoned().session_observation.clone()
455 }
456
457 pub fn body_as<T: DeserializeOwned + 'static>(&self) -> AuthResult<T> {
460 Ok(crate::utils::json::from_value(
461 self.body.clone().unwrap_or(JsValue::Null),
462 )?)
463 }
464
465 pub fn query_as<T: DeserializeOwned + 'static>(&self) -> AuthResult<T> {
468 Ok(crate::utils::json::from_value(
469 self.query.clone().unwrap_or(JsValue::Null),
470 )?)
471 }
472}
473
474#[derive(Clone, Debug, Default)]
476pub struct EndpointContextPatch {
477 pub method: Option<HttpMethod>,
478 pub request: Option<AuthRequest>,
479 pub path: Option<String>,
480 pub body: Option<JsValue>,
481 pub query: Option<JsValue>,
482 pub params: Option<JsValue>,
483 pub headers: Option<HashMap<String, String>>,
484}
485
486impl EndpointContextPatch {
487 pub fn merge(&mut self, patch: Self) {
488 if let Some(method) = patch.method {
489 self.method = Some(method);
490 }
491 if let Some(request) = patch.request {
492 self.request = Some(request);
493 }
494 if let Some(path) = patch.path {
495 self.path = Some(path);
496 }
497 merge_optional(&mut self.body, patch.body);
498 merge_optional(&mut self.query, patch.query);
499 merge_optional(&mut self.params, patch.params);
500 if let Some(headers) = patch.headers {
501 self.headers.get_or_insert_with(HashMap::new).extend(
502 headers
503 .into_iter()
504 .map(|(name, value)| (name.to_ascii_lowercase(), value)),
505 );
506 }
507 }
508
509 pub fn apply(self, call: &mut EndpointCall) {
510 if let Some(method) = self.method {
511 call.method = Some(method);
512 }
513 if let Some(request) = self.request {
514 call.request = Some(fresh_request(request));
515 }
516 if let Some(path) = self.path {
517 call.path = Some(path);
518 }
519 merge_optional(&mut call.body, self.body);
520 merge_optional(&mut call.query, self.query);
521 merge_optional(&mut call.params, self.params);
522 if let Some(headers) = self.headers {
523 call.headers
524 .get_or_insert_with(HashMap::new)
525 .extend(headers);
526 }
527 }
528}
529
530fn merge_optional(target: &mut Option<JsValue>, patch: Option<JsValue>) {
531 if let Some(patch) = patch.filter(|value| !value.is_null()) {
532 if let Some(target) = target {
533 merge_value(target, patch);
534 } else {
535 *target = Some(patch);
536 }
537 }
538}
539
540fn merge_value(target: &mut JsValue, patch: JsValue) {
541 if let (JsValue::Object(target), JsValue::Object(patch)) = (&mut *target, &patch) {
542 for (name, value) in patch {
543 if value.is_null() || matches!(name.as_str(), "__proto__" | "constructor") {
544 continue;
545 }
546 if let Some(target) = target.get_mut(name) {
547 merge_value(target, value.clone());
548 } else {
549 _ = target.insert(name.clone(), value.clone());
550 }
551 }
552 } else {
553 *target = patch;
554 }
555}
556
557#[derive(Debug)]
559pub enum BeforeEndpointAction {
560 Patch(Box<EndpointContextPatch>),
561 Respond(EndpointResponse),
562 Reject(EndpointResponse),
564}
565
566#[derive(Clone, Debug, Default)]
568pub struct EndpointInput {
569 pub body: Option<JsValue>,
570 pub query: Option<JsValue>,
571}
572
573#[derive(Debug)]
575pub struct EndpointResponse {
576 result: Result<JsValue, AuthError>,
577 http_response: Option<crate::AuthResponse>,
578 error_body: Option<JsValue>,
579 error_body_absent: bool,
580 headers: Headers,
581 status: Option<u16>,
582}
583
584impl EndpointResponse {
585 #[must_use]
586 pub fn value(value: JsValue) -> Self {
587 Self {
588 result: Ok(value),
589 http_response: None,
590 error_body: None,
591 error_body_absent: false,
592 headers: Headers::new(),
593 status: None,
594 }
595 }
596
597 pub fn json(value: &impl Serialize) -> AuthResult<Self> {
600 Ok(Self::value(crate::utils::json::parse_value(
601 &crate::utils::json::to_string(value)?,
602 )?))
603 }
604
605 #[must_use]
607 pub fn empty() -> Self {
608 let mut response = Self::value(JsValue::Null);
609 response.http_response = Some(crate::AuthResponse::new(200));
610 response
611 }
612
613 #[must_use]
618 pub fn from_http_response(response: crate::AuthResponse) -> Self {
619 let mut result = Self::value(JsValue::Object(indexmap::IndexMap::new()));
620 result.http_response = Some(response);
621 result
622 }
623
624 #[must_use]
625 pub const fn http_response(&self) -> Option<&crate::AuthResponse> {
626 self.http_response.as_ref()
627 }
628
629 pub fn http_response_mut(&mut self) -> Option<&mut crate::AuthResponse> {
630 self.http_response.as_mut()
631 }
632
633 pub fn to_response(&self) -> AuthResult<crate::AuthResponse> {
638 let response = match &self.result {
639 Ok(value) => match &self.http_response {
640 Some(response) => response.clone(),
641 None => crate::AuthResponse::json(self.status.unwrap_or(200), value)?,
642 },
643 Err(error) => {
644 let (status, code, message) = error.error_payload();
645 let mut response = crate::AuthResponse::json(
646 self.status.unwrap_or(status),
647 &crate::ErrorCodeMessageResponse { code, message },
648 )?;
649 if self.error_body_absent {
650 response.body.clear();
651 } else if let Some(body) = &self.error_body {
652 response.body = crate::utils::json::to_vec(body)?;
653 }
654 response
655 }
656 };
657 Ok(forward_response_headers(response, &self.headers))
658 }
659
660 #[must_use]
661 pub fn error(error: AuthError) -> Self {
662 let status = Some(error.status_code());
663 Self {
664 result: Err(error),
665 http_response: None,
666 error_body: None,
667 error_body_absent: false,
668 headers: Headers::new(),
669 status,
670 }
671 }
672
673 #[must_use]
675 pub fn redirect(location: impl Into<String>) -> Self {
676 Self::error(AuthError::Api {
677 status: 302,
678 code: None,
679 message: String::new(),
680 })
681 .without_error_body()
682 .with_header("location", location)
683 }
684
685 #[must_use]
687 pub fn without_error_body(mut self) -> Self {
688 self.error_body = None;
689 self.error_body_absent = true;
690 self
691 }
692
693 #[must_use]
694 pub const fn error_body_is_absent(&self) -> bool {
695 self.error_body_absent
696 }
697
698 #[must_use]
699 pub fn with_error_body(mut self, body: JsValue) -> Self {
700 self.error_body_absent = false;
701 self.error_body = Some(body);
702 self
703 }
704
705 #[must_use]
706 pub const fn error_body(&self) -> Option<&JsValue> {
707 self.error_body.as_ref()
708 }
709
710 pub const fn result(&self) -> &Result<JsValue, AuthError> {
711 &self.result
712 }
713
714 #[must_use]
715 pub const fn headers(&self) -> &Headers {
716 &self.headers
717 }
718
719 #[must_use]
720 pub const fn status(&self) -> Option<u16> {
721 self.status
722 }
723
724 pub fn set_status(&mut self, status: Option<u16>) {
725 self.status = status;
726 }
727
728 #[must_use]
729 pub fn with_status(mut self, status: u16) -> Self {
730 self.status = Some(status);
731 self
732 }
733
734 #[must_use]
735 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
736 merge_header(&mut self.headers, name.into(), value.into());
737 self
738 }
739
740 pub fn merge_headers(&mut self, headers: Headers) {
741 for (name, value) in headers {
742 merge_header(&mut self.headers, name, value);
743 }
744 }
745
746 pub fn replace(&mut self, value: JsValue) {
748 self.result = Ok(value);
749 self.http_response = None;
750 self.error_body = None;
751 self.error_body_absent = false;
752 }
753
754 pub fn replace_error(&mut self, error: AuthError) {
755 self.result = Err(error);
756 self.http_response = None;
757 self.error_body = None;
758 self.error_body_absent = false;
759 }
760
761 pub fn into_output<T>(self) -> Result<EndpointOutput<T>, EndpointError> {
762 match self.result {
763 Ok(value) => Ok(EndpointOutput {
764 value,
765 http_response: self.http_response,
766 headers: self.headers,
767 status: self.status,
768 output: PhantomData,
769 }),
770 Err(error) => Err(EndpointError {
771 error,
772 headers: Some(self.headers),
773 body: self.error_body.map(Box::new),
774 body_absent: self.error_body_absent,
775 }),
776 }
777 }
778}
779
780fn merge_header(headers: &mut Headers, name: String, value: String) {
781 if name.eq_ignore_ascii_case("set-cookie") {
782 headers.append(name, value);
783 } else {
784 _ = headers.insert(name, value);
785 }
786}
787
788#[async_trait::async_trait]
790pub trait EndpointHook<S: AuthSchema>: Send + Sync {
791 fn matches_before(&self, _call: &EndpointCall, _ctx: &AuthContext<S>) -> AuthResult<bool> {
795 Ok(true)
796 }
797
798 async fn before(
801 &self,
802 _call: &EndpointCall,
803 _ctx: &AuthContext<S>,
804 ) -> AuthResult<Option<BeforeEndpointAction>> {
805 Ok(None)
806 }
807
808 fn matches_after(
811 &self,
812 _call: &EndpointCall,
813 _ctx: &AuthContext<S>,
814 _response: &EndpointResponse,
815 ) -> AuthResult<bool> {
816 Ok(true)
817 }
818
819 async fn after(
822 &self,
823 _call: &EndpointCall,
824 _ctx: &AuthContext<S>,
825 response: EndpointResponse,
826 ) -> AuthResult<EndpointResponse> {
827 Ok(response)
828 }
829}
830
831#[derive(Debug)]
833pub struct EndpointOutput<T> {
834 value: JsValue,
835 http_response: Option<crate::AuthResponse>,
836 headers: Headers,
837 status: Option<u16>,
838 output: PhantomData<fn() -> T>,
839}
840
841impl<T> EndpointOutput<T> {
842 #[must_use]
844 pub const fn http_response(&self) -> Option<&crate::AuthResponse> {
845 self.http_response.as_ref()
846 }
847
848 pub fn into_response(self) -> AuthResult<crate::AuthResponse> {
856 let response = match self.http_response {
857 Some(response) => response,
858 None => crate::AuthResponse::json(self.status.unwrap_or(200), &self.value)?,
859 };
860 Ok(forward_response_headers(response, &self.headers))
861 }
862 #[must_use]
863 pub const fn value(&self) -> &JsValue {
864 &self.value
865 }
866
867 #[must_use]
868 pub const fn headers(&self) -> &Headers {
869 &self.headers
870 }
871
872 #[must_use]
873 pub const fn status(&self) -> Option<u16> {
874 self.status
875 }
876}
877
878impl<T: DeserializeOwned + 'static> EndpointOutput<T> {
879 pub fn decode(&self) -> AuthResult<T> {
882 if self
883 .http_response
884 .as_ref()
885 .is_some_and(|response| !(response.body.is_empty() && self.value == JsValue::Null))
886 {
887 return Err(AuthError::internal(
888 "Endpoint returned an HTTP response; use http_response or into_response",
889 ));
890 }
891 Ok(crate::utils::json::from_value(self.value.clone())?)
892 }
893}
894
895fn forward_response_headers(
896 mut response: crate::AuthResponse,
897 headers: &Headers,
898) -> crate::AuthResponse {
899 for (name, value) in headers {
900 if matches!(
903 name.to_ascii_lowercase().as_str(),
904 "host"
905 | "user-agent"
906 | "referer"
907 | "from"
908 | "expect"
909 | "authorization"
910 | "proxy-authorization"
911 | "cookie"
912 | "origin"
913 | "accept-charset"
914 | "accept-encoding"
915 | "accept-language"
916 | "if-match"
917 | "if-none-match"
918 | "if-modified-since"
919 | "if-unmodified-since"
920 | "if-range"
921 | "range"
922 | "max-forwards"
923 | "connection"
924 | "keep-alive"
925 | "transfer-encoding"
926 | "te"
927 | "upgrade"
928 | "trailer"
929 | "proxy-connection"
930 | "content-length"
931 ) {
932 continue;
933 }
934 merge_header(&mut response.headers, name.clone(), value.clone());
935 }
936 response
937}
938
939#[derive(Debug)]
941pub struct EndpointError {
942 pub error: AuthError,
943 pub headers: Option<Headers>,
944 pub body: Option<Box<JsValue>>,
945 pub body_absent: bool,
947}
948
949impl From<AuthError> for EndpointError {
950 fn from(error: AuthError) -> Self {
951 Self {
952 error,
953 headers: None,
954 body: None,
955 body_absent: false,
956 }
957 }
958}
959
960impl std::fmt::Display for EndpointError {
961 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
962 self.error.fmt(formatter)
963 }
964}
965
966impl std::error::Error for EndpointError {
967 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
968 Some(&self.error)
969 }
970}
971
972#[must_use]
974pub fn is_endpoint_api_error(error: &AuthError) -> bool {
975 matches!(error, AuthError::Api { .. } | AuthError::Upstream { .. }) || error.status_code() < 500
976}
977
978fn fresh_request(request: AuthRequest) -> AuthRequest {
979 let pairs = request
980 .query
981 .keys()
982 .flat_map(|name| {
983 request
984 .query_values(name)
985 .into_iter()
986 .flatten()
987 .map(|value| (name.clone(), value.clone()))
988 })
989 .collect::<Vec<_>>();
990 let url = request.url().cloned();
991 let mut fresh = AuthRequest::from_parts(
992 request.method,
993 request.path,
994 request.headers,
995 request.body,
996 request.query,
997 );
998 if let Some(url) = url {
999 fresh = fresh.with_url(url);
1000 }
1001 fresh.set_query_pairs(pairs);
1002 fresh
1003}
1004
1005tokio::task_local! { static ENDPOINT_CALL_CONTEXT: Option<EndpointCall>; }
1006
1007#[must_use]
1008pub fn current_endpoint_call_context() -> Option<EndpointCall> {
1009 ENDPOINT_CALL_CONTEXT.try_with(Clone::clone).ok().flatten()
1010}
1011
1012pub async fn with_endpoint_call_context<T>(
1013 call: EndpointCall,
1014 future: impl Future<Output = T>,
1015) -> T {
1016 let request = call
1017 .request()
1018 .map(crate::hooks::RequestHookContext::from_request);
1019 crate::hooks::with_optional_request_hook_context(
1020 request,
1021 ENDPOINT_CALL_CONTEXT.scope(Some(call), future),
1022 )
1023 .await
1024}
1025
1026pub async fn without_endpoint_call_context<T>(future: impl Future<Output = T>) -> T {
1028 ENDPOINT_CALL_CONTEXT.scope(None, future).await
1029}