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

zbraniecki / icu4x / 6960837701

22 Nov 2023 02:34PM UTC coverage: 73.023% (+0.4%) from 72.607%
6960837701

push

github

web-flow
Put the units conversion info in a `ZeroTrie` (#4327)

8 of 11 new or added lines in 2 files covered. (72.73%)

1655 existing lines in 63 files now uncovered.

45357 of 62113 relevant lines covered (73.02%)

266109.06 hits per line

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

75.19
/components/casemap/src/provider/exceptions.rs
1
// This file is part of ICU4X. For terms of use, please see the file
1,173✔
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
//! This is the main module pertaining to casemapping exceptions.
6
//!
7
//! A single exception is represented by the [`Exception`] type and its ULE equivalent.
8
//!
9
//! The storage format is complicated (and documented on [`Exception`]), but the data format is
10
//! represented equally by [`DecodedException`], which is more human-readable.
11
use icu_provider::prelude::*;
12

13
use super::data::MappingKind;
14
use super::exception_helpers::{ExceptionBits, ExceptionSlot, SlotPresence};
15
use crate::set::ClosureSink;
16
use alloc::borrow::Cow;
17
use core::fmt;
18
#[cfg(any(feature = "serde", feature = "datagen"))]
19
use core::ops::Range;
20
use core::ptr;
21
use zerovec::ule::AsULE;
22
use zerovec::VarZeroVec;
23

24
const SURROGATES_START: u32 = 0xD800;
25
const SURROGATES_LEN: u32 = 0xDFFF - SURROGATES_START + 1;
26

27
/// This represents case mapping exceptions that can't be represented as a delta applied to
28
/// the original code point. The codepoint
29
/// trie in CaseMapper stores indices into this VarZeroVec.
30
///
31
/// <div class="stab unstable">
32
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
33
/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
34
/// to be stable, their Rust representation might not be. Use with caution.
35
/// </div>
36
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
×
37
#[cfg_attr(
38
    feature = "datagen", 
39
    derive(serde::Serialize, databake::Bake),
×
40
    databake(path = icu_casemap::provider::exceptions),
41
)]
42
#[derive(Debug, Eq, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
×
43
pub struct CaseMapExceptions<'data> {
44
    #[cfg_attr(feature = "serde", serde(borrow))]
45
    /// The list of exceptions
46
    pub exceptions: VarZeroVec<'data, ExceptionULE>,
×
47
}
48

49
impl<'data> CaseMapExceptions<'data> {
50
    /// Obtain the exception at index `idx`. Will
51
    /// return a default value if not present (GIGO behavior),
52
    /// as these indices should come from a paired CaseMapData object
53
    ///
54
    /// Will also panic in debug mode
55
    pub fn get(&self, idx: u16) -> &ExceptionULE {
685✔
56
        let exception = self.exceptions.get(idx.into());
685✔
57
        debug_assert!(exception.is_some());
685✔
58

59
        exception.unwrap_or(ExceptionULE::empty_exception())
685✔
60
    }
685✔
61

62
    #[cfg(any(feature = "serde", feature = "datagen"))]
63
    pub(crate) fn validate(&self) -> Result<Range<u16>, &'static str> {
×
64
        for exception in self.exceptions.iter() {
×
65
            exception.validate()?;
×
66
        }
67
        u16::try_from(self.exceptions.len())
×
68
            .map_err(|_| "Too many exceptions")
×
69
            .map(|l| 0..l)
×
70
    }
×
71
}
72
/// A type representing the wire format of `Exception`. The data contained is
73
/// equivalently represented by [`DecodedException`].
74
///
75
/// This type is itself not used that much, most of its relevant methods live
76
/// on [`ExceptionULE`].
77
///
78
/// The `bits` contain supplementary data, whereas
79
/// `slot_presence` marks te presence of various extra data
80
/// in the `data` field.
81
///
82
/// The `data` field is not validated to contain all of this data,
83
/// this type will have GIGO behavior when constructed with invalid `data`.
84
///
85
/// The format of `data` is documented on the field
86
///
87
/// <div class="stab unstable">
88
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
89
/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
90
/// to be stable, their Rust representation might not be. Use with caution.
91
/// </div>
92
#[zerovec::make_varule(ExceptionULE)]
714✔
93
#[derive(PartialEq, Eq, Clone, Default, Debug)]
×
94
#[zerovec::skip_derive(Ord)]
95
#[cfg_attr(
96
    feature = "serde",
97
    derive(serde::Deserialize),
×
98
    zerovec::derive(Deserialize)
99
)]
100
#[cfg_attr(
101
    feature = "datagen",
102
    derive(serde::Serialize),
×
103
    zerovec::derive(Serialize)
104
)]
105
pub struct Exception<'a> {
106
    /// The various bit based exception data associated with this.
7✔
107
    ///
108
    /// Format: Just a u8 of bitflags, some flags unused. See [`ExceptionBits`] and its ULE type for more.
109
    pub bits: ExceptionBits,
×
110
    /// Which slots are present in `data`.
111
    ///
112
    /// Format: a u8 of bitflags
113
    pub slot_presence: SlotPresence,
×
114
    /// Format : `[char slots] [optional closure length] [ closure slot ] [ full mappings data ]`
115
    ///
116
    /// For each set SlotPresence bit, except for the two stringy slots (Closure/FullMapping),
117
    /// this will have one entry in the string, packed together.
118
    ///
119
    /// Note that the simple_case delta is stored as a u32 normalized to a `char`, where u32s
120
    /// which are from or beyond the surrogate range 0xD800-0xDFFF are stored as chars
121
    /// starting from 0xE000. The sign is stored in bits.negative_delta.
122
    ///
123
    /// If both Closure/FullMapping are present, the next char will be the length of the closure slot,
124
    /// bisecting the rest of the data.
125
    /// If only one is present, the rest of the data represents that slot.
126
    ///
127
    /// The closure slot simply represents one string. The full-mappings slot represents four strings,
128
    /// packed in a way similar to VarZeroVec, in the following format:
129
    /// `i1 i2 i3 [ str0 ] [ str1 ] [ str2 ] [ str3 ]`
130
    ///
131
    /// where `i1 i2 i3` are the indices of the relevant mappings string. The strings are stored in
132
    /// the order corresponding to the MappingKind enum.
133
    pub data: Cow<'a, str>,
×
134
}
135

136
impl ExceptionULE {
137
    #[inline]
138
    fn empty_exception() -> &'static Self {
684✔
139
        static EMPTY_BYTES: &[u8] = &[0, 0];
140
        // Safety:
141
        // ExceptionULE is a packed DST with `(u8, u8, unsized)` fields. All bit patterns are valid for the two u8s
142
        //
143
        // An "empty" one can be constructed from a slice of two u8s
144
        unsafe {
145
            let slice: *const [u8] = ptr::slice_from_raw_parts(EMPTY_BYTES.as_ptr(), 0);
684✔
146
            &*(slice as *const Self)
147
        }
148
    }
684✔
149
    pub(crate) fn has_slot(&self, slot: ExceptionSlot) -> bool {
1✔
150
        self.slot_presence.has_slot(slot)
1✔
151
    }
1✔
152
    /// Obtain a `char` slot, if occupied. If `slot` represents a string slot,
153
    /// will return `None`
154
    pub(crate) fn get_char_slot(&self, slot: ExceptionSlot) -> Option<char> {
952✔
155
        if slot >= ExceptionSlot::STRING_SLOTS_START {
952✔
UNCOV
156
            return None;
×
157
        }
158
        let bit = 1 << (slot as u8);
952✔
159
        // check if slot is occupied
160
        if self.slot_presence.0 & bit == 0 {
952✔
161
            return None;
626✔
162
        }
163

164
        let previous_slot_mask = bit - 1;
326✔
165
        let previous_slots = self.slot_presence.0 & previous_slot_mask;
326✔
166
        let slot_num = previous_slots.count_ones() as usize;
326✔
167
        self.data.chars().nth(slot_num)
326✔
168
    }
952✔
169

170
    /// Get the `simple_case` delta (i.e. the `delta` slot), given the character
171
    /// this data belongs to.
172
    ///
173
    /// Normalizes the delta from char-format to u32 format
174
    ///
175
    /// Does *not* handle the sign of the delta; see self.bits.negative_delta
176
    fn get_simple_case_delta(&self) -> Option<u32> {
317✔
177
        let delta_ch = self.get_char_slot(ExceptionSlot::Delta)?;
317✔
178
        let mut delta = u32::from(delta_ch);
68✔
179
        // We "fill in" the surrogates range by offsetting deltas greater than it
180
        if delta >= SURROGATES_START {
68✔
181
            delta -= SURROGATES_LEN;
1✔
182
        }
183
        Some(delta)
68✔
184
    }
317✔
185

186
    /// Get the `simple_case` value (i.e. the `delta` slot), given the character
187
    /// this data belongs to.
188
    ///
189
    /// The data is stored as a delta so the character must be provided.
190
    ///
191
    /// The data cannot be stored directly as a character because the trie is more
192
    /// compact with adjacent characters sharing deltas.
193
    pub(crate) fn get_simple_case_slot_for(&self, ch: char) -> Option<char> {
310✔
194
        let delta = self.get_simple_case_delta()?;
310✔
195
        let mut delta = i32::try_from(delta).ok()?;
66✔
196
        if self.bits.negative_delta() {
66✔
197
            delta = -delta;
27✔
198
        }
199

200
        let new_ch = i32::try_from(u32::from(ch)).ok()? + delta;
66✔
201

202
        char::try_from(u32::try_from(new_ch).ok()?).ok()
66✔
203
    }
310✔
204

205
    /// Returns *all* the data in the closure/full slots, including length metadata
206
    fn get_stringy_data(&self) -> Option<&str> {
71✔
207
        const CHAR_MASK: u8 = (1 << ExceptionSlot::STRING_SLOTS_START as u8) - 1;
208
        let char_slot_count = (self.slot_presence.0 & CHAR_MASK).count_ones() as usize;
71✔
209
        let mut chars = self.data.chars();
71✔
210
        for _ in 0..char_slot_count {
92✔
211
            let res = chars.next();
21✔
212
            res?;
21✔
213
        }
214
        Some(chars.as_str())
71✔
215
    }
71✔
216

217
    /// Returns a single stringy slot, either ExceptionSlot::Closure
218
    /// or ExceptionSlot::FullMappings.
219
    fn get_stringy_slot(&self, slot: ExceptionSlot) -> Option<&str> {
618✔
220
        debug_assert!(slot == ExceptionSlot::Closure || slot == ExceptionSlot::FullMappings);
618✔
221
        let other_slot = if slot == ExceptionSlot::Closure {
618✔
222
            ExceptionSlot::FullMappings
18✔
223
        } else {
224
            ExceptionSlot::Closure
600✔
225
        };
226
        if !self.slot_presence.has_slot(slot) {
618✔
227
            return None;
547✔
228
        }
229
        let stringy_data = self.get_stringy_data()?;
71✔
230

231
        if self.slot_presence.has_slot(other_slot) {
71✔
232
            // both stringy slots are used, we need a length
233
            let mut chars = stringy_data.chars();
25✔
234
            // GIGO: to have two strings there must be a length, if not present return None
235
            let length_char = chars.next()?;
25✔
236

237
            let length = usize::try_from(u32::from(length_char)).unwrap_or(0);
25✔
238
            // The length indexes into the string after the first char
239
            let remaining_slice = chars.as_str();
25✔
240
            // GIGO: will return none if there wasn't enough space in this slot
241
            if slot == ExceptionSlot::Closure {
25✔
242
                remaining_slice.get(0..length)
5✔
243
            } else {
244
                remaining_slice.get(length..)
20✔
245
            }
246
        } else {
247
            // only a single stringy slot, there is no length stored
248
            Some(stringy_data)
46✔
249
        }
250
    }
618✔
251

252
    /// Get the data behind the `closure` slot
253
    pub(crate) fn get_closure_slot(&self) -> Option<&str> {
18✔
254
        self.get_stringy_slot(ExceptionSlot::Closure)
18✔
255
    }
18✔
256

257
    /// Get all the slot data for the FullMappings slot
258
    ///
259
    /// This needs to be further segmented into four based on length metadata
260
    fn get_fullmappings_slot_data(&self) -> Option<&str> {
600✔
261
        self.get_stringy_slot(ExceptionSlot::FullMappings)
600✔
262
    }
600✔
263

264
    /// Get a specific FullMappings slot value
265
    pub(crate) fn get_fullmappings_slot_for_kind(&self, kind: MappingKind) -> Option<&str> {
600✔
266
        let data = self.get_fullmappings_slot_data()?;
600✔
267

268
        let mut chars = data.chars();
63✔
269
        // GIGO: must have three index strings, else return None
270
        let i1 = usize::try_from(u32::from(chars.next()?)).ok()?;
63✔
271
        let i2 = usize::try_from(u32::from(chars.next()?)).ok()?;
63✔
272
        let i3 = usize::try_from(u32::from(chars.next()?)).ok()?;
63✔
273
        let remaining_slice = chars.as_str();
63✔
274
        // GIGO: if the indices are wrong, return None
275
        match kind {
63✔
276
            MappingKind::Lower => remaining_slice.get(..i1),
17✔
277
            MappingKind::Fold => remaining_slice.get(i1..i2),
16✔
278
            MappingKind::Upper => remaining_slice.get(i2..i3),
23✔
279
            MappingKind::Title => remaining_slice.get(i3..),
7✔
280
        }
281
    }
600✔
282

283
    // convenience function that lets us use the ? operator
284
    fn get_all_fullmapping_slots(&self) -> Option<[Cow<'_, str>; 4]> {
7✔
285
        Some([
3✔
286
            self.get_fullmappings_slot_for_kind(MappingKind::Lower)?
7✔
287
                .into(),
288
            self.get_fullmappings_slot_for_kind(MappingKind::Fold)?
3✔
289
                .into(),
290
            self.get_fullmappings_slot_for_kind(MappingKind::Upper)?
3✔
291
                .into(),
292
            self.get_fullmappings_slot_for_kind(MappingKind::Title)?
3✔
293
                .into(),
UNCOV
294
        ])
×
295
    }
7✔
296

297
    // Given a mapping kind, returns the character for that kind, if it exists. Fold falls
298
    // back to Lower; Title falls back to Upper.
299
    #[inline]
300
    pub(crate) fn slot_char_for_kind(&self, kind: MappingKind) -> Option<char> {
503✔
301
        match kind {
503✔
302
            MappingKind::Lower | MappingKind::Upper => self.get_char_slot(kind.into()),
427✔
303
            MappingKind::Fold => self
106✔
304
                .get_char_slot(ExceptionSlot::Fold)
53✔
305
                .or_else(|| self.get_char_slot(ExceptionSlot::Lower)),
95✔
306
            MappingKind::Title => self
46✔
307
                .get_char_slot(ExceptionSlot::Title)
23✔
308
                .or_else(|| self.get_char_slot(ExceptionSlot::Upper)),
42✔
309
        }
310
    }
503✔
311

UNCOV
312
    pub(crate) fn add_full_and_closure_mappings<S: ClosureSink>(&self, set: &mut S) {
×
UNCOV
313
        if let Some(full) = self.get_fullmappings_slot_for_kind(MappingKind::Fold) {
×
314
            if !full.is_empty() {
×
315
                set.add_string(full);
×
316
            }
317
        };
UNCOV
318
        if let Some(closure) = self.get_closure_slot() {
×
319
            for c in closure.chars() {
×
UNCOV
320
                set.add_char(c);
×
321
            }
322
        };
UNCOV
323
    }
×
324

325
    /// Extract all the data out into a structured form
326
    ///
327
    /// Useful for serialization and debugging
328
    pub fn decode(&self) -> DecodedException<'_> {
7✔
329
        // Potential future optimization: This can
330
        // directly access each bit one after the other and iterate the string
331
        // which avoids recomputing slot offsets over and over again.
332
        //
333
        // If we're doing so we may wish to retain this older impl so that we can still roundtrip test
334
        let bits = self.bits;
7✔
335
        let lowercase = self.get_char_slot(ExceptionSlot::Lower);
7✔
336
        let casefold = self.get_char_slot(ExceptionSlot::Fold);
7✔
337
        let uppercase = self.get_char_slot(ExceptionSlot::Upper);
7✔
338
        let titlecase = self.get_char_slot(ExceptionSlot::Title);
7✔
339
        let simple_case_delta = self.get_simple_case_delta();
7✔
340
        let closure = self.get_closure_slot().map(Into::into);
7✔
341
        let full = self.get_all_fullmapping_slots();
7✔
342

343
        DecodedException {
7✔
344
            bits: ExceptionBits::from_unaligned(bits),
7✔
345
            lowercase,
346
            casefold,
347
            uppercase,
348
            titlecase,
349
            simple_case_delta,
350
            closure,
7✔
351
            full,
7✔
352
        }
353
    }
7✔
354

355
    #[cfg(any(feature = "serde", feature = "datagen"))]
356
    pub(crate) fn validate(&self) -> Result<(), &'static str> {
×
357
        // check that ICU4C specific fields are not set
358
        // check that there is enough space for all the offsets
UNCOV
359
        if self.bits.double_width_slots() {
×
360
            return Err("double-width-slots should not be used in ICU4C");
×
361
        }
362

363
        // just run all of the slot getters at once and then check
364
        let decoded = self.decode();
×
365

366
        for (slot, decoded_slot) in [
×
UNCOV
367
            (ExceptionSlot::Lower, &decoded.lowercase),
×
368
            (ExceptionSlot::Fold, &decoded.casefold),
×
UNCOV
369
            (ExceptionSlot::Upper, &decoded.uppercase),
×
370
            (ExceptionSlot::Title, &decoded.titlecase),
×
371
        ] {
UNCOV
372
            if self.has_slot(slot) && decoded_slot.is_none() {
×
373
                // decoding hit GIGO behavior, oops!
UNCOV
374
                return Err("Slot decoding failed");
×
375
            }
376
        }
UNCOV
377
        if self.has_slot(ExceptionSlot::Delta) && decoded.simple_case_delta.is_none() {
×
378
            // decoding hit GIGO behavior, oops!
379
            return Err("Slot decoding failed");
×
380
        }
381

382
        if self.has_slot(ExceptionSlot::Closure) && decoded.closure.is_none() {
×
383
            return Err("Slot decoding failed");
×
384
        }
385

386
        if self.has_slot(ExceptionSlot::FullMappings) {
×
387
            if decoded.full.is_some() {
×
388
                let data = self
×
389
                    .get_fullmappings_slot_data()
390
                    .ok_or("fullmappings slot doesn't parse")?;
×
UNCOV
391
                let mut chars = data.chars();
×
392
                let i1 = u32::from(chars.next().ok_or("fullmappings string too small")?);
×
393
                let i2 = u32::from(chars.next().ok_or("fullmappings string too small")?);
×
UNCOV
394
                let i3 = u32::from(chars.next().ok_or("fullmappings string too small")?);
×
395

396
                if i2 < i1 || i3 < i2 {
×
UNCOV
397
                    return Err("fullmappings string contains non-sequential indices");
×
398
                }
399
                let rest = chars.as_str();
×
UNCOV
400
                let len = u32::try_from(rest.len()).map_err(|_| "len too large for u32")?;
×
401

402
                if i1 > len || i2 > len || i3 > len {
×
UNCOV
403
                    return Err("fullmappings string contains out-of-bounds indices");
×
404
                }
405
            } else {
406
                return Err("Slot decoding failed");
×
407
            }
408
        }
409

UNCOV
410
        Ok(())
×
411
    }
×
412
}
413

414
impl fmt::Debug for ExceptionULE {
UNCOV
415
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
UNCOV
416
        self.decode().fmt(f)
×
UNCOV
417
    }
×
418
}
419

420
/// A decoded [`Exception`] type, with all of the data parsed out into
421
/// separate fields.
422
///
423
/// <div class="stab unstable">
424
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
425
/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
426
/// to be stable, their Rust representation might not be. Use with caution.
427
/// </div>
UNCOV
428
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
×
429
#[cfg_attr(feature = "datagen", derive(serde::Serialize))]
430
#[derive(Debug, Clone, PartialEq, Eq, Default)]
21✔
431
pub struct DecodedException<'a> {
432
    /// The various bit-based data associated with this exception
433
    pub bits: ExceptionBits,
14✔
434
    /// Lowercase mapping
435
    pub lowercase: Option<char>,
14✔
436
    /// Case folding
437
    pub casefold: Option<char>,
14✔
438
    /// Uppercase mapping
439
    pub uppercase: Option<char>,
14✔
440
    /// Titlecase mapping
441
    pub titlecase: Option<char>,
14✔
442
    /// The simple casefold delta. Its sign is stored in bits.negative_delta
443
    pub simple_case_delta: Option<u32>,
14✔
444
    /// Closure mappings
445
    pub closure: Option<Cow<'a, str>>,
14✔
446
    /// The four full-mappings strings, indexed by MappingKind u8 value
447
    pub full: Option<[Cow<'a, str>; 4]>,
14✔
448
}
449

450
impl<'a> DecodedException<'a> {
451
    /// Convert to a wire-format encodeable (VarULE-encodeable) [`Exception`]
452
    pub fn encode(&self) -> Exception<'static> {
13✔
453
        let bits = self.bits;
13✔
454
        let mut slot_presence = SlotPresence(0);
13✔
455
        let mut data = alloc::string::String::new();
13✔
456
        if let Some(lowercase) = self.lowercase {
13✔
457
            slot_presence.add_slot(ExceptionSlot::Lower);
2✔
458
            data.push(lowercase)
2✔
459
        }
460
        if let Some(casefold) = self.casefold {
13✔
461
            slot_presence.add_slot(ExceptionSlot::Fold);
×
462
            data.push(casefold)
×
463
        }
464
        if let Some(uppercase) = self.uppercase {
13✔
UNCOV
465
            slot_presence.add_slot(ExceptionSlot::Upper);
×
UNCOV
466
            data.push(uppercase)
×
467
        }
468
        if let Some(titlecase) = self.titlecase {
13✔
469
            slot_presence.add_slot(ExceptionSlot::Title);
3✔
470
            data.push(titlecase)
3✔
471
        }
472
        if let Some(mut simple_case_delta) = self.simple_case_delta {
13✔
473
            slot_presence.add_slot(ExceptionSlot::Delta);
2✔
474

475
            if simple_case_delta >= SURROGATES_START {
2✔
476
                simple_case_delta += SURROGATES_LEN;
1✔
477
            }
478
            let simple_case_delta = char::try_from(simple_case_delta).unwrap_or('\0');
2✔
479
            data.push(simple_case_delta)
2✔
480
        }
481

482
        if let Some(ref closure) = self.closure {
13✔
483
            slot_presence.add_slot(ExceptionSlot::Closure);
3✔
484
            if self.full.is_some() {
3✔
485
                // GIGO: if the closure length is more than 0xD800 this will error. Plenty of space.
486
                debug_assert!(
2✔
487
                    closure.len() < 0xD800,
2✔
488
                    "Found overlarge closure value when encoding exception"
489
                );
490
                let len_char = u32::try_from(closure.len())
2✔
491
                    .ok()
492
                    .and_then(|c| char::try_from(c).ok())
2✔
493
                    .unwrap_or('\0');
494
                data.push(len_char);
2✔
495
            }
496
            data.push_str(closure);
3✔
497
        }
498
        if let Some(ref full) = self.full {
13✔
499
            slot_presence.add_slot(ExceptionSlot::FullMappings);
3✔
500
            let mut idx = 0;
3✔
501
            // iterate all elements except the last, whose length we can calculate from context
502
            for mapping in full.iter().take(3) {
12✔
503
                idx += mapping.len();
9✔
504
                data.push(char::try_from(u32::try_from(idx).unwrap_or(0)).unwrap_or('\0'));
9✔
505
            }
506
            for mapping in full {
15✔
507
                data.push_str(mapping);
12✔
508
            }
509
        }
510
        Exception {
7✔
511
            bits,
10✔
512
            slot_presence,
10✔
513
            data: data.into(),
10✔
514
        }
515
    }
7✔
516

517
    // Potential optimization: Write an `EncodeAsVarULE` that
518
    // directly produces an ExceptionULE
519
}
520

521
#[cfg(test)]
522
mod tests {
523
    use super::*;
524

525
    fn test_roundtrip_once(exception: DecodedException) {
7✔
526
        let encoded = exception.encode();
7✔
527
        let encoded = zerovec::ule::encode_varule_to_box(&encoded);
7✔
528
        let decoded = encoded.decode();
7✔
529
        assert_eq!(decoded, exception);
7✔
530
    }
7✔
531

532
    #[test]
533
    fn test_roundtrip() {
2✔
534
        test_roundtrip_once(DecodedException {
1✔
535
            lowercase: Some('ø'),
1✔
536
            ..Default::default()
1✔
537
        });
538
        test_roundtrip_once(DecodedException {
1✔
539
            titlecase: Some('X'),
1✔
540
            lowercase: Some('ø'),
1✔
541
            ..Default::default()
1✔
542
        });
543
        test_roundtrip_once(DecodedException {
1✔
544
            titlecase: Some('X'),
1✔
545
            ..Default::default()
1✔
546
        });
547
        test_roundtrip_once(DecodedException {
1✔
548
            titlecase: Some('X'),
1✔
549
            simple_case_delta: Some(0xE999),
1✔
550
            closure: Some("hello world".into()),
1✔
551
            ..Default::default()
1✔
552
        });
1✔
553
        test_roundtrip_once(DecodedException {
1✔
554
            simple_case_delta: Some(10),
1✔
555
            closure: Some("hello world".into()),
1✔
556
            full: Some(["你好世界".into(), "".into(), "hi".into(), "å".into()]),
1✔
557
            ..Default::default()
1✔
558
        });
1✔
559
        test_roundtrip_once(DecodedException {
1✔
560
            closure: Some("hello world".into()),
1✔
561
            full: Some(["aa".into(), "È›".into(), "".into(), "Ã¥".into()]),
1✔
562
            ..Default::default()
1✔
563
        });
1✔
564
        test_roundtrip_once(DecodedException {
1✔
565
            full: Some(["你好世界".into(), "".into(), "hi".into(), "å".into()]),
1✔
566
            ..Default::default()
1✔
567
        });
1✔
568
    }
2✔
569
}
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

© 2025 Coveralls, Inc