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