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() {
566 let remaining = &request[prefix.len()..];
569 remaining.ends_with(&**suffix)
570 } else if let Pattern::Concatenation(req) = self.request
571 && let [
572 Pattern::Constant(req_prefix),
573 Pattern::Dynamic | Pattern::DynamicNoSlash,
574 ] = req.as_slice()
575 {
576 req_prefix.starts_with(&**prefix)
582 } else if let Pattern::Concatenation(req) = self.request
583 && let [
584 Pattern::Constant(req_prefix),
585 Pattern::Dynamic | Pattern::DynamicNoSlash,
586 Pattern::Constant(req_suffix),
587 ] = req.as_slice()
588 {
589 req_prefix.starts_with(&**prefix) && req_suffix.ends_with(&**suffix)
590 } else {
591 return Some(Err(anyhow::anyhow!(
592 "complex patterns into wildcard exports fields are not \
593 implemented yet: {} into '{}*{}'",
594 self.request.describe_as_string(),
595 prefix,
596 suffix,
597 )));
598 };
599
600 if is_match {
601 let mut remaining = self.request.clone();
602 remaining.strip_prefix_len(prefix.len());
603 remaining.strip_suffix_len(suffix.len());
604
605 let output = template.replace(&remaining);
606 return Some(Ok(AliasMatch {
607 prefix: prefix.clone(),
608 key,
609 output,
610 }));
611 }
612 }
613 }
614 }
615
616 let (new_prefix, new_current_prefix_iterator) = self.prefixes_stack.pop()?;
617 *prefix = new_prefix;
618 *current_prefix_iterator = new_current_prefix_iterator.iter();
619 }
620 }
621}
622
623#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
625pub enum AliasPattern {
626 Exact(RcStr),
628 Wildcard { prefix: RcStr, suffix: RcStr },
630}
631
632impl AliasPattern {
633 pub fn parse<'a, T>(pattern: T) -> Self
638 where
639 T: Into<RcStr> + 'a,
640 {
641 let pattern = pattern.into();
642 if let Some(wildcard_index) = pattern.find('*') {
643 let mut pattern = pattern.into_owned();
644
645 let suffix = pattern[wildcard_index + 1..].into();
646 pattern.truncate(wildcard_index);
647 AliasPattern::Wildcard {
648 prefix: pattern.into(),
649 suffix,
650 }
651 } else {
652 AliasPattern::Exact(pattern)
653 }
654 }
655
656 pub fn exact<'a, T>(pattern: T) -> Self
658 where
659 T: Into<RcStr> + 'a,
660 {
661 AliasPattern::Exact(pattern.into())
662 }
663
664 pub fn wildcard<'p, 's, P, S>(prefix: P, suffix: S) -> Self
669 where
670 P: Into<RcStr> + 'p,
671 S: Into<RcStr> + 's,
672 {
673 AliasPattern::Wildcard {
674 prefix: prefix.into(),
675 suffix: suffix.into(),
676 }
677 }
678}
679
680#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
681pub enum AliasKey {
682 Exact,
683 Wildcard { suffix: RcStr },
684}
685
686#[derive(Debug, PartialEq, Clone)]
688pub struct AliasMatch<'a, T>
689where
690 T: AliasTemplate + Clone + 'a,
691{
692 pub prefix: Cow<'a, str>,
693 pub key: &'a AliasKey,
694 pub output: T::Output<'a>,
695}
696
697impl PartialOrd for AliasKey {
698 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
699 Some(self.cmp(other))
700 }
701}
702
703impl Ord for AliasKey {
704 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
708 match (self, other) {
709 (AliasKey::Wildcard { suffix: l_suffix }, AliasKey::Wildcard { suffix: r_suffix }) => {
710 l_suffix
711 .len()
712 .cmp(&r_suffix.len())
713 .reverse()
714 .then_with(|| l_suffix.cmp(r_suffix))
715 }
716 (AliasKey::Wildcard { .. }, _) => std::cmp::Ordering::Less,
717 (_, AliasKey::Wildcard { .. }) => std::cmp::Ordering::Greater,
718 _ => std::cmp::Ordering::Equal,
719 }
720 }
721}
722
723pub trait AliasTemplate {
725 type Output<'a>
727 where
728 Self: 'a;
729
730 fn convert(&self) -> Self::Output<'_>;
732
733 fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a>;
735}
736
737#[cfg(test)]
738mod test {
739 use std::assert_matches::assert_matches;
740
741 use anyhow::Result;
742 use turbo_rcstr::rcstr;
743
744 use super::{AliasMap, AliasPattern, AliasTemplate};
745 use crate::resolve::{alias_map::AliasKey, pattern::Pattern};
746
747 macro_rules! assert_alias_matches {
752 ($map:expr, $request:expr$(, $($tail:tt)*)?) => {
753 let request = Pattern::Constant($request.into());
754 let mut lookup = $map.lookup(&request);
755
756 $(assert_alias_matches!(@next lookup, $($tail)*);)?
757 assert_matches!(lookup.next(), None);
758 };
759
760 (@next $lookup:ident, exact($pattern:expr)$(, $($tail:tt)*)?) => {
761 match $lookup.next().unwrap().unwrap() {
762 super::AliasMatch{key: super::AliasKey::Exact, output: Pattern::Constant(c), ..} if c == $pattern => {}
763 m => panic!("unexpected match {:?}", m),
764 }
765 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
766 };
767
768 (@next $lookup:ident, replaced($pattern:expr)$(, $($tail:tt)*)?) => {
769 match $lookup.next().unwrap().unwrap() {
770 super::AliasMatch{key: super::AliasKey::Wildcard{..}, output: Pattern::Constant(c), ..} if c == $pattern => {}
771 m => panic!("unexpected match {:?}", m),
772 }
773 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
774 };
775
776 (@next $lookup:ident, replaced_owned($value:expr)$(, $($tail:tt)*)?) => {
777 match $lookup.next().unwrap().unwrap() {
778 super::AliasMatch{key: super::AliasKey::Wildcard{..}, output: Pattern::Constant(c), ..} if c == $value => {}
779 m => panic!("unexpected match {:?}", m),
780 }
781 $(assert_alias_matches!(@next $lookup, $($tail)*);)?
782 };
783
784 (@next $lookup:ident,) => {};
786 }
787
788 impl<'a> AliasTemplate for &'a str {
789 type Output<'b>
790 = Pattern
791 where
792 Self: 'b;
793
794 fn replace(&self, capture: &Pattern) -> Self::Output<'a> {
795 capture.spread_into_star(self)
796 }
797
798 fn convert(&self) -> Self::Output<'a> {
799 Pattern::Constant(self.to_string().into())
800 }
801 }
802
803 #[test]
804 fn test_one_exact() {
805 let mut map = AliasMap::new();
806 map.insert(AliasPattern::parse("foo"), "bar");
807
808 assert_alias_matches!(map, "");
809 assert_alias_matches!(map, "foo", exact("bar"));
810 assert_alias_matches!(map, "foobar");
811 }
812
813 #[test]
814 fn test_many_exact() {
815 let mut map = AliasMap::new();
816 map.insert(AliasPattern::parse("foo"), "bar");
817 map.insert(AliasPattern::parse("bar"), "foo");
818 map.insert(AliasPattern::parse("foobar"), "barfoo");
819
820 assert_alias_matches!(map, "");
821 assert_alias_matches!(map, "foo", exact("bar"));
822 assert_alias_matches!(map, "bar", exact("foo"));
823 assert_alias_matches!(map, "foobar", exact("barfoo"));
824 }
825
826 #[test]
827 fn test_empty() {
828 let mut map = AliasMap::new();
829 map.insert(AliasPattern::parse(""), "empty");
830 map.insert(AliasPattern::parse("foo"), "bar");
831
832 assert_alias_matches!(map, "", exact("empty"));
833 assert_alias_matches!(map, "foo", exact("bar"));
834 }
835
836 #[test]
837 fn test_left_wildcard() {
838 let mut map = AliasMap::new();
839 map.insert(AliasPattern::parse("foo*"), "bar");
840
841 assert_alias_matches!(map, "");
842 assert_alias_matches!(map, "foo", replaced("bar"));
843 assert_alias_matches!(map, "foobar", replaced("bar"));
844 }
845
846 #[test]
847 fn test_wildcard_replace_suffix() {
848 let mut map = AliasMap::new();
849 map.insert(AliasPattern::parse("foo*"), "bar*");
850 map.insert(AliasPattern::parse("foofoo*"), "barbar*");
851
852 assert_alias_matches!(map, "");
853 assert_alias_matches!(map, "foo", replaced_owned("bar"));
854 assert_alias_matches!(map, "foobar", replaced_owned("barbar"));
855 assert_alias_matches!(
856 map,
857 "foofoobar",
858 replaced_owned("barbarbar"),
860 replaced_owned("barfoobar"),
861 );
862 }
863
864 #[test]
865 fn test_wildcard_replace_prefix() {
866 let mut map = AliasMap::new();
867 map.insert(AliasPattern::parse("*foo"), "*bar");
868 map.insert(AliasPattern::parse("*foofoo"), "*barbar");
869
870 assert_alias_matches!(map, "");
871 assert_alias_matches!(map, "foo", replaced_owned("bar"));
872 assert_alias_matches!(map, "barfoo", replaced_owned("barbar"));
873 assert_alias_matches!(
874 map,
875 "barfoofoo",
876 replaced_owned("barbarbar"),
878 replaced_owned("barfoobar"),
879 );
880 }
881
882 #[test]
883 fn test_wildcard_replace_infix() {
884 let mut map = AliasMap::new();
885 map.insert(AliasPattern::parse("foo*foo"), "bar*bar");
886 map.insert(AliasPattern::parse("foo*foofoo"), "bar*barbar");
887 map.insert(AliasPattern::parse("foofoo*foo"), "bazbaz*baz");
888
889 assert_alias_matches!(map, "");
890 assert_alias_matches!(map, "foo");
891 assert_alias_matches!(map, "foofoo", replaced_owned("barbar"));
892 assert_alias_matches!(map, "foobazfoo", replaced_owned("barbazbar"));
893 assert_alias_matches!(
894 map,
895 "foofoofoo",
896 replaced_owned("bazbazbaz"),
898 replaced_owned("barbarbar"),
900 replaced_owned("barfoobar"),
901 );
902 assert_alias_matches!(
903 map,
904 "foobazfoofoo",
905 replaced_owned("barbazbarbar"),
907 replaced_owned("barbazfoobar"),
908 );
909 assert_alias_matches!(
910 map,
911 "foofoobarfoo",
912 replaced_owned("bazbazbarbaz"),
914 replaced_owned("barfoobarbar"),
915 );
916 assert_alias_matches!(
917 map,
918 "foofoofoofoofoo",
919 replaced_owned("bazbazfoofoobaz"),
921 replaced_owned("barfoofoobarbar"),
923 replaced_owned("barfoofoofoobar"),
924 );
925 }
926
927 #[test]
928 fn test_wildcard_replace_only() {
929 let mut map = AliasMap::new();
930 map.insert(AliasPattern::parse("*"), "foo*foo");
931 map.insert(AliasPattern::parse("**"), "bar*foo");
932
933 assert_alias_matches!(map, "", replaced_owned("foofoo"));
934 assert_alias_matches!(map, "bar", replaced_owned("foobarfoo"));
935 assert_alias_matches!(
936 map,
937 "*",
938 replaced_owned("barfoo"),
939 replaced_owned("foo*foo"),
940 );
941 assert_alias_matches!(
942 map,
943 "**",
944 replaced_owned("bar*foo"),
945 replaced_owned("foo**foo")
946 );
947 }
948
949 #[test]
950 fn test_pattern() {
951 let mut map = AliasMap::new();
952 map.insert(AliasPattern::parse("card/*"), "src/cards/*");
953 map.insert(AliasPattern::parse("comp/*/x"), "src/comps/*/x");
954 map.insert(AliasPattern::parse("head/*/x"), "src/heads/*");
955
956 assert_eq!(
957 map.lookup(&Pattern::Concatenation(vec![
958 Pattern::Constant(rcstr!("card/")),
959 Pattern::Dynamic
960 ]))
961 .collect::<Result<Vec<_>>>()
962 .unwrap(),
963 vec![super::AliasMatch {
964 prefix: "card/".into(),
965 key: &super::AliasKey::Wildcard { suffix: rcstr!("") },
966 output: Pattern::Concatenation(vec![
967 Pattern::Constant(rcstr!("src/cards/")),
968 Pattern::Dynamic
969 ]),
970 }]
971 );
972 assert_eq!(
973 map.lookup(&Pattern::Concatenation(vec![
974 Pattern::Constant(rcstr!("comp/")),
975 Pattern::Dynamic,
976 Pattern::Constant(rcstr!("/x")),
977 ]))
978 .collect::<Result<Vec<_>>>()
979 .unwrap(),
980 vec![super::AliasMatch {
981 prefix: "comp/".into(),
982 key: &super::AliasKey::Wildcard {
983 suffix: rcstr!("/x")
984 },
985 output: Pattern::Concatenation(vec![
986 Pattern::Constant(rcstr!("src/comps/")),
987 Pattern::Dynamic,
988 Pattern::Constant(rcstr!("/x")),
989 ]),
990 }]
991 );
992 assert_eq!(
993 map.lookup(&Pattern::Concatenation(vec![
994 Pattern::Constant(rcstr!("head/")),
995 Pattern::Dynamic,
996 Pattern::Constant(rcstr!("/x")),
997 ]))
998 .collect::<Result<Vec<_>>>()
999 .unwrap(),
1000 vec![super::AliasMatch {
1001 prefix: "head/".into(),
1002 key: &super::AliasKey::Wildcard {
1003 suffix: rcstr!("/x")
1004 },
1005 output: Pattern::Concatenation(vec![
1006 Pattern::Constant(rcstr!("src/heads/")),
1007 Pattern::Dynamic,
1008 ]),
1009 }]
1010 );
1011 }
1012
1013 #[test]
1014 fn test_pattern_very_dynamic() {
1015 let mut map = AliasMap::new();
1016 map.insert(AliasPattern::parse("bar-a"), "src/bar/a");
1018 map.insert(AliasPattern::parse("bar-b"), "src/bar/b");
1019
1020 assert_eq!(
1039 map.lookup(&Pattern::Concatenation(vec![
1040 Pattern::Dynamic,
1041 Pattern::Constant(rcstr!("bar-a")),
1042 ]))
1043 .collect::<Result<Vec<_>>>()
1044 .unwrap(),
1045 vec![super::AliasMatch {
1046 prefix: "bar-a".into(),
1047 key: &AliasKey::Exact,
1048 output: Pattern::Constant(rcstr!("src/bar/a"))
1049 }]
1050 );
1051 assert_eq!(
1052 map.lookup(&Pattern::Concatenation(vec![
1053 Pattern::Constant(rcstr!("bar-")),
1054 Pattern::Dynamic,
1055 ]))
1056 .collect::<Result<Vec<_>>>()
1057 .unwrap(),
1058 vec![
1059 super::AliasMatch {
1060 prefix: "bar-b".into(),
1061 key: &AliasKey::Exact,
1062 output: Pattern::Constant(rcstr!("src/bar/b"))
1063 },
1064 super::AliasMatch {
1065 prefix: "bar-a".into(),
1066 key: &AliasKey::Exact,
1067 output: Pattern::Constant(rcstr!("src/bar/a"))
1068 }
1069 ]
1070 );
1071 }
1072}