1use std::{
2 borrow::Cow,
3 collections::BTreeMap,
4 fmt::{Debug, Formatter},
5};
6
7use anyhow::Result;
8use patricia_tree::PatriciaMap;
9use serde::{Deserialize, Serialize};
10use turbo_rcstr::RcStr;
11use turbo_tasks::{
12 NonLocalValue,
13 debug::{ValueDebugFormat, ValueDebugFormatString, internal::PassthroughDebug},
14 trace::{TraceRawVcs, TraceRawVcsContext},
15};
16
17use super::pattern::Pattern;
18
19#[derive(Clone, Serialize, Deserialize)]
28#[serde(transparent)]
29pub struct AliasMap<T> {
30 map: PatriciaMap<BTreeMap<AliasKey, T>>,
31}
32
33impl<T> Default for AliasMap<T> {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl<T> PartialEq for AliasMap<T>
40where
41 T: PartialEq,
42{
43 fn eq(&self, other: &Self) -> bool {
44 if self.map.len() != other.map.len() {
45 return false;
46 }
47
48 self.map.iter().zip(other.map.iter()).all(|(a, b)| a == b)
49 }
50}
51
52impl<T> Eq for AliasMap<T> where T: Eq {}
53
54impl<T> TraceRawVcs for AliasMap<T>
55where
56 T: TraceRawVcs,
57{
58 fn trace_raw_vcs(&self, trace_context: &mut TraceRawVcsContext) {
59 for (_, map) in self.map.iter() {
60 for value in map.values() {
61 value.trace_raw_vcs(trace_context);
62 }
63 }
64 }
65}
66
67unsafe impl<T: NonLocalValue> NonLocalValue for AliasMap<T> {}
68
69impl<T> ValueDebugFormat for AliasMap<T>
70where
71 T: ValueDebugFormat,
72{
73 fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
74 if depth == 0 {
75 return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
76 }
77
78 let values = self
79 .map
80 .iter()
81 .flat_map(|(key, map)| {
82 let key = String::from_utf8(key).expect("invalid UTF-8 key in AliasMap");
83 map.iter().map(move |(alias_key, value)| match alias_key {
84 AliasKey::Exact => (
85 key.clone(),
86 value.value_debug_format(depth.saturating_sub(1)),
87 ),
88 AliasKey::Wildcard { suffix } => (
89 format!("{key}*{suffix}"),
90 value.value_debug_format(depth.saturating_sub(1)),
91 ),
92 })
93 })
94 .collect::<Vec<_>>();
95
96 ValueDebugFormatString::Async(Box::pin(async move {
97 let mut values_string = std::collections::HashMap::new();
98 for (key, value) in values {
99 match value {
100 ValueDebugFormatString::Sync(string) => {
101 values_string.insert(key, PassthroughDebug::new_string(string));
102 }
103 ValueDebugFormatString::Async(future) => {
104 values_string.insert(key, PassthroughDebug::new_string(future.await?));
105 }
106 }
107 }
108 Ok(format!("{values_string:#?}"))
109 }))
110 }
111}
112
113impl<T> Debug for AliasMap<T>
114where
115 T: Debug,
116{
117 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
118 f.debug_map()
119 .entries(self.map.iter().flat_map(|(key, map)| {
120 let key = String::from_utf8(key).expect("invalid UTF-8 key in AliasMap");
121 map.iter().map(move |(alias_key, value)| match alias_key {
122 AliasKey::Exact => (key.clone(), value),
123 AliasKey::Wildcard { suffix } => (format!("{key}*{suffix}"), value),
124 })
125 }))
126 .finish()
127 }
128}
129
130impl<T> AliasMap<T> {
131 pub fn new() -> Self {
133 AliasMap {
134 map: PatriciaMap::new(),
135 }
136 }
137
138 pub fn lookup<'a>(&'a self, request: &'a Pattern) -> AliasMapLookupIterator<'a, T>
142 where
143 T: Debug,
144 {
145 if matches!(request, Pattern::Alternatives(_)) {
146 panic!("AliasMap::lookup must not be called on alternatives, received {request:?}");
147 }
148
149 let mut prefixes_stack = if let Some(request) = request.as_constant_string() {
153 let common_prefixes = self.map.common_prefixes(request.as_bytes());
155 common_prefixes
156 .map(|(p, tree)| {
157 let s = match std::str::from_utf8(p) {
158 Ok(s) => s,
159 Err(e) => std::str::from_utf8(&p[..e.valid_up_to()]).unwrap(),
160 };
161 (Cow::Borrowed(s), tree)
162 })
163 .collect::<Vec<_>>()
164 } else {
165 self.map
170 .iter()
171 .map(|(p, tree)| {
172 let s = match String::from_utf8(p) {
173 Ok(s) => s,
174 Err(e) => {
175 let valid_up_to = e.utf8_error().valid_up_to();
176 let mut p = e.into_bytes();
177 p.drain(valid_up_to..);
178 String::from_utf8(p).unwrap()
179 }
180 };
181 (Cow::Owned(s), tree)
182 })
183 .collect::<Vec<_>>()
184 };
185
186 AliasMapLookupIterator {
190 request,
191 current_prefix_iterator: prefixes_stack
192 .pop()
193 .map(|(prefix, map)| (prefix, map.iter())),
194 prefixes_stack,
195 }
196 }
197
198 pub fn lookup_with_prefix_predicate<'a>(
203 &'a self,
204 request: &'a Pattern,
205 mut prefix_predicate: impl FnMut(&str) -> bool,
206 ) -> AliasMapLookupIterator<'a, T>
207 where
208 T: Debug,
209 {
210 if matches!(request, Pattern::Alternatives(_)) {
211 panic!("AliasMap::lookup must not be called on alternatives, received {request:?}");
212 }
213
214 let mut prefixes_stack = if let Some(request) = request.as_constant_string() {
218 let common_prefixes = self.map.common_prefixes(request.as_bytes());
220 common_prefixes
221 .filter_map(|(p, tree)| {
222 let s = match std::str::from_utf8(p) {
223 Ok(s) => s,
224 Err(e) => std::str::from_utf8(&p[..e.valid_up_to()]).unwrap(),
225 };
226 if prefix_predicate(s) {
227 Some((Cow::Borrowed(s), tree))
228 } else {
229 None
230 }
231 })
232 .collect::<Vec<_>>()
233 } else {
234 self.map
239 .iter()
240 .filter_map(|(p, tree)| {
241 let s = match String::from_utf8(p) {
242 Ok(s) => s,
243 Err(e) => {
244 let valid_up_to = e.utf8_error().valid_up_to();
245 let mut p = e.into_bytes();
246 p.drain(valid_up_to..);
247 String::from_utf8(p).unwrap()
248 }
249 };
250 if prefix_predicate(&s) {
251 Some((Cow::Owned(s), tree))
252 } else {
253 None
254 }
255 })
256 .collect::<Vec<_>>()
257 };
258
259 AliasMapLookupIterator {
263 request,
264 current_prefix_iterator: prefixes_stack
265 .pop()
266 .map(|(prefix, map)| (prefix, map.iter())),
267 prefixes_stack,
268 }
269 }
270
271 pub fn insert(&mut self, pattern: AliasPattern, template: T) -> Option<T> {
278 let (prefix_key, alias_key, value) = match pattern {
279 AliasPattern::Exact(exact) => (exact, AliasKey::Exact, template),
280 AliasPattern::Wildcard { prefix, suffix } => {
281 (prefix, AliasKey::Wildcard { suffix }, template)
282 }
283 };
284 if let Some(map) = self.map.get_mut(&prefix_key) {
291 map.insert(alias_key, value)
292 } else {
293 let mut map = BTreeMap::new();
294 map.insert(alias_key, value);
295 self.map.insert(prefix_key, map);
296 None
297 }
298 }
299}
300
301impl<T> IntoIterator for AliasMap<T> {
302 type Item = (AliasPattern, T);
303
304 type IntoIter = AliasMapIntoIter<T>;
305
306 fn into_iter(self) -> Self::IntoIter {
307 AliasMapIntoIter {
308 iter: self.map.into_iter(),
309 current_prefix_iterator: None,
310 }
311 }
312}
313
314impl<'a, T> IntoIterator for &'a AliasMap<T> {
315 type Item = (AliasPattern, &'a T);
316
317 type IntoIter = AliasMapIter<'a, T>;
318
319 fn into_iter(self) -> Self::IntoIter {
320 AliasMapIter {
321 iter: self.map.iter(),
322 current_prefix_iterator: None,
323 }
324 }
325}
326
327pub struct AliasMapIntoIter<T> {
334 iter: patricia_tree::map::IntoIter<Vec<u8>, BTreeMap<AliasKey, T>>,
335 current_prefix_iterator: Option<AliasMapIntoIterItem<T>>,
336}
337
338struct AliasMapIntoIterItem<T> {
339 prefix: RcStr,
340 iterator: std::collections::btree_map::IntoIter<AliasKey, T>,
341}
342
343impl<T> AliasMapIntoIter<T> {
344 fn advance_iter(&mut self) -> Option<&mut AliasMapIntoIterItem<T>> {
345 let (prefix, map) = self.iter.next()?;
346 let prefix = String::from_utf8(prefix)
347 .expect("invalid UTF-8 key in AliasMap")
348 .into();
349 self.current_prefix_iterator = Some(AliasMapIntoIterItem {
350 prefix,
351 iterator: map.into_iter(),
352 });
353 self.current_prefix_iterator.as_mut()
354 }
355}
356
357impl<T> Iterator for AliasMapIntoIter<T> {
358 type Item = (AliasPattern, T);
359
360 fn next(&mut self) -> Option<Self::Item> {
361 let mut current_prefix_iterator = match self.current_prefix_iterator {
362 None => self.advance_iter()?,
363 Some(ref mut current_prefix_iterator) => current_prefix_iterator,
364 };
365 let mut current_value = current_prefix_iterator.iterator.next();
366 loop {
367 match current_value {
368 None => {
369 current_prefix_iterator = self.advance_iter()?;
370 current_value = current_prefix_iterator.iterator.next();
371 }
372 Some(current_value) => {
373 return Some(match current_value {
374 (AliasKey::Exact, value) => (
375 AliasPattern::Exact(current_prefix_iterator.prefix.clone()),
376 value,
377 ),
378 (AliasKey::Wildcard { suffix }, value) => (
379 AliasPattern::Wildcard {
380 prefix: current_prefix_iterator.prefix.clone(),
381 suffix,
382 },
383 value,
384 ),
385 });
386 }
387 }
388 }
389 }
390}
391
392pub struct AliasMapIter<'a, T> {
399 iter: patricia_tree::map::Iter<'a, Vec<u8>, BTreeMap<AliasKey, T>>,
400 current_prefix_iterator: Option<AliasMapIterItem<'a, T>>,
401}
402
403struct AliasMapIterItem<'a, T> {
404 prefix: RcStr,
405 iterator: std::collections::btree_map::Iter<'a, AliasKey, T>,
406}
407
408impl<T> AliasMapIter<'_, T> {
409 fn advance_iter(&mut self) -> bool {
410 let Some((prefix, map)) = self.iter.next() else {
411 return false;
412 };
413 let prefix = String::from_utf8(prefix)
414 .expect("invalid UTF-8 key in AliasMap")
415 .into();
416 self.current_prefix_iterator = Some(AliasMapIterItem {
417 prefix,
418 iterator: map.iter(),
419 });
420 true
421 }
422}
423
424impl<'a, T> Iterator for AliasMapIter<'a, T> {
425 type Item = (AliasPattern, &'a T);
426
427 fn next(&mut self) -> Option<Self::Item> {
428 let (current_prefix_iterator, current_value) = loop {
429 let Some(current_prefix_iterator) = &mut self.current_prefix_iterator else {
430 if !self.advance_iter() {
431 return None;
432 }
433 continue;
434 };
435 if let Some(current_value) = current_prefix_iterator.iterator.next() {
436 break (&*current_prefix_iterator, current_value);
437 }
438 self.current_prefix_iterator = None;
439 continue;
440 };
441 Some(match current_value {
442 (AliasKey::Exact, value) => (
443 AliasPattern::Exact(current_prefix_iterator.prefix.clone()),
444 value,
445 ),
446 (AliasKey::Wildcard { suffix }, value) => (
447 AliasPattern::Wildcard {
448 prefix: current_prefix_iterator.prefix.clone(),
449 suffix: suffix.clone(),
450 },
451 value,
452 ),
453 })
454 }
455}
456
457impl<T> Extend<(AliasPattern, T)> for AliasMap<T> {
458 fn extend<It>(&mut self, iter: It)
459 where
460 It: IntoIterator<Item = (AliasPattern, T)>,
461 {
462 for (pattern, value) in iter {
463 self.insert(pattern, value);
464 }
465 }
466}
467
468pub struct AliasMapLookupIterator<'a, T> {
474 request: &'a Pattern,
475 prefixes_stack: Vec<(Cow<'a, str>, &'a BTreeMap<AliasKey, T>)>,
476 current_prefix_iterator: Option<(
477 Cow<'a, str>,
478 std::collections::btree_map::Iter<'a, AliasKey, T>,
479 )>,
480}
481
482impl<'a, T> Iterator for AliasMapLookupIterator<'a, T>
483where
484 T: AliasTemplate + Clone,
485{
486 type Item = Result<AliasMatch<'a, T>>;
487
488 fn next(&mut self) -> Option<Self::Item> {
489 let (prefix, current_prefix_iterator) = self.current_prefix_iterator.as_mut()?;
490
491 loop {
492 for (key, template) in &mut *current_prefix_iterator {
493 match key {
494 AliasKey::Exact => {
495 if self.request.is_match(prefix) {
496 return Some(Ok(AliasMatch {
497 prefix: prefix.clone(),
498 key,
499 output: template.convert(),
500 }));
501 }
502 }
503 AliasKey::Wildcard { suffix } => {
504 let is_match = if let Some(request) = self.request.as_constant_string() {
509 let remaining = &request[prefix.len()..];
512 remaining.ends_with(&**suffix)
513 } else if let Pattern::Concatenation(req) = self.request
514 && let [
515 Pattern::Constant(req_prefix),
516 Pattern::Dynamic | Pattern::DynamicNoSlash,
517 ] = req.as_slice()
518 {
519 req_prefix.starts_with(&**prefix)
525 } else if let Pattern::Concatenation(req) = self.request
526 && let [
527 Pattern::Constant(req_prefix),
528 Pattern::Dynamic | Pattern::DynamicNoSlash,
529 Pattern::Constant(req_suffix),
530 ] = req.as_slice()
531 {
532 req_prefix.starts_with(&**prefix) && req_suffix.ends_with(&**suffix)
533 } else if !self.request.could_match(prefix) {
534 false
536 } else if suffix.is_empty() {
537 true
539 } else {
540 return Some(Err(anyhow::anyhow!(
542 "Complex patterns into wildcard exports fields are not \
543 implemented yet: {} into '{}*{}'",
544 self.request.describe_as_string(),
545 prefix,
546 suffix,
547 )));
548 };
549
550 if is_match {
551 let mut remaining = self.request.clone();
552 if let Err(e) = remaining.strip_prefix_len(prefix.len()) {
553 return Some(Err(e.context(self.request.describe_as_string())));
554 }
555 remaining.strip_suffix_len(suffix.len());
556
557 let output = template.replace(&remaining);
558 return Some(Ok(AliasMatch {
559 prefix: prefix.clone(),
560 key,
561 output,
562 }));
563 }
564 }
565 }
566 }
567
568 let (new_prefix, new_current_prefix_iterator) = self.prefixes_stack.pop()?;
569 *prefix = new_prefix;
570 *current_prefix_iterator = new_current_prefix_iterator.iter();
571 }
572 }
573}
574
575#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
577pub enum AliasPattern {
578 Exact(RcStr),
580 Wildcard { prefix: RcStr, suffix: RcStr },
582}
583
584impl AliasPattern {
585 pub fn parse<'a, T>(pattern: T) -> Self
590 where
591 T: Into<RcStr> + 'a,
592 {
593 let pattern = pattern.into();
594 if let Some(wildcard_index) = pattern.find('*') {
595 let mut pattern = pattern.into_owned();
596
597 let suffix = pattern[wildcard_index + 1..].into();
598 pattern.truncate(wildcard_index);
599 AliasPattern::Wildcard {
600 prefix: pattern.into(),
601 suffix,
602 }
603 } else {
604 AliasPattern::Exact(pattern)
605 }
606 }
607
608 pub fn exact<'a, T>(pattern: T) -> Self
610 where
611 T: Into<RcStr> + 'a,
612 {
613 AliasPattern::Exact(pattern.into())
614 }
615
616 pub fn wildcard<'p, 's, P, S>(prefix: P, suffix: S) -> Self
621 where
622 P: Into<RcStr> + 'p,
623 S: Into<RcStr> + 's,
624 {
625 AliasPattern::Wildcard {
626 prefix: prefix.into(),
627 suffix: suffix.into(),
628 }
629 }
630}
631
632#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
633pub enum AliasKey {
634 Exact,
635 Wildcard { suffix: RcStr },
636}
637
638#[derive(Debug, PartialEq, Clone)]
640pub struct AliasMatch<'a, T>
641where
642 T: AliasTemplate + Clone + 'a,
643{
644 pub prefix: Cow<'a, str>,
645 pub key: &'a AliasKey,
646 pub output: T::Output<'a>,
647}
648
649impl PartialOrd for AliasKey {
650 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
651 Some(self.cmp(other))
652 }
653}
654
655impl Ord for AliasKey {
656 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
660 match (self, other) {
661 (AliasKey::Wildcard { suffix: l_suffix }, AliasKey::Wildcard { suffix: r_suffix }) => {
662 l_suffix
663 .len()
664 .cmp(&r_suffix.len())
665 .reverse()
666 .then_with(|| l_suffix.cmp(r_suffix))
667 }
668 (AliasKey::Wildcard { .. }, _) => std::cmp::Ordering::Less,
669 (_, AliasKey::Wildcard { .. }) => std::cmp::Ordering::Greater,
670 _ => std::cmp::Ordering::Equal,
671 }
672 }
673}
674
675pub trait AliasTemplate {
677 type Output<'a>
679 where
680 Self: 'a;
681
682 fn convert(&self) -> Self::Output<'_>;
684
685 fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a>;
687}
688
689#[cfg(test)]
690mod test {
691 use std::assert_matches::assert_matches;
692
693 use anyhow::Result;
694 use turbo_rcstr::rcstr;
695
696 use super::{AliasMap, AliasPattern, AliasTemplate};
697 use crate::resolve::{alias_map::AliasKey, pattern::Pattern};
698
699 macro_rules! assert_alias_matches {
704 ($map:expr, $request:expr$(, $($tail:tt)*)?) => {
705 let request = Pattern::Constant($request.into());
706 let mut lookup = $map.lookup(&request);
707
708 $(assert_alias_matches!(@next lookup, $($tail)*);)?
709 assert_matches!(lookup.next(), None);
710 };
711
712 (@next $lookup:ident, exact($pattern:expr)$(, $($tail:tt)*)?) => {
713 match $lookup.next().unwrap().unwrap() {
714 super::AliasMatch{key: super::AliasKey::Exact, output: Pattern::Constant(c), ..} if c == $pattern => {}
715 m => panic!("unexpected match {:?}", m),
716 }
717 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
718 };
719
720 (@next $lookup:ident, replaced($pattern:expr)$(, $($tail:tt)*)?) => {
721 match $lookup.next().unwrap().unwrap() {
722 super::AliasMatch{key: super::AliasKey::Wildcard{..}, output: Pattern::Constant(c), ..} if c == $pattern => {}
723 m => panic!("unexpected match {:?}", m),
724 }
725 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
726 };
727
728 (@next $lookup:ident, replaced_owned($value:expr)$(, $($tail:tt)*)?) => {
729 match $lookup.next().unwrap().unwrap() {
730 super::AliasMatch{key: super::AliasKey::Wildcard{..}, output: Pattern::Constant(c), ..} if c == $value => {}
731 m => panic!("unexpected match {:?}", m),
732 }
733 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
734 };
735
736 (@next $lookup:ident,) => {};
738 }
739
740 impl<'a> AliasTemplate for &'a str {
741 type Output<'b>
742 = Pattern
743 where
744 Self: 'b;
745
746 fn replace(&self, capture: &Pattern) -> Self::Output<'a> {
747 capture.spread_into_star(self)
748 }
749
750 fn convert(&self) -> Self::Output<'a> {
751 Pattern::Constant(self.to_string().into())
752 }
753 }
754
755 #[test]
756 fn test_one_exact() {
757 let mut map = AliasMap::new();
758 map.insert(AliasPattern::parse("foo"), "bar");
759
760 assert_alias_matches!(map, "");
761 assert_alias_matches!(map, "foo", exact("bar"));
762 assert_alias_matches!(map, "foobar");
763 }
764
765 #[test]
766 fn test_many_exact() {
767 let mut map = AliasMap::new();
768 map.insert(AliasPattern::parse("foo"), "bar");
769 map.insert(AliasPattern::parse("bar"), "foo");
770 map.insert(AliasPattern::parse("foobar"), "barfoo");
771
772 assert_alias_matches!(map, "");
773 assert_alias_matches!(map, "foo", exact("bar"));
774 assert_alias_matches!(map, "bar", exact("foo"));
775 assert_alias_matches!(map, "foobar", exact("barfoo"));
776 }
777
778 #[test]
779 fn test_empty() {
780 let mut map = AliasMap::new();
781 map.insert(AliasPattern::parse(""), "empty");
782 map.insert(AliasPattern::parse("foo"), "bar");
783
784 assert_alias_matches!(map, "", exact("empty"));
785 assert_alias_matches!(map, "foo", exact("bar"));
786 }
787
788 #[test]
789 fn test_left_wildcard() {
790 let mut map = AliasMap::new();
791 map.insert(AliasPattern::parse("foo*"), "bar");
792
793 assert_alias_matches!(map, "");
794 assert_alias_matches!(map, "foo", replaced("bar"));
795 assert_alias_matches!(map, "foobar", replaced("bar"));
796 }
797
798 #[test]
799 fn test_wildcard_replace_suffix() {
800 let mut map = AliasMap::new();
801 map.insert(AliasPattern::parse("foo*"), "bar*");
802 map.insert(AliasPattern::parse("foofoo*"), "barbar*");
803
804 assert_alias_matches!(map, "");
805 assert_alias_matches!(map, "foo", replaced_owned("bar"));
806 assert_alias_matches!(map, "foobar", replaced_owned("barbar"));
807 assert_alias_matches!(
808 map,
809 "foofoobar",
810 replaced_owned("barbarbar"),
812 replaced_owned("barfoobar"),
813 );
814 }
815
816 #[test]
817 fn test_wildcard_replace_prefix() {
818 let mut map = AliasMap::new();
819 map.insert(AliasPattern::parse("*foo"), "*bar");
820 map.insert(AliasPattern::parse("*foofoo"), "*barbar");
821
822 assert_alias_matches!(map, "");
823 assert_alias_matches!(map, "foo", replaced_owned("bar"));
824 assert_alias_matches!(map, "barfoo", replaced_owned("barbar"));
825 assert_alias_matches!(
826 map,
827 "barfoofoo",
828 replaced_owned("barbarbar"),
830 replaced_owned("barfoobar"),
831 );
832 }
833
834 #[test]
835 fn test_wildcard_replace_infix() {
836 let mut map = AliasMap::new();
837 map.insert(AliasPattern::parse("foo*foo"), "bar*bar");
838 map.insert(AliasPattern::parse("foo*foofoo"), "bar*barbar");
839 map.insert(AliasPattern::parse("foofoo*foo"), "bazbaz*baz");
840
841 assert_alias_matches!(map, "");
842 assert_alias_matches!(map, "foo");
843 assert_alias_matches!(map, "foofoo", replaced_owned("barbar"));
844 assert_alias_matches!(map, "foobazfoo", replaced_owned("barbazbar"));
845 assert_alias_matches!(
846 map,
847 "foofoofoo",
848 replaced_owned("bazbazbaz"),
850 replaced_owned("barbarbar"),
852 replaced_owned("barfoobar"),
853 );
854 assert_alias_matches!(
855 map,
856 "foobazfoofoo",
857 replaced_owned("barbazbarbar"),
859 replaced_owned("barbazfoobar"),
860 );
861 assert_alias_matches!(
862 map,
863 "foofoobarfoo",
864 replaced_owned("bazbazbarbaz"),
866 replaced_owned("barfoobarbar"),
867 );
868 assert_alias_matches!(
869 map,
870 "foofoofoofoofoo",
871 replaced_owned("bazbazfoofoobaz"),
873 replaced_owned("barfoofoobarbar"),
875 replaced_owned("barfoofoofoobar"),
876 );
877 }
878
879 #[test]
880 fn test_wildcard_replace_only() {
881 let mut map = AliasMap::new();
882 map.insert(AliasPattern::parse("*"), "foo*foo");
883 map.insert(AliasPattern::parse("**"), "bar*foo");
884
885 assert_alias_matches!(map, "", replaced_owned("foofoo"));
886 assert_alias_matches!(map, "bar", replaced_owned("foobarfoo"));
887 assert_alias_matches!(
888 map,
889 "*",
890 replaced_owned("barfoo"),
891 replaced_owned("foo*foo"),
892 );
893 assert_alias_matches!(
894 map,
895 "**",
896 replaced_owned("bar*foo"),
897 replaced_owned("foo**foo")
898 );
899 }
900
901 #[test]
902 fn test_pattern() {
903 let mut map = AliasMap::new();
904 map.insert(AliasPattern::parse("card/*"), "src/cards/*");
905 map.insert(AliasPattern::parse("comp/*/x"), "src/comps/*/x");
906 map.insert(AliasPattern::parse("head/*/x"), "src/heads/*");
907
908 assert_eq!(
909 map.lookup(&Pattern::Concatenation(vec![
910 Pattern::Constant(rcstr!("card/")),
911 Pattern::Dynamic
912 ]))
913 .collect::<Result<Vec<_>>>()
914 .unwrap(),
915 vec![super::AliasMatch {
916 prefix: "card/".into(),
917 key: &super::AliasKey::Wildcard { suffix: rcstr!("") },
918 output: Pattern::Concatenation(vec![
919 Pattern::Constant(rcstr!("src/cards/")),
920 Pattern::Dynamic
921 ]),
922 }]
923 );
924 assert_eq!(
925 map.lookup(&Pattern::Concatenation(vec![
926 Pattern::Constant(rcstr!("comp/")),
927 Pattern::Dynamic,
928 Pattern::Constant(rcstr!("/x")),
929 ]))
930 .collect::<Result<Vec<_>>>()
931 .unwrap(),
932 vec![super::AliasMatch {
933 prefix: "comp/".into(),
934 key: &super::AliasKey::Wildcard {
935 suffix: rcstr!("/x")
936 },
937 output: Pattern::Concatenation(vec![
938 Pattern::Constant(rcstr!("src/comps/")),
939 Pattern::Dynamic,
940 Pattern::Constant(rcstr!("/x")),
941 ]),
942 }]
943 );
944 assert_eq!(
945 map.lookup(&Pattern::Concatenation(vec![
946 Pattern::Constant(rcstr!("head/")),
947 Pattern::Dynamic,
948 Pattern::Constant(rcstr!("/x")),
949 ]))
950 .collect::<Result<Vec<_>>>()
951 .unwrap(),
952 vec![super::AliasMatch {
953 prefix: "head/".into(),
954 key: &super::AliasKey::Wildcard {
955 suffix: rcstr!("/x")
956 },
957 output: Pattern::Concatenation(vec![
958 Pattern::Constant(rcstr!("src/heads/")),
959 Pattern::Dynamic,
960 ]),
961 }]
962 );
963 }
964
965 #[test]
966 fn test_pattern_very_dynamic() {
967 let mut map = AliasMap::new();
968 map.insert(AliasPattern::parse("bar-a"), "src/bar/a");
970 map.insert(AliasPattern::parse("bar-b"), "src/bar/b");
971
972 assert_eq!(
991 map.lookup(&Pattern::Concatenation(vec![
992 Pattern::Dynamic,
993 Pattern::Constant(rcstr!("bar-a")),
994 ]))
995 .collect::<Result<Vec<_>>>()
996 .unwrap(),
997 vec![super::AliasMatch {
998 prefix: "bar-a".into(),
999 key: &AliasKey::Exact,
1000 output: Pattern::Constant(rcstr!("src/bar/a"))
1001 }]
1002 );
1003 assert_eq!(
1004 map.lookup(&Pattern::Concatenation(vec![
1005 Pattern::Constant(rcstr!("bar-")),
1006 Pattern::Dynamic,
1007 ]))
1008 .collect::<Result<Vec<_>>>()
1009 .unwrap(),
1010 vec![
1011 super::AliasMatch {
1012 prefix: "bar-b".into(),
1013 key: &AliasKey::Exact,
1014 output: Pattern::Constant(rcstr!("src/bar/b"))
1015 },
1016 super::AliasMatch {
1017 prefix: "bar-a".into(),
1018 key: &AliasKey::Exact,
1019 output: Pattern::Constant(rcstr!("src/bar/a"))
1020 }
1021 ]
1022 );
1023 }
1024}