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

zbraniecki / icu4x / 9014530096

08 May 2024 07:27PM UTC coverage: 76.402% (+0.2%) from 76.234%
9014530096

push

github

web-flow
Add missing std pointer-like impls for DataProvider, DynamicDataProvider (#4880)

0 of 3 new or added lines in 1 file covered. (0.0%)

3218 existing lines in 167 files now uncovered.

53328 of 69799 relevant lines covered (76.4%)

504343.42 hits per line

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

88.76
/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 icu_pattern::DoublePlaceholderKey;
26
/// use icu_pattern::DoublePlaceholderPattern;
27
/// use icu_pattern::PatternItem;
28
///
29
/// // Parse the string syntax and check the resulting data store:
30
/// let pattern =
31
///     DoublePlaceholderPattern::try_from_str("Hello, {0} and {1}!").unwrap();
32
///
33
/// assert_eq!(
34
///     pattern.iter().cmp(
35
///         [
36
///             PatternItem::Literal("Hello, "),
37
///             PatternItem::Placeholder(DoublePlaceholderKey::Place0),
38
///             PatternItem::Literal(" and "),
39
///             PatternItem::Placeholder(DoublePlaceholderKey::Place1),
40
///             PatternItem::Literal("!")
41
///         ]
42
///         .into_iter()
43
///     ),
44
///     Ordering::Equal
45
/// );
46
/// ```
47
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
268✔
48
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
×
49
#[allow(clippy::exhaustive_enums)] // Defined to have two entries
UNCOV
50
pub enum DoublePlaceholderKey {
×
51
    /// The placeholder `{0}`.
52
    Place0,
53
    /// The placeholder `{1}`.
54
    Place1,
55
}
56

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

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

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

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

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

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

263
impl crate::private::Sealed for DoublePlaceholder {}
264

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

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

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

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

369
        result.insert(0, second_ph.try_to_char()?);
102✔
370
        result.insert(0, first_ph.try_to_char()?);
102✔
371

372
        Ok(result)
104✔
373
    }
104✔
374
}
375

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

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

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

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

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

546
#[cfg(test)]
547
mod tests {
548
    use super::*;
549
    use crate::DoublePlaceholderPattern;
550

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

708
    #[test]
709
    fn test_invalid() {
2✔
710
        let cases = [
1✔
711
            "",               // too short
712
            "\x00",           // too short
713
            "\x00\x00",       // duplicate placeholders
714
            "\x04\x03",       // first offset is after second offset
715
            "\x04\x05",       // second offset out of range (also first offset)
716
            "\x04\u{10001}@", // second offset too large for UTF-8
717
        ];
718
        let long_str = "0123456789".repeat(1000000);
1✔
719
        for cas in cases {
1✔
720
            let cas = cas.replace('@', &long_str);
6✔
721
            assert!(
6✔
722
                DoublePlaceholderPattern::try_from_store(&cas).is_err(),
6✔
723
                "{cas:?}"
724
            );
725
        }
6✔
726
    }
2✔
727
}
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