1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
94#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
95#![allow(clippy::multiple_crate_versions)]
96
97use std::{
98 collections::BTreeMap,
99 pin::Pin,
100 sync::{Arc, RwLock},
101};
102
103use bytes::Bytes;
104use flume::{Receiver, Sender};
105use futures::Future;
106use hyperchad_renderer::Content;
107pub use hyperchad_transformer::{Container, Element};
108use qstring::QString;
109use switchy::http::models::Method;
110use switchy_async::task::JoinHandle;
111use thiserror::Error;
112
113pub static DEFAULT_CLIENT_INFO: std::sync::LazyLock<std::sync::Arc<ClientInfo>> =
118 std::sync::LazyLock::new(|| {
119 let os_name = os_info::get().os_type().to_string();
120 std::sync::Arc::new(ClientInfo {
121 os: ClientOs { name: os_name },
122 })
123 });
124
125pub type RouteFunc = Arc<
130 Box<
131 dyn (Fn(
132 RouteRequest,
133 ) -> Pin<
134 Box<
135 dyn Future<Output = Result<Option<Content>, Box<dyn std::error::Error>>> + Send,
136 >,
137 >) + Send
138 + Sync,
139 >,
140>;
141
142#[cfg(feature = "serde")]
144#[derive(Debug, Error)]
145pub enum ParseError {
146 #[error(transparent)]
148 SerdeJson(#[from] serde_json::Error),
149 #[error(transparent)]
151 SerdeUrlEncoded(#[from] serde_urlencoded::de::Error),
152 #[error("Missing body")]
154 MissingBody,
155 #[error("Invalid Content-Type")]
157 InvalidContentType,
158 #[cfg(feature = "form")]
160 #[error(transparent)]
161 IO(#[from] std::io::Error),
162 #[cfg(feature = "form")]
164 #[error("Missing boundary")]
165 MissingBoundary,
166 #[cfg(feature = "form")]
168 #[error(transparent)]
169 ParseUtf8(#[from] std::string::FromUtf8Error),
170 #[cfg(feature = "form")]
172 #[error(transparent)]
173 Multipart(#[from] mime_multipart::Error),
174 #[cfg(feature = "form")]
176 #[error("Invalid Content‑Disposition")]
177 InvalidContentDisposition,
178 #[cfg(feature = "form")]
180 #[error("Custom deserialization error: {0}")]
181 CustomDeserialize(String),
182}
183
184#[cfg(feature = "form")]
185mod form_deserializer {
190 use serde::de::{self, Deserializer, IntoDeserializer, MapAccess, Visitor};
191 use std::collections::BTreeMap;
192 use std::fmt;
193
194 pub struct FormDataDeserializer {
198 fields: std::collections::btree_map::IntoIter<String, String>,
199 }
200
201 impl FormDataDeserializer {
202 #[must_use]
204 pub fn new(data: BTreeMap<String, String>) -> Self {
205 Self {
206 fields: data.into_iter(),
207 }
208 }
209 }
210
211 pub struct StringValueDeserializer {
216 value: String,
217 }
218
219 impl StringValueDeserializer {
220 #[must_use]
222 #[allow(clippy::missing_const_for_fn)]
223 pub fn new(value: String) -> Self {
224 Self { value }
225 }
226 }
227
228 #[derive(Debug)]
230 pub struct DeserializeError(String);
231
232 impl fmt::Display for DeserializeError {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 write!(f, "{}", self.0)
235 }
236 }
237
238 impl std::error::Error for DeserializeError {}
239
240 impl de::Error for DeserializeError {
241 fn custom<T: fmt::Display>(msg: T) -> Self {
242 Self(msg.to_string())
243 }
244 }
245
246 macro_rules! deserialize_primitive {
247 ($method:ident, $visit:ident, $ty:ty) => {
248 fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
249 where
250 V: Visitor<'de>,
251 {
252 self.value
253 .parse::<$ty>()
254 .map_err(|e| {
255 de::Error::custom(format!(
256 "failed to parse '{}' as {}: {}",
257 self.value,
258 stringify!($ty),
259 e
260 ))
261 })
262 .and_then(|v| visitor.$visit(v))
263 }
264 };
265 }
266
267 impl<'de> Deserializer<'de> for StringValueDeserializer {
268 type Error = DeserializeError;
269
270 fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
271 where
272 V: Visitor<'de>,
273 {
274 if self.value.eq_ignore_ascii_case("true") {
275 return visitor.visit_bool(true);
276 }
277 if self.value.eq_ignore_ascii_case("false") {
278 return visitor.visit_bool(false);
279 }
280
281 if self.value.eq_ignore_ascii_case("null") {
282 return visitor.visit_unit();
283 }
284
285 if let Ok(v) = self.value.parse::<u64>() {
286 return visitor.visit_u64(v);
287 }
288
289 if let Ok(v) = self.value.parse::<i64>() {
290 return visitor.visit_i64(v);
291 }
292
293 if let Ok(v) = self.value.parse::<f64>() {
294 return visitor.visit_f64(v);
295 }
296
297 visitor.visit_string(self.value)
298 }
299
300 deserialize_primitive!(deserialize_bool, visit_bool, bool);
301 deserialize_primitive!(deserialize_i8, visit_i8, i8);
302 deserialize_primitive!(deserialize_i16, visit_i16, i16);
303 deserialize_primitive!(deserialize_i32, visit_i32, i32);
304 deserialize_primitive!(deserialize_i64, visit_i64, i64);
305 deserialize_primitive!(deserialize_i128, visit_i128, i128);
306 deserialize_primitive!(deserialize_u8, visit_u8, u8);
307 deserialize_primitive!(deserialize_u16, visit_u16, u16);
308 deserialize_primitive!(deserialize_u32, visit_u32, u32);
309 deserialize_primitive!(deserialize_u64, visit_u64, u64);
310 deserialize_primitive!(deserialize_u128, visit_u128, u128);
311 deserialize_primitive!(deserialize_f32, visit_f32, f32);
312 deserialize_primitive!(deserialize_f64, visit_f64, f64);
313
314 fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
315 where
316 V: Visitor<'de>,
317 {
318 if self.value.len() == 1 {
319 visitor.visit_char(self.value.chars().next().unwrap())
320 } else {
321 Err(de::Error::custom(format!(
322 "expected single character, got '{}'",
323 self.value
324 )))
325 }
326 }
327
328 fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
329 where
330 V: Visitor<'de>,
331 {
332 visitor.visit_string(self.value)
333 }
334
335 fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
336 where
337 V: Visitor<'de>,
338 {
339 visitor.visit_string(self.value)
340 }
341
342 fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
343 where
344 V: Visitor<'de>,
345 {
346 visitor.visit_byte_buf(self.value.into_bytes())
347 }
348
349 fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
350 where
351 V: Visitor<'de>,
352 {
353 visitor.visit_byte_buf(self.value.into_bytes())
354 }
355
356 fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
357 where
358 V: Visitor<'de>,
359 {
360 if self.value.is_empty() || self.value.eq_ignore_ascii_case("null") {
361 visitor.visit_none()
362 } else {
363 visitor.visit_some(self)
364 }
365 }
366
367 fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
368 where
369 V: Visitor<'de>,
370 {
371 visitor.visit_unit()
372 }
373
374 fn deserialize_unit_struct<V>(
375 self,
376 _name: &'static str,
377 visitor: V,
378 ) -> Result<V::Value, Self::Error>
379 where
380 V: Visitor<'de>,
381 {
382 visitor.visit_unit()
383 }
384
385 fn deserialize_newtype_struct<V>(
386 self,
387 _name: &'static str,
388 visitor: V,
389 ) -> Result<V::Value, Self::Error>
390 where
391 V: Visitor<'de>,
392 {
393 visitor.visit_newtype_struct(self)
394 }
395
396 fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
397 where
398 V: Visitor<'de>,
399 {
400 self.value.into_deserializer().deserialize_seq(visitor)
401 }
402
403 fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
404 where
405 V: Visitor<'de>,
406 {
407 self.deserialize_seq(visitor)
408 }
409
410 fn deserialize_tuple_struct<V>(
411 self,
412 _name: &'static str,
413 _len: usize,
414 visitor: V,
415 ) -> Result<V::Value, Self::Error>
416 where
417 V: Visitor<'de>,
418 {
419 self.deserialize_seq(visitor)
420 }
421
422 fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
423 where
424 V: Visitor<'de>,
425 {
426 self.value.into_deserializer().deserialize_map(visitor)
427 }
428
429 fn deserialize_struct<V>(
430 self,
431 _name: &'static str,
432 _fields: &'static [&'static str],
433 visitor: V,
434 ) -> Result<V::Value, Self::Error>
435 where
436 V: Visitor<'de>,
437 {
438 self.deserialize_map(visitor)
439 }
440
441 fn deserialize_enum<V>(
442 self,
443 _name: &'static str,
444 _variants: &'static [&'static str],
445 visitor: V,
446 ) -> Result<V::Value, Self::Error>
447 where
448 V: Visitor<'de>,
449 {
450 visitor.visit_enum(self.value.into_deserializer())
451 }
452
453 fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
454 where
455 V: Visitor<'de>,
456 {
457 visitor.visit_string(self.value)
458 }
459
460 fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
461 where
462 V: Visitor<'de>,
463 {
464 visitor.visit_unit()
465 }
466 }
467
468 struct FieldsMapAccess {
470 fields: std::collections::btree_map::IntoIter<String, String>,
471 value: Option<String>,
472 }
473
474 impl FieldsMapAccess {
475 #[allow(clippy::missing_const_for_fn)]
477 fn new(fields: std::collections::btree_map::IntoIter<String, String>) -> Self {
478 Self {
479 fields,
480 value: None,
481 }
482 }
483 }
484
485 impl<'de> MapAccess<'de> for FieldsMapAccess {
486 type Error = DeserializeError;
487
488 fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
489 where
490 K: de::DeserializeSeed<'de>,
491 {
492 if let Some((key, value)) = self.fields.next() {
493 self.value = Some(value);
494 seed.deserialize(key.into_deserializer()).map(Some)
495 } else {
496 Ok(None)
497 }
498 }
499
500 fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
501 where
502 V: de::DeserializeSeed<'de>,
503 {
504 let value = self
505 .value
506 .take()
507 .ok_or_else(|| de::Error::custom("value is missing"))?;
508 seed.deserialize(StringValueDeserializer::new(value))
509 }
510 }
511
512 impl<'de> Deserializer<'de> for FormDataDeserializer {
513 type Error = DeserializeError;
514
515 fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
516 where
517 V: Visitor<'de>,
518 {
519 self.deserialize_map(visitor)
520 }
521
522 fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
523 where
524 V: Visitor<'de>,
525 {
526 visitor.visit_map(FieldsMapAccess::new(self.fields))
527 }
528
529 fn deserialize_struct<V>(
530 self,
531 _name: &'static str,
532 _fields: &'static [&'static str],
533 visitor: V,
534 ) -> Result<V::Value, Self::Error>
535 where
536 V: Visitor<'de>,
537 {
538 self.deserialize_map(visitor)
539 }
540
541 fn deserialize_bool<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
542 where
543 V: Visitor<'de>,
544 {
545 Err(de::Error::custom(
546 "cannot deserialize bool from form data map",
547 ))
548 }
549
550 fn deserialize_i8<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
551 where
552 V: Visitor<'de>,
553 {
554 Err(de::Error::custom(
555 "cannot deserialize i8 from form data map",
556 ))
557 }
558
559 fn deserialize_i16<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
560 where
561 V: Visitor<'de>,
562 {
563 Err(de::Error::custom(
564 "cannot deserialize i16 from form data map",
565 ))
566 }
567
568 fn deserialize_i32<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
569 where
570 V: Visitor<'de>,
571 {
572 Err(de::Error::custom(
573 "cannot deserialize i32 from form data map",
574 ))
575 }
576
577 fn deserialize_i64<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
578 where
579 V: Visitor<'de>,
580 {
581 Err(de::Error::custom(
582 "cannot deserialize i64 from form data map",
583 ))
584 }
585
586 fn deserialize_i128<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
587 where
588 V: Visitor<'de>,
589 {
590 Err(de::Error::custom(
591 "cannot deserialize i128 from form data map",
592 ))
593 }
594
595 fn deserialize_u8<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
596 where
597 V: Visitor<'de>,
598 {
599 Err(de::Error::custom(
600 "cannot deserialize u8 from form data map",
601 ))
602 }
603
604 fn deserialize_u16<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
605 where
606 V: Visitor<'de>,
607 {
608 Err(de::Error::custom(
609 "cannot deserialize u16 from form data map",
610 ))
611 }
612
613 fn deserialize_u32<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
614 where
615 V: Visitor<'de>,
616 {
617 Err(de::Error::custom(
618 "cannot deserialize u32 from form data map",
619 ))
620 }
621
622 fn deserialize_u64<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
623 where
624 V: Visitor<'de>,
625 {
626 Err(de::Error::custom(
627 "cannot deserialize u64 from form data map",
628 ))
629 }
630
631 fn deserialize_u128<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
632 where
633 V: Visitor<'de>,
634 {
635 Err(de::Error::custom(
636 "cannot deserialize u128 from form data map",
637 ))
638 }
639
640 fn deserialize_f32<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
641 where
642 V: Visitor<'de>,
643 {
644 Err(de::Error::custom(
645 "cannot deserialize f32 from form data map",
646 ))
647 }
648
649 fn deserialize_f64<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
650 where
651 V: Visitor<'de>,
652 {
653 Err(de::Error::custom(
654 "cannot deserialize f64 from form data map",
655 ))
656 }
657
658 fn deserialize_char<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
659 where
660 V: Visitor<'de>,
661 {
662 Err(de::Error::custom(
663 "cannot deserialize char from form data map",
664 ))
665 }
666
667 fn deserialize_str<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
668 where
669 V: Visitor<'de>,
670 {
671 Err(de::Error::custom(
672 "cannot deserialize str from form data map",
673 ))
674 }
675
676 fn deserialize_string<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
677 where
678 V: Visitor<'de>,
679 {
680 Err(de::Error::custom(
681 "cannot deserialize string from form data map",
682 ))
683 }
684
685 fn deserialize_bytes<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
686 where
687 V: Visitor<'de>,
688 {
689 Err(de::Error::custom(
690 "cannot deserialize bytes from form data map",
691 ))
692 }
693
694 fn deserialize_byte_buf<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
695 where
696 V: Visitor<'de>,
697 {
698 Err(de::Error::custom(
699 "cannot deserialize byte_buf from form data map",
700 ))
701 }
702
703 fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
704 where
705 V: Visitor<'de>,
706 {
707 visitor.visit_some(self)
708 }
709
710 fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
711 where
712 V: Visitor<'de>,
713 {
714 visitor.visit_unit()
715 }
716
717 fn deserialize_unit_struct<V>(
718 self,
719 _name: &'static str,
720 visitor: V,
721 ) -> Result<V::Value, Self::Error>
722 where
723 V: Visitor<'de>,
724 {
725 visitor.visit_unit()
726 }
727
728 fn deserialize_newtype_struct<V>(
729 self,
730 _name: &'static str,
731 visitor: V,
732 ) -> Result<V::Value, Self::Error>
733 where
734 V: Visitor<'de>,
735 {
736 visitor.visit_newtype_struct(self)
737 }
738
739 fn deserialize_seq<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
740 where
741 V: Visitor<'de>,
742 {
743 Err(de::Error::custom(
744 "cannot deserialize seq from form data map",
745 ))
746 }
747
748 fn deserialize_tuple<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
749 where
750 V: Visitor<'de>,
751 {
752 Err(de::Error::custom(
753 "cannot deserialize tuple from form data map",
754 ))
755 }
756
757 fn deserialize_tuple_struct<V>(
758 self,
759 _name: &'static str,
760 _len: usize,
761 _visitor: V,
762 ) -> Result<V::Value, Self::Error>
763 where
764 V: Visitor<'de>,
765 {
766 Err(de::Error::custom(
767 "cannot deserialize tuple_struct from form data map",
768 ))
769 }
770
771 fn deserialize_enum<V>(
772 self,
773 _name: &'static str,
774 _variants: &'static [&'static str],
775 _visitor: V,
776 ) -> Result<V::Value, Self::Error>
777 where
778 V: Visitor<'de>,
779 {
780 Err(de::Error::custom(
781 "cannot deserialize enum from form data map",
782 ))
783 }
784
785 fn deserialize_identifier<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
786 where
787 V: Visitor<'de>,
788 {
789 Err(de::Error::custom(
790 "cannot deserialize identifier from form data map",
791 ))
792 }
793
794 fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
795 where
796 V: Visitor<'de>,
797 {
798 visitor.visit_unit()
799 }
800 }
801}
802
803#[derive(Debug, Clone, PartialEq, Eq, Default)]
805pub struct ClientOs {
806 pub name: String,
808}
809
810#[derive(Debug, Clone, PartialEq, Eq)]
812pub struct ClientInfo {
813 pub os: ClientOs,
815}
816
817impl Default for ClientInfo {
818 fn default() -> Self {
819 DEFAULT_CLIENT_INFO.as_ref().clone()
820 }
821}
822
823#[derive(Debug, Clone, PartialEq, Eq, Default)]
825pub struct RequestInfo {
826 pub client: Arc<ClientInfo>,
828}
829
830#[derive(Debug, Clone, PartialEq, Eq)]
835pub struct RouteRequest {
836 pub path: String,
838 pub method: Method,
840 pub query: BTreeMap<String, String>,
842 pub headers: BTreeMap<String, String>,
844 pub cookies: BTreeMap<String, String>,
846 pub info: RequestInfo,
848 pub body: Option<Arc<Bytes>>,
850}
851
852impl RouteRequest {
853 #[must_use]
858 pub fn from_path(path: &str, info: RequestInfo) -> Self {
859 let (path, query) = if let Some((path, query)) = path.split_once('?') {
860 (path, query)
861 } else {
862 (path, "")
863 };
864
865 Self {
866 path: path.to_owned(),
867 method: Method::Get,
868 query: QString::from(query).into_iter().collect(),
869 headers: BTreeMap::new(),
870 cookies: BTreeMap::new(),
871 info,
872 body: None,
873 }
874 }
875
876 #[must_use]
878 pub fn content_type(&self) -> Option<&str> {
879 self.headers.get("content-type").map(String::as_str)
880 }
881
882 #[cfg(feature = "form")]
921 pub fn parse_form<T: serde::de::DeserializeOwned>(&self) -> Result<T, ParseError> {
922 use base64::engine::{Engine as _, general_purpose};
923 use hyper_old::header::{ContentDisposition, ContentType, DispositionParam, Headers};
924 use mime_multipart::{Node, read_multipart_body};
925 use mime_old::Mime;
926 use std::io::{Cursor, Read as _};
927 fn parse_multipart_form_data(
928 body: &[u8],
929 content_type: &str,
930 ) -> Result<BTreeMap<String, String>, ParseError> {
931 {
932 fn process_nodes(
933 nodes: Vec<Node>,
934 map: &mut BTreeMap<String, String>,
935 ) -> Result<(), ParseError> {
936 for node in nodes {
937 match node {
938 Node::Part(part) => {
939 let cd = part
940 .headers
941 .get::<ContentDisposition>()
942 .ok_or(ParseError::InvalidContentDisposition)?;
943 let field_name = cd
944 .parameters
945 .iter()
946 .find_map(|param| {
947 if let DispositionParam::Ext(key, val) = param
948 && key.eq_ignore_ascii_case("name")
949 {
950 return Some(val.clone());
951 }
952 None
953 })
954 .ok_or(ParseError::InvalidContentDisposition)?;
955
956 let text = String::from_utf8(part.body)?;
957 map.insert(field_name, text);
958 }
959
960 Node::File(filepart) => {
961 let cd = filepart
962 .headers
963 .get::<ContentDisposition>()
964 .ok_or(ParseError::InvalidContentDisposition)?;
965 let field_name = cd
966 .parameters
967 .iter()
968 .find_map(|param| {
969 if let DispositionParam::Ext(key, val) = param
970 && key.eq_ignore_ascii_case("name")
971 {
972 return Some(val.clone());
973 }
974 None
975 })
976 .ok_or(ParseError::InvalidContentDisposition)?;
977
978 let mut f = std::fs::File::open(&filepart.path)?;
979 let mut data = Vec::new();
980 f.read_to_end(&mut data)?;
981
982 let b64 = general_purpose::STANDARD.encode(&data);
983 map.insert(field_name, b64);
984 }
985
986 Node::Multipart((_hdrs, subparts)) => {
987 process_nodes(subparts, map)?;
988 }
989 }
990 }
991 Ok(())
992 }
993
994 let mut headers = Headers::new();
995 let mime_type: Mime = content_type
996 .parse()
997 .map_err(|()| ParseError::InvalidContentType)?;
998 headers.set(ContentType(mime_type));
999
1000 let mut cursor = Cursor::new(body);
1001 let parts: Vec<Node> = read_multipart_body(&mut cursor, &headers, false)?;
1002
1003 let mut map = BTreeMap::new();
1004 process_nodes(parts, &mut map)?;
1005
1006 Ok(map)
1007 }
1008 }
1009
1010 let content_type = self.content_type().ok_or(ParseError::InvalidContentType)?;
1011
1012 if let Some(body) = &self.body {
1013 if content_type.starts_with("application/x-www-form-urlencoded") {
1014 serde_urlencoded::from_bytes(body).map_err(ParseError::SerdeUrlEncoded)
1016 } else if content_type.starts_with("multipart/form-data") {
1017 let data = parse_multipart_form_data(body, content_type)?;
1019 let deserializer = form_deserializer::FormDataDeserializer::new(data);
1020 T::deserialize(deserializer)
1021 .map_err(|e| ParseError::CustomDeserialize(e.to_string()))
1022 } else {
1023 Err(ParseError::InvalidContentType)
1024 }
1025 } else {
1026 Err(ParseError::MissingBody)
1027 }
1028 }
1029
1030 #[cfg(feature = "serde")]
1060 pub fn parse_body<T: serde::de::DeserializeOwned>(&self) -> Result<T, ParseError> {
1061 if let Some(body) = &self.body {
1062 Ok(serde_json::from_slice(body)?)
1063 } else {
1064 Err(ParseError::MissingBody)
1065 }
1066 }
1067}
1068
1069impl From<Navigation> for RouteRequest {
1070 fn from(value: Navigation) -> Self {
1071 Self {
1072 path: value.0,
1073 method: Method::Get,
1074 query: BTreeMap::new(),
1075 headers: BTreeMap::new(),
1076 cookies: BTreeMap::new(),
1077 info: RequestInfo { client: value.1 },
1078 body: None,
1079 }
1080 }
1081}
1082
1083impl From<&Navigation> for RouteRequest {
1084 fn from(value: &Navigation) -> Self {
1085 value.clone().into()
1086 }
1087}
1088
1089#[derive(Debug, Clone, PartialEq, Eq)]
1093pub enum RoutePath {
1094 Literal(String),
1096 Literals(Vec<String>),
1098 LiteralPrefix(String),
1100}
1101
1102impl RoutePath {
1103 #[must_use]
1105 pub fn matches(&self, path: &str) -> bool {
1106 match self {
1107 Self::Literal(route_path) => route_path == path,
1108 Self::Literals(route_paths) => route_paths.iter().any(|x| x == path),
1109 Self::LiteralPrefix(route_path) => path.starts_with(route_path),
1110 }
1111 }
1112
1113 #[must_use]
1119 pub fn strip_match<'a>(&'a self, path: &'a str) -> Option<&'a str> {
1120 const EMPTY: &str = "";
1121
1122 match self {
1123 Self::Literal(..) | Self::Literals(..) => {
1124 if self.matches(path) {
1125 Some(EMPTY)
1126 } else {
1127 None
1128 }
1129 }
1130 Self::LiteralPrefix(route_path) => path.strip_prefix(route_path),
1131 }
1132 }
1133}
1134
1135impl From<&str> for RoutePath {
1136 fn from(value: &str) -> Self {
1137 Self::Literal(value.to_owned())
1138 }
1139}
1140
1141impl From<&String> for RoutePath {
1142 fn from(value: &String) -> Self {
1143 Self::Literal(value.to_owned())
1144 }
1145}
1146
1147impl From<&[&str; 1]> for RoutePath {
1148 fn from(value: &[&str; 1]) -> Self {
1149 Self::Literals(value.iter().map(ToString::to_string).collect())
1150 }
1151}
1152
1153impl From<&[&str; 2]> for RoutePath {
1154 fn from(value: &[&str; 2]) -> Self {
1155 Self::Literals(value.iter().map(ToString::to_string).collect())
1156 }
1157}
1158
1159impl From<&[&str; 3]> for RoutePath {
1160 fn from(value: &[&str; 3]) -> Self {
1161 Self::Literals(value.iter().map(ToString::to_string).collect())
1162 }
1163}
1164
1165impl From<&[&str; 4]> for RoutePath {
1166 fn from(value: &[&str; 4]) -> Self {
1167 Self::Literals(value.iter().map(ToString::to_string).collect())
1168 }
1169}
1170
1171impl From<&[&str; 5]> for RoutePath {
1172 fn from(value: &[&str; 5]) -> Self {
1173 Self::Literals(value.iter().map(ToString::to_string).collect())
1174 }
1175}
1176
1177impl From<&[&str; 6]> for RoutePath {
1178 fn from(value: &[&str; 6]) -> Self {
1179 Self::Literals(value.iter().map(ToString::to_string).collect())
1180 }
1181}
1182
1183impl From<&[&str; 7]> for RoutePath {
1184 fn from(value: &[&str; 7]) -> Self {
1185 Self::Literals(value.iter().map(ToString::to_string).collect())
1186 }
1187}
1188
1189impl From<&[&str; 8]> for RoutePath {
1190 fn from(value: &[&str; 8]) -> Self {
1191 Self::Literals(value.iter().map(ToString::to_string).collect())
1192 }
1193}
1194
1195impl From<&[&str; 9]> for RoutePath {
1196 fn from(value: &[&str; 9]) -> Self {
1197 Self::Literals(value.iter().map(ToString::to_string).collect())
1198 }
1199}
1200
1201impl From<&[&str; 10]> for RoutePath {
1202 fn from(value: &[&str; 10]) -> Self {
1203 Self::Literals(value.iter().map(ToString::to_string).collect())
1204 }
1205}
1206
1207impl From<&[&str]> for RoutePath {
1208 fn from(value: &[&str]) -> Self {
1209 Self::Literals(value.iter().map(ToString::to_string).collect())
1210 }
1211}
1212
1213impl From<Vec<&str>> for RoutePath {
1214 fn from(value: Vec<&str>) -> Self {
1215 Self::Literals(value.into_iter().map(ToString::to_string).collect())
1216 }
1217}
1218
1219impl From<String> for RoutePath {
1220 fn from(value: String) -> Self {
1221 Self::Literal(value)
1222 }
1223}
1224
1225impl From<&[String]> for RoutePath {
1226 fn from(value: &[String]) -> Self {
1227 Self::Literals(value.iter().map(ToString::to_string).collect())
1228 }
1229}
1230
1231impl From<&[&String]> for RoutePath {
1232 fn from(value: &[&String]) -> Self {
1233 Self::Literals(value.iter().map(ToString::to_string).collect())
1234 }
1235}
1236
1237impl From<Vec<String>> for RoutePath {
1238 fn from(value: Vec<String>) -> Self {
1239 Self::Literals(value)
1240 }
1241}
1242
1243#[derive(Debug, Error)]
1245pub enum NavigateError {
1246 #[error("Invalid path")]
1248 InvalidPath,
1249 #[error("Handler error: {0:?}")]
1251 Handler(Box<dyn std::error::Error + Send + Sync>),
1252 #[error("Sender error")]
1254 Sender,
1255}
1256
1257#[derive(Clone)]
1263pub struct Router {
1264 #[cfg(feature = "static-routes")]
1266 pub static_routes: Arc<RwLock<Vec<(RoutePath, RouteFunc)>>>,
1267 pub routes: Arc<RwLock<Vec<(RoutePath, RouteFunc)>>>,
1269 sender: Sender<Content>,
1270 pub receiver: Receiver<Content>,
1272}
1273
1274impl std::fmt::Debug for Router {
1275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1276 f.debug_struct("Router")
1277 .field("sender", &self.sender)
1278 .field("receiver", &self.receiver)
1279 .finish_non_exhaustive()
1280 }
1281}
1282
1283impl Default for Router {
1284 fn default() -> Self {
1285 Self::new()
1286 }
1287}
1288
1289#[derive(Debug, Clone, PartialEq, Eq)]
1294pub struct Navigation(String, Arc<ClientInfo>);
1295
1296impl From<RouteRequest> for Navigation {
1297 fn from(value: RouteRequest) -> Self {
1298 let mut query = String::new();
1299
1300 for (key, value) in &value.query {
1301 if query.is_empty() {
1302 query.push('?');
1303 } else {
1304 query.push('&');
1305 }
1306 query.push_str(key);
1307 query.push('=');
1308 query.push_str(value);
1309 }
1310
1311 Self(format!("{}{query}", value.path), value.info.client)
1312 }
1313}
1314
1315impl From<&str> for RouteRequest {
1316 fn from(value: &str) -> Self {
1317 value.to_string().into()
1318 }
1319}
1320
1321impl From<String> for RouteRequest {
1322 fn from(value: String) -> Self {
1323 Self {
1324 path: value,
1325 method: Method::Get,
1326 query: BTreeMap::new(),
1327 headers: BTreeMap::new(),
1328 cookies: BTreeMap::new(),
1329 info: RequestInfo::default(),
1330 body: None,
1331 }
1332 }
1333}
1334
1335impl From<&String> for RouteRequest {
1336 fn from(value: &String) -> Self {
1337 value.clone().into()
1338 }
1339}
1340
1341impl From<(&str, ClientInfo)> for RouteRequest {
1342 fn from(value: (&str, ClientInfo)) -> Self {
1343 (value.0.to_string(), Arc::new(value.1)).into()
1344 }
1345}
1346
1347impl From<(String, ClientInfo)> for RouteRequest {
1348 fn from(value: (String, ClientInfo)) -> Self {
1349 (value.0, Arc::new(value.1)).into()
1350 }
1351}
1352
1353impl From<(&String, ClientInfo)> for RouteRequest {
1354 fn from(value: (&String, ClientInfo)) -> Self {
1355 (value.0.clone(), Arc::new(value.1)).into()
1356 }
1357}
1358
1359impl From<(&str, Arc<ClientInfo>)> for RouteRequest {
1360 fn from(value: (&str, Arc<ClientInfo>)) -> Self {
1361 (value.0.to_string(), value.1).into()
1362 }
1363}
1364
1365impl From<(String, Arc<ClientInfo>)> for RouteRequest {
1366 fn from(value: (String, Arc<ClientInfo>)) -> Self {
1367 (value.0, RequestInfo { client: value.1 }).into()
1368 }
1369}
1370
1371impl From<(&String, Arc<ClientInfo>)> for RouteRequest {
1372 fn from(value: (&String, Arc<ClientInfo>)) -> Self {
1373 (value.0.clone(), value.1).into()
1374 }
1375}
1376
1377impl From<(&str, RequestInfo)> for RouteRequest {
1378 fn from(value: (&str, RequestInfo)) -> Self {
1379 (value.0.to_string(), value.1).into()
1380 }
1381}
1382
1383impl From<(String, RequestInfo)> for RouteRequest {
1384 fn from(value: (String, RequestInfo)) -> Self {
1385 let (path, query) = if let Some((path, query)) = value.0.split_once('?') {
1386 (path.to_string(), query)
1387 } else {
1388 (value.0, "")
1389 };
1390
1391 Self {
1392 path,
1393 method: Method::Get,
1394 query: QString::from(query).into_iter().collect(),
1395 headers: BTreeMap::new(),
1396 cookies: BTreeMap::new(),
1397 info: value.1,
1398 body: None,
1399 }
1400 }
1401}
1402
1403impl From<(&String, RequestInfo)> for RouteRequest {
1404 fn from(value: (&String, RequestInfo)) -> Self {
1405 (value.0.clone(), value.1).into()
1406 }
1407}
1408
1409impl From<&RouteRequest> for Navigation {
1410 fn from(value: &RouteRequest) -> Self {
1411 value.clone().into()
1412 }
1413}
1414
1415impl From<&str> for Navigation {
1416 fn from(value: &str) -> Self {
1417 Self(value.to_string(), DEFAULT_CLIENT_INFO.clone())
1418 }
1419}
1420
1421impl From<String> for Navigation {
1422 fn from(value: String) -> Self {
1423 Self(value, DEFAULT_CLIENT_INFO.clone())
1424 }
1425}
1426
1427impl From<&String> for Navigation {
1428 fn from(value: &String) -> Self {
1429 Self(value.clone(), DEFAULT_CLIENT_INFO.clone())
1430 }
1431}
1432
1433impl From<(&str, ClientInfo)> for Navigation {
1434 fn from(value: (&str, ClientInfo)) -> Self {
1435 Self(value.0.to_string(), Arc::new(value.1))
1436 }
1437}
1438
1439impl From<(String, ClientInfo)> for Navigation {
1440 fn from(value: (String, ClientInfo)) -> Self {
1441 Self(value.0, Arc::new(value.1))
1442 }
1443}
1444
1445impl From<(&String, ClientInfo)> for Navigation {
1446 fn from(value: (&String, ClientInfo)) -> Self {
1447 Self(value.0.clone(), Arc::new(value.1))
1448 }
1449}
1450
1451impl From<(&str, Arc<ClientInfo>)> for Navigation {
1452 fn from(value: (&str, Arc<ClientInfo>)) -> Self {
1453 Self(value.0.to_string(), value.1)
1454 }
1455}
1456
1457impl From<(String, Arc<ClientInfo>)> for Navigation {
1458 fn from(value: (String, Arc<ClientInfo>)) -> Self {
1459 Self(value.0, value.1)
1460 }
1461}
1462
1463impl From<(&String, Arc<ClientInfo>)> for Navigation {
1464 fn from(value: (&String, Arc<ClientInfo>)) -> Self {
1465 Self(value.0.clone(), value.1)
1466 }
1467}
1468
1469impl From<(&str, RequestInfo)> for Navigation {
1470 fn from(value: (&str, RequestInfo)) -> Self {
1471 Self(value.0.to_string(), value.1.client)
1472 }
1473}
1474
1475impl From<(String, RequestInfo)> for Navigation {
1476 fn from(value: (String, RequestInfo)) -> Self {
1477 Self(value.0, value.1.client)
1478 }
1479}
1480
1481impl From<(&String, RequestInfo)> for Navigation {
1482 fn from(value: (&String, RequestInfo)) -> Self {
1483 Self(value.0.clone(), value.1.client)
1484 }
1485}
1486
1487impl Router {
1488 #[must_use]
1490 pub fn new() -> Self {
1491 let (tx, rx) = flume::unbounded();
1492
1493 Self {
1494 #[cfg(feature = "static-routes")]
1495 static_routes: Arc::new(RwLock::new(vec![])),
1496 routes: Arc::new(RwLock::new(vec![])),
1497 sender: tx,
1498 receiver: rx,
1499 }
1500 }
1501
1502 #[must_use]
1508 pub fn with_route_result<
1509 C: TryInto<Content>,
1510 Response: Into<Option<C>>,
1511 F: Future<Output = Result<Response, BoxE>> + Send + 'static,
1512 BoxE: Into<Box<dyn std::error::Error>>,
1513 >(
1514 self,
1515 route: impl Into<RoutePath>,
1516 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1517 ) -> Self
1518 where
1519 C::Error: Into<Box<dyn std::error::Error>>,
1520 {
1521 self.routes
1522 .write()
1523 .unwrap()
1524 .push((route.into(), gen_route_func_result(handler)));
1525 self
1526 }
1527
1528 #[must_use]
1534 pub fn with_no_content_result<
1535 F: Future<Output = Result<(), BoxE>> + Send + 'static,
1536 BoxE: Into<Box<dyn std::error::Error>>,
1537 >(
1538 self,
1539 route: impl Into<RoutePath>,
1540 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1541 ) -> Self {
1542 self.with_route_result::<Content, Option<Content>, _, _>(route, move |req: RouteRequest| {
1543 let fut = handler(req);
1544 async move { fut.await.map(|()| None::<Content>).map_err(Into::into) }
1545 })
1546 }
1547
1548 #[allow(clippy::needless_pass_by_value)]
1556 #[must_use]
1557 pub fn with_static_route_result<
1558 C: TryInto<Content>,
1559 Response: Into<Option<C>>,
1560 F: Future<Output = Result<Response, BoxE>> + Send + 'static,
1561 BoxE: Into<Box<dyn std::error::Error>>,
1562 >(
1563 self,
1564 #[allow(unused_variables)] route: impl Into<RoutePath>,
1565 #[allow(unused_variables)] handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1566 ) -> Self
1567 where
1568 C::Error: Into<Box<dyn std::error::Error>>,
1569 {
1570 #[cfg(feature = "static-routes")]
1571 self.static_routes
1572 .write()
1573 .unwrap()
1574 .push((route.into(), gen_route_func_result(handler)));
1575 self
1576 }
1577
1578 #[must_use]
1584 pub fn with_route<
1585 C: TryInto<Content>,
1586 Response: Into<Option<C>>,
1587 F: Future<Output = Response> + Send + 'static,
1588 >(
1589 self,
1590 route: impl Into<RoutePath>,
1591 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1592 ) -> Self
1593 where
1594 C::Error: std::error::Error + 'static,
1595 {
1596 self.routes
1597 .write()
1598 .unwrap()
1599 .push((route.into(), gen_route_func(handler)));
1600 self
1601 }
1602
1603 #[allow(clippy::needless_pass_by_value)]
1611 #[must_use]
1612 pub fn with_static_route<
1613 C: TryInto<Content>,
1614 Response: Into<Option<C>>,
1615 F: Future<Output = Response> + Send + 'static,
1616 >(
1617 self,
1618 #[allow(unused_variables)] route: impl Into<RoutePath>,
1619 #[allow(unused_variables)] handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1620 ) -> Self
1621 where
1622 C::Error: std::error::Error + 'static,
1623 {
1624 #[cfg(feature = "static-routes")]
1625 self.static_routes
1626 .write()
1627 .unwrap()
1628 .push((route.into(), gen_route_func(handler)));
1629 self
1630 }
1631
1632 #[must_use]
1641 pub fn get_route_func(&self, path: &str) -> Option<RouteFunc> {
1642 let dyn_route = self
1643 .routes
1644 .read()
1645 .unwrap()
1646 .iter()
1647 .find(|(route, _)| route.matches(path))
1648 .cloned()
1649 .map(|(_, handler)| handler);
1650
1651 #[cfg(feature = "static-routes")]
1652 if dyn_route.is_none() {
1653 return self
1654 .static_routes
1655 .read()
1656 .unwrap()
1657 .iter()
1658 .find(|(route, _)| route.matches(path))
1659 .cloned()
1660 .map(|(_, handler)| handler);
1661 }
1662
1663 dyn_route
1664 }
1665
1666 pub async fn navigate(
1677 &self,
1678 navigation: impl Into<RouteRequest>,
1679 ) -> Result<Option<Content>, NavigateError> {
1680 let req = navigation.into();
1681
1682 log::debug!("navigate: method={} path={}", req.method, req.path);
1683
1684 let handler = self.get_route_func(&req.path);
1685
1686 Ok(if let Some(handler) = handler {
1687 match handler(req).await {
1688 Ok(view) => view,
1689 Err(e) => {
1690 log::error!("Failed to fetch route view: {e:?}");
1691 return Err(NavigateError::Handler(Box::new(std::io::Error::other(
1692 e.to_string(),
1693 ))));
1694 }
1695 }
1696 } else {
1697 log::warn!("Invalid navigation path={}", req.path);
1698 return Err(NavigateError::InvalidPath);
1699 })
1700 }
1701
1702 pub async fn navigate_send(
1714 &self,
1715 navigation: impl Into<RouteRequest>,
1716 ) -> Result<(), NavigateError> {
1717 let req = navigation.into();
1718
1719 log::debug!("navigate_send: method={} path={}", req.method, req.path);
1720
1721 let view = {
1722 let handler = self.get_route_func(&req.path);
1723
1724 if let Some(handler) = handler {
1725 match handler(req).await {
1726 Ok(view) => view,
1727 Err(e) => {
1728 log::error!("Failed to fetch route view: {e:?}");
1729 return Err(NavigateError::Handler(Box::new(std::io::Error::other(
1730 e.to_string(),
1731 ))));
1732 }
1733 }
1734 } else {
1735 log::warn!("Invalid navigation path={}", req.path);
1736 return Err(NavigateError::InvalidPath);
1737 }
1738 };
1739
1740 if let Some(view) = view {
1741 self.sender.send(view).map_err(|e| {
1742 log::error!("Failed to send: {e:?}");
1743 NavigateError::Sender
1744 })?;
1745 }
1746
1747 Ok(())
1748 }
1749
1750 #[must_use]
1762 pub fn navigate_spawn(
1763 &self,
1764 navigation: impl Into<RouteRequest>,
1765 ) -> JoinHandle<Result<(), Box<dyn std::error::Error + Send>>> {
1766 let navigation = navigation.into();
1767
1768 log::debug!("navigate_spawn: navigation={navigation:?}");
1769
1770 self.navigate_spawn_on(&switchy_async::runtime::Handle::current(), navigation)
1771 }
1772
1773 #[must_use]
1779 pub fn navigate_spawn_on(
1780 &self,
1781 handle: &switchy_async::runtime::Handle,
1782 navigation: impl Into<RouteRequest>,
1783 ) -> JoinHandle<Result<(), Box<dyn std::error::Error + Send>>> {
1784 let navigation = navigation.into();
1785
1786 log::debug!("navigate_spawn_on: navigation={navigation:?}");
1787
1788 let router = self.clone();
1789 handle.spawn_with_name("NativeApp navigate_spawn", async move {
1790 router
1791 .navigate_send(navigation)
1792 .await
1793 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)
1794 })
1795 }
1796
1797 #[must_use]
1801 pub async fn wait_for_navigation(&self) -> Option<Content> {
1802 self.receiver.recv_async().await.ok()
1803 }
1804
1805 #[must_use]
1811 pub fn has_route(&self, path: &str) -> bool {
1812 self.routes
1813 .read()
1814 .unwrap()
1815 .iter()
1816 .any(|(route, _)| route.matches(path))
1817 }
1818
1819 #[allow(clippy::missing_const_for_fn)]
1825 #[must_use]
1826 pub fn has_static_route(&self, path: &str) -> bool {
1827 #[cfg(feature = "static-routes")]
1828 {
1829 self.static_routes
1830 .read()
1831 .unwrap()
1832 .iter()
1833 .any(|(route, _)| route.matches(path))
1834 }
1835 #[cfg(not(feature = "static-routes"))]
1836 {
1837 let _ = path;
1838 false
1839 }
1840 }
1841
1842 pub fn add_route_result<
1848 C: TryInto<Content>,
1849 Response: Into<Option<C>>,
1850 F: Future<Output = Result<Response, BoxE>> + Send + 'static,
1851 BoxE: Into<Box<dyn std::error::Error>>,
1852 >(
1853 &self,
1854 route: impl Into<RoutePath>,
1855 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1856 ) where
1857 C::Error: Into<Box<dyn std::error::Error>>,
1858 {
1859 self.routes
1860 .write()
1861 .unwrap()
1862 .push((route.into(), gen_route_func_result(handler)));
1863 }
1864}
1865
1866fn gen_route_func<
1870 C: TryInto<Content>,
1871 Response: Into<Option<C>>,
1872 F: Future<Output = Response> + Send + 'static,
1873>(
1874 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1875) -> RouteFunc
1876where
1877 C::Error: std::error::Error + 'static,
1878{
1879 Arc::new(Box::new(move |req| {
1880 Box::pin({
1881 let handler = handler.clone();
1882 async move {
1883 let resp: Result<Option<Content>, Box<dyn std::error::Error>> = handler(req)
1884 .await
1885 .into()
1886 .map(TryInto::try_into)
1887 .transpose()
1888 .map_err(|e| {
1889 log::error!("Failed to handle route: {e:?}");
1890 Box::new(e) as Box<dyn std::error::Error>
1891 });
1892 resp
1893 }
1894 })
1895 }))
1896}
1897
1898fn gen_route_func_result<
1902 C: TryInto<Content>,
1903 Response: Into<Option<C>>,
1904 F: Future<Output = Result<Response, BoxE>> + Send + 'static,
1905 BoxE: Into<Box<dyn std::error::Error>>,
1906>(
1907 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
1908) -> RouteFunc
1909where
1910 C::Error: Into<Box<dyn std::error::Error>>,
1911{
1912 Arc::new(Box::new(move |req| {
1913 Box::pin({
1914 let handler = handler.clone();
1915 async move {
1916 let resp: Result<Response, Box<dyn std::error::Error>> =
1917 handler(req).await.map_err(Into::into);
1918 match resp.map(|x| {
1919 let x: Result<Option<Content>, Box<dyn std::error::Error>> = x
1920 .into()
1921 .map(TryInto::try_into)
1922 .transpose()
1923 .map_err(Into::into);
1924 x
1925 }) {
1926 Ok(x) => match x {
1927 Ok(x) => Ok(x),
1928 Err(e) => Err(e),
1929 },
1930 Err(e) => Err(e),
1931 }
1932 }
1933 })
1934 }))
1935}
1936
1937#[cfg(test)]
1938mod tests {
1939 #[allow(unused)]
1940 use super::*;
1941
1942 mod route_path_tests {
1943 use super::*;
1944
1945 #[test_log::test]
1946 fn test_literal_exact_match() {
1947 let route = RoutePath::Literal("/home".to_string());
1948 assert!(route.matches("/home"));
1949 assert!(!route.matches("/about"));
1950 assert!(!route.matches("/home/page"));
1951 }
1952
1953 #[test_log::test]
1954 fn test_literals_multiple_matches() {
1955 let route = RoutePath::Literals(vec!["/api/v1".to_string(), "/api/v2".to_string()]);
1956 assert!(route.matches("/api/v1"));
1957 assert!(route.matches("/api/v2"));
1958 assert!(!route.matches("/api/v3"));
1959 }
1960
1961 #[test_log::test]
1962 fn test_literal_prefix_matching() {
1963 let route = RoutePath::LiteralPrefix("/static/".to_string());
1964 assert!(route.matches("/static/"));
1965 assert!(route.matches("/static/css/style.css"));
1966 assert!(route.matches("/static/js/app.js"));
1967 assert!(!route.matches("/api/static"));
1968 assert!(!route.matches("/stati"));
1969 }
1970
1971 #[test_log::test]
1972 fn test_strip_match_literal() {
1973 let route = RoutePath::Literal("/home".to_string());
1974 assert_eq!(route.strip_match("/home"), Some(""));
1975 assert_eq!(route.strip_match("/about"), None);
1976 }
1977
1978 #[test_log::test]
1979 fn test_strip_match_literals() {
1980 let route = RoutePath::Literals(vec!["/api/v1".to_string(), "/api/v2".to_string()]);
1981 assert_eq!(route.strip_match("/api/v1"), Some(""));
1982 assert_eq!(route.strip_match("/api/v2"), Some(""));
1983 assert_eq!(route.strip_match("/api/v3"), None);
1984 }
1985
1986 #[test_log::test]
1987 fn test_strip_match_prefix() {
1988 let route = RoutePath::LiteralPrefix("/static/".to_string());
1989 assert_eq!(route.strip_match("/static/"), Some(""));
1990 assert_eq!(
1991 route.strip_match("/static/css/style.css"),
1992 Some("css/style.css")
1993 );
1994 assert_eq!(route.strip_match("/static/js/app.js"), Some("js/app.js"));
1995 assert_eq!(route.strip_match("/api/static"), None);
1996 }
1997
1998 #[test_log::test]
1999 fn test_from_str() {
2000 let route: RoutePath = "/home".into();
2001 assert_eq!(route, RoutePath::Literal("/home".to_string()));
2002 }
2003
2004 #[test_log::test]
2005 fn test_from_slice() {
2006 let routes: &[&str] = &["/api/v1", "/api/v2"];
2007 let route: RoutePath = routes.into();
2008 assert_eq!(
2009 route,
2010 RoutePath::Literals(vec!["/api/v1".to_string(), "/api/v2".to_string()])
2011 );
2012 }
2013 }
2014
2015 mod route_request_tests {
2016 use super::*;
2017
2018 #[test_log::test]
2019 fn test_from_path_without_query() {
2020 let req = RouteRequest::from_path("/home", RequestInfo::default());
2021 assert_eq!(req.path, "/home");
2022 assert_eq!(req.method, Method::Get);
2023 assert!(req.query.is_empty());
2024 }
2025
2026 #[test_log::test]
2027 fn test_from_path_with_query() {
2028 let req = RouteRequest::from_path("/search?q=rust&lang=en", RequestInfo::default());
2029 assert_eq!(req.path, "/search");
2030 assert_eq!(req.method, Method::Get);
2031 assert_eq!(req.query.get("q"), Some(&"rust".to_string()));
2032 assert_eq!(req.query.get("lang"), Some(&"en".to_string()));
2033 }
2034
2035 #[test_log::test]
2036 fn test_from_path_with_empty_query() {
2037 let req = RouteRequest::from_path("/page?", RequestInfo::default());
2038 assert_eq!(req.path, "/page");
2039 assert!(req.query.is_empty());
2040 }
2041
2042 #[test_log::test]
2043 fn test_content_type_present() {
2044 let mut req = RouteRequest::from_path("/api", RequestInfo::default());
2045 req.headers
2046 .insert("content-type".to_string(), "application/json".to_string());
2047 assert_eq!(req.content_type(), Some("application/json"));
2048 }
2049
2050 #[test_log::test]
2051 fn test_content_type_missing() {
2052 let req = RouteRequest::from_path("/api", RequestInfo::default());
2053 assert_eq!(req.content_type(), None);
2054 }
2055
2056 #[cfg(feature = "serde")]
2057 #[test_log::test]
2058 fn test_parse_body_missing() {
2059 {
2060 use serde::Deserialize;
2061 #[derive(Deserialize)]
2062 struct Data {
2063 #[allow(dead_code)]
2064 value: String,
2065 }
2066
2067 let req = RouteRequest::from_path("/api", RequestInfo::default());
2068 let result: Result<Data, _> = req.parse_body();
2069 assert!(matches!(result, Err(ParseError::MissingBody)));
2070 }
2071 }
2072
2073 #[cfg(feature = "serde")]
2074 #[test_log::test]
2075 fn test_parse_body_valid_json() {
2076 {
2077 use serde::Deserialize;
2078 #[derive(Deserialize, PartialEq, Debug)]
2079 struct Data {
2080 value: String,
2081 count: u32,
2082 }
2083
2084 let mut req = RouteRequest::from_path("/api", RequestInfo::default());
2085 let json_data = r#"{"value":"test","count":42}"#;
2086 req.body = Some(Arc::new(Bytes::from(json_data)));
2087
2088 let result: Result<Data, _> = req.parse_body();
2089 assert!(result.is_ok());
2090 let data = result.unwrap();
2091 assert_eq!(data.value, "test");
2092 assert_eq!(data.count, 42);
2093 }
2094 }
2095
2096 #[cfg(feature = "serde")]
2097 #[test_log::test]
2098 fn test_parse_body_invalid_json() {
2099 {
2100 use serde::Deserialize;
2101 #[allow(dead_code)]
2102 #[derive(Deserialize)]
2103 struct Data {
2104 value: String,
2105 }
2106
2107 let mut req = RouteRequest::from_path("/api", RequestInfo::default());
2108 req.body = Some(Arc::new(Bytes::from("not valid json")));
2109
2110 let result: Result<Data, _> = req.parse_body();
2111 assert!(matches!(result, Err(ParseError::SerdeJson(_))));
2112 }
2113 }
2114
2115 #[test_log::test]
2116 fn test_from_string() {
2117 let req: RouteRequest = "/home".to_string().into();
2118 assert_eq!(req.path, "/home");
2119 assert_eq!(req.method, Method::Get);
2120 }
2121
2122 #[test_log::test]
2123 fn test_from_tuple_with_client_info() {
2124 let client_info = ClientInfo {
2125 os: ClientOs {
2126 name: "TestOS".to_string(),
2127 },
2128 };
2129 let req: RouteRequest = ("/api".to_string(), client_info).into();
2130 assert_eq!(req.path, "/api");
2131 assert_eq!(req.info.client.os.name, "TestOS");
2132 }
2133
2134 #[test_log::test]
2135 fn test_from_tuple_with_request_info() {
2136 let client_info = Arc::new(ClientInfo {
2137 os: ClientOs {
2138 name: "TestOS".to_string(),
2139 },
2140 });
2141 let req_info = RequestInfo {
2142 client: client_info,
2143 };
2144 let req: RouteRequest = ("/search?q=test".to_string(), req_info).into();
2145 assert_eq!(req.path, "/search");
2146 assert_eq!(req.query.get("q"), Some(&"test".to_string()));
2147 assert_eq!(req.info.client.os.name, "TestOS");
2148 }
2149 }
2150
2151 mod router_tests {
2152 use super::*;
2153
2154 #[test_log::test(switchy_async::test)]
2155 async fn test_router_new() {
2156 let router = Router::new();
2157 assert_eq!(router.routes.read().unwrap().len(), 0);
2158 }
2159
2160 #[test_log::test(switchy_async::test)]
2161 async fn test_with_route_simple() {
2162 let router =
2163 Router::new().with_route("/home", |_req| async { "Home Page".to_string() });
2164
2165 let content = router.navigate("/home").await.unwrap();
2166 assert!(content.is_some());
2167 }
2168
2169 #[test_log::test(switchy_async::test)]
2170 async fn test_with_route_not_found() {
2171 let router = Router::new().with_route("/home", |_req| async { "Home".to_string() });
2172
2173 let result = router.navigate("/about").await;
2174 assert!(matches!(result, Err(NavigateError::InvalidPath)));
2175 }
2176
2177 #[test_log::test(switchy_async::test)]
2178 async fn test_multiple_routes() {
2179 let router = Router::new()
2180 .with_route("/home", |_req| async { "Home".to_string() })
2181 .with_route("/about", |_req| async { "About".to_string() });
2182
2183 let home = router.navigate("/home").await.unwrap();
2184 let about = router.navigate("/about").await.unwrap();
2185 assert!(home.is_some());
2186 assert!(about.is_some());
2187 }
2188
2189 #[test_log::test(switchy_async::test)]
2190 async fn test_route_with_prefix() {
2191 let router = Router::new().with_route(
2192 RoutePath::LiteralPrefix("/static/".to_string()),
2193 |req| async move { format!("Static file: {}", req.path) },
2194 );
2195
2196 let result = router.navigate("/static/css/style.css").await.unwrap();
2197 assert!(result.is_some());
2198 }
2199
2200 #[test_log::test(switchy_async::test)]
2201 async fn test_route_with_multiple_paths() {
2202 let router = Router::new().with_route(&["/api/v1", "/api/v2"][..], |_req| async {
2203 "API".to_string()
2204 });
2205
2206 let v1 = router.navigate("/api/v1").await.unwrap();
2207 let v2 = router.navigate("/api/v2").await.unwrap();
2208 assert!(v1.is_some());
2209 assert!(v2.is_some());
2210
2211 let v3 = router.navigate("/api/v3").await;
2212 assert!(matches!(v3, Err(NavigateError::InvalidPath)));
2213 }
2214
2215 #[test_log::test(switchy_async::test)]
2216 async fn test_with_route_result_success() {
2217 let router = Router::new().with_route_result("/data", |_req| async {
2218 Ok::<_, Box<dyn std::error::Error>>("Success".to_string())
2219 });
2220
2221 let result = router.navigate("/data").await.unwrap();
2222 assert!(result.is_some());
2223 }
2224
2225 #[test_log::test(switchy_async::test)]
2226 async fn test_with_route_result_error() {
2227 let router = Router::new().with_route_result("/error", |_req| async {
2228 Err::<String, _>(
2229 Box::new(std::io::Error::other("Test error")) as Box<dyn std::error::Error>
2230 )
2231 });
2232
2233 let result = router.navigate("/error").await;
2234 assert!(matches!(result, Err(NavigateError::Handler(_))));
2235 }
2236
2237 #[test_log::test(switchy_async::test)]
2238 async fn test_with_no_content_result() {
2239 let router = Router::new().with_no_content_result("/action", |_req| async {
2240 Ok::<_, Box<dyn std::error::Error>>(())
2241 });
2242
2243 let result = router.navigate("/action").await.unwrap();
2244 assert!(result.is_none());
2245 }
2246
2247 #[test_log::test(switchy_async::test)]
2248 async fn test_has_route() {
2249 let router = Router::new()
2250 .with_route("/home", |_req| async { "Home".to_string() })
2251 .with_route("/about", |_req| async { "About".to_string() });
2252
2253 assert!(router.has_route("/home"));
2254 assert!(router.has_route("/about"));
2255 assert!(!router.has_route("/contact"));
2256 }
2257
2258 #[cfg(feature = "static-routes")]
2259 #[test_log::test(switchy_async::test)]
2260 async fn test_with_static_route() {
2261 let router = Router::new()
2262 .with_static_route("/static", |_req| async { "Static".to_string() })
2263 .with_route("/dynamic", |_req| async { "Dynamic".to_string() });
2264
2265 assert!(!router.has_route("/static"));
2266 assert!(router.has_static_route("/static"));
2267 assert!(router.has_route("/dynamic"));
2268 }
2269
2270 #[cfg(feature = "static-routes")]
2271 #[test_log::test(switchy_async::test)]
2272 async fn test_static_route_navigation() {
2273 let router = Router::new()
2274 .with_static_route("/page", |_req| async { "Static Page".to_string() });
2275
2276 let result = router.navigate("/page").await.unwrap();
2277 assert!(result.is_some());
2278 }
2279
2280 #[test_log::test(switchy_async::test)]
2281 async fn test_get_route_func_present() {
2282 let router = Router::new().with_route("/home", |_req| async { "Home".to_string() });
2283
2284 let route_func = router.get_route_func("/home");
2285 assert!(route_func.is_some());
2286 }
2287
2288 #[test_log::test(switchy_async::test)]
2289 async fn test_get_route_func_missing() {
2290 let router = Router::new().with_route("/home", |_req| async { "Home".to_string() });
2291
2292 let route_func = router.get_route_func("/about");
2293 assert!(route_func.is_none());
2294 }
2295
2296 #[test_log::test(switchy_async::test)]
2297 async fn test_add_route_result() {
2298 let router = Router::new();
2299 router.add_route_result("/dynamic", |_req| async {
2300 Ok::<_, Box<dyn std::error::Error>>("Added".to_string())
2301 });
2302
2303 let result = router.navigate("/dynamic").await.unwrap();
2304 assert!(result.is_some());
2305 }
2306
2307 #[test_log::test(switchy_async::test)]
2308 async fn test_navigate_with_query_params() {
2309 let router = Router::new().with_route("/search", |req| async move {
2310 let query = req.query.get("q").cloned().unwrap_or_default();
2311 format!("Search: {query}")
2312 });
2313
2314 let req = RouteRequest::from_path("/search?q=rust", RequestInfo::default());
2315 let result = router.navigate(req).await.unwrap();
2316 assert!(result.is_some());
2317 }
2318
2319 #[test_log::test(switchy_async::test)]
2320 async fn test_navigate_with_different_methods() {
2321 let router = Router::new().with_route("/api", |req| async move {
2322 match req.method {
2323 Method::Get => "GET request".to_string(),
2324 Method::Post => "POST request".to_string(),
2325 _ => "Other method".to_string(),
2326 }
2327 });
2328
2329 let mut get_req = RouteRequest::from_path("/api", RequestInfo::default());
2330 get_req.method = Method::Get;
2331 let get_result = router.navigate(get_req).await.unwrap();
2332 assert!(get_result.is_some());
2333
2334 let mut post_req = RouteRequest::from_path("/api", RequestInfo::default());
2335 post_req.method = Method::Post;
2336 let post_result = router.navigate(post_req).await.unwrap();
2337 assert!(post_result.is_some());
2338 }
2339
2340 #[test_log::test(switchy_async::test)]
2341 async fn test_router_clone() {
2342 let router = Router::new().with_route("/home", |_req| async { "Home".to_string() });
2343
2344 let cloned = router.clone();
2345 let result = cloned.navigate("/home").await.unwrap();
2346 assert!(result.is_some());
2347 }
2348 }
2349
2350 mod navigation_tests {
2351 use super::*;
2352
2353 #[test_log::test]
2354 fn test_navigation_from_str() {
2355 let nav: Navigation = "/home".into();
2356 assert_eq!(nav.0, "/home");
2357 }
2358
2359 #[test_log::test]
2360 fn test_navigation_from_string() {
2361 let nav: Navigation = "/about".to_string().into();
2362 assert_eq!(nav.0, "/about");
2363 }
2364
2365 #[test_log::test]
2366 fn test_navigation_from_tuple() {
2367 let client = ClientInfo {
2368 os: ClientOs {
2369 name: "TestOS".to_string(),
2370 },
2371 };
2372 let nav: Navigation = ("/page".to_string(), client).into();
2373 assert_eq!(nav.0, "/page");
2374 assert_eq!(nav.1.os.name, "TestOS");
2375 }
2376
2377 #[test_log::test]
2378 fn test_navigation_from_route_request() {
2379 let mut req = RouteRequest::from_path("/search", RequestInfo::default());
2380 req.query.insert("q".to_string(), "test".to_string());
2381 req.query.insert("limit".to_string(), "10".to_string());
2382
2383 let nav: Navigation = req.into();
2384 assert!(nav.0.starts_with("/search?"));
2385 assert!(nav.0.contains("q=test"));
2386 assert!(nav.0.contains("limit=10"));
2387 }
2388
2389 #[test_log::test]
2390 fn test_route_request_from_navigation() {
2391 let nav = Navigation("/page".to_string(), DEFAULT_CLIENT_INFO.clone());
2392 let req: RouteRequest = nav.into();
2393 assert_eq!(req.path, "/page");
2394 assert_eq!(req.method, Method::Get);
2395 }
2396
2397 #[test_log::test]
2398 fn test_navigation_roundtrip() {
2399 let original = Navigation("/test".to_string(), DEFAULT_CLIENT_INFO.clone());
2400 let req: RouteRequest = original.clone().into();
2401 let nav: Navigation = req.into();
2402 assert_eq!(original.0, nav.0);
2403 }
2404
2405 #[test_log::test]
2406 fn test_navigation_from_route_request_without_query() {
2407 let req = RouteRequest::from_path("/page", RequestInfo::default());
2408 assert!(req.query.is_empty());
2410
2411 let nav: Navigation = req.into();
2412 assert_eq!(nav.0, "/page");
2414 assert!(!nav.0.contains('?'));
2415 }
2416
2417 #[test_log::test]
2418 fn test_navigation_from_route_request_with_single_query_param() {
2419 let mut req = RouteRequest::from_path("/search", RequestInfo::default());
2420 req.query.insert("q".to_string(), "rust".to_string());
2421
2422 let nav: Navigation = req.into();
2423 assert_eq!(nav.0.matches('?').count(), 1);
2425 assert!(nav.0.contains("q=rust"));
2426 }
2427 }
2428
2429 mod client_info_tests {
2430 use super::*;
2431
2432 #[test_log::test]
2433 fn test_client_os_default() {
2434 let os = ClientOs::default();
2435 assert_eq!(os.name, "");
2436 }
2437
2438 #[test_log::test]
2439 fn test_client_info_default() {
2440 let info = ClientInfo::default();
2441 assert!(!info.os.name.is_empty());
2442 }
2443
2444 #[test_log::test]
2445 fn test_default_client_info_static() {
2446 let info = DEFAULT_CLIENT_INFO.clone();
2447 assert!(!info.os.name.is_empty());
2448 }
2449
2450 #[test_log::test]
2451 fn test_request_info_default() {
2452 let info = RequestInfo::default();
2453 assert!(!info.client.os.name.is_empty());
2454 }
2455 }
2456
2457 #[cfg(feature = "form")]
2458 mod form_deserializer_tests {
2459 use super::*;
2460 use serde::Deserialize;
2461 use std::collections::BTreeMap;
2462
2463 #[test_log::test]
2464 fn test_deserialize_primitives() {
2465 {
2466 #[derive(Debug, Deserialize, PartialEq)]
2467 struct TestForm {
2468 age: u64,
2469 score: i32,
2470 ratio: f64,
2471 active: bool,
2472 letter: char,
2473 }
2474
2475 let mut data = BTreeMap::new();
2476 data.insert("age".to_string(), "2445072108".to_string());
2477 data.insert("score".to_string(), "-42".to_string());
2478 data.insert("ratio".to_string(), "5.5".to_string());
2479 data.insert("active".to_string(), "true".to_string());
2480 data.insert("letter".to_string(), "A".to_string());
2481
2482 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2483 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2484
2485 assert!(result.is_ok());
2486 let form = result.unwrap();
2487 assert_eq!(form.age, 2_445_072_108);
2488 assert_eq!(form.score, -42);
2489 assert!((form.ratio - 5.5).abs() < f64::EPSILON);
2490 assert!(form.active);
2491 assert_eq!(form.letter, 'A');
2492 }
2493 }
2494
2495 #[test_log::test]
2496 fn test_deserialize_strings() {
2497 {
2498 #[derive(Debug, Deserialize, PartialEq)]
2499 struct TestForm {
2500 name: String,
2501 email: String,
2502 }
2503
2504 let mut data = BTreeMap::new();
2505 data.insert("name".to_string(), "Alice".to_string());
2506 data.insert("email".to_string(), "alice@example.com".to_string());
2507
2508 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2509 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2510
2511 assert!(result.is_ok());
2512 let form = result.unwrap();
2513 assert_eq!(form.name, "Alice");
2514 assert_eq!(form.email, "alice@example.com");
2515 }
2516 }
2517
2518 #[test_log::test]
2519 fn test_deserialize_options() {
2520 {
2521 #[allow(clippy::struct_field_names)]
2522 #[derive(Debug, Deserialize, PartialEq)]
2523 struct TestForm {
2524 optional_field: Option<u64>,
2525 empty_field: Option<String>,
2526 null_field: Option<i32>,
2527 present_field: Option<String>,
2528 }
2529
2530 let mut data = BTreeMap::new();
2531 data.insert("optional_field".to_string(), "123".to_string());
2532 data.insert("empty_field".to_string(), String::new());
2533 data.insert("null_field".to_string(), "null".to_string());
2534 data.insert("present_field".to_string(), "value".to_string());
2535
2536 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2537 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2538
2539 assert!(result.is_ok());
2540 let form = result.unwrap();
2541 assert_eq!(form.optional_field, Some(123));
2542 assert_eq!(form.empty_field, None);
2543 assert_eq!(form.null_field, None);
2544 assert_eq!(form.present_field, Some("value".to_string()));
2545 }
2546 }
2547
2548 #[test_log::test]
2549 fn test_deserialize_all_integer_types() {
2550 {
2551 #[allow(clippy::struct_field_names)]
2552 #[derive(Debug, Deserialize, PartialEq)]
2553 struct TestForm {
2554 u8_field: u8,
2555 u16_field: u16,
2556 u32_field: u32,
2557 u64_field: u64,
2558 i8_field: i8,
2559 i16_field: i16,
2560 i32_field: i32,
2561 i64_field: i64,
2562 }
2563
2564 let mut data = BTreeMap::new();
2565 data.insert("u8_field".to_string(), "255".to_string());
2566 data.insert("u16_field".to_string(), "65535".to_string());
2567 data.insert("u32_field".to_string(), "4294967295".to_string());
2568 data.insert("u64_field".to_string(), "18446744073709551615".to_string());
2569 data.insert("i8_field".to_string(), "-128".to_string());
2570 data.insert("i16_field".to_string(), "-32768".to_string());
2571 data.insert("i32_field".to_string(), "-2147483648".to_string());
2572 data.insert("i64_field".to_string(), "-9223372036854775808".to_string());
2573
2574 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2575 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2576
2577 assert!(result.is_ok());
2578 let form = result.unwrap();
2579 assert_eq!(form.u8_field, 255);
2580 assert_eq!(form.u16_field, 65_535);
2581 assert_eq!(form.u32_field, 4_294_967_295);
2582 assert_eq!(form.u64_field, 18_446_744_073_709_551_615);
2583 assert_eq!(form.i8_field, -128);
2584 assert_eq!(form.i16_field, -32_768);
2585 assert_eq!(form.i32_field, -2_147_483_648);
2586 assert_eq!(form.i64_field, -9_223_372_036_854_775_808);
2587 }
2588 }
2589
2590 #[test_log::test]
2591 fn test_deserialize_booleans() {
2592 {
2593 #[derive(Debug, Deserialize, PartialEq)]
2594 struct TestForm {
2595 bool1: bool,
2596 bool2: bool,
2597 }
2598
2599 let mut data = BTreeMap::new();
2600 data.insert("bool1".to_string(), "true".to_string());
2601 data.insert("bool2".to_string(), "false".to_string());
2602
2603 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2604 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2605
2606 assert!(result.is_ok());
2607 let form = result.unwrap();
2608 assert!(form.bool1);
2609 assert!(!form.bool2);
2610 }
2611 }
2612
2613 #[test_log::test]
2614 fn test_deserialize_with_serde_rename() {
2615 {
2616 #[derive(Debug, Deserialize, PartialEq)]
2617 struct TestForm {
2618 #[serde(rename = "user_age")]
2619 age: u64,
2620 #[serde(rename = "user_name")]
2621 name: String,
2622 }
2623
2624 let mut data = BTreeMap::new();
2625 data.insert("user_age".to_string(), "30".to_string());
2626 data.insert("user_name".to_string(), "Bob".to_string());
2627
2628 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2629 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2630
2631 assert!(result.is_ok());
2632 let form = result.unwrap();
2633 assert_eq!(form.age, 30);
2634 assert_eq!(form.name, "Bob");
2635 }
2636 }
2637
2638 #[test_log::test]
2639 fn test_deserialize_with_default() {
2640 {
2641 #[derive(Debug, Deserialize, PartialEq)]
2642 struct TestForm {
2643 required: String,
2644 #[serde(default)]
2645 optional: String,
2646 }
2647
2648 let mut data = BTreeMap::new();
2649 data.insert("required".to_string(), "value".to_string());
2650
2651 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2652 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2653
2654 assert!(result.is_ok());
2655 let form = result.unwrap();
2656 assert_eq!(form.required, "value");
2657 assert_eq!(form.optional, "");
2658 }
2659 }
2660
2661 #[test_log::test]
2662 fn test_invalid_integer_format() {
2663 {
2664 #[allow(dead_code)]
2665 #[derive(Debug, Deserialize)]
2666 struct TestForm {
2667 age: u64,
2668 }
2669
2670 let mut data = BTreeMap::new();
2671 data.insert("age".to_string(), "not_a_number".to_string());
2672
2673 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2674 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2675
2676 assert!(result.is_err());
2677 }
2678 }
2679
2680 #[test_log::test]
2681 fn test_integer_overflow() {
2682 {
2683 #[allow(dead_code)]
2684 #[derive(Debug, Deserialize)]
2685 struct TestForm {
2686 small: u8,
2687 }
2688
2689 let mut data = BTreeMap::new();
2690 data.insert("small".to_string(), "999999".to_string());
2691
2692 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2693 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2694
2695 assert!(result.is_err());
2696 }
2697 }
2698
2699 #[test_log::test]
2700 fn test_original_error_case() {
2701 {
2702 #[derive(Debug, Deserialize, PartialEq)]
2703 struct TestForm {
2704 id: u64,
2705 }
2706
2707 let mut data = BTreeMap::new();
2708 data.insert("id".to_string(), "2445072108".to_string());
2709
2710 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2711 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
2712
2713 assert!(result.is_ok());
2714 let form = result.unwrap();
2715 assert_eq!(form.id, 2_445_072_108);
2716 }
2717 }
2718
2719 #[test_log::test]
2720 fn test_flatten_with_tagged_enum() {
2721 {
2722 #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
2723 #[serde(tag = "comment_type")]
2724 enum CommentType {
2725 General,
2726 Reply { in_reply_to: u64 },
2727 }
2728 #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
2729 struct CreateComment {
2730 body: String,
2731 #[serde(flatten)]
2732 comment_type: CommentType,
2733 }
2734
2735 let mut data = BTreeMap::new();
2736 data.insert("body".to_string(), "test comment".to_string());
2737 data.insert("comment_type".to_string(), "Reply".to_string());
2738 data.insert("in_reply_to".to_string(), "2445072108".to_string());
2739
2740 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2741 let result: Result<CreateComment, _> = CreateComment::deserialize(deserializer);
2742
2743 assert!(result.is_ok());
2744 let comment = result.unwrap();
2745 assert_eq!(comment.body, "test comment");
2746 assert_eq!(
2747 comment.comment_type,
2748 CommentType::Reply {
2749 in_reply_to: 2_445_072_108
2750 }
2751 );
2752 }
2753 }
2754
2755 #[test_log::test]
2756 fn test_flatten_with_tagged_enum_general_variant() {
2757 {
2758 #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
2759 #[serde(tag = "comment_type")]
2760 enum CommentType {
2761 General,
2762 Reply { in_reply_to: u64 },
2763 }
2764 #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
2765 struct CreateComment {
2766 body: String,
2767 #[serde(flatten)]
2768 comment_type: CommentType,
2769 }
2770
2771 let mut data = BTreeMap::new();
2772 data.insert("body".to_string(), "test comment".to_string());
2773 data.insert("comment_type".to_string(), "General".to_string());
2774
2775 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2776 let result: Result<CreateComment, _> = CreateComment::deserialize(deserializer);
2777
2778 assert!(result.is_ok());
2779 let comment = result.unwrap();
2780 assert_eq!(comment.body, "test comment");
2781 assert_eq!(comment.comment_type, CommentType::General);
2782 }
2783 }
2784
2785 #[test_log::test]
2786 fn test_flatten_with_multiple_integer_fields() {
2787 {
2788 #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
2789 #[serde(tag = "action_type")]
2790 enum Action {
2791 Transfer { from: u64, to: u64, amount: u64 },
2792 }
2793 #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
2794 struct Request {
2795 user_id: u64,
2796 #[serde(flatten)]
2797 action: Action,
2798 }
2799
2800 let mut data = BTreeMap::new();
2801 data.insert("user_id".to_string(), "100".to_string());
2802 data.insert("action_type".to_string(), "Transfer".to_string());
2803 data.insert("from".to_string(), "200".to_string());
2804 data.insert("to".to_string(), "300".to_string());
2805 data.insert("amount".to_string(), "1000".to_string());
2806
2807 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2808 let result: Result<Request, _> = Request::deserialize(deserializer);
2809
2810 assert!(result.is_ok());
2811 let request = result.unwrap();
2812 assert_eq!(request.user_id, 100);
2813 assert_eq!(
2814 request.action,
2815 Action::Transfer {
2816 from: 200,
2817 to: 300,
2818 amount: 1000
2819 }
2820 );
2821 }
2822 }
2823
2824 #[test_log::test]
2825 fn test_deserialize_any_type_inference() {
2826 {
2827 use serde::de::Deserialize;
2828
2829 let bool_true = form_deserializer::StringValueDeserializer::new("true".to_string());
2830 let result: Result<bool, _> = bool::deserialize(bool_true);
2831 assert!(result.unwrap());
2832
2833 let bool_false =
2834 form_deserializer::StringValueDeserializer::new("false".to_string());
2835 let result: Result<bool, _> = bool::deserialize(bool_false);
2836 assert!(!result.unwrap());
2837
2838 let number = form_deserializer::StringValueDeserializer::new("42".to_string());
2839 let result: Result<u64, _> = u64::deserialize(number);
2840 assert_eq!(result.unwrap(), 42);
2841
2842 let negative = form_deserializer::StringValueDeserializer::new("-42".to_string());
2843 let result: Result<i64, _> = i64::deserialize(negative);
2844 assert_eq!(result.unwrap(), -42);
2845
2846 let float_val = form_deserializer::StringValueDeserializer::new("2.5".to_string());
2847 let result: Result<f64, _> = f64::deserialize(float_val);
2848 assert!((result.unwrap() - 2.5).abs() < f64::EPSILON);
2849
2850 let string_val =
2851 form_deserializer::StringValueDeserializer::new("hello".to_string());
2852 let result: Result<String, _> = String::deserialize(string_val);
2853 assert_eq!(result.unwrap(), "hello");
2854 }
2855 }
2856
2857 #[test_log::test]
2858 fn test_flatten_with_mixed_types() {
2859 {
2860 #[derive(Debug, Clone, Deserialize, PartialEq)]
2861 #[serde(tag = "type")]
2862 enum Metadata {
2863 Numeric { count: u64, ratio: f64 },
2864 Text { description: String },
2865 }
2866 #[derive(Debug, Clone, Deserialize, PartialEq)]
2867 struct Item {
2868 name: String,
2869 active: bool,
2870 #[serde(flatten)]
2871 metadata: Metadata,
2872 }
2873
2874 let mut data = BTreeMap::new();
2875 data.insert("name".to_string(), "Test Item".to_string());
2876 data.insert("active".to_string(), "true".to_string());
2877 data.insert("type".to_string(), "Numeric".to_string());
2878 data.insert("count".to_string(), "42".to_string());
2879 data.insert("ratio".to_string(), "0.75".to_string());
2880
2881 let deserializer = form_deserializer::FormDataDeserializer::new(data);
2882 let result: Result<Item, _> = Item::deserialize(deserializer);
2883
2884 assert!(result.is_ok());
2885 let item = result.unwrap();
2886 assert_eq!(item.name, "Test Item");
2887 assert!(item.active);
2888 if let Metadata::Numeric { count, ratio } = item.metadata {
2889 assert_eq!(count, 42);
2890 assert!((ratio - 0.75).abs() < f64::EPSILON);
2891 } else {
2892 panic!("Expected Numeric variant");
2893 }
2894 }
2895 }
2896
2897 #[test_log::test]
2898 fn test_deserialize_invalid_char_multi_character_string() {
2899 let deserializer = form_deserializer::StringValueDeserializer::new("abc".to_string());
2900 let result: Result<char, _> = char::deserialize(deserializer);
2901 assert!(result.is_err());
2902 }
2903
2904 #[test_log::test]
2905 fn test_deserialize_bytes_with_visitor() {
2906 {
2907 use serde::de::Deserializer;
2908 struct ByteBufVisitor;
2909 impl serde::de::Visitor<'_> for ByteBufVisitor {
2910 type Value = Vec<u8>;
2911
2912 fn expecting(
2913 &self,
2914 formatter: &mut std::fmt::Formatter<'_>,
2915 ) -> std::fmt::Result {
2916 write!(formatter, "byte buffer")
2917 }
2918
2919 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E> {
2920 Ok(v)
2921 }
2922 }
2923
2924 let deserializer =
2925 form_deserializer::StringValueDeserializer::new("test data".to_string());
2926 let result = deserializer.deserialize_byte_buf(ByteBufVisitor);
2927 assert!(result.is_ok());
2928 assert_eq!(result.unwrap(), b"test data".to_vec());
2929 }
2930 }
2931
2932 #[test_log::test]
2933 fn test_deserialize_unit_from_string() {
2934 let deserializer =
2935 form_deserializer::StringValueDeserializer::new("anything".to_string());
2936 let result: Result<(), _> = serde::Deserialize::deserialize(deserializer);
2937 assert!(result.is_ok());
2938 }
2939
2940 #[test_log::test]
2941 fn test_deserialize_null_value_as_unit() {
2942 {
2943 use serde::de::Deserializer;
2944 struct UnitVisitor;
2945 impl serde::de::Visitor<'_> for UnitVisitor {
2946 type Value = ();
2947
2948 fn expecting(
2949 &self,
2950 formatter: &mut std::fmt::Formatter<'_>,
2951 ) -> std::fmt::Result {
2952 write!(formatter, "null")
2953 }
2954
2955 fn visit_unit<E>(self) -> Result<Self::Value, E> {
2956 Ok(())
2957 }
2958 }
2959
2960 let deserializer =
2961 form_deserializer::StringValueDeserializer::new("null".to_string());
2962 let result = deserializer.deserialize_any(UnitVisitor);
2963 assert!(result.is_ok());
2964 }
2965 }
2966
2967 #[test_log::test]
2968 fn test_deserialize_any_case_insensitive_booleans() {
2969 {
2970 use serde::de::Deserializer;
2971 struct BoolVisitor;
2972 impl serde::de::Visitor<'_> for BoolVisitor {
2973 type Value = bool;
2974
2975 fn expecting(
2976 &self,
2977 formatter: &mut std::fmt::Formatter<'_>,
2978 ) -> std::fmt::Result {
2979 write!(formatter, "boolean")
2980 }
2981
2982 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
2983 Ok(v)
2984 }
2985 }
2986
2987 let true_upper =
2990 form_deserializer::StringValueDeserializer::new("TRUE".to_string());
2991 let result = true_upper.deserialize_any(BoolVisitor);
2992 assert!(result.is_ok());
2993 assert!(result.unwrap());
2994
2995 let false_mixed =
2996 form_deserializer::StringValueDeserializer::new("False".to_string());
2997 let result = false_mixed.deserialize_any(BoolVisitor);
2998 assert!(result.is_ok());
2999 assert!(!result.unwrap());
3000 }
3001 }
3002
3003 #[test_log::test]
3004 fn test_deserialize_f32() {
3005 let deserializer = form_deserializer::StringValueDeserializer::new("1.234".to_string());
3006 let result: Result<f32, _> = f32::deserialize(deserializer);
3007 assert!(result.is_ok());
3008 assert!((result.unwrap() - 1.234).abs() < f32::EPSILON);
3009 }
3010
3011 #[test_log::test]
3012 fn test_deserialize_i128_and_u128() {
3013 let i128_deser = form_deserializer::StringValueDeserializer::new(
3014 "-170141183460469231731687303715884105728".to_string(),
3015 );
3016 let result: Result<i128, _> = i128::deserialize(i128_deser);
3017 assert!(result.is_ok());
3018 assert_eq!(result.unwrap(), i128::MIN);
3019
3020 let u128_deser = form_deserializer::StringValueDeserializer::new(
3021 "340282366920938463463374607431768211455".to_string(),
3022 );
3023 let result: Result<u128, _> = u128::deserialize(u128_deser);
3024 assert!(result.is_ok());
3025 assert_eq!(result.unwrap(), u128::MAX);
3026 }
3027
3028 #[test_log::test]
3029 fn test_form_data_deserializer_error_on_primitive_types() {
3030 {
3031 use serde::de::Deserializer;
3032 struct BoolVisitor;
3033 impl serde::de::Visitor<'_> for BoolVisitor {
3034 type Value = bool;
3035 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3036 write!(f, "bool")
3037 }
3038 }
3039
3040 let data = BTreeMap::new();
3041 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3042 let result = deserializer.deserialize_bool(BoolVisitor);
3043 assert!(result.is_err());
3044 }
3045 }
3046
3047 #[test_log::test]
3048 fn test_form_data_deserializer_error_on_seq() {
3049 {
3050 use serde::de::Deserializer;
3051 struct SeqVisitor;
3052 impl serde::de::Visitor<'_> for SeqVisitor {
3053 type Value = Vec<String>;
3054 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3055 write!(f, "seq")
3056 }
3057 }
3058
3059 let data = BTreeMap::new();
3060 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3061 let result = deserializer.deserialize_seq(SeqVisitor);
3062 assert!(result.is_err());
3063 }
3064 }
3065
3066 #[test_log::test]
3067 fn test_form_data_deserializer_error_on_tuple() {
3068 {
3069 use serde::de::Deserializer;
3070 struct TupleVisitor;
3071 impl serde::de::Visitor<'_> for TupleVisitor {
3072 type Value = (String, i32);
3073 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3074 write!(f, "tuple")
3075 }
3076 }
3077
3078 let data = BTreeMap::new();
3079 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3080 let result = deserializer.deserialize_tuple(2, TupleVisitor);
3081 assert!(result.is_err());
3082 }
3083 }
3084
3085 #[test_log::test]
3086 fn test_form_data_deserializer_error_on_enum() {
3087 {
3088 use serde::de::Deserializer;
3089 struct EnumVisitor;
3090 impl serde::de::Visitor<'_> for EnumVisitor {
3091 type Value = String;
3092 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3093 write!(f, "enum")
3094 }
3095 }
3096
3097 let data = BTreeMap::new();
3098 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3099 let result = deserializer.deserialize_enum("Test", &["A", "B"], EnumVisitor);
3100 assert!(result.is_err());
3101 }
3102 }
3103
3104 #[test_log::test]
3105 fn test_deserialize_newtype_struct() {
3106 {
3107 #[derive(Debug, Deserialize, PartialEq)]
3108 struct UserId(u64);
3109 #[derive(Debug, Deserialize, PartialEq)]
3110 struct TestForm {
3111 user_id: UserId,
3112 }
3113
3114 let mut data = BTreeMap::new();
3115 data.insert("user_id".to_string(), "12345".to_string());
3116
3117 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3118 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
3119
3120 assert!(result.is_ok());
3121 let form = result.unwrap();
3122 assert_eq!(form.user_id, UserId(12345));
3123 }
3124 }
3125
3126 #[test_log::test]
3127 fn test_deserialize_newtype_string_wrapper() {
3128 {
3129 #[derive(Debug, Deserialize, PartialEq)]
3130 struct Email(String);
3131 #[derive(Debug, Deserialize, PartialEq)]
3132 struct TestForm {
3133 email: Email,
3134 }
3135
3136 let mut data = BTreeMap::new();
3137 data.insert("email".to_string(), "test@example.com".to_string());
3138
3139 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3140 let result: Result<TestForm, _> = TestForm::deserialize(deserializer);
3141
3142 assert!(result.is_ok());
3143 let form = result.unwrap();
3144 assert_eq!(form.email, Email("test@example.com".to_string()));
3145 }
3146 }
3147
3148 #[test_log::test]
3149 fn test_form_data_deserializer_unit_struct() {
3150 {
3151 use serde::de::Deserializer;
3152 struct UnitVisitor;
3153 impl serde::de::Visitor<'_> for UnitVisitor {
3154 type Value = ();
3155 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3156 write!(f, "unit")
3157 }
3158 fn visit_unit<E>(self) -> Result<Self::Value, E> {
3159 Ok(())
3160 }
3161 }
3162
3163 let data = BTreeMap::new();
3164 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3165 let result = deserializer.deserialize_unit_struct("TestUnit", UnitVisitor);
3166 assert!(result.is_ok());
3167 }
3168 }
3169
3170 #[test_log::test]
3171 fn test_form_data_deserializer_ignored_any() {
3172 {
3173 use serde::de::Deserializer;
3174 struct IgnoredVisitor;
3175 impl serde::de::Visitor<'_> for IgnoredVisitor {
3176 type Value = ();
3177 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3178 write!(f, "ignored")
3179 }
3180 fn visit_unit<E>(self) -> Result<Self::Value, E> {
3181 Ok(())
3182 }
3183 }
3184
3185 let mut data = BTreeMap::new();
3186 data.insert("field1".to_string(), "value1".to_string());
3187 data.insert("field2".to_string(), "value2".to_string());
3188
3189 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3190 let result = deserializer.deserialize_ignored_any(IgnoredVisitor);
3191 assert!(result.is_ok());
3192 }
3193 }
3194
3195 #[test_log::test]
3196 fn test_form_data_deserializer_option_visits_some() {
3197 {
3198 use serde::de::Deserializer;
3199 struct OptionVisitor;
3200
3201 impl<'de> serde::de::Visitor<'de> for OptionVisitor {
3202 type Value = Option<BTreeMap<String, String>>;
3203 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3204 write!(f, "option")
3205 }
3206 fn visit_some<D>(self, _deserializer: D) -> Result<Self::Value, D::Error>
3207 where
3208 D: serde::de::Deserializer<'de>,
3209 {
3210 Ok(Some(BTreeMap::new()))
3211 }
3212 }
3213
3214 let mut data = BTreeMap::new();
3215 data.insert("field".to_string(), "value".to_string());
3216
3217 let deserializer = form_deserializer::FormDataDeserializer::new(data);
3218 let result = deserializer.deserialize_option(OptionVisitor);
3219 assert!(result.is_ok());
3220 assert!(result.unwrap().is_some());
3221 }
3222 }
3223 }
3224
3225 #[cfg(feature = "form")]
3226 mod parse_form_tests {
3227 use super::*;
3228 use bytes::Bytes;
3229 use serde::Deserialize;
3230 use std::sync::Arc;
3231
3232 #[derive(Debug, Deserialize, PartialEq)]
3233 struct TestForm {
3234 name: String,
3235 age: u32,
3236 }
3237
3238 #[test_log::test]
3239 fn test_parse_urlencoded_form() {
3240 let body = b"name=Alice&age=30";
3241 let mut req = RouteRequest::from_path("/submit", RequestInfo::default());
3242 req.body = Some(Arc::new(Bytes::from_static(body)));
3243 req.headers.insert(
3244 "content-type".to_string(),
3245 "application/x-www-form-urlencoded".to_string(),
3246 );
3247
3248 let result: Result<TestForm, _> = req.parse_form();
3249
3250 assert!(result.is_ok());
3251 let form = result.unwrap();
3252 assert_eq!(form.name, "Alice");
3253 assert_eq!(form.age, 30);
3254 }
3255
3256 #[test_log::test]
3257 fn test_parse_urlencoded_form_with_special_chars() {
3258 let body = b"name=Hello%20World&age=25";
3260 let mut req = RouteRequest::from_path("/submit", RequestInfo::default());
3261 req.body = Some(Arc::new(Bytes::from_static(body)));
3262 req.headers.insert(
3263 "content-type".to_string(),
3264 "application/x-www-form-urlencoded".to_string(),
3265 );
3266
3267 let result: Result<TestForm, _> = req.parse_form();
3268
3269 assert!(result.is_ok());
3270 let form = result.unwrap();
3271 assert_eq!(form.name, "Hello World");
3272 assert_eq!(form.age, 25);
3273 }
3274
3275 #[test_log::test]
3276 fn test_parse_urlencoded_form_with_charset() {
3277 let body = b"name=Bob&age=42";
3278 let mut req = RouteRequest::from_path("/submit", RequestInfo::default());
3279 req.body = Some(Arc::new(Bytes::from_static(body)));
3280 req.headers.insert(
3281 "content-type".to_string(),
3282 "application/x-www-form-urlencoded; charset=UTF-8".to_string(),
3283 );
3284
3285 let result: Result<TestForm, _> = req.parse_form();
3286
3287 assert!(result.is_ok());
3288 let form = result.unwrap();
3289 assert_eq!(form.name, "Bob");
3290 assert_eq!(form.age, 42);
3291 }
3292
3293 #[test_log::test]
3294 fn test_parse_form_missing_body() {
3295 let mut req = RouteRequest::from_path("/submit", RequestInfo::default());
3296 req.headers.insert(
3297 "content-type".to_string(),
3298 "application/x-www-form-urlencoded".to_string(),
3299 );
3300
3301 let result: Result<TestForm, _> = req.parse_form();
3302
3303 assert!(matches!(result, Err(ParseError::MissingBody)));
3304 }
3305
3306 #[test_log::test]
3307 fn test_parse_form_missing_content_type() {
3308 let body = b"name=Alice&age=30";
3309 let mut req = RouteRequest::from_path("/submit", RequestInfo::default());
3310 req.body = Some(Arc::new(Bytes::from_static(body)));
3311
3312 let result: Result<TestForm, _> = req.parse_form();
3313
3314 assert!(matches!(result, Err(ParseError::InvalidContentType)));
3315 }
3316
3317 #[test_log::test]
3318 fn test_parse_form_unsupported_content_type() {
3319 let body = b"name=Alice&age=30";
3320 let mut req = RouteRequest::from_path("/submit", RequestInfo::default());
3321 req.body = Some(Arc::new(Bytes::from_static(body)));
3322 req.headers
3323 .insert("content-type".to_string(), "text/plain".to_string());
3324
3325 let result: Result<TestForm, _> = req.parse_form();
3326
3327 assert!(matches!(result, Err(ParseError::InvalidContentType)));
3328 }
3329
3330 #[test_log::test]
3331 fn test_parse_urlencoded_form_with_optional_fields() {
3332 {
3333 #[derive(Debug, Deserialize, PartialEq)]
3334 struct FormWithOptional {
3335 name: String,
3336 email: Option<String>,
3337 }
3338
3339 let body = b"name=Alice";
3340 let mut req = RouteRequest::from_path("/submit", RequestInfo::default());
3341 req.body = Some(Arc::new(Bytes::from_static(body)));
3342 req.headers.insert(
3343 "content-type".to_string(),
3344 "application/x-www-form-urlencoded".to_string(),
3345 );
3346
3347 let result: Result<FormWithOptional, _> = req.parse_form();
3348
3349 assert!(result.is_ok());
3350 let form = result.unwrap();
3351 assert_eq!(form.name, "Alice");
3352 assert_eq!(form.email, None);
3353 }
3354 }
3355 }
3356
3357 mod channel_tests {
3358 use super::*;
3359
3360 #[test_log::test(switchy_async::test)]
3361 async fn test_navigate_send_success() {
3362 let router =
3363 Router::new().with_route("/home", |_req| async { "Home Content".to_string() });
3364
3365 let result = router.navigate_send("/home").await;
3366 assert!(result.is_ok());
3367
3368 let content = router.receiver.try_recv();
3370 assert!(content.is_ok());
3371 }
3372
3373 #[test_log::test(switchy_async::test)]
3374 async fn test_navigate_send_invalid_path() {
3375 let router = Router::new().with_route("/home", |_req| async { "Home".to_string() });
3376
3377 let result = router.navigate_send("/nonexistent").await;
3378 assert!(matches!(result, Err(NavigateError::InvalidPath)));
3379 }
3380
3381 #[test_log::test(switchy_async::test)]
3382 async fn test_navigate_send_handler_error() {
3383 let router = Router::new().with_route_result("/error", |_req| async {
3384 Err::<String, _>(
3385 Box::new(std::io::Error::other("Handler failed")) as Box<dyn std::error::Error>
3386 )
3387 });
3388
3389 let result = router.navigate_send("/error").await;
3390 assert!(matches!(result, Err(NavigateError::Handler(_))));
3391 }
3392
3393 #[test_log::test(switchy_async::test)]
3394 async fn test_navigate_send_no_content() {
3395 let router = Router::new().with_no_content_result("/action", |_req| async {
3396 Ok::<_, Box<dyn std::error::Error>>(())
3397 });
3398
3399 let result = router.navigate_send("/action").await;
3400 assert!(result.is_ok());
3401
3402 let content = router.receiver.try_recv();
3404 assert!(content.is_err());
3405 }
3406
3407 #[test_log::test(switchy_async::test)]
3408 async fn test_wait_for_navigation() {
3409 let router =
3410 Router::new().with_route("/page", |_req| async { "Page Content".to_string() });
3411
3412 let router_clone = router.clone();
3414 switchy_async::task::spawn(async move {
3415 router_clone.navigate_send("/page").await.unwrap();
3416 });
3417
3418 let content = router.wait_for_navigation().await;
3420 assert!(content.is_some());
3421 }
3422
3423 #[test_log::test(switchy_async::test)]
3424 async fn test_navigate_spawn_success() {
3425 let router = Router::new().with_route("/spawn", |_req| async { "Spawned".to_string() });
3426
3427 let handle = router.navigate_spawn("/spawn");
3428 let result = handle.await.unwrap();
3429 assert!(result.is_ok());
3430
3431 let content = router.receiver.try_recv();
3433 assert!(content.is_ok());
3434 }
3435
3436 #[test_log::test(switchy_async::test)]
3437 async fn test_navigate_spawn_invalid_path() {
3438 let router = Router::new();
3439
3440 let handle = router.navigate_spawn("/nonexistent");
3441 let result = handle.await.unwrap();
3442 assert!(result.is_err());
3443 }
3444
3445 #[test_log::test(switchy_async::test)]
3446 async fn test_navigate_spawn_on_with_handle() {
3447 let router =
3448 Router::new().with_route("/handle", |_req| async { "Handle Test".to_string() });
3449
3450 let handle = switchy_async::runtime::Handle::current();
3451 let join_handle = router.navigate_spawn_on(&handle, "/handle");
3452 let result = join_handle.await.unwrap();
3453 assert!(result.is_ok());
3454 }
3455 }
3456
3457 mod static_route_tests {
3458 use super::*;
3459
3460 #[cfg(feature = "static-routes")]
3461 #[test_log::test(switchy_async::test)]
3462 async fn test_static_route_result_success() {
3463 let router = Router::new().with_static_route_result("/static", |_req| async {
3464 Ok::<_, Box<dyn std::error::Error>>("Static Content".to_string())
3465 });
3466
3467 let result = router.navigate("/static").await.unwrap();
3468 assert!(result.is_some());
3469 }
3470
3471 #[cfg(feature = "static-routes")]
3472 #[test_log::test(switchy_async::test)]
3473 async fn test_static_route_result_error() {
3474 let router = Router::new().with_static_route_result("/static_err", |_req| async {
3475 Err::<String, _>(
3476 Box::new(std::io::Error::other("Static error")) as Box<dyn std::error::Error>
3477 )
3478 });
3479
3480 let result = router.navigate("/static_err").await;
3481 assert!(matches!(result, Err(NavigateError::Handler(_))));
3482 }
3483
3484 #[cfg(feature = "static-routes")]
3485 #[test_log::test(switchy_async::test)]
3486 async fn test_dynamic_route_takes_precedence() {
3487 let router = Router::new()
3488 .with_static_route("/page", |_req| async { "Static".to_string() })
3489 .with_route("/page", |_req| async { "Dynamic".to_string() });
3490
3491 let func = router.get_route_func("/page");
3494 assert!(func.is_some());
3495 }
3496
3497 #[cfg(feature = "static-routes")]
3498 #[test_log::test]
3499 fn test_has_static_route_with_prefix() {
3500 let router = Router::new().with_static_route(
3501 RoutePath::LiteralPrefix("/assets/".to_string()),
3502 |req| async move { format!("Asset: {}", req.path) },
3503 );
3504
3505 assert!(router.has_static_route("/assets/css/style.css"));
3506 assert!(router.has_static_route("/assets/js/app.js"));
3507 assert!(!router.has_static_route("/api/data"));
3508 }
3509
3510 #[cfg(not(feature = "static-routes"))]
3511 #[test_log::test]
3512 fn test_has_static_route_returns_false_without_feature() {
3513 let router = Router::new();
3514 assert!(!router.has_static_route("/any/path"));
3515 }
3516 }
3517
3518 mod route_path_edge_cases {
3519 use super::*;
3520
3521 #[test_log::test]
3522 fn test_empty_literals_vec() {
3523 let route = RoutePath::Literals(vec![]);
3524 assert!(!route.matches("/any"));
3525 assert!(route.strip_match("/any").is_none());
3526 }
3527
3528 #[test_log::test]
3529 fn test_literal_prefix_empty_string() {
3530 let route = RoutePath::LiteralPrefix(String::new());
3531 assert!(route.matches("/any/path"));
3533 assert!(route.matches(""));
3534 assert_eq!(route.strip_match("/test"), Some("/test"));
3535 }
3536
3537 #[test_log::test]
3538 fn test_from_vec_string() {
3539 let paths: Vec<String> = vec!["/a".to_string(), "/b".to_string()];
3540 let route: RoutePath = paths.into();
3541 assert!(route.matches("/a"));
3542 assert!(route.matches("/b"));
3543 }
3544
3545 #[test_log::test]
3546 fn test_from_slice_ref_string() {
3547 let path_a = "/x".to_string();
3548 let path_b = "/y".to_string();
3549 let paths: &[&String] = &[&path_a, &path_b];
3550 let route: RoutePath = paths.into();
3551 assert!(route.matches("/x"));
3552 assert!(route.matches("/y"));
3553 }
3554
3555 #[test_log::test]
3556 fn test_from_array_size_variants() {
3557 let arr3: &[&str; 3] = &["/a", "/b", "/c"];
3559 let route3: RoutePath = arr3.into();
3560 assert!(route3.matches("/a"));
3561 assert!(route3.matches("/c"));
3562
3563 let arr5: &[&str; 5] = &["/1", "/2", "/3", "/4", "/5"];
3564 let route5: RoutePath = arr5.into();
3565 assert!(route5.matches("/1"));
3566 assert!(route5.matches("/5"));
3567 }
3568 }
3569
3570 mod request_conversion_tests {
3571 use super::*;
3572
3573 #[test_log::test]
3574 fn test_route_request_from_ref_str() {
3575 let req: RouteRequest = "/path".into();
3576 assert_eq!(req.path, "/path");
3577 }
3578
3579 #[test_log::test]
3580 fn test_route_request_from_ref_string() {
3581 let path = "/path".to_string();
3582 let req: RouteRequest = (&path).into();
3583 assert_eq!(req.path, "/path");
3584 }
3585
3586 #[test_log::test]
3587 fn test_route_request_from_ref_str_with_arc_client_info() {
3588 let client = Arc::new(ClientInfo {
3589 os: ClientOs {
3590 name: "TestOS".to_string(),
3591 },
3592 });
3593 let req: RouteRequest = ("/test", client).into();
3594 assert_eq!(req.path, "/test");
3595 assert_eq!(req.info.client.os.name, "TestOS");
3596 }
3597
3598 #[test_log::test]
3599 fn test_route_request_from_ref_string_with_arc_client_info() {
3600 let path = "/test".to_string();
3601 let client = Arc::new(ClientInfo {
3602 os: ClientOs {
3603 name: "TestOS".to_string(),
3604 },
3605 });
3606 let req: RouteRequest = (&path, client).into();
3607 assert_eq!(req.path, "/test");
3608 }
3609
3610 #[test_log::test]
3611 fn test_route_request_from_ref_string_with_client_info() {
3612 let path = "/test".to_string();
3613 let client = ClientInfo {
3614 os: ClientOs {
3615 name: "TestOS".to_string(),
3616 },
3617 };
3618 let req: RouteRequest = (&path, client).into();
3619 assert_eq!(req.path, "/test");
3620 }
3621
3622 #[test_log::test]
3623 fn test_route_request_from_ref_str_with_request_info() {
3624 let info = RequestInfo::default();
3625 let req: RouteRequest = ("/test", info).into();
3626 assert_eq!(req.path, "/test");
3627 }
3628
3629 #[test_log::test]
3630 fn test_route_request_from_ref_string_with_request_info() {
3631 let path = "/test".to_string();
3632 let info = RequestInfo::default();
3633 let req: RouteRequest = (&path, info).into();
3634 assert_eq!(req.path, "/test");
3635 }
3636
3637 #[test_log::test]
3638 fn test_navigation_from_ref_route_request() {
3639 let req = RouteRequest::from_path("/test", RequestInfo::default());
3640 let nav: Navigation = (&req).into();
3641 assert_eq!(nav.0, "/test");
3642 }
3643
3644 #[test_log::test]
3645 fn test_navigation_from_ref_string() {
3646 let path = "/test".to_string();
3647 let nav: Navigation = (&path).into();
3648 assert_eq!(nav.0, "/test");
3649 }
3650
3651 #[test_log::test]
3652 fn test_navigation_from_ref_str_with_client_info() {
3653 let client = ClientInfo {
3654 os: ClientOs {
3655 name: "TestOS".to_string(),
3656 },
3657 };
3658 let nav: Navigation = ("/test", client).into();
3659 assert_eq!(nav.0, "/test");
3660 assert_eq!(nav.1.os.name, "TestOS");
3661 }
3662
3663 #[test_log::test]
3664 fn test_navigation_from_ref_string_with_client_info() {
3665 let path = "/test".to_string();
3666 let client = ClientInfo {
3667 os: ClientOs {
3668 name: "TestOS".to_string(),
3669 },
3670 };
3671 let nav: Navigation = (&path, client).into();
3672 assert_eq!(nav.0, "/test");
3673 }
3674
3675 #[test_log::test]
3676 fn test_navigation_from_ref_str_with_arc_client_info() {
3677 let client = Arc::new(ClientInfo {
3678 os: ClientOs {
3679 name: "TestOS".to_string(),
3680 },
3681 });
3682 let nav: Navigation = ("/test", client).into();
3683 assert_eq!(nav.0, "/test");
3684 }
3685
3686 #[test_log::test]
3687 fn test_navigation_from_ref_string_with_arc_client_info() {
3688 let path = "/test".to_string();
3689 let client = Arc::new(ClientInfo {
3690 os: ClientOs {
3691 name: "TestOS".to_string(),
3692 },
3693 });
3694 let nav: Navigation = (&path, client).into();
3695 assert_eq!(nav.0, "/test");
3696 }
3697
3698 #[test_log::test]
3699 fn test_navigation_from_ref_str_with_request_info() {
3700 let info = RequestInfo::default();
3701 let nav: Navigation = ("/test", info).into();
3702 assert_eq!(nav.0, "/test");
3703 }
3704
3705 #[test_log::test]
3706 fn test_navigation_from_ref_string_with_request_info() {
3707 let path = "/test".to_string();
3708 let info = RequestInfo::default();
3709 let nav: Navigation = (&path, info).into();
3710 assert_eq!(nav.0, "/test");
3711 }
3712 }
3713}