1use std::{cmp::Ordering, collections::BTreeMap, str::FromStr};
6
7use crate::{
8 backend::BackendId,
9 shared::{Duration, Fraction, Regex},
10 Hostname, Name, Service,
11};
12use serde::{Deserialize, Serialize};
13
14#[cfg(feature = "typeinfo")]
15use junction_typeinfo::TypeInfo;
16
17#[doc(hidden)]
18pub mod tags {
19 pub const GENERATED_BY: &str = "junctionlabs.io/generated-by";
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
32#[serde(try_from = "String", into = "String")]
33pub enum HostnameMatch {
34 Subdomain(Hostname),
48
49 Exact(Hostname),
51}
52
53impl HostnameMatch {
54 pub fn matches(&self, hostname: &Hostname) -> bool {
56 self.matches_str_validated(hostname)
57 }
58
59 pub fn matches_str(&self, s: &str) -> bool {
61 if Hostname::validate(s.as_bytes()).is_err() {
62 return false;
63 }
64 self.matches_str_validated(s)
65 }
66
67 fn matches_str_validated(&self, s: &str) -> bool {
68 match self {
69 HostnameMatch::Subdomain(d) => {
70 let (subdomain, domain) = s.split_at(s.len() - d.len());
71 domain == &d[..] && subdomain.ends_with('.')
72 }
73 HostnameMatch::Exact(e) => s == e.as_ref(),
74 }
75 }
76}
77
78#[cfg(feature = "typeinfo")]
79impl junction_typeinfo::TypeInfo for HostnameMatch {
80 fn kind() -> junction_typeinfo::Kind {
81 junction_typeinfo::Kind::String
82 }
83}
84
85impl From<Hostname> for HostnameMatch {
86 fn from(hostname: Hostname) -> Self {
87 Self::Exact(hostname)
88 }
89}
90
91impl std::fmt::Display for HostnameMatch {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self {
94 HostnameMatch::Subdomain(hostname) => write!(f, "*.{hostname}"),
95 HostnameMatch::Exact(hostname) => f.write_str(hostname),
96 }
97 }
98}
99
100impl FromStr for HostnameMatch {
101 type Err = crate::Error;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 Ok(match s.strip_prefix("*.") {
105 Some(hostname) => Self::Subdomain(Hostname::from_str(hostname)?),
106 None => Self::Exact(Hostname::from_str(s)?),
107 })
108 }
109}
110
111impl TryFrom<String> for HostnameMatch {
113 type Error = crate::Error;
114
115 fn try_from(s: String) -> Result<Self, Self::Error> {
116 Ok(match s.strip_prefix("*.") {
117 Some(hostname) => Self::Subdomain(Hostname::from_str(hostname)?),
121 None => Self::Exact(Hostname::try_from(s)?),
124 })
125 }
126}
127impl From<HostnameMatch> for String {
129 fn from(value: HostnameMatch) -> Self {
130 match value {
131 HostnameMatch::Subdomain(_) => value.to_string(),
132 HostnameMatch::Exact(inner) => inner.0.to_string(),
133 }
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
140#[serde(deny_unknown_fields)]
141#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
142pub struct Route {
143 pub id: Name,
149
150 #[serde(default)]
152 pub tags: BTreeMap<String, String>,
154
155 #[serde(default)]
157 pub hostnames: Vec<HostnameMatch>,
158
159 #[serde(default)]
161 pub ports: Vec<u16>,
162
163 #[serde(default)]
166 pub rules: Vec<RouteRule>,
167}
168
169impl Route {
170 pub fn passthrough_route(id: Name, service: Service) -> Route {
174 Route {
175 id,
176 hostnames: vec![service.hostname().into()],
177 ports: vec![],
178 tags: Default::default(),
179 rules: vec![RouteRule {
180 matches: vec![RouteMatch {
181 path: Some(PathMatch::empty_prefix()),
182 ..Default::default()
183 }],
184 backends: vec![BackendRef {
185 service,
186 port: None,
187 weight: 1,
188 }],
189 ..Default::default()
190 }],
191 }
192 }
193}
194
195#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
212#[serde(deny_unknown_fields)]
213#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
214pub struct RouteRule {
215 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub name: Option<Name>,
222
223 #[serde(default, skip_serializing_if = "Vec::is_empty")]
229 pub matches: Vec<RouteMatch>,
230
231 #[serde(default, skip_serializing_if = "Vec::is_empty")]
241 #[doc(hidden)]
242 pub filters: Vec<RouteFilter>,
243
244 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub timeouts: Option<RouteTimeouts>,
247
248 #[serde(default, skip_serializing_if = "Option::is_none")]
250 pub retry: Option<RouteRetry>,
251
252 #[serde(default)]
257 pub backends: Vec<BackendRef>,
258}
259
260impl PartialOrd for RouteRule {
261 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
262 Some(self.cmp(other))
263 }
264}
265
266impl Ord for RouteRule {
267 fn cmp(&self, other: &Self) -> Ordering {
268 let mut self_matches: Vec<_> = self.matches.iter().collect();
269 self_matches.sort();
270 let mut self_matches = self_matches.iter().rev();
271
272 let mut other_matches: Vec<_> = other.matches.iter().collect();
273 other_matches.sort();
274 let mut other_matches = other_matches.iter().rev();
275
276 loop {
277 match (self_matches.next(), other_matches.next()) {
278 (None, None) => return Ordering::Equal,
279 (None, Some(_)) => return Ordering::Less,
280 (Some(_), None) => return Ordering::Greater,
281 (Some(a), Some(b)) => match a.cmp(b) {
282 Ordering::Equal => {}
283 ord => return ord,
284 },
285 }
286 }
287 }
288}
289
290#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
292#[serde(deny_unknown_fields)]
293#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
294pub struct RouteTimeouts {
295 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub request: Option<Duration>,
303
304 #[serde(
312 default,
313 skip_serializing_if = "Option::is_none",
314 alias = "backendRequest"
315 )]
316 pub backend_request: Option<Duration>,
317}
318
319#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
346#[serde(deny_unknown_fields)]
347#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
348pub struct RouteMatch {
349 #[serde(default, skip_serializing_if = "Option::is_none")]
351 pub path: Option<PathMatch>,
352
353 #[serde(default, skip_serializing_if = "Vec::is_empty")]
356 pub headers: Vec<HeaderMatch>,
357
358 #[serde(default, skip_serializing_if = "Vec::is_empty", alias = "queryParams")]
362 pub query_params: Vec<QueryParamMatch>,
363
364 #[serde(default, skip_serializing_if = "Option::is_none")]
367 pub method: Option<Method>,
368}
369
370impl PartialOrd for RouteMatch {
371 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
372 Some(self.cmp(other))
373 }
374}
375
376impl Ord for RouteMatch {
377 fn cmp(&self, other: &Self) -> Ordering {
378 match self.path.cmp(&other.path) {
379 Ordering::Equal => (),
380 cmp => return cmp,
381 }
382
383 match (&self.method, &other.method) {
384 (None, Some(_)) => return Ordering::Less,
385 (Some(_), None) => return Ordering::Greater,
386 _ => (),
387 }
388
389 match self.headers.len().cmp(&other.headers.len()) {
390 Ordering::Equal => (),
391 cmp => return cmp,
392 }
393
394 self.query_params.len().cmp(&other.query_params.len())
395 }
396}
397
398#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
405#[serde(tag = "type")]
406#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
407pub enum PathMatch {
408 #[serde(alias = "prefix")]
409 Prefix { value: String },
410
411 #[serde(alias = "regularExpression", alias = "regular_expression")]
412 RegularExpression { value: Regex },
413
414 #[serde(untagged)]
415 Exact { value: String },
416}
417
418impl PathMatch {
419 pub fn empty_prefix() -> Self {
424 Self::Prefix {
425 value: String::new(),
426 }
427 }
428}
429
430impl PartialOrd for PathMatch {
431 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
432 Some(self.cmp(other))
433 }
434}
435
436impl Ord for PathMatch {
437 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
438 match (self, other) {
439 (Self::Exact { value: v1 }, Self::Exact { value: v2 }) => v1.len().cmp(&v2.len()),
441 (Self::Exact { .. }, _) => Ordering::Greater,
442 (Self::Prefix { .. }, Self::Exact { .. }) => Ordering::Less,
444 (Self::Prefix { value: v1 }, Self::Prefix { value: v2 }) => v1.len().cmp(&v2.len()),
445 (Self::Prefix { .. }, _) => Ordering::Greater,
446 (Self::RegularExpression { value: v1 }, Self::RegularExpression { value: v2 }) => {
448 v1.as_str().len().cmp(&v2.as_str().len())
449 }
450 (Self::RegularExpression { .. }, _) => Ordering::Less,
451 }
452 }
453}
454
455pub type HeaderName = String;
471
472#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
485#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
486#[serde(tag = "type", deny_unknown_fields)]
487pub enum HeaderMatch {
488 #[serde(
489 alias = "regex",
490 alias = "regular_expression",
491 alias = "regularExpression"
492 )]
493 RegularExpression { name: String, value: Regex },
494
495 #[serde(untagged)]
496 Exact { name: String, value: String },
497}
498
499impl HeaderMatch {
500 pub fn name(&self) -> &str {
501 match self {
502 HeaderMatch::RegularExpression { name, .. } => name,
503 HeaderMatch::Exact { name, .. } => name,
504 }
505 }
506
507 pub fn is_match(&self, header_value: &str) -> bool {
508 match self {
509 HeaderMatch::RegularExpression { value, .. } => value.is_match(header_value),
510 HeaderMatch::Exact { value, .. } => value == header_value,
511 }
512 }
513}
514
515#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
517#[serde(deny_unknown_fields, tag = "type")]
518#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
519pub enum QueryParamMatch {
520 #[serde(
521 alias = "regex",
522 alias = "regular_expression",
523 alias = "regularExpression"
524 )]
525 RegularExpression { name: String, value: Regex },
526
527 #[serde(untagged)]
528 Exact { name: String, value: String },
529}
530
531impl QueryParamMatch {
532 pub fn name(&self) -> &str {
533 match self {
534 QueryParamMatch::RegularExpression { name, .. } => name,
535 QueryParamMatch::Exact { name, .. } => name,
536 }
537 }
538
539 pub fn is_match(&self, param_value: &str) -> bool {
540 match self {
541 QueryParamMatch::RegularExpression { value, .. } => value.is_match(param_value),
542 QueryParamMatch::Exact { value, .. } => value == param_value,
543 }
544 }
545}
546
547pub type Method = String;
554
555#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
561#[serde(tag = "type", deny_unknown_fields)]
562#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
563pub enum RouteFilter {
564 RequestHeaderModifier {
566 #[serde(alias = "requestHeaderModifier")]
568 request_header_modifier: HeaderFilter,
569 },
570
571 ResponseHeaderModifier {
573 #[serde(alias = "responseHeaderModifier")]
575 response_header_modifier: HeaderFilter,
576 },
577
578 RequestMirror {
584 #[serde(alias = "requestMirror")]
585 request_mirror: RequestMirrorFilter,
586 },
587
588 RequestRedirect {
590 #[serde(alias = "requestRedirect")]
591 request_redirect: RequestRedirectFilter,
593 },
594
595 URLRewrite {
597 #[serde(alias = "urlRewrite")]
599 url_rewrite: UrlRewriteFilter,
600 },
601}
602
603#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
605#[serde(deny_unknown_fields)]
606#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
607pub struct HeaderFilter {
608 #[serde(default, skip_serializing_if = "Vec::is_empty")]
619 pub set: Vec<HeaderValue>,
620
621 #[serde(default, skip_serializing_if = "Vec::is_empty")]
631 pub add: Vec<HeaderValue>,
632
633 #[serde(default, skip_serializing_if = "Vec::is_empty")]
643 pub remove: Vec<String>,
644}
645
646#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
647#[serde(deny_unknown_fields)]
648#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
649pub struct HeaderValue {
650 pub name: HeaderName,
653
654 pub value: String,
656}
657
658#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
660#[serde(tag = "type", deny_unknown_fields)]
661#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
662pub enum PathModifier {
663 ReplaceFullPath {
666 #[serde(alias = "replaceFullPath")]
668 replace_full_path: String,
669 },
670
671 ReplacePrefixMatch {
698 #[serde(alias = "replacePrefixMatch")]
699 replace_prefix_match: String,
700 },
701}
702
703#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
706#[serde(deny_unknown_fields)]
707#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
708pub struct RequestRedirectFilter {
709 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub scheme: Option<String>,
716
717 #[serde(default, skip_serializing_if = "Option::is_none")]
720 pub hostname: Option<Name>,
721
722 #[serde(default, skip_serializing_if = "Option::is_none")]
725 pub path: Option<PathModifier>,
726
727 #[serde(default, skip_serializing_if = "Option::is_none")]
744 pub port: Option<u16>,
745
746 #[serde(default, skip_serializing_if = "Option::is_none", alias = "statusCode")]
748 pub status_code: Option<u16>,
749}
750
751#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
754#[serde(deny_unknown_fields)]
755#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
756pub struct UrlRewriteFilter {
757 #[serde(default, skip_serializing_if = "Option::is_none")]
759 pub hostname: Option<Hostname>,
760
761 #[serde(default, skip_serializing_if = "Option::is_none")]
763 pub path: Option<PathModifier>,
764}
765
766#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
768#[serde(deny_unknown_fields)]
769#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
770pub struct RequestMirrorFilter {
771 pub percent: Option<i32>,
778
779 pub fraction: Option<Fraction>,
782
783 pub backend: Service,
784}
785
786#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
788#[serde(deny_unknown_fields)]
789#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
790pub struct RouteRetry {
791 #[serde(default, skip_serializing_if = "Vec::is_empty")]
795 pub codes: Vec<u16>,
796
797 #[serde(default, skip_serializing_if = "Option::is_none")]
799 pub attempts: Option<u32>,
800
801 #[serde(default, skip_serializing_if = "Option::is_none")]
804 pub backoff: Option<Duration>,
805}
806
807const fn default_weight() -> u32 {
808 1
809}
810
811#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
815#[cfg_attr(feature = "typeinfo", derive(TypeInfo))]
816pub struct BackendRef {
817 #[serde(flatten)]
821 pub service: Service,
822
823 pub port: Option<u16>,
829
830 #[serde(default = "default_weight")]
838 pub weight: u32,
839}
840
841impl BackendRef {
842 #[doc(hidden)]
843 pub fn into_backend_id(&self, default_port: u16) -> BackendId {
844 let port = self.port.unwrap_or(default_port);
845
846 BackendId {
847 service: self.service.clone(),
848 port,
849 }
850 }
851
852 #[doc(hidden)]
853 pub fn as_backend_id(&self) -> Option<BackendId> {
854 let port = self.port?;
855
856 Some(BackendId {
857 service: self.service.clone(),
858 port,
859 })
860 }
861
862 #[cfg(feature = "xds")]
863 pub(crate) fn name(&self) -> String {
864 let mut buf = String::new();
865 self.write_name(&mut buf).unwrap();
866 buf
867 }
868
869 #[cfg(feature = "xds")]
870 fn write_name(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
871 self.service.write_name(w)?;
872 if let Some(port) = self.port {
873 write!(w, ":{port}")?;
874 }
875
876 Ok(())
877 }
878}
879
880impl FromStr for BackendRef {
881 type Err = crate::Error;
882
883 fn from_str(s: &str) -> Result<Self, Self::Err> {
884 let (name, port) = super::parse_port(s)?;
885 let backend = Service::from_str(name)?;
886
887 Ok(Self {
888 service: backend,
889 port,
890 weight: default_weight(),
891 })
892 }
893}
894
895#[cfg(test)]
896mod test {
897 use std::str::FromStr;
898
899 use rand::seq::SliceRandom;
900 use serde::de::DeserializeOwned;
901 use serde_json::json;
902
903 use super::*;
904 use crate::{
905 http::{HeaderMatch, RouteRule},
906 shared::Regex,
907 Service,
908 };
909
910 #[test]
911 fn test_hostname_match() {
912 let exact_matcher = HostnameMatch::from_str("foo.bar").unwrap();
913 let subdomain_matcher = HostnameMatch::from_str("*.foo.bar").unwrap();
914
915 for invalid_hostname in [
916 "",
917 "*",
918 ".*",
919 ".",
920 "!@#@!#!@",
921 "foo....bar",
922 ".foo.bar",
923 "...foo.bar",
924 ] {
925 assert!(!exact_matcher.matches_str(invalid_hostname));
926 assert!(!subdomain_matcher.matches_str(invalid_hostname));
927 }
928
929 for not_matching in ["blahfoo.bar", "bfoo.bar", "bar.foo"] {
930 assert!(!exact_matcher.matches_str(not_matching));
931 assert!(!subdomain_matcher.matches_str(not_matching));
932 }
933
934 assert!(exact_matcher.matches_str("foo.bar"));
935 assert!(!subdomain_matcher.matches_str("foo.bar"));
936
937 assert!(!exact_matcher.matches_str("blah.foo.bar"));
938 assert!(subdomain_matcher.matches_str("blah.foo.bar"));
939 assert!(subdomain_matcher.matches_str("b.foo.bar"));
940 }
941
942 #[test]
943 fn test_hostname_match_json() {
944 let json_value = json!(["foo.bar.baz", "*.foo.bar.baz",]);
945 let matchers = vec![
946 HostnameMatch::Exact(Hostname::from_static("foo.bar.baz")),
947 HostnameMatch::Subdomain(Hostname::from_static("foo.bar.baz")),
948 ];
949
950 assert_eq!(
951 serde_json::from_value::<Vec<HostnameMatch>>(json_value.clone()).unwrap(),
952 matchers,
953 );
954 assert_eq!(serde_json::to_value(&matchers).unwrap(), json_value,);
955 }
956
957 #[test]
958 fn test_header_matcher_json() {
959 let test_json = json!([
960 { "name":"bar", "type" : "RegularExpression", "value": ".*foo"},
961 { "name":"bar", "value": "a literal"},
962 ]);
963 let obj: Vec<HeaderMatch> = serde_json::from_value(test_json.clone()).unwrap();
964
965 assert_eq!(
966 obj,
967 vec![
968 HeaderMatch::RegularExpression {
969 name: "bar".to_string(),
970 value: Regex::from_str(".*foo").unwrap(),
971 },
972 HeaderMatch::Exact {
973 name: "bar".to_string(),
974 value: "a literal".to_string(),
975 }
976 ]
977 );
978
979 let output_json = serde_json::to_value(&obj).unwrap();
980 assert_eq!(test_json, output_json);
981 }
982
983 #[test]
984 fn test_retry_policy_json() {
985 let test_json = json!({
986 "codes":[ 1, 2 ],
987 "attempts": 3,
988 "backoff": 60.0,
990 });
991 let obj: RouteRetry = serde_json::from_value(test_json.clone()).unwrap();
992 let output_json = serde_json::to_value(obj).unwrap();
993 assert_eq!(test_json, output_json);
994 }
995
996 #[test]
997 fn test_route_rule_json() {
998 let test_json = json!({
999 "matches":[
1000 {
1001 "method": "GET",
1002 "path": { "value": "foo" },
1003 "headers": [
1004 {"name":"ian", "value": "foo"},
1005 {"name": "bar", "type":"RegularExpression", "value": ".*foo"}
1006 ]
1007 },
1008 {
1009 "query_params": [
1010 {"name":"ian", "value": "foo"},
1011 {"name": "bar", "type":"RegularExpression", "value": ".*foo"}
1012 ]
1013 }
1014 ],
1015 "filters":[{
1016 "type": "URLRewrite",
1017 "url_rewrite":{
1018 "hostname":"ian.com",
1019 "path": {"type":"ReplacePrefixMatch", "replace_prefix_match":"/"}
1020 }
1021 }],
1022 "backends":[
1023 {
1024 "type": "kube",
1025 "name": "timeout-svc",
1026 "namespace": "foo",
1027 "port": 80,
1028 "weight": 1,
1029 }
1030 ],
1031 "timeouts": {
1032 "request": 1.0,
1033 }
1034 });
1035 let obj: RouteRule = serde_json::from_value(test_json.clone()).unwrap();
1036 let output_json = serde_json::to_value(&obj).unwrap();
1037 assert_eq!(test_json, output_json);
1038 }
1039
1040 #[test]
1041 fn test_route_json() {
1042 assert_deserialize(
1043 json!({
1044 "id": "sweet-potato",
1045 "hostnames": ["foo.bar.svc.cluster.local"],
1046 "rules": [
1047 {
1048 "backends": [
1049 {
1050 "type": "kube",
1051 "name": "foo",
1052 "namespace": "bar",
1053 "port": 80,
1054 }
1055 ],
1056 }
1057 ]
1058 }),
1059 Route {
1060 id: Name::from_static("sweet-potato"),
1061 hostnames: vec![Hostname::from_static("foo.bar.svc.cluster.local").into()],
1062 ports: vec![],
1063 tags: Default::default(),
1064 rules: vec![RouteRule {
1065 name: None,
1066 matches: vec![],
1067 filters: vec![],
1068 timeouts: None,
1069 retry: None,
1070 backends: vec![BackendRef {
1071 service: Service::kube("bar", "foo").unwrap(),
1072 port: Some(80),
1073 weight: 1,
1074 }],
1075 }],
1076 },
1077 );
1078 }
1079
1080 #[test]
1081 fn test_route_json_missing_fields() {
1082 assert_deserialize_err::<Route>(json!({
1083 "uhhhh": ["foo.bar"],
1084 "rules": [
1085 {
1086 "matches": [],
1087 }
1088 ]
1089 }));
1090 }
1091
1092 #[track_caller]
1093 fn assert_deserialize<T: DeserializeOwned + PartialEq + std::fmt::Debug>(
1094 json: serde_json::Value,
1095 expected: T,
1096 ) {
1097 let actual: T = serde_json::from_value(json).unwrap();
1098 assert_eq!(expected, actual);
1099 }
1100
1101 #[track_caller]
1102 fn assert_deserialize_err<T: DeserializeOwned + PartialEq + std::fmt::Debug>(
1103 json: serde_json::Value,
1104 ) -> serde_json::Error {
1105 serde_json::from_value::<T>(json).unwrap_err()
1106 }
1107
1108 #[test]
1109 fn test_path_match() {
1110 arbtest::arbtest(|u| {
1112 let s1: String = u.arbitrary()?;
1113 let s2: String = u.arbitrary()?;
1114
1115 let m1 = PathMatch::Exact { value: s1.clone() };
1116 let m2 = PathMatch::Exact { value: s2.clone() };
1117
1118 assert_eq!(s1.len().cmp(&s2.len()), m1.cmp(&m2));
1119
1120 Ok(())
1121 });
1122
1123 arbtest::arbtest(|u| {
1125 let s1: String = u.arbitrary()?;
1126 let s2: String = u.arbitrary()?;
1127
1128 let m1 = PathMatch::Prefix { value: s1.clone() };
1129 let m2 = PathMatch::Prefix { value: s2.clone() };
1130
1131 assert_eq!(s1.len().cmp(&s2.len()), m1.cmp(&m2));
1132
1133 Ok(())
1134 });
1135
1136 arbtest::arbtest(|u| {
1138 let m1 = PathMatch::Exact {
1139 value: u.arbitrary()?,
1140 };
1141 let m2 = PathMatch::Prefix {
1142 value: u.arbitrary()?,
1143 };
1144 assert!(m1 > m2);
1145
1146 Ok(())
1147 });
1148 }
1149
1150 #[test]
1151 fn test_order_route_match() {
1152 let path_match = RouteMatch {
1153 path: Some(PathMatch::Exact {
1154 value: "/potato".to_string(),
1155 }),
1156 ..Default::default()
1157 };
1158 let method_match = RouteMatch {
1159 method: Some("PUT".to_string()),
1160 ..Default::default()
1161 };
1162 let header_match = RouteMatch {
1163 headers: vec![HeaderMatch::Exact {
1164 name: "x-user".to_string(),
1165 value: "a-user".to_string(),
1166 }],
1167 ..Default::default()
1168 };
1169 let query_match = RouteMatch {
1170 query_params: vec![QueryParamMatch::Exact {
1171 name: "q".to_string(),
1172 value: "value".to_string(),
1173 }],
1174 ..Default::default()
1175 };
1176
1177 assert_eq!(
1179 vec![&query_match, &header_match, &method_match, &path_match],
1180 shuffle_and_sort([&path_match, &query_match, &header_match, &method_match]),
1181 );
1182
1183 let m1 = RouteMatch {
1185 path: Some(PathMatch::Exact {
1186 value: "fooooooooooo".to_string(),
1187 }),
1188 query_params: query_match.query_params.clone(),
1189 ..Default::default()
1190 };
1191 let m2 = RouteMatch {
1192 path: Some(PathMatch::Exact {
1193 value: "foo".to_string(),
1194 }),
1195 query_params: query_match.query_params.clone(),
1196 ..Default::default()
1197 };
1198 assert!(m1 > m2, "should tie break by comparing path_match");
1199
1200 let m1 = RouteMatch {
1202 path: path_match.path.clone(),
1203 query_params: query_match.query_params.clone(),
1204 ..Default::default()
1205 };
1206 let m2 = RouteMatch {
1207 path: path_match.path.clone(),
1208 ..Default::default()
1209 };
1210 assert!(m1 > m2, "should tie-break with query params");
1211
1212 let m1 = RouteMatch {
1213 method: Some("GET".to_string()),
1214 query_params: query_match.query_params.clone(),
1215 ..Default::default()
1216 };
1217 let m2 = RouteMatch {
1218 method: Some("PUT".to_string()),
1219 ..Default::default()
1220 };
1221 assert!(m1 > m2, "should tie-break with query params");
1222 }
1223
1224 #[test]
1225 fn test_order_route_rule() {
1226 let path_match = RouteMatch {
1227 path: Some(PathMatch::Exact {
1228 value: "/potato".to_string(),
1229 }),
1230 ..Default::default()
1231 };
1232 let header_match = RouteMatch {
1233 headers: vec![HeaderMatch::Exact {
1234 name: "x-user".to_string(),
1235 value: "a-user".to_string(),
1236 }],
1237 ..Default::default()
1238 };
1239 let query_match = RouteMatch {
1240 query_params: vec![QueryParamMatch::Exact {
1241 name: "q".to_string(),
1242 value: "value".to_string(),
1243 }],
1244 ..Default::default()
1245 };
1246
1247 let r1 = RouteRule {
1249 matches: vec![path_match.clone()],
1250 ..Default::default()
1251 };
1252 let r2 = RouteRule {
1253 matches: vec![header_match.clone()],
1254 ..Default::default()
1255 };
1256 assert!(r1 > r2);
1257 assert!(r2 < r1);
1258
1259 let r1 = RouteRule {
1261 matches: vec![path_match.clone()],
1262 ..Default::default()
1263 };
1264 let r2 = RouteRule {
1265 matches: vec![path_match.clone(), header_match.clone()],
1266 ..Default::default()
1267 };
1268 assert!(r1 < r2);
1269 assert!(r2 > r1);
1270
1271 let r1 = RouteRule {
1273 matches: vec![query_match.clone()],
1274 ..Default::default()
1275 };
1276 let r2 = RouteRule {
1277 matches: vec![],
1278 ..Default::default()
1279 };
1280 assert!(r2 < r1);
1281 assert!(r1 > r2);
1282 }
1283
1284 fn shuffle_and_sort<T: Ord>(xs: impl IntoIterator<Item = T>) -> Vec<T> {
1285 let mut rng = rand::thread_rng();
1286
1287 let mut v: Vec<_> = xs.into_iter().collect();
1288 v.shuffle(&mut rng);
1289 v.sort();
1290 v
1291 }
1292}