1use std::{
2 borrow::Cow,
3 collections::BTreeMap,
4 fmt::{Debug, Formatter},
5};
6
7use anyhow::Result;
8use patricia_tree::PatriciaMap;
9use serde::{
10 Deserialize, Deserializer, Serialize, Serializer,
11 de::{MapAccess, Visitor},
12 ser::SerializeMap,
13};
14use serde_bytes::{ByteBuf, Bytes};
15use turbo_rcstr::RcStr;
16use turbo_tasks::{
17 NonLocalValue,
18 debug::{ValueDebugFormat, ValueDebugFormatString, internal::PassthroughDebug},
19 trace::{TraceRawVcs, TraceRawVcsContext},
20};
21
22use super::pattern::Pattern;
23
24#[derive(Clone)]
33pub struct AliasMap<T> {
34 map: PatriciaMap<BTreeMap<AliasKey, T>>,
35}
36
37impl<T> Default for AliasMap<T> {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl<T> PartialEq for AliasMap<T>
44where
45 T: PartialEq,
46{
47 fn eq(&self, other: &Self) -> bool {
48 if self.map.len() != other.map.len() {
49 return false;
50 }
51
52 self.map.iter().zip(other.map.iter()).all(|(a, b)| a == b)
53 }
54}
55
56impl<T> Eq for AliasMap<T> where T: Eq {}
57
58impl<T> Serialize for AliasMap<T>
59where
60 T: Serialize,
61{
62 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63 where
64 S: Serializer,
65 {
66 let mut map = serializer.serialize_map(Some(self.map.len()))?;
67 for (prefix, value) in self.map.iter() {
68 let key = ByteBuf::from(prefix);
69 map.serialize_entry(&key, value)?;
70 }
71 map.end()
72 }
73}
74
75struct AliasMapVisitor<T> {
76 marker: std::marker::PhantomData<T>,
77}
78
79impl<'de, T> Visitor<'de> for AliasMapVisitor<T>
80where
81 T: Deserialize<'de>,
82{
83 type Value = AliasMap<T>;
84
85 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
86 formatter.write_str("a map of alias patterns to templates")
87 }
88
89 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
90 where
91 M: MapAccess<'de>,
92 {
93 let mut map = AliasMap::new();
94 while let Some((key, value)) = access.next_entry::<&Bytes, _>()? {
95 map.map.insert(key, value);
96 }
97 Ok(map)
98 }
99}
100
101impl<'a, T> Deserialize<'a> for AliasMap<T>
102where
103 T: Deserialize<'a>,
104{
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: Deserializer<'a>,
108 {
109 deserializer.deserialize_map(AliasMapVisitor {
110 marker: std::marker::PhantomData,
111 })
112 }
113}
114
115impl<T> TraceRawVcs for AliasMap<T>
116where
117 T: TraceRawVcs,
118{
119 fn trace_raw_vcs(&self, trace_context: &mut TraceRawVcsContext) {
120 for (_, map) in self.map.iter() {
121 for value in map.values() {
122 value.trace_raw_vcs(trace_context);
123 }
124 }
125 }
126}
127
128unsafe impl<T: NonLocalValue> NonLocalValue for AliasMap<T> {}
129
130impl<T> ValueDebugFormat for AliasMap<T>
131where
132 T: ValueDebugFormat,
133{
134 fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
135 if depth == 0 {
136 return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
137 }
138
139 let values = self
140 .map
141 .iter()
142 .flat_map(|(key, map)| {
143 let key = String::from_utf8(key).expect("invalid UTF-8 key in AliasMap");
144 map.iter().map(move |(alias_key, value)| match alias_key {
145 AliasKey::Exact => (
146 key.clone(),
147 value.value_debug_format(depth.saturating_sub(1)),
148 ),
149 AliasKey::Wildcard { suffix } => (
150 format!("{key}*{suffix}"),
151 value.value_debug_format(depth.saturating_sub(1)),
152 ),
153 })
154 })
155 .collect::<Vec<_>>();
156
157 ValueDebugFormatString::Async(Box::pin(async move {
158 let mut values_string = std::collections::HashMap::new();
159 for (key, value) in values {
160 match value {
161 ValueDebugFormatString::Sync(string) => {
162 values_string.insert(key, PassthroughDebug::new_string(string));
163 }
164 ValueDebugFormatString::Async(future) => {
165 values_string.insert(key, PassthroughDebug::new_string(future.await?));
166 }
167 }
168 }
169 Ok(format!("{values_string:#?}"))
170 }))
171 }
172}
173
174impl<T> Debug for AliasMap<T>
175where
176 T: Debug,
177{
178 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
179 f.debug_map()
180 .entries(self.map.iter().flat_map(|(key, map)| {
181 let key = String::from_utf8(key).expect("invalid UTF-8 key in AliasMap");
182 map.iter().map(move |(alias_key, value)| match alias_key {
183 AliasKey::Exact => (key.clone(), value),
184 AliasKey::Wildcard { suffix } => (format!("{key}*{suffix}"), value),
185 })
186 }))
187 .finish()
188 }
189}
190
191impl<T> AliasMap<T> {
192 pub fn new() -> Self {
194 AliasMap {
195 map: PatriciaMap::new(),
196 }
197 }
198
199 pub fn lookup<'a>(&'a self, request: &'a Pattern) -> AliasMapLookupIterator<'a, T>
203 where
204 T: Debug,
205 {
206 if matches!(request, Pattern::Alternatives(_)) {
207 panic!("AliasMap::lookup must not be called on alternatives, received {request:?}");
208 }
209
210 let mut prefixes_stack = if let Some(request) = request.as_constant_string() {
214 let common_prefixes = self.map.common_prefixes(request.as_bytes());
216 common_prefixes
217 .map(|(p, tree)| {
218 let s = match std::str::from_utf8(p) {
219 Ok(s) => s,
220 Err(e) => std::str::from_utf8(&p[..e.valid_up_to()]).unwrap(),
221 };
222 (Cow::Borrowed(s), tree)
223 })
224 .collect::<Vec<_>>()
225 } else {
226 self.map
231 .iter()
232 .map(|(p, tree)| {
233 let s = match String::from_utf8(p) {
234 Ok(s) => s,
235 Err(e) => {
236 let valid_up_to = e.utf8_error().valid_up_to();
237 let mut p = e.into_bytes();
238 p.drain(valid_up_to..);
239 String::from_utf8(p).unwrap()
240 }
241 };
242 (Cow::Owned(s), tree)
243 })
244 .collect::<Vec<_>>()
245 };
246
247 AliasMapLookupIterator {
251 request,
252 current_prefix_iterator: prefixes_stack
253 .pop()
254 .map(|(prefix, map)| (prefix, map.iter())),
255 prefixes_stack,
256 }
257 }
258
259 pub fn lookup_with_prefix_predicate<'a>(
264 &'a self,
265 request: &'a Pattern,
266 mut prefix_predicate: impl FnMut(&str) -> bool,
267 ) -> AliasMapLookupIterator<'a, T>
268 where
269 T: Debug,
270 {
271 if matches!(request, Pattern::Alternatives(_)) {
272 panic!("AliasMap::lookup must not be called on alternatives, received {request:?}");
273 }
274
275 let mut prefixes_stack = if let Some(request) = request.as_constant_string() {
279 let common_prefixes = self.map.common_prefixes(request.as_bytes());
281 common_prefixes
282 .filter_map(|(p, tree)| {
283 let s = match std::str::from_utf8(p) {
284 Ok(s) => s,
285 Err(e) => std::str::from_utf8(&p[..e.valid_up_to()]).unwrap(),
286 };
287 if prefix_predicate(s) {
288 Some((Cow::Borrowed(s), tree))
289 } else {
290 None
291 }
292 })
293 .collect::<Vec<_>>()
294 } else {
295 self.map
300 .iter()
301 .filter_map(|(p, tree)| {
302 let s = match String::from_utf8(p) {
303 Ok(s) => s,
304 Err(e) => {
305 let valid_up_to = e.utf8_error().valid_up_to();
306 let mut p = e.into_bytes();
307 p.drain(valid_up_to..);
308 String::from_utf8(p).unwrap()
309 }
310 };
311 if prefix_predicate(&s) {
312 Some((Cow::Owned(s), tree))
313 } else {
314 None
315 }
316 })
317 .collect::<Vec<_>>()
318 };
319
320 AliasMapLookupIterator {
324 request,
325 current_prefix_iterator: prefixes_stack
326 .pop()
327 .map(|(prefix, map)| (prefix, map.iter())),
328 prefixes_stack,
329 }
330 }
331
332 pub fn insert(&mut self, pattern: AliasPattern, template: T) -> Option<T> {
339 let (prefix_key, alias_key, value) = match pattern {
340 AliasPattern::Exact(exact) => (exact, AliasKey::Exact, template),
341 AliasPattern::Wildcard { prefix, suffix } => {
342 (prefix, AliasKey::Wildcard { suffix }, template)
343 }
344 };
345 if let Some(map) = self.map.get_mut(&prefix_key) {
352 map.insert(alias_key, value)
353 } else {
354 let mut map = BTreeMap::new();
355 map.insert(alias_key, value);
356 self.map.insert(prefix_key, map);
357 None
358 }
359 }
360}
361
362impl<T> IntoIterator for AliasMap<T> {
363 type Item = (AliasPattern, T);
364
365 type IntoIter = AliasMapIntoIter<T>;
366
367 fn into_iter(self) -> Self::IntoIter {
368 AliasMapIntoIter {
369 iter: self.map.into_iter(),
370 current_prefix_iterator: None,
371 }
372 }
373}
374
375impl<'a, T> IntoIterator for &'a AliasMap<T> {
376 type Item = (AliasPattern, &'a T);
377
378 type IntoIter = AliasMapIter<'a, T>;
379
380 fn into_iter(self) -> Self::IntoIter {
381 AliasMapIter {
382 iter: self.map.iter(),
383 current_prefix_iterator: None,
384 }
385 }
386}
387
388pub struct AliasMapIntoIter<T> {
395 iter: patricia_tree::map::IntoIter<BTreeMap<AliasKey, T>>,
396 current_prefix_iterator: Option<AliasMapIntoIterItem<T>>,
397}
398
399struct AliasMapIntoIterItem<T> {
400 prefix: RcStr,
401 iterator: std::collections::btree_map::IntoIter<AliasKey, T>,
402}
403
404impl<T> AliasMapIntoIter<T> {
405 fn advance_iter(&mut self) -> Option<&mut AliasMapIntoIterItem<T>> {
406 let (prefix, map) = self.iter.next()?;
407 let prefix = String::from_utf8(prefix)
408 .expect("invalid UTF-8 key in AliasMap")
409 .into();
410 self.current_prefix_iterator = Some(AliasMapIntoIterItem {
411 prefix,
412 iterator: map.into_iter(),
413 });
414 self.current_prefix_iterator.as_mut()
415 }
416}
417
418impl<T> Iterator for AliasMapIntoIter<T> {
419 type Item = (AliasPattern, T);
420
421 fn next(&mut self) -> Option<Self::Item> {
422 let mut current_prefix_iterator = match self.current_prefix_iterator {
423 None => self.advance_iter()?,
424 Some(ref mut current_prefix_iterator) => current_prefix_iterator,
425 };
426 let mut current_value = current_prefix_iterator.iterator.next();
427 loop {
428 match current_value {
429 None => {
430 current_prefix_iterator = self.advance_iter()?;
431 current_value = current_prefix_iterator.iterator.next();
432 }
433 Some(current_value) => {
434 return Some(match current_value {
435 (AliasKey::Exact, value) => (
436 AliasPattern::Exact(current_prefix_iterator.prefix.clone()),
437 value,
438 ),
439 (AliasKey::Wildcard { suffix }, value) => (
440 AliasPattern::Wildcard {
441 prefix: current_prefix_iterator.prefix.clone(),
442 suffix,
443 },
444 value,
445 ),
446 });
447 }
448 }
449 }
450 }
451}
452
453pub struct AliasMapIter<'a, T> {
460 iter: patricia_tree::map::Iter<'a, BTreeMap<AliasKey, T>>,
461 current_prefix_iterator: Option<AliasMapIterItem<'a, T>>,
462}
463
464struct AliasMapIterItem<'a, T> {
465 prefix: RcStr,
466 iterator: std::collections::btree_map::Iter<'a, AliasKey, T>,
467}
468
469impl<T> AliasMapIter<'_, T> {
470 fn advance_iter(&mut self) -> bool {
471 let Some((prefix, map)) = self.iter.next() else {
472 return false;
473 };
474 let prefix = String::from_utf8(prefix)
475 .expect("invalid UTF-8 key in AliasMap")
476 .into();
477 self.current_prefix_iterator = Some(AliasMapIterItem {
478 prefix,
479 iterator: map.iter(),
480 });
481 true
482 }
483}
484
485impl<'a, T> Iterator for AliasMapIter<'a, T> {
486 type Item = (AliasPattern, &'a T);
487
488 fn next(&mut self) -> Option<Self::Item> {
489 let (current_prefix_iterator, current_value) = loop {
490 let Some(current_prefix_iterator) = &mut self.current_prefix_iterator else {
491 if !self.advance_iter() {
492 return None;
493 }
494 continue;
495 };
496 if let Some(current_value) = current_prefix_iterator.iterator.next() {
497 break (&*current_prefix_iterator, current_value);
498 }
499 self.current_prefix_iterator = None;
500 continue;
501 };
502 Some(match current_value {
503 (AliasKey::Exact, value) => (
504 AliasPattern::Exact(current_prefix_iterator.prefix.clone()),
505 value,
506 ),
507 (AliasKey::Wildcard { suffix }, value) => (
508 AliasPattern::Wildcard {
509 prefix: current_prefix_iterator.prefix.clone(),
510 suffix: suffix.clone(),
511 },
512 value,
513 ),
514 })
515 }
516}
517
518impl<T> Extend<(AliasPattern, T)> for AliasMap<T> {
519 fn extend<It>(&mut self, iter: It)
520 where
521 It: IntoIterator<Item = (AliasPattern, T)>,
522 {
523 for (pattern, value) in iter {
524 self.insert(pattern, value);
525 }
526 }
527}
528
529pub struct AliasMapLookupIterator<'a, T> {
535 request: &'a Pattern,
536 prefixes_stack: Vec<(Cow<'a, str>, &'a BTreeMap<AliasKey, T>)>,
537 current_prefix_iterator: Option<(
538 Cow<'a, str>,
539 std::collections::btree_map::Iter<'a, AliasKey, T>,
540 )>,
541}
542
543impl<'a, T> Iterator for AliasMapLookupIterator<'a, T>
544where
545 T: AliasTemplate + Clone,
546{
547 type Item = Result<AliasMatch<'a, T>>;
548
549 fn next(&mut self) -> Option<Self::Item> {
550 let (prefix, current_prefix_iterator) = self.current_prefix_iterator.as_mut()?;
551
552 loop {
553 for (key, template) in &mut *current_prefix_iterator {
554 match key {
555 AliasKey::Exact => {
556 if self.request.is_match(prefix) {
557 return Some(Ok(AliasMatch {
558 prefix: prefix.clone(),
559 key,
560 output: template.convert(),
561 }));
562 }
563 }
564 AliasKey::Wildcard { suffix } => {
565 let is_match = if let Some(request) = self.request.as_constant_string() {
570 let remaining = &request[prefix.len()..];
573 remaining.ends_with(&**suffix)
574 } else if let Pattern::Concatenation(req) = self.request
575 && let [
576 Pattern::Constant(req_prefix),
577 Pattern::Dynamic | Pattern::DynamicNoSlash,
578 ] = req.as_slice()
579 {
580 req_prefix.starts_with(&**prefix)
586 } else if let Pattern::Concatenation(req) = self.request
587 && let [
588 Pattern::Constant(req_prefix),
589 Pattern::Dynamic | Pattern::DynamicNoSlash,
590 Pattern::Constant(req_suffix),
591 ] = req.as_slice()
592 {
593 req_prefix.starts_with(&**prefix) && req_suffix.ends_with(&**suffix)
594 } else if !self.request.could_match(prefix) {
595 false
597 } else if suffix.is_empty() {
598 true
600 } else {
601 return Some(Err(anyhow::anyhow!(
603 "Complex patterns into wildcard exports fields are not \
604 implemented yet: {} into '{}*{}'",
605 self.request.describe_as_string(),
606 prefix,
607 suffix,
608 )));
609 };
610
611 if is_match {
612 let mut remaining = self.request.clone();
613 if let Err(e) = remaining.strip_prefix_len(prefix.len()) {
614 return Some(Err(e.context(self.request.describe_as_string())));
615 }
616 remaining.strip_suffix_len(suffix.len());
617
618 let output = template.replace(&remaining);
619 return Some(Ok(AliasMatch {
620 prefix: prefix.clone(),
621 key,
622 output,
623 }));
624 }
625 }
626 }
627 }
628
629 let (new_prefix, new_current_prefix_iterator) = self.prefixes_stack.pop()?;
630 *prefix = new_prefix;
631 *current_prefix_iterator = new_current_prefix_iterator.iter();
632 }
633 }
634}
635
636#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
638pub enum AliasPattern {
639 Exact(RcStr),
641 Wildcard { prefix: RcStr, suffix: RcStr },
643}
644
645impl AliasPattern {
646 pub fn parse<'a, T>(pattern: T) -> Self
651 where
652 T: Into<RcStr> + 'a,
653 {
654 let pattern = pattern.into();
655 if let Some(wildcard_index) = pattern.find('*') {
656 let mut pattern = pattern.into_owned();
657
658 let suffix = pattern[wildcard_index + 1..].into();
659 pattern.truncate(wildcard_index);
660 AliasPattern::Wildcard {
661 prefix: pattern.into(),
662 suffix,
663 }
664 } else {
665 AliasPattern::Exact(pattern)
666 }
667 }
668
669 pub fn exact<'a, T>(pattern: T) -> Self
671 where
672 T: Into<RcStr> + 'a,
673 {
674 AliasPattern::Exact(pattern.into())
675 }
676
677 pub fn wildcard<'p, 's, P, S>(prefix: P, suffix: S) -> Self
682 where
683 P: Into<RcStr> + 'p,
684 S: Into<RcStr> + 's,
685 {
686 AliasPattern::Wildcard {
687 prefix: prefix.into(),
688 suffix: suffix.into(),
689 }
690 }
691}
692
693#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
694pub enum AliasKey {
695 Exact,
696 Wildcard { suffix: RcStr },
697}
698
699#[derive(Debug, PartialEq, Clone)]
701pub struct AliasMatch<'a, T>
702where
703 T: AliasTemplate + Clone + 'a,
704{
705 pub prefix: Cow<'a, str>,
706 pub key: &'a AliasKey,
707 pub output: T::Output<'a>,
708}
709
710impl PartialOrd for AliasKey {
711 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
712 Some(self.cmp(other))
713 }
714}
715
716impl Ord for AliasKey {
717 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
721 match (self, other) {
722 (AliasKey::Wildcard { suffix: l_suffix }, AliasKey::Wildcard { suffix: r_suffix }) => {
723 l_suffix
724 .len()
725 .cmp(&r_suffix.len())
726 .reverse()
727 .then_with(|| l_suffix.cmp(r_suffix))
728 }
729 (AliasKey::Wildcard { .. }, _) => std::cmp::Ordering::Less,
730 (_, AliasKey::Wildcard { .. }) => std::cmp::Ordering::Greater,
731 _ => std::cmp::Ordering::Equal,
732 }
733 }
734}
735
736pub trait AliasTemplate {
738 type Output<'a>
740 where
741 Self: 'a;
742
743 fn convert(&self) -> Self::Output<'_>;
745
746 fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a>;
748}
749
750#[cfg(test)]
751mod test {
752 use std::assert_matches::assert_matches;
753
754 use anyhow::Result;
755 use turbo_rcstr::rcstr;
756
757 use super::{AliasMap, AliasPattern, AliasTemplate};
758 use crate::resolve::{alias_map::AliasKey, pattern::Pattern};
759
760 macro_rules! assert_alias_matches {
765 ($map:expr, $request:expr$(, $($tail:tt)*)?) => {
766 let request = Pattern::Constant($request.into());
767 let mut lookup = $map.lookup(&request);
768
769 $(assert_alias_matches!(@next lookup, $($tail)*);)?
770 assert_matches!(lookup.next(), None);
771 };
772
773 (@next $lookup:ident, exact($pattern:expr)$(, $($tail:tt)*)?) => {
774 match $lookup.next().unwrap().unwrap() {
775 super::AliasMatch{key: super::AliasKey::Exact, output: Pattern::Constant(c), ..} if c == $pattern => {}
776 m => panic!("unexpected match {:?}", m),
777 }
778 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
779 };
780
781 (@next $lookup:ident, replaced($pattern:expr)$(, $($tail:tt)*)?) => {
782 match $lookup.next().unwrap().unwrap() {
783 super::AliasMatch{key: super::AliasKey::Wildcard{..}, output: Pattern::Constant(c), ..} if c == $pattern => {}
784 m => panic!("unexpected match {:?}", m),
785 }
786 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
787 };
788
789 (@next $lookup:ident, replaced_owned($value:expr)$(, $($tail:tt)*)?) => {
790 match $lookup.next().unwrap().unwrap() {
791 super::AliasMatch{key: super::AliasKey::Wildcard{..}, output: Pattern::Constant(c), ..} if c == $value => {}
792 m => panic!("unexpected match {:?}", m),
793 }
794 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
795 };
796
797 (@next $lookup:ident,) => {};
799 }
800
801 impl<'a> AliasTemplate for &'a str {
802 type Output<'b>
803 = Pattern
804 where
805 Self: 'b;
806
807 fn replace(&self, capture: &Pattern) -> Self::Output<'a> {
808 capture.spread_into_star(self)
809 }
810
811 fn convert(&self) -> Self::Output<'a> {
812 Pattern::Constant(self.to_string().into())
813 }
814 }
815
816 #[test]
817 fn test_one_exact() {
818 let mut map = AliasMap::new();
819 map.insert(AliasPattern::parse("foo"), "bar");
820
821 assert_alias_matches!(map, "");
822 assert_alias_matches!(map, "foo", exact("bar"));
823 assert_alias_matches!(map, "foobar");
824 }
825
826 #[test]
827 fn test_many_exact() {
828 let mut map = AliasMap::new();
829 map.insert(AliasPattern::parse("foo"), "bar");
830 map.insert(AliasPattern::parse("bar"), "foo");
831 map.insert(AliasPattern::parse("foobar"), "barfoo");
832
833 assert_alias_matches!(map, "");
834 assert_alias_matches!(map, "foo", exact("bar"));
835 assert_alias_matches!(map, "bar", exact("foo"));
836 assert_alias_matches!(map, "foobar", exact("barfoo"));
837 }
838
839 #[test]
840 fn test_empty() {
841 let mut map = AliasMap::new();
842 map.insert(AliasPattern::parse(""), "empty");
843 map.insert(AliasPattern::parse("foo"), "bar");
844
845 assert_alias_matches!(map, "", exact("empty"));
846 assert_alias_matches!(map, "foo", exact("bar"));
847 }
848
849 #[test]
850 fn test_left_wildcard() {
851 let mut map = AliasMap::new();
852 map.insert(AliasPattern::parse("foo*"), "bar");
853
854 assert_alias_matches!(map, "");
855 assert_alias_matches!(map, "foo", replaced("bar"));
856 assert_alias_matches!(map, "foobar", replaced("bar"));
857 }
858
859 #[test]
860 fn test_wildcard_replace_suffix() {
861 let mut map = AliasMap::new();
862 map.insert(AliasPattern::parse("foo*"), "bar*");
863 map.insert(AliasPattern::parse("foofoo*"), "barbar*");
864
865 assert_alias_matches!(map, "");
866 assert_alias_matches!(map, "foo", replaced_owned("bar"));
867 assert_alias_matches!(map, "foobar", replaced_owned("barbar"));
868 assert_alias_matches!(
869 map,
870 "foofoobar",
871 replaced_owned("barbarbar"),
873 replaced_owned("barfoobar"),
874 );
875 }
876
877 #[test]
878 fn test_wildcard_replace_prefix() {
879 let mut map = AliasMap::new();
880 map.insert(AliasPattern::parse("*foo"), "*bar");
881 map.insert(AliasPattern::parse("*foofoo"), "*barbar");
882
883 assert_alias_matches!(map, "");
884 assert_alias_matches!(map, "foo", replaced_owned("bar"));
885 assert_alias_matches!(map, "barfoo", replaced_owned("barbar"));
886 assert_alias_matches!(
887 map,
888 "barfoofoo",
889 replaced_owned("barbarbar"),
891 replaced_owned("barfoobar"),
892 );
893 }
894
895 #[test]
896 fn test_wildcard_replace_infix() {
897 let mut map = AliasMap::new();
898 map.insert(AliasPattern::parse("foo*foo"), "bar*bar");
899 map.insert(AliasPattern::parse("foo*foofoo"), "bar*barbar");
900 map.insert(AliasPattern::parse("foofoo*foo"), "bazbaz*baz");
901
902 assert_alias_matches!(map, "");
903 assert_alias_matches!(map, "foo");
904 assert_alias_matches!(map, "foofoo", replaced_owned("barbar"));
905 assert_alias_matches!(map, "foobazfoo", replaced_owned("barbazbar"));
906 assert_alias_matches!(
907 map,
908 "foofoofoo",
909 replaced_owned("bazbazbaz"),
911 replaced_owned("barbarbar"),
913 replaced_owned("barfoobar"),
914 );
915 assert_alias_matches!(
916 map,
917 "foobazfoofoo",
918 replaced_owned("barbazbarbar"),
920 replaced_owned("barbazfoobar"),
921 );
922 assert_alias_matches!(
923 map,
924 "foofoobarfoo",
925 replaced_owned("bazbazbarbaz"),
927 replaced_owned("barfoobarbar"),
928 );
929 assert_alias_matches!(
930 map,
931 "foofoofoofoofoo",
932 replaced_owned("bazbazfoofoobaz"),
934 replaced_owned("barfoofoobarbar"),
936 replaced_owned("barfoofoofoobar"),
937 );
938 }
939
940 #[test]
941 fn test_wildcard_replace_only() {
942 let mut map = AliasMap::new();
943 map.insert(AliasPattern::parse("*"), "foo*foo");
944 map.insert(AliasPattern::parse("**"), "bar*foo");
945
946 assert_alias_matches!(map, "", replaced_owned("foofoo"));
947 assert_alias_matches!(map, "bar", replaced_owned("foobarfoo"));
948 assert_alias_matches!(
949 map,
950 "*",
951 replaced_owned("barfoo"),
952 replaced_owned("foo*foo"),
953 );
954 assert_alias_matches!(
955 map,
956 "**",
957 replaced_owned("bar*foo"),
958 replaced_owned("foo**foo")
959 );
960 }
961
962 #[test]
963 fn test_pattern() {
964 let mut map = AliasMap::new();
965 map.insert(AliasPattern::parse("card/*"), "src/cards/*");
966 map.insert(AliasPattern::parse("comp/*/x"), "src/comps/*/x");
967 map.insert(AliasPattern::parse("head/*/x"), "src/heads/*");
968
969 assert_eq!(
970 map.lookup(&Pattern::Concatenation(vec![
971 Pattern::Constant(rcstr!("card/")),
972 Pattern::Dynamic
973 ]))
974 .collect::<Result<Vec<_>>>()
975 .unwrap(),
976 vec![super::AliasMatch {
977 prefix: "card/".into(),
978 key: &super::AliasKey::Wildcard { suffix: rcstr!("") },
979 output: Pattern::Concatenation(vec![
980 Pattern::Constant(rcstr!("src/cards/")),
981 Pattern::Dynamic
982 ]),
983 }]
984 );
985 assert_eq!(
986 map.lookup(&Pattern::Concatenation(vec![
987 Pattern::Constant(rcstr!("comp/")),
988 Pattern::Dynamic,
989 Pattern::Constant(rcstr!("/x")),
990 ]))
991 .collect::<Result<Vec<_>>>()
992 .unwrap(),
993 vec![super::AliasMatch {
994 prefix: "comp/".into(),
995 key: &super::AliasKey::Wildcard {
996 suffix: rcstr!("/x")
997 },
998 output: Pattern::Concatenation(vec![
999 Pattern::Constant(rcstr!("src/comps/")),
1000 Pattern::Dynamic,
1001 Pattern::Constant(rcstr!("/x")),
1002 ]),
1003 }]
1004 );
1005 assert_eq!(
1006 map.lookup(&Pattern::Concatenation(vec![
1007 Pattern::Constant(rcstr!("head/")),
1008 Pattern::Dynamic,
1009 Pattern::Constant(rcstr!("/x")),
1010 ]))
1011 .collect::<Result<Vec<_>>>()
1012 .unwrap(),
1013 vec![super::AliasMatch {
1014 prefix: "head/".into(),
1015 key: &super::AliasKey::Wildcard {
1016 suffix: rcstr!("/x")
1017 },
1018 output: Pattern::Concatenation(vec![
1019 Pattern::Constant(rcstr!("src/heads/")),
1020 Pattern::Dynamic,
1021 ]),
1022 }]
1023 );
1024 }
1025
1026 #[test]
1027 fn test_pattern_very_dynamic() {
1028 let mut map = AliasMap::new();
1029 map.insert(AliasPattern::parse("bar-a"), "src/bar/a");
1031 map.insert(AliasPattern::parse("bar-b"), "src/bar/b");
1032
1033 assert_eq!(
1052 map.lookup(&Pattern::Concatenation(vec![
1053 Pattern::Dynamic,
1054 Pattern::Constant(rcstr!("bar-a")),
1055 ]))
1056 .collect::<Result<Vec<_>>>()
1057 .unwrap(),
1058 vec![super::AliasMatch {
1059 prefix: "bar-a".into(),
1060 key: &AliasKey::Exact,
1061 output: Pattern::Constant(rcstr!("src/bar/a"))
1062 }]
1063 );
1064 assert_eq!(
1065 map.lookup(&Pattern::Concatenation(vec![
1066 Pattern::Constant(rcstr!("bar-")),
1067 Pattern::Dynamic,
1068 ]))
1069 .collect::<Result<Vec<_>>>()
1070 .unwrap(),
1071 vec![
1072 super::AliasMatch {
1073 prefix: "bar-b".into(),
1074 key: &AliasKey::Exact,
1075 output: Pattern::Constant(rcstr!("src/bar/b"))
1076 },
1077 super::AliasMatch {
1078 prefix: "bar-a".into(),
1079 key: &AliasKey::Exact,
1080 output: Pattern::Constant(rcstr!("src/bar/a"))
1081 }
1082 ]
1083 );
1084 }
1085}