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