• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 9357137046

03 Jun 2024 08:51PM UTC coverage: 75.121% (-1.1%) from 76.254%
9357137046

push

github

web-flow
Switch locid Value to use Subtag (#4941)

This is part of #1833 switching Value API to use Subtag.

61 of 71 new or added lines in 11 files covered. (85.92%)

3224 existing lines in 178 files now uncovered.

52958 of 70497 relevant lines covered (75.12%)

572757.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.38
/utils/pattern/src/double.rs
1
// This file is part of ICU4X. For terms of use, please see the file
4✔
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4

5
//! Code for the [`DoublePlaceholder`] pattern backend.
6

7
use core::convert::Infallible;
8
use core::{cmp::Ordering, str::FromStr};
9
use either::Either;
10
use writeable::adapters::WriteableAsTryWriteableInfallible;
11
use writeable::Writeable;
12

13
use crate::common::*;
14
use crate::Error;
15

16
#[cfg(feature = "alloc")]
17
use alloc::string::String;
18

19
/// A two-value enum for the [`DoublePlaceholder`] pattern backend.
20
///
21
/// # Examples
22
///
23
/// ```
24
/// use core::cmp::Ordering;
25
/// use core::str::FromStr;
26
/// use icu_pattern::DoublePlaceholderKey;
27
/// use icu_pattern::DoublePlaceholderPattern;
28
/// use icu_pattern::PatternItem;
29
///
30
/// // Parse the string syntax and check the resulting data store:
31
/// let pattern =
32
///     DoublePlaceholderPattern::from_str("Hello, {0} and {1}!").unwrap();
33
///
34
/// assert_eq!(
35
///     pattern.iter().cmp(
36
///         [
37
///             PatternItem::Literal("Hello, "),
38
///             PatternItem::Placeholder(DoublePlaceholderKey::Place0),
39
///             PatternItem::Literal(" and "),
40
///             PatternItem::Placeholder(DoublePlaceholderKey::Place1),
41
///             PatternItem::Literal("!")
42
///         ]
43
///         .into_iter()
44
///     ),
45
///     Ordering::Equal
46
/// );
47
/// ```
48
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
269✔
49
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
×
50
#[allow(clippy::exhaustive_enums)] // Defined to have two entries
51
pub enum DoublePlaceholderKey {
×
52
    /// The placeholder `{0}`.
53
    Place0,
54
    /// The placeholder `{1}`.
55
    Place1,
56
}
57

58
impl FromStr for DoublePlaceholderKey {
59
    type Err = Error;
60
    fn from_str(s: &str) -> Result<Self, Self::Err> {
158✔
61
        match s {
62
            "0" => Ok(Self::Place0),
158✔
63
            "1" => Ok(Self::Place1),
79✔
64
            _ => Err(Error::InvalidPlaceholder),
×
65
        }
66
    }
158✔
67
}
68

69
impl<W0, W1> PlaceholderValueProvider<DoublePlaceholderKey> for (W0, W1)
70
where
71
    W0: Writeable,
72
    W1: Writeable,
73
{
74
    type Error = Infallible;
75
    type W<'a> = WriteableAsTryWriteableInfallible<Either<&'a W0, &'a W1>> where W0: 'a, W1: 'a;
76
    const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
77
    #[inline]
78
    fn value_for(&self, key: DoublePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
10✔
79
        let writeable = match key {
10✔
80
            DoublePlaceholderKey::Place0 => Either::Left(&self.0),
5✔
81
            DoublePlaceholderKey::Place1 => Either::Right(&self.1),
5✔
82
        };
83
        (
10✔
84
            WriteableAsTryWriteableInfallible(writeable),
10✔
85
            crate::PATTERN_PLACEHOLDER_PART,
86
        )
87
    }
10✔
88
}
89

90
impl<W> PlaceholderValueProvider<DoublePlaceholderKey> for [W; 2]
91
where
92
    W: Writeable,
93
{
94
    type Error = Infallible;
95
    type W<'a> = WriteableAsTryWriteableInfallible<&'a W> where W: 'a;
96
    const LITERAL_PART: writeable::Part = crate::PATTERN_LITERAL_PART;
97
    #[inline]
98
    fn value_for(&self, key: DoublePlaceholderKey) -> (Self::W<'_>, writeable::Part) {
42✔
99
        let [item0, item1] = self;
42✔
100
        let writeable = match key {
42✔
101
            DoublePlaceholderKey::Place0 => item0,
21✔
102
            DoublePlaceholderKey::Place1 => item1,
21✔
103
        };
104
        (
42✔
105
            WriteableAsTryWriteableInfallible(writeable),
42✔
106
            crate::PATTERN_PLACEHOLDER_PART,
107
        )
108
    }
42✔
109
}
110

111
/// Internal representation of a placeholder
112
#[derive(Debug, Copy, Clone)]
×
113
struct DoublePlaceholderInfo {
114
    /// The placeholder key: 0 or 1
115
    pub key: DoublePlaceholderKey,
×
116
    /// An offset field. This can take one of two forms:
117
    /// - Encoded form: 1 + offset from start of literals
118
    /// - Decoded form: offset from start of store
119
    pub offset: usize,
×
120
}
121

122
impl DoublePlaceholderInfo {
123
    pub fn from_char(ch: char) -> Self {
446✔
124
        Self {
446✔
125
            key: if ((ch as usize) & 0x1) == 0 {
446✔
126
                DoublePlaceholderKey::Place0
224✔
127
            } else {
128
                DoublePlaceholderKey::Place1
222✔
129
            },
130
            offset: (ch as usize) >> 1,
446✔
131
        }
132
    }
446✔
133
    #[cfg(feature = "alloc")]
134
    pub fn try_to_char(self) -> Result<char, Error> {
224✔
135
        let usize_val = match self.key {
448✔
136
            DoublePlaceholderKey::Place0 => 0,
112✔
137
            DoublePlaceholderKey::Place1 => 1,
112✔
138
        } | (self.offset << 1);
224✔
139
        u32::try_from(usize_val)
448✔
140
            .ok()
141
            .and_then(|x| char::try_from(x).ok())
223✔
142
            .ok_or(Error::InvalidPattern)
224✔
143
    }
224✔
144
    /// Creates a PlaceholderInfo for an empty Place0
145
    pub fn no_place0() -> Self {
4✔
146
        Self {
4✔
147
            key: DoublePlaceholderKey::Place0,
4✔
148
            offset: 0,
149
        }
150
    }
4✔
151
    /// Changes Place0 to Place1 and vice-versa
152
    pub fn swap(self) -> Self {
12✔
153
        Self {
12✔
154
            key: match self.key {
12✔
155
                DoublePlaceholderKey::Place0 => DoublePlaceholderKey::Place1,
7✔
156
                DoublePlaceholderKey::Place1 => DoublePlaceholderKey::Place0,
5✔
157
            },
158
            offset: self.offset,
12✔
159
        }
160
    }
12✔
161
    /// Sets the offset to 0 (ignored placeholder), retaining the key
162
    #[cfg(feature = "alloc")]
163
    pub fn clear(self) -> Self {
10✔
164
        Self {
10✔
165
            key: self.key,
166
            offset: 0,
167
        }
168
    }
10✔
169
}
170

171
/// Backend for patterns containing zero, one, or two placeholders.
172
///
173
/// This empty type is not constructible.
174
///
175
/// # Placeholder Keys
176
///
177
/// The placeholder is either [`DoublePlaceholderKey::Place0`] or [`DoublePlaceholderKey::Place1`].
178
///
179
/// In [`Pattern::interpolate()`], pass a two-element array or tuple.
180
///
181
/// # Encoding Details
182
///
183
/// The first two code points contain the indices of the first and second placeholders with
184
/// the following encoding:
185
///
186
/// 1. First bit: 0 for [`DoublePlaceholderKey::Place0`], 1 for [`DoublePlaceholderKey::Place1`].
187
/// 2. Second and higher bits: 1 plus the byte offset of the placeholder counting from after
188
///    the placeholder index code points. If zero, skip this placeholder.
189
///
190
/// # Examples
191
///
192
/// Parsing a pattern into the encoding:
193
///
194
/// ```
195
/// use core::str::FromStr;
196
/// use icu_pattern::DoublePlaceholder;
197
/// use icu_pattern::Pattern;
198
///
199
/// // Parse the string syntax and check the resulting data store:
200
/// let store =
201
///     Pattern::<DoublePlaceholder, _>::from_str("Hello, {0} and {1}!")
202
///         .unwrap()
203
///         .take_store();
204
///
205
/// assert_eq!("\x10\x1BHello,  and !", store);
206
/// ```
207
///
208
/// Explanation of the lead code points:
209
///
210
/// 1. `\x10` is placeholder 0 at index 7: ((7 + 1) << 1) | 0
211
/// 2. `\x1B` is placeholder 1 at index 12: ((12 + 1) << 1) | 1
212
///
213
/// Example patterns supported by this backend:
214
///
215
/// ```
216
/// use core::str::FromStr;
217
/// use icu_pattern::DoublePlaceholder;
218
/// use icu_pattern::Pattern;
219
///
220
/// // Single numeric placeholder (note, "5" is used):
221
/// assert_eq!(
222
///     Pattern::<DoublePlaceholder, _>::from_str("{0} days ago")
223
///         .unwrap()
224
///         .interpolate_to_string([5, 7]),
225
///     "5 days ago",
226
/// );
227
///
228
/// // No placeholder (note, the placeholder value is never accessed):
229
/// assert_eq!(
230
///     Pattern::<DoublePlaceholder, _>::from_str("yesterday")
231
///         .unwrap()
232
///         .interpolate_to_string(["foo", "bar"]),
233
///     "yesterday",
234
/// );
235
///
236
/// // Escaped placeholder and a placeholder value 1 (note, "bar" is used):
237
/// assert_eq!(
238
///     Pattern::<DoublePlaceholder, _>::from_str("'{0}' {1}")
239
///         .unwrap()
240
///         .interpolate_to_string(("foo", "bar")),
241
///     "{0} bar",
242
/// );
243
///
244
/// // Pattern with the placeholders in the opposite order:
245
/// assert_eq!(
246
///     Pattern::<DoublePlaceholder, _>::from_str("A {1} B {0} C")
247
///         .unwrap()
248
///         .interpolate_to_string(("D", "E")),
249
///     "A E B D C",
250
/// );
251
///
252
/// // No literals, only placeholders:
253
/// assert_eq!(
254
///     Pattern::<DoublePlaceholder, _>::from_str("{1}{0}")
255
///         .unwrap()
256
///         .interpolate_to_string((1, "A")),
257
///     "A1",
258
/// );
259
/// ```
260
///
261
/// [`Pattern::interpolate()`]: crate::Pattern::interpolate
262
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
×
263
#[allow(clippy::exhaustive_enums)] // Empty Enum
264
pub enum DoublePlaceholder {}
265

266
impl crate::private::Sealed for DoublePlaceholder {}
267

268
impl PatternBackend for DoublePlaceholder {
269
    type PlaceholderKey<'a> = DoublePlaceholderKey;
270
    #[cfg(feature = "alloc")]
271
    type PlaceholderKeyCow<'a> = DoublePlaceholderKey;
272
    type Error<'a> = Infallible;
273
    type Store = str;
274
    type Iter<'a> = DoublePlaceholderPatternIterator<'a>;
275

276
    fn validate_store(store: &Self::Store) -> Result<(), Error> {
138✔
277
        let mut chars = store.chars();
138✔
278
        let ph_first_char = chars.next().ok_or(Error::InvalidPattern)?;
138✔
279
        let ph_second_char = chars.next().ok_or(Error::InvalidPattern)?;
137✔
280
        let initial_offset = ph_first_char.len_utf8() + ph_second_char.len_utf8();
136✔
281
        let ph_first = DoublePlaceholderInfo::from_char(ph_first_char);
136✔
282
        let ph_second = DoublePlaceholderInfo::from_char(ph_second_char);
136✔
283
        if ph_first.key == ph_second.key {
136✔
284
            return Err(Error::InvalidPattern);
1✔
285
        }
286
        if ph_first.offset > ph_second.offset && ph_second.offset > 0 {
135✔
287
            return Err(Error::InvalidPattern);
1✔
288
        }
289
        if ph_second.offset > store.len() - initial_offset + 1 {
134✔
290
            return Err(Error::InvalidPattern);
1✔
291
        }
292
        if (ph_second_char as usize) >= 0xD800 {
133✔
293
            return Err(Error::InvalidPattern);
1✔
294
        }
295
        Ok(())
132✔
296
    }
138✔
297

298
    fn iter_items(store: &Self::Store) -> Self::Iter<'_> {
43✔
299
        let mut chars = store.chars();
43✔
300
        let (mut ph_first, ph_first_len) = match chars.next() {
86✔
301
            Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
43✔
302
            None => {
303
                debug_assert!(false);
×
UNCOV
304
                (DoublePlaceholderInfo::no_place0(), 0)
×
305
            }
306
        };
307
        let (mut ph_second, ph_second_len) = match chars.next() {
86✔
308
            Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
43✔
309
            None => {
310
                debug_assert!(false);
×
UNCOV
311
                (ph_first.swap(), ph_first_len)
×
312
            }
313
        };
314
        let initial_offset = ph_first_len + ph_second_len;
43✔
315
        ph_first.offset += initial_offset - 1;
43✔
316
        ph_second.offset += initial_offset - 1;
43✔
317
        DoublePlaceholderPatternIterator {
43✔
318
            store,
319
            ph_first,
43✔
320
            ph_second,
43✔
321
            current_offset: initial_offset,
322
        }
323
    }
43✔
324

325
    #[cfg(feature = "alloc")]
326
    fn try_from_items<
103✔
327
        'cow,
328
        'ph,
329
        I: Iterator<Item = Result<PatternItemCow<'cow, Self::PlaceholderKey<'ph>>, Error>>,
330
    >(
331
        items: I,
332
    ) -> Result<String, Error> {
333
        let mut result = String::new();
457✔
334
        let mut first_ph = None;
580✔
335
        let mut second_ph = None;
580✔
336
        for item in items {
478✔
337
            match item? {
359✔
338
                PatternItemCow::Literal(s) => result.push_str(&s),
164✔
339
                PatternItemCow::Placeholder(ph_key) => {
198✔
340
                    if second_ph.is_some() {
198✔
341
                        return Err(Error::InvalidPattern);
×
342
                    }
343
                    let placeholder_offset = result.len() + 1;
198✔
344
                    if placeholder_offset >= 0xD800 {
198✔
345
                        return Err(Error::InvalidPattern);
×
346
                    }
347
                    let ph_info = DoublePlaceholderInfo {
198✔
348
                        key: ph_key,
349
                        offset: placeholder_offset,
350
                    };
351
                    if first_ph.is_none() {
198✔
352
                        first_ph.replace(ph_info);
103✔
353
                    } else {
354
                        second_ph.replace(ph_info);
95✔
355
                    }
356
                }
357
            }
358
        }
359
        let (first_ph, second_ph) = match (first_ph, second_ph) {
206✔
360
            (Some(a), Some(b)) => (a, b),
94✔
361
            (Some(a), None) => (a, a.swap().clear()),
8✔
362
            (None, None) => (
1✔
363
                DoublePlaceholderInfo::no_place0(),
1✔
364
                DoublePlaceholderInfo::no_place0().swap(),
1✔
365
            ),
1✔
366
            (None, Some(_)) => unreachable!("first_ph always populated before second_ph"),
×
367
        };
368
        if first_ph.key == second_ph.key {
103✔
369
            return Err(Error::InvalidPattern);
×
370
        }
371

372
        result.insert(0, second_ph.try_to_char()?);
104✔
373
        result.insert(0, first_ph.try_to_char()?);
104✔
374

375
        Ok(result)
104✔
376
    }
254✔
377
}
378

379
#[doc(hidden)] // TODO(#4467): Should be internal
380
#[derive(Debug)]
×
381
pub struct DoublePlaceholderPatternIterator<'a> {
382
    store: &'a str,
383
    ph_first: DoublePlaceholderInfo,
×
384
    ph_second: DoublePlaceholderInfo,
×
385
    current_offset: usize,
×
386
}
387

388
// Note: This impl is not exported via public bounds, but it might be in the
389
// future, and the compiler might be able to find it. The code is also
390
// reachable from `Iterator::size_hint`.
391
impl ExactSizeIterator for DoublePlaceholderPatternIterator<'_> {
392
    fn len(&self) -> usize {
42✔
393
        let mut chars = self.store.chars();
42✔
394
        let (mut ph_first, ph_first_len) = match chars.next() {
84✔
395
            Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
42✔
396
            None => {
397
                debug_assert!(false);
×
UNCOV
398
                (DoublePlaceholderInfo::no_place0(), 0)
×
399
            }
400
        };
401
        let (mut ph_second, ph_second_len) = match chars.next() {
84✔
402
            Some(ch) => (DoublePlaceholderInfo::from_char(ch), ch.len_utf8()),
42✔
403
            None => {
404
                debug_assert!(false);
×
UNCOV
405
                (ph_first.swap(), ph_first_len)
×
406
            }
407
        };
408
        let initial_offset = ph_first_len + ph_second_len;
42✔
409
        ph_first.offset += initial_offset - 1;
42✔
410
        ph_second.offset += initial_offset - 1;
42✔
411
        let store_len = self.store.len();
42✔
412

413
        #[allow(clippy::comparison_chain)]
414
        if ph_first.offset < initial_offset {
42✔
415
            // No placeholders
416
            if initial_offset < store_len {
2✔
417
                // No placeholder, non-empty literal
418
                1
1✔
419
            } else {
420
                // No placeholder, empty literal
421
                0
1✔
422
            }
423
        } else if ph_first.offset == initial_offset {
40✔
424
            // At least 1 placeholder, empty prefix
425
            if ph_second.offset < initial_offset {
18✔
426
                // Single placeholder
427
                if ph_first.offset < store_len {
5✔
428
                    // Single placeholder, empty prefix, non-empty suffix
429
                    2
3✔
430
                } else {
431
                    // Single placeholder, empty prefix, empty suffix
432
                    1
2✔
433
                }
434
            } else if ph_second.offset == ph_first.offset {
13✔
435
                // Two placeholders, empty infix
436
                if ph_first.offset < store_len {
7✔
437
                    // Two placeholders, empty prefix, empty infix, non-empty suffix
438
                    3
2✔
439
                } else {
440
                    // Two placeholders, empty prefix, empty infix, empty suffix
441
                    2
5✔
442
                }
443
            } else if ph_second.offset < store_len {
6✔
444
                // Two placeholders, empty prefix, non-empty infix, non-empty suffix
445
                4
2✔
446
            } else {
447
                // Two placeholders, empty prefix, non-empty infix, empty suffix
448
                3
4✔
449
            }
450
        } else {
451
            // At least 1 placeholder, non-empty prefix
452
            if ph_second.offset < initial_offset {
22✔
453
                // Single placeholder
454
                if ph_first.offset < store_len {
5✔
455
                    // Single placeholder, non-empty prefix, non-empty suffix
456
                    3
2✔
457
                } else {
458
                    // Single placeholder, non-empty prefix, empty suffix
459
                    2
3✔
460
                }
461
            } else if ph_second.offset == ph_first.offset {
17✔
462
                // Two placeholders, empty infix
463
                if ph_first.offset < store_len {
4✔
464
                    // Two placeholders, non-empty prefix, empty infix, non-empty suffix
465
                    4
2✔
466
                } else {
467
                    // Two placeholders, non-empty prefix, empty infix, empty suffix
468
                    3
2✔
469
                }
470
            } else if ph_second.offset < store_len {
13✔
471
                // Two placeholders, non-empty prefix, non-empty infix, non-empty suffix
472
                5
10✔
473
            } else {
474
                // Two placeholders, non-empty prefix, non-empty infix, empty suffix
475
                4
3✔
476
            }
477
        }
478
    }
42✔
479
}
480

481
impl<'a> Iterator for DoublePlaceholderPatternIterator<'a> {
482
    type Item = PatternItem<'a, DoublePlaceholderKey>;
483
    fn next(&mut self) -> Option<Self::Item> {
181✔
484
        match self.current_offset.cmp(&self.ph_first.offset) {
181✔
485
            Ordering::Less => {
486
                // Prefix
487
                let literal_str = match self.store.get(self.current_offset..self.ph_first.offset) {
23✔
488
                    Some(s) => s,
23✔
489
                    None => {
490
                        debug_assert!(false, "offsets are in range");
×
UNCOV
491
                        ""
×
UNCOV
492
                    }
×
493
                };
494
                self.current_offset = self.ph_first.offset;
23✔
495
                Some(PatternItem::Literal(literal_str))
23✔
496
            }
23✔
497
            Ordering::Equal => {
498
                // Placeholder
499
                self.ph_first.offset = 0;
41✔
500
                Some(PatternItem::Placeholder(self.ph_first.key))
41✔
501
            }
502
            Ordering::Greater => match self.current_offset.cmp(&self.ph_second.offset) {
117✔
503
                Ordering::Less => {
504
                    // Infix
505
                    let literal_str =
506
                        match self.store.get(self.current_offset..self.ph_second.offset) {
20✔
507
                            Some(s) => s,
20✔
508
                            None => {
509
                                debug_assert!(false, "offsets are in range");
×
UNCOV
510
                                ""
×
UNCOV
511
                            }
×
512
                        };
513
                    self.current_offset = self.ph_second.offset;
20✔
514
                    Some(PatternItem::Literal(literal_str))
20✔
515
                }
20✔
516
                Ordering::Equal => {
517
                    // Placeholder
518
                    self.ph_second.offset = 0;
31✔
519
                    Some(PatternItem::Placeholder(self.ph_second.key))
31✔
520
                }
521
                Ordering::Greater => {
522
                    // Suffix or end of string
523
                    let literal_str = match self.store.get(self.current_offset..) {
66✔
524
                        Some(s) => s,
66✔
525
                        None => {
526
                            debug_assert!(false, "offsets are in range");
×
UNCOV
527
                            ""
×
UNCOV
528
                        }
×
529
                    };
530
                    if literal_str.is_empty() {
66✔
531
                        // End of string
532
                        None
43✔
533
                    } else {
534
                        // Suffix
535
                        self.current_offset = self.store.len();
23✔
536
                        Some(PatternItem::Literal(literal_str))
23✔
537
                    }
538
                }
539
            },
540
        }
541
    }
181✔
542

543
    fn size_hint(&self) -> (usize, Option<usize>) {
42✔
544
        let len = self.len();
42✔
545
        (len, Some(len))
42✔
546
    }
42✔
547
}
548

549
#[cfg(test)]
550
mod tests {
551
    use super::*;
552
    use crate::DoublePlaceholderPattern;
553

554
    #[test]
555
    fn test_permutations() {
2✔
556
        #[derive(Debug)]
×
557
        struct TestCase<'a> {
558
            pattern: &'a str,
559
            store: &'a str,
×
560
            /// Interpolation with 0=apple, 1=orange
561
            interpolated: &'a str,
×
562
        }
563
        let cases = [
2✔
564
            TestCase {
1✔
565
                pattern: "",
566
                store: "\x00\x01",
567
                interpolated: "",
568
            },
569
            TestCase {
1✔
570
                pattern: "{0}",
571
                store: "\x02\x01",
572
                interpolated: "apple",
573
            },
574
            TestCase {
1✔
575
                pattern: "X{0}",
576
                store: "\x04\x01X",
577
                interpolated: "Xapple",
578
            },
579
            TestCase {
1✔
580
                pattern: "{0}Y",
581
                store: "\x02\x01Y",
582
                interpolated: "appleY",
583
            },
584
            TestCase {
1✔
585
                pattern: "X{0}Y",
586
                store: "\x04\x01XY",
587
                interpolated: "XappleY",
588
            },
589
            TestCase {
1✔
590
                pattern: "{1}",
591
                store: "\x03\x00",
592
                interpolated: "orange",
593
            },
594
            TestCase {
1✔
595
                pattern: "X{1}",
596
                store: "\x05\x00X",
597
                interpolated: "Xorange",
598
            },
599
            TestCase {
1✔
600
                pattern: "{1}Y",
601
                store: "\x03\x00Y",
602
                interpolated: "orangeY",
603
            },
604
            TestCase {
1✔
605
                pattern: "X{1}Y",
606
                store: "\x05\x00XY",
607
                interpolated: "XorangeY",
608
            },
609
            TestCase {
1✔
610
                pattern: "{0}{1}",
611
                store: "\x02\x03",
612
                interpolated: "appleorange",
613
            },
614
            TestCase {
1✔
615
                pattern: "X{0}{1}",
616
                store: "\x04\x05X",
617
                interpolated: "Xappleorange",
618
            },
619
            TestCase {
1✔
620
                pattern: "{0}Y{1}",
621
                store: "\x02\x05Y",
622
                interpolated: "appleYorange",
623
            },
624
            TestCase {
1✔
625
                pattern: "{0}{1}Z",
626
                store: "\x02\x03Z",
627
                interpolated: "appleorangeZ",
628
            },
629
            TestCase {
1✔
630
                pattern: "X{0}Y{1}",
631
                store: "\x04\x07XY",
632
                interpolated: "XappleYorange",
633
            },
634
            TestCase {
1✔
635
                pattern: "X{0}{1}Z",
636
                store: "\x04\x05XZ",
637
                interpolated: "XappleorangeZ",
638
            },
639
            TestCase {
1✔
640
                pattern: "{0}Y{1}Z",
641
                store: "\x02\x05YZ",
642
                interpolated: "appleYorangeZ",
643
            },
644
            TestCase {
1✔
645
                pattern: "X{0}Y{1}Z",
646
                store: "\x04\x07XYZ",
647
                interpolated: "XappleYorangeZ",
648
            },
649
            TestCase {
1✔
650
                pattern: "{1}{0}",
651
                store: "\x03\x02",
652
                interpolated: "orangeapple",
653
            },
654
            TestCase {
1✔
655
                pattern: "X{1}{0}",
656
                store: "\x05\x04X",
657
                interpolated: "Xorangeapple",
658
            },
659
            TestCase {
1✔
660
                pattern: "{1}Y{0}",
661
                store: "\x03\x04Y",
662
                interpolated: "orangeYapple",
663
            },
664
            TestCase {
1✔
665
                pattern: "{1}{0}Z",
666
                store: "\x03\x02Z",
667
                interpolated: "orangeappleZ",
668
            },
669
            TestCase {
1✔
670
                pattern: "X{1}Y{0}",
671
                store: "\x05\x06XY",
672
                interpolated: "XorangeYapple",
673
            },
674
            TestCase {
1✔
675
                pattern: "X{1}{0}Z",
676
                store: "\x05\x04XZ",
677
                interpolated: "XorangeappleZ",
678
            },
679
            TestCase {
1✔
680
                pattern: "{1}Y{0}Z",
681
                store: "\x03\x04YZ",
682
                interpolated: "orangeYappleZ",
683
            },
684
            TestCase {
1✔
685
                pattern: "X{1}Y{0}Z",
686
                store: "\x05\x06XYZ",
687
                interpolated: "XorangeYappleZ",
688
            },
689
            TestCase {
1✔
690
                pattern: "01234567890123456789012345678901234567890123456789012345678901234567890123456789{0}Y{1}Z",
691
                store: "\u{A2}\u{A5}01234567890123456789012345678901234567890123456789012345678901234567890123456789YZ",
692
                interpolated: "01234567890123456789012345678901234567890123456789012345678901234567890123456789appleYorangeZ",
693
            },
694
        ];
695
        for cas in cases {
27✔
696
            let TestCase {
697
                pattern,
26✔
698
                store,
26✔
699
                interpolated,
26✔
700
            } = cas;
701
            let parsed = DoublePlaceholderPattern::from_str(pattern).unwrap();
26✔
702
            let actual = parsed
26✔
703
                .interpolate(["apple", "orange"])
26✔
704
                .write_to_string()
705
                .into_owned();
706
            assert_eq!(parsed.take_store(), store, "{cas:?}");
26✔
707
            assert_eq!(actual, interpolated, "{cas:?}");
26✔
708
        }
26✔
709
    }
2✔
710

711
    #[test]
712
    fn test_invalid() {
2✔
713
        let cases = [
1✔
714
            "",               // too short
715
            "\x00",           // too short
716
            "\x00\x00",       // duplicate placeholders
717
            "\x04\x03",       // first offset is after second offset
718
            "\x04\x05",       // second offset out of range (also first offset)
719
            "\x04\u{10001}@", // second offset too large for UTF-8
720
        ];
721
        let long_str = "0123456789".repeat(1000000);
1✔
722
        for cas in cases {
1✔
723
            let cas = cas.replace('@', &long_str);
6✔
724
            assert!(
6✔
725
                DoublePlaceholderPattern::try_from_store(&cas).is_err(),
6✔
726
                "{cas:?}"
727
            );
728
        }
6✔
729
    }
2✔
730
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc