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

zbraniecki / icu4x / 11904027177

19 Nov 2024 12:33AM UTC coverage: 75.477% (+0.3%) from 75.174%
11904027177

push

github

web-flow
Move DateTimePattern into pattern module (#5834)

#1317

Also removes `NeoNeverMarker` and fixes #5689

258 of 319 new or added lines in 6 files covered. (80.88%)

6967 existing lines in 278 files now uncovered.

54522 of 72237 relevant lines covered (75.48%)

655305.49 hits per line

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

0.0
/utils/resb/src/binary/serializer.rs
1
// This file is part of ICU4X. For terms of use, please see the file
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
use std::{
6
    cell::RefCell,
7
    cmp::Ordering,
8
    collections::{BTreeMap, HashMap, HashSet},
9
    fmt,
10
    rc::Rc,
11
};
12

13
use crate::bundle::{Key, Resource, ResourceBundle};
14

15
use super::{
16
    header::{BinHeader, BinReprInfo},
17
    BinIndex, CharsetFamily, Endianness, FormatVersion, ResDescriptor, ResourceReprType,
18
};
19

20
const DATA_FORMAT: &[u8; 4] = b"ResB";
21
const DATA_VERSION: &[u8; 4] = &[1, 4, 0, 0];
22
const PADDED_HEADER_SIZE: usize = (core::mem::size_of::<BinHeader>() + 15) & !0xf;
23
const REPR_INFO_SIZE: usize = core::mem::size_of::<BinReprInfo>();
24

25
/// The value of the magic word appearing in the header.
26
///
27
/// All versions as of [`FormatVersion::V3_0`] share the same value.
28
const MAGIC_WORD: &[u8; 2] = &[0xda, 0x27];
29

30
/// The size of the characters used in representations of string resources.
31
const SIZE_OF_STRING_CHAR: u8 = 2;
32

33
/// The endianness of the current system.
34
#[cfg(target_endian = "little")]
35
const SYSTEM_ENDIANNESS: Endianness = Endianness::Little;
36

37
/// The endianness of the current system.
38
#[cfg(target_endian = "big")]
39
const SYSTEM_ENDIANNESS: Endianness = Endianness::Big;
40

41
/// The `StringResourceData` struct encapsulates information necessary for
42
/// building string resource representations.
43
#[derive(Debug)]
×
44
struct StringResourceData<'a> {
45
    /// If this string is a suffix of another string, the full string which
46
    /// contains it. See [`build_string_data`] for more details.
47
    ///
48
    /// [`build_string_data`]: Writer::build_string_data
49
    containing_string: Option<&'a str>,
50

51
    /// The offset of the string within the 16-bit data block.
52
    offset: u32,
×
53

54
    /// The number of copies of this string which appear in the bundle.
55
    copy_count: usize,
×
56

57
    /// The number of characters saved by deduplicating and suffix-sharing this
58
    /// string.
59
    characters_saved: usize,
×
60
}
61

62
/// Data necessary for building resource representations by resource type.
63
#[derive(Debug)]
×
64
enum BinResourceTypeData<'a> {
65
    String {
66
        /// The string represented by the resource.
67
        string: &'a str,
×
68

69
        /// A reference to string resource data which can be shared among string
70
        /// resources with the same represented string.
71
        data: Rc<RefCell<StringResourceData<'a>>>,
×
72
    },
73
    Array {
74
        /// Resource data for all children of the array.
75
        children: Vec<BinResourceData<'a>>,
×
76
    },
77
    Table {
78
        /// Resource data for all entries of the table.
79
        map: BTreeMap<Key<'a>, BinResourceData<'a>>,
×
80
    },
81
    Binary {
82
        /// The binary data represented by the resource.
83
        binary: &'a [u8],
×
84
    },
85
    Integer,
86
    IntVector {
87
        /// The integers contained in the vector.
88
        int_vector: &'a [u32],
×
89
    },
90
    _Alias,
91
}
92

93
impl std::fmt::Display for BinResourceTypeData<'_> {
94
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
95
        // Provide a user-friendly string for the resource type in order to
96
        // create helpful error messages.
97
        write!(
×
98
            f,
99
            "{}",
100
            match self {
×
101
                BinResourceTypeData::String { .. } => "String",
×
102
                BinResourceTypeData::Array { .. } => "Array",
×
103
                BinResourceTypeData::Table { .. } => "Table",
×
104
                BinResourceTypeData::Binary { .. } => "Binary",
×
105
                BinResourceTypeData::Integer => "Integer",
×
106
                BinResourceTypeData::IntVector { .. } => "IntVector",
×
107
                BinResourceTypeData::_Alias => "Alias",
×
108
            }
109
        )
110
    }
×
111
}
112

113
/// The `BinResourceData` struct maintains data necessary for representing a
114
/// resource as part of a binary resource bundle.
115
#[derive(Debug)]
×
116
struct BinResourceData<'a> {
117
    /// The type descriptor for this resource if it has been processed.
118
    descriptor: Option<ResDescriptor>,
×
119

120
    /// Any per-type data necessary for representing this resource.
121
    type_data: BinResourceTypeData<'a>,
×
122
}
123

124
impl<'a> BinResourceData<'a> {
125
    /// Prepares a new data struct for processing a resource.
126
    fn new(type_data: BinResourceTypeData<'a>) -> Self {
×
127
        Self {
×
128
            descriptor: None,
×
129
            type_data,
130
        }
131
    }
×
132
}
133

134
impl<'a> From<&'a Resource<'a>> for BinResourceData<'a> {
135
    fn from(value: &'a Resource<'a>) -> Self {
×
136
        match value {
×
137
            Resource::String(string) => Self::new(BinResourceTypeData::String {
×
138
                string,
×
139
                data: Rc::new(RefCell::new(StringResourceData {
×
140
                    containing_string: None,
×
141
                    offset: 0,
142
                    copy_count: 0,
143
                    characters_saved: 0,
144
                })),
145
            }),
146
            Resource::Array(array) => {
×
147
                // Build resource data structs for each child in the array.
148
                let mut children = Vec::new();
×
149
                for resource in array {
×
150
                    children.push(Self::from(resource));
×
151
                }
152

153
                Self::new(BinResourceTypeData::Array { children })
×
154
            }
×
155
            Resource::Table(table) => {
×
156
                // Build resource data structs for each entry in the table.
157
                let map = table
×
158
                    .iter()
159
                    .map(|(key, resource)| (key.clone(), BinResourceData::from(resource)))
×
160
                    .collect::<BTreeMap<_, _>>();
161

162
                Self::new(BinResourceTypeData::Table { map })
×
163
            }
164
            Resource::Binary(binary) => Self::new(BinResourceTypeData::Binary { binary }),
×
165
            Resource::Integer(integer) => Self {
×
166
                // Integers are stored as part of the resource descriptor, so no
167
                // further processing is needed.
168
                descriptor: Some(ResDescriptor::new(ResourceReprType::Int, (*integer).into())),
×
169
                type_data: BinResourceTypeData::Integer,
×
170
            },
×
171
            Resource::IntVector(int_vector) => {
×
172
                Self::new(BinResourceTypeData::IntVector { int_vector })
×
173
            }
174
        }
175
    }
×
176
}
177

178
/// The `Serializer` type provides a means of generating a vector of bytes
179
/// representing a [`ResourceBundle`] in the ICU binary resource bundle
180
/// format.
181
#[derive(Debug)]
×
182
pub struct Serializer {
183
    /// The format version for which to generate the binary bundle.
184
    format_version: FormatVersion,
185

186
    /// The count of entries in the largest table in the bundle.
187
    ///
188
    /// Used for generating the [`BinIndex`].
189
    largest_table_entry_count: u32,
×
190
}
191

192
impl Serializer {
193
    /// Makes a new write state.
194
    fn new(format_version: FormatVersion) -> Self {
×
195
        Self {
196
            format_version,
197
            largest_table_entry_count: 0,
198
        }
199
    }
×
200

201
    /// Generates a representation of a [`ResourceBundle`] in the binary
202
    /// resource bundle format.
203
    ///
204
    /// Returns a vector of bytes containing the binary representation on
205
    /// success.
206
    pub fn to_bytes(
×
207
        bundle: &ResourceBundle,
208
        keys_in_discovery_order: &[Key],
209
    ) -> Result<Vec<u8>, BinarySerializerError> {
210
        // For now, we hardcode the format version value and do not support
211
        // writing pool bundles.
212
        let format_version = FormatVersion::V2_0;
×
213
        let write_pool_bundle_checksum = false;
×
214

215
        let mut serializer = Self::new(format_version);
×
216

217
        // Walk the resource tree to generate the necessary structures for
218
        // tracking write state for each resource.
219
        let mut root = BinResourceData::from(bundle.root());
×
220

221
        // Determine the size of the index. We can't generate the index until
222
        // we've finished building the rest of the file, but its size is
223
        // important for creating resource offsets.
224
        let index_field_count = match format_version {
×
225
            // Format version 1.0 does not include an index.
226
            FormatVersion::V1_0 => 0,
×
227
            // Format version 1.1 specifies the index, with fields `field_count`
228
            // through `largest_table_entry_count`.
229
            FormatVersion::V1_1 => 5,
×
230
            // Format version 1.2 adds the `bundle_attributes` field.
231
            FormatVersion::V1_2 | FormatVersion::V1_3 => 6,
×
232
            // Format version 2.0 adds the `data_16_bit_end` field and, if the
233
            // bundle is either a pool bundle or uses a pool bundle, the
234
            // `pool_checksum` field.
235
            FormatVersion::V2_0 | FormatVersion::V3_0 => {
236
                if write_pool_bundle_checksum {
×
237
                    8
238
                } else {
239
                    7
×
240
                }
241
            }
242
        };
243

244
        // The root descriptor is considered to be at offset `0`, so include it
245
        // in the determination of the offset of the end of the index.
246
        let index_end = (1 + index_field_count) * std::mem::size_of::<u32>() as u32;
×
247

248
        // Build the key block.
249
        let mut key_position_map = HashMap::new();
×
250
        let keys = serializer.build_key_block(
×
251
            &root,
252
            index_end,
253
            keys_in_discovery_order,
254
            &mut key_position_map,
255
        );
256
        let keys_end = index_end + keys.len() as u32;
×
257

258
        // Build the 16-bit data block.
259
        let data_16_bit = serializer.build_16_bit_data_block(&mut root, &key_position_map)?;
×
260
        let data_16_bit_end = keys_end + data_16_bit.len() as u32;
×
261

262
        // Build the resource block.
263
        let resources =
264
            serializer.build_resource_block(&mut root, data_16_bit_end, &key_position_map)?;
×
265
        let resources_end = data_16_bit_end + resources.len() as u32;
×
266

267
        // Build the index now that we know the sizes of all blocks.
268
        let index = serializer.build_index(
×
269
            bundle,
270
            index_field_count,
×
271
            keys_end >> 2,
×
272
            resources_end >> 2,
×
273
            data_16_bit_end >> 2,
×
274
        );
275

276
        // Build the final structs for writing.
277
        let repr_info = BinReprInfo {
×
278
            size: REPR_INFO_SIZE as u16,
279
            reserved_word: 0,
280
            endianness: SYSTEM_ENDIANNESS,
281
            charset_family: CharsetFamily::Ascii,
×
282
            size_of_char: SIZE_OF_STRING_CHAR,
283
            reserved_byte: 0,
284
            data_format: *DATA_FORMAT,
×
285
            format_version: serializer.format_version,
×
286
            data_version: *DATA_VERSION,
×
287
        };
288

289
        let header = BinHeader {
×
290
            size: PADDED_HEADER_SIZE as u16,
291
            magic: *MAGIC_WORD,
×
292
            repr_info,
293
        };
294

295
        let root_descriptor = root.descriptor.ok_or(BinarySerializerError::unexpected(
×
296
            "root descriptor was never populated",
297
        ))?;
×
298
        let bundle_struct = BinResBundle {
×
299
            header,
300
            root_descriptor,
301
            index,
302
            keys: &keys,
×
303
            data_16_bit: &data_16_bit,
×
304
            resources: &resources,
×
305
        };
306

307
        // Write the bundle as bytes and return.
308
        Vec::<u8>::try_from(bundle_struct)
×
309
    }
×
310

311
    /// Collects the set of keys used in tables.
312
    fn collect_keys<'a>(resource: &BinResourceTypeData<'a>, collected_keys: &mut HashSet<Key<'a>>) {
×
313
        match resource {
×
314
            BinResourceTypeData::Table { map } => {
×
315
                for (key, resource) in map {
×
316
                    collected_keys.insert(key.clone());
×
317
                    Self::collect_keys(&resource.type_data, collected_keys);
×
318
                }
319
            }
320
            BinResourceTypeData::Array { children } => {
×
321
                for child in children {
×
322
                    Self::collect_keys(&child.type_data, collected_keys);
×
323
                }
324
            }
325
            _ => (),
326
        }
327
    }
×
328

329
    /// Generates a vector of bytes representing the block of table keys.
330
    ///
331
    /// Returns the vector of bytes.
332
    fn build_key_block<'a>(
×
333
        &self,
334
        root: &BinResourceData,
335
        block_start_position: u32,
336
        keys_in_discovery_order: &[Key<'a>],
337
        key_position_map: &mut HashMap<Key<'a>, u32>,
338
    ) -> Vec<u8> {
339
        let mut encountered_keys = HashSet::new();
×
340
        Self::collect_keys(&root.type_data, &mut encountered_keys);
×
341

342
        // Get the keys sorted in the order we encountered them during parse.
343
        // While not strictly a part of the specification, ICU4C's `genrb` tool
344
        // writes keys into the binary bundle in this order and so that behavior
345
        // is replicated here.
346
        let sorted_keys = keys_in_discovery_order
×
347
            .iter()
348
            .filter(|&key| encountered_keys.contains(key));
×
349

350
        let mut key_block = Vec::new();
×
351
        for key in sorted_keys {
×
352
            // Track the position of each key as we insert it for later
353
            // reference in writing tables.
354
            key_position_map.insert(key.clone(), block_start_position + key_block.len() as u32);
×
355

356
            // Write keys as null-terminated 8-bit strings.
357
            key_block.append(&mut key.as_bytes().to_vec());
×
358
            key_block.push(0);
×
359
        }
360

361
        // Pad the key block such that the end is aligned with a 32-bit
362
        // boundary.
363
        let padding = (block_start_position as usize + key_block.len()) % 3;
×
364
        key_block.resize(key_block.len() + padding, 0xaa);
×
365

366
        key_block
×
367
    }
×
368

369
    /// Collects the set of strings appearing in string resources and tracks the
370
    /// number of copies of each.
371
    fn collect_strings<'a>(
×
372
        resource: &mut BinResourceData<'a>,
373
        strings: &mut HashMap<&'a str, Rc<RefCell<StringResourceData<'a>>>>,
374
    ) {
375
        let mut existing_string_data = None;
×
376
        match &mut resource.type_data {
×
377
            BinResourceTypeData::String { string, data } => {
×
378
                if strings.contains_key(string) {
×
379
                    // Record the data struct for the already-encountered copy
380
                    // of this string so that we can reuse it for this copy. We
381
                    // want each instance of a string to share a reference to
382
                    // the same data in order to allow multiple string resources
383
                    // to share a single set of bytes in the 16-bit data block.
384
                    // We can't use `if let` here because we need to borrow as
385
                    // mutable in the `else` case.
386
                    #[allow(clippy::unwrap_used)]
387
                    let data = strings.get(string).unwrap();
×
388
                    data.borrow_mut().copy_count += 1;
×
389
                    existing_string_data = Some((string, data));
×
390
                } else {
391
                    strings.insert(string, data.clone());
×
392
                    data.borrow_mut().copy_count += 1;
×
393
                }
394
            }
395

396
            // Walk the resource tree.
397
            BinResourceTypeData::Array { children } => {
×
398
                for child in children {
×
399
                    Self::collect_strings(child, strings);
×
400
                }
401
            }
402
            BinResourceTypeData::Table { map, .. } => {
×
403
                for resource in map.values_mut() {
×
404
                    Self::collect_strings(resource, strings);
×
405
                }
406
            }
407
            _ => (),
408
        };
409

410
        // We will only have recorded string data for this resource if it is a
411
        // string resource.
412
        if let Some((string, data)) = existing_string_data {
×
413
            // Ensure that this string resource uses the same data as every
414
            // string resource with a matching string.
415
            resource.type_data = BinResourceTypeData::String {
×
416
                string,
×
417
                data: data.clone(),
×
418
            }
419
        };
420
    }
×
421

422
    /// Generates a vector of 16-bit values representing the UTF-16 string
423
    /// resources present in the bundle.
424
    ///
425
    /// This builds a block containing all of the string resources in the
426
    /// bundle, which are compacted by deduplicating strings and reusing the
427
    /// ends of longer strings when they wholly contain a shorter string
428
    /// ("suffix").
429
    fn build_string_data(
×
430
        &mut self,
431
        root: &mut BinResourceData,
432
        data_16_bit: &mut Vec<u16>,
433
    ) -> Result<(), BinarySerializerError> {
434
        let mut strings = HashMap::new();
×
435
        Self::collect_strings(root, &mut strings);
×
436
        let count = strings.len();
×
437

438
        // Sort the strings such that any suffixes occur immediately after their
439
        // containing string.
440
        let mut sorted_strings = strings.keys().cloned().collect::<Vec<_>>();
×
441
        sorted_strings.sort_unstable_by(cmp_string_descending_suffix_aware);
×
442

443
        // Locate strings which are suffixes of other strings and link them to
444
        // their containing string.
445
        for (i, string) in sorted_strings.iter().enumerate() {
×
446
            // We can safely unwrap here as `sorted_strings` is just a sorted
447
            // list of keys for the map in question.
448
            #[allow(clippy::unwrap_used)]
449
            let data = strings.get(string).unwrap();
×
450

451
            if data.borrow().containing_string.is_some() {
×
452
                // We've already processed this string as a suffix in the inner
453
                // loop.
454
                continue;
455
            }
456

457
            // Calculate the total number of characters saved by deduplicating
458
            // copies of this string.
459
            let copy_count = data.borrow().copy_count;
×
460
            data.borrow_mut().characters_saved = (copy_count - 1) * get_total_string_size(string)?;
×
461

462
            for suffix in sorted_strings.iter().take(count).skip(i + 1) {
×
463
                if !string.ends_with(suffix) {
×
464
                    // This string is not a suffix of the preceding string;
465
                    // because suffixes are sorted immediately after their
466
                    // containing string, no further strings will be
467
                    break;
468
                }
469

470
                if get_string_length_marker_size(suffix)? != 0 {
×
471
                    // Skip the suffix if it is long enough to require an
472
                    // explicit length marker, as we can't include those in the
473
                    // middle of another string.
474
                    continue;
475
                }
476

477
                // Note the offset of the suffix into its containing string and
478
                // link the two. We can safely unwrap here as `sorted_strings`
479
                // are all keys for the map in question.
480
                #[allow(clippy::unwrap_used)]
481
                let suffix_data = strings.get(suffix).unwrap();
×
482
                suffix_data.borrow_mut().offset =
×
483
                    (string.chars().count() - suffix.chars().count()) as u32;
×
484
                suffix_data.borrow_mut().containing_string = Some(string);
×
485

486
                // Update the characters saved by the containing string.
487
                data.borrow_mut().characters_saved +=
×
488
                    suffix_data.borrow().copy_count * get_total_string_size(suffix)?;
×
489
            }
490
        }
491

492
        // Sort the strings such that suffixes are in ascending length order
493
        // with suffixes sorted to the end. Additionally, strings which save
494
        // more characters are sorted earlier in order to maximize space savings
495
        // in the pool bundle case.
496
        //
497
        // Ascending length order allows for the maximum number of strings to be
498
        // addressable by 16-bit offsets in cases where there are a large number
499
        // of strings.
500
        let mut sorted_strings = strings
×
501
            .iter()
502
            .collect::<Vec<(&&str, &Rc<RefCell<StringResourceData>>)>>();
×
503
        sorted_strings.sort_unstable_by(cmp_string_ascending_suffix_aware);
×
504

505
        for (string, data) in sorted_strings {
×
506
            if data.borrow().containing_string.is_some() {
×
507
                // This string is a suffix of another. Because suffixes are
508
                // sorted to the end, we are guaranteed to have already written
509
                // the containing string. We can't use `if let` here because we
510
                // need to borrow as mutable, but we can safely unwrap.
511
                #[allow(clippy::unwrap_used)]
512
                let containing_string = data.borrow().containing_string.unwrap();
×
513
                let containing_data =
514
                    strings
×
515
                        .get(containing_string)
516
                        .ok_or(BinarySerializerError::unexpected(
×
517
                            "containing string not present in string map",
518
                        ))?;
×
519

520
                // Update the offset of the suffix from a relative position in
521
                // the containing string to an absolute position in the 16-bit
522
                // data block. Ensure that we account for the containing
523
                // string's length marker.
524
                let containing_offset = containing_data.borrow().offset
×
525
                    + get_string_length_marker_size(containing_string)? as u32;
×
526
                data.borrow_mut().offset += containing_offset;
×
527

528
                continue;
529
            }
530

531
            data.borrow_mut().offset = data_16_bit.len() as u32;
×
532

533
            // Build a length marker for the string if one is required.
534
            let length = string.chars().count();
×
535
            let length_words = get_string_length_marker_size(string)?;
×
536

537
            match length_words {
×
538
                0 => (),
539
                1 => data_16_bit.push(0xdc00 & length as u16),
×
540
                2 => {
541
                    data_16_bit.push(0xdfef + (length >> 16) as u16);
×
542
                    data_16_bit.push(length as u16);
×
543
                }
544
                3 => {
545
                    data_16_bit.push(0xdfff);
×
546
                    data_16_bit.push((length >> 16) as u16);
×
547
                    data_16_bit.push(length as u16);
×
548
                }
549
                _ => return Err(BinarySerializerError::string_too_long(length)),
×
550
            };
551

552
            // Write the string to the 16-bit data block as UTF-16.
553
            data_16_bit.append(&mut string.encode_utf16().collect());
×
554
            data_16_bit.push(0);
×
UNCOV
555
        }
×
556

557
        Ok(())
×
558
    }
×
559

560
    /// Generates a vector of 16-bit values representing the string block and
561
    /// any collections whose contents can be addressed by 16-bit values.
562
    ///
563
    /// Returns a 16-bit offset to the resource from the start of the 16-bit
564
    /// data block if the resource can be stored in the block.
565
    fn build_16_bit_resource_data(
×
566
        &mut self,
567
        resource: &mut BinResourceData,
568
        data_16_bit: &mut Vec<u16>,
569
        key_position_map: &HashMap<Key, u32>,
570
    ) -> Option<u16> {
571
        match &mut resource.type_data {
×
572
            BinResourceTypeData::String { data, .. } => {
×
573
                let string_offset = data.borrow().offset;
×
574

575
                // We've already processed the string resources into 16-bit
576
                // data. Add resource descriptors to allow 32-bit collections to
577
                // address them.
578
                resource.descriptor = Some(ResDescriptor::new(
×
579
                    ResourceReprType::StringV2,
×
580
                    string_offset,
581
                ));
582

583
                // If this string is addressable by a 16-bit offset, return
584
                // that offset.
585
                match string_offset <= u16::MAX as u32 {
×
586
                    true => Some(string_offset as u16),
×
587
                    false => None,
×
588
                }
589
            }
590
            BinResourceTypeData::Array { ref mut children } => {
×
591
                if children.is_empty() {
×
592
                    // We match ICU4C's `genrb` tool in representing empty
593
                    // arrays as the `Array` type.
594
                    return None;
×
595
                }
596

597
                let mut data_16_bit_offsets = Vec::new();
×
598
                for child in &mut *children {
×
599
                    let offset =
600
                        self.build_16_bit_resource_data(child, data_16_bit, key_position_map);
×
601

602
                    // If the offset is `None`, it isn't possible to represent
603
                    // this array as an `Array16`, but we continue to walk the
604
                    // tree in case any of its children are representable in 16
605
                    // bits.
606
                    data_16_bit_offsets.push(offset);
×
607
                }
608

609
                // If any of this array's children can't be represented as a
610
                // 16-bit resource, it cannot be represented as an `Array16`.
611
                let data_16_bit_offsets = data_16_bit_offsets
×
612
                    .into_iter()
613
                    .collect::<Option<Vec<_>>>()?;
614

615
                // Write the array as an `Array16` and update the resource data
616
                // appropriately.
617
                resource.descriptor = Some(ResDescriptor::new(
×
618
                    ResourceReprType::Array16,
×
619
                    data_16_bit.len() as u32,
×
620
                ));
621

622
                data_16_bit.push(children.len() as u16);
×
623
                for offset in data_16_bit_offsets {
×
624
                    data_16_bit.push(offset);
×
UNCOV
625
                }
×
626

627
                None
×
628
            }
×
629
            BinResourceTypeData::Table { ref mut map, .. } => {
×
630
                if map.is_empty() {
×
631
                    // We match ICU4C's `genrb` tool in representing empty
632
                    // tables as the `Table` type.
633
                    return None;
×
634
                }
635

636
                let mut data_16_bit_offsets = Vec::new();
×
637
                for resource in map.values_mut() {
×
638
                    let offset =
639
                        self.build_16_bit_resource_data(resource, data_16_bit, key_position_map);
×
640

641
                    // If the offset is `None`, it isn't possible to represent
642
                    // this table as a `Table16`, but we continue to walk the
643
                    // tree in case any of its children are representable in 16
644
                    // bits.
645
                    data_16_bit_offsets.push(offset);
×
646
                }
647

648
                // If any of this table's children can't be represented as a
649
                // 16-bit resource, it cannot be represented as a `Table16`.
650
                let data_16_bit_offsets = data_16_bit_offsets
×
651
                    .into_iter()
652
                    .collect::<Option<Vec<_>>>()?;
×
653

654
                // Update the largest table value as appropriate.
655
                let size = map.len() as u32;
×
656
                self.largest_table_entry_count =
×
657
                    std::cmp::max(self.largest_table_entry_count, size);
×
658

659
                // Write the table as a `Table16` and update the resource data
660
                // appropriately.
661
                resource.descriptor = Some(ResDescriptor::new(
×
662
                    ResourceReprType::Table16,
×
663
                    data_16_bit.len() as u32,
×
664
                ));
665

666
                data_16_bit.push(size as u16);
×
667
                for position in key_position_map.values() {
×
668
                    data_16_bit.push(*position as u16);
×
669
                }
670
                for descriptor in data_16_bit_offsets {
×
671
                    data_16_bit.push(descriptor);
×
UNCOV
672
                }
×
673

674
                None
×
675
            }
×
676
            _ => None,
×
677
        }
678
    }
×
679

680
    /// Generates a vector of bytes representing the 16-bit resource block.
681
    ///
682
    /// Returns the vector of bytes on success.
683
    fn build_16_bit_data_block(
×
684
        &mut self,
685
        root: &mut BinResourceData,
686
        key_position_map: &HashMap<Key, u32>,
687
    ) -> Result<Vec<u8>, BinarySerializerError> {
688
        // Begin the 16-bit data block with a 16-bit `0`. While ICU4C's `genrb`
689
        // tool does this in order to provide empty 16-bit collections with an
690
        // appropriate offset, the tool does not actually generate any such
691
        // collections. We retain this behavior solely for compatibility.
692
        let mut data_16_bit = vec![0];
×
693

694
        self.build_string_data(root, &mut data_16_bit)?;
×
695

696
        self.build_16_bit_resource_data(root, &mut data_16_bit, key_position_map);
×
697

698
        // Pad the 16-bit data block so that the end aligns with a 32-bit
699
        // boundary.
700
        if data_16_bit.len() & 1 != 0 {
×
701
            data_16_bit.push(0xaaaa);
×
702
        }
703

704
        // Reinterpret the 16-bit block as native-endian bytes to ease later
705
        // writing.
706
        let data_16_bit = data_16_bit
×
707
            .iter()
708
            .flat_map(|value| value.to_ne_bytes())
×
709
            .collect();
710

711
        Ok(data_16_bit)
×
712
    }
×
713

714
    /// Generates a vector of bytes representing a single 32-bit resource.
715
    ///
716
    /// Returns the resource descriptor for the resource on success..
717
    fn build_32_bit_resource(
×
718
        &mut self,
719
        resource: &mut BinResourceData,
720
        block_start_position: u32,
721
        data: &mut Vec<u8>,
722
        key_position_map: &HashMap<Key, u32>,
723
    ) -> Result<ResDescriptor, BinarySerializerError> {
724
        if let Some(descriptor) = resource.descriptor {
×
725
            // We've already processed this resource in an earlier step.
726
            return Ok(descriptor);
×
727
        }
728

729
        match &mut resource.type_data {
×
730
            BinResourceTypeData::Array { children } => {
×
731
                if children.is_empty() {
×
732
                    return Ok(ResDescriptor::_new_empty(ResourceReprType::Array));
×
733
                }
734

735
                // Add all child resources to the data and collect their
736
                // resource descriptors.
737
                let child_descriptors = children
×
738
                    .iter_mut()
739
                    .map(|child| {
×
740
                        self.build_32_bit_resource(
×
741
                            child,
742
                            block_start_position,
×
743
                            data,
×
744
                            key_position_map,
×
745
                        )
746
                    })
×
747
                    .collect::<Result<Vec<_>, BinarySerializerError>>()?;
×
748

749
                // Build a resource descriptor for this array.
750
                let offset = block_start_position + data.len() as u32;
×
751
                resource.descriptor =
×
752
                    Some(ResDescriptor::new(ResourceReprType::Array, offset >> 2));
×
753

754
                // Build the array representation.
755
                let mut vector = (children.len() as u32).to_ne_bytes().to_vec();
×
756
                data.append(&mut vector);
×
757

758
                for descriptor in child_descriptors {
×
759
                    data.append(&mut u32::from(descriptor).to_ne_bytes().to_vec());
×
UNCOV
760
                }
×
761
            }
×
762
            BinResourceTypeData::Table { map, .. } => {
×
763
                if map.is_empty() {
×
764
                    return Ok(ResDescriptor::_new_empty(ResourceReprType::Table));
×
765
                }
766

767
                // Update the largest table value as appropriate.
768
                let size = map.len() as u32;
×
769
                self.largest_table_entry_count =
×
770
                    std::cmp::max(self.largest_table_entry_count, size);
×
771

772
                // Add all child resources to the data and collect their
773
                // resource descriptors.
774
                let child_descriptors = map
×
775
                    .values_mut()
776
                    .map(|child| {
×
777
                        self.build_32_bit_resource(
×
778
                            child,
779
                            block_start_position,
×
780
                            data,
×
781
                            key_position_map,
×
782
                        )
783
                    })
×
784
                    .collect::<Result<Vec<_>, BinarySerializerError>>()?;
×
785

786
                // Build a resource descriptor for this table.
787
                let offset = block_start_position + data.len() as u32;
×
788
                resource.descriptor =
×
789
                    Some(ResDescriptor::new(ResourceReprType::Table, offset >> 2));
×
790

791
                // Build the table representation.
792
                data.append(&mut (map.len() as u16).to_ne_bytes().to_vec());
×
793

794
                for key in map.keys() {
×
795
                    let position =
796
                        key_position_map
×
797
                            .get(key)
798
                            .ok_or(BinarySerializerError::unexpected(
×
799
                                "key not present in position map",
800
                            ))?;
×
801
                    data.append(&mut (*position as u16).to_ne_bytes().to_vec());
×
802
                }
803

804
                // Pad the key listing to end at a 32-bit boundary.
805
                if map.len() & 1 == 0 {
×
806
                    data.resize(data.len() + 2, 0xaa);
×
807
                }
808

809
                for descriptor in child_descriptors {
×
810
                    data.append(&mut u32::from(descriptor).to_ne_bytes().to_vec());
×
UNCOV
811
                }
×
812
            }
×
813
            BinResourceTypeData::Binary { binary } => {
×
814
                if binary.is_empty() {
×
815
                    return Ok(ResDescriptor::_new_empty(ResourceReprType::Binary));
×
816
                }
817

818
                // Pad before the start of the binary data such that the number
819
                // of bytes in the body is divisible by 16.
820
                let offset = block_start_position as usize + data.len();
×
821
                let aligned = (offset + std::mem::size_of::<u32>()) % 16;
×
822
                if aligned != 0 {
×
823
                    data.resize(data.len() + (16 - aligned), 0xaa);
×
824
                }
825

826
                // Build a resource descriptor for the binary data.
827
                let offset = block_start_position + data.len() as u32;
×
828
                resource.descriptor =
×
829
                    Some(ResDescriptor::new(ResourceReprType::Binary, offset >> 2));
×
830

831
                // Build the binary data representation.
832
                data.append(&mut (binary.len() as u32).to_ne_bytes().to_vec());
×
833
                data.extend_from_slice(binary);
×
834
            }
835
            BinResourceTypeData::IntVector { int_vector } => {
×
836
                if int_vector.is_empty() {
×
837
                    return Ok(ResDescriptor::_new_empty(ResourceReprType::IntVector));
×
838
                }
839

840
                // Build a resource descriptor for the vector.
841
                let offset = block_start_position + data.len() as u32;
×
842
                resource.descriptor =
×
843
                    Some(ResDescriptor::new(ResourceReprType::IntVector, offset >> 2));
×
844

845
                // Build the vector representation.
846
                data.append(&mut (int_vector.len() as u32).to_ne_bytes().to_vec());
×
847

848
                for int in *int_vector {
×
849
                    data.append(&mut (*int).to_ne_bytes().to_vec());
×
850
                }
851
            }
852
            BinResourceTypeData::_Alias => todo!(),
×
853
            _ => {
854
                return Err(BinarySerializerError::unexpected(
×
855
                    "expected resource to have been processed already",
856
                ))
857
            }
858
        };
859

860
        // Pad the resource body to end at a 32-bit boundary.
861
        let position = block_start_position as usize + data.len();
×
862
        let u32_size = std::mem::size_of::<u32>();
×
863
        if position % u32_size != 0 {
×
864
            data.resize(data.len() + (u32_size - position % u32_size), 0xaa);
×
865
        }
866

867
        resource.descriptor.ok_or(BinarySerializerError::unexpected(
×
868
            "resource descriptor has not been populated",
869
        ))
870
    }
×
871

872
    /// Generates a vector of bytes representing the 32-bit resource block.
873
    ///
874
    /// Returns the vector of bytes on success.
875
    fn build_resource_block(
×
876
        &mut self,
877
        root: &mut BinResourceData,
878
        block_start_position: u32,
879
        key_position_map: &HashMap<Key, u32>,
880
    ) -> Result<Vec<u8>, BinarySerializerError> {
881
        let mut body = Vec::new();
×
882
        self.build_32_bit_resource(root, block_start_position, &mut body, key_position_map)?;
×
883

884
        Ok(body)
×
885
    }
×
886

887
    /// Generates the index block of the bundle data.
888
    ///
889
    /// Returns the final index as a struct.
890
    fn build_index(
×
891
        &self,
892
        bundle: &ResourceBundle,
893
        field_count: u32,
894
        keys_end: u32,
895
        resources_end: u32,
896
        data_16_bit_end: u32,
897
    ) -> BinIndex {
898
        BinIndex {
×
899
            field_count,
900
            keys_end,
901
            resources_end,
902
            bundle_end: resources_end,
903
            largest_table_entry_count: self.largest_table_entry_count,
×
904
            bundle_attributes: Some(!bundle.is_locale_fallback_enabled as u32),
×
905
            data_16_bit_end: Some(data_16_bit_end),
×
906
            pool_checksum: None,
×
907
        }
908
    }
×
909
}
910

911
/// Gets the total size of a UTF-16 string in 16-bit characters.
912
///
913
/// This count includes the string length marker and a null terminator.
914
fn get_total_string_size(string: &str) -> Result<usize, BinarySerializerError> {
×
915
    Ok(string.chars().count() + get_string_length_marker_size(string)? + 1)
×
916
}
×
917

918
/// Gets the size of the length marker of a UTF-16 string in 16-bit characters.
919
///
920
/// Strings of no more than 40 characters are terminated with a 16-bit U+0000
921
/// character, while longer strings are marked with one to three UTF-16 low
922
/// surrogates to indicate length.`
923
///
924
/// For more details, see [`ResourceReprType::StringV2`].
925
fn get_string_length_marker_size(string: &str) -> Result<usize, BinarySerializerError> {
×
926
    let length = match string.chars().count() {
×
927
        0..=40 => 0,
×
928
        41..=0x3ee => 1,
×
929
        0x3ef..=0xf_ffff => 2,
×
930
        0x10_0000..=0xffff_ffff => 3,
×
931
        length => return Err(BinarySerializerError::string_too_long(length)),
×
932
    };
933

934
    Ok(length)
×
935
}
×
936

937
/// Determines the suffix-aware descending length ordering of two keys.
938
///
939
/// When used to sort keys, any key which is wholly contained within the end of
940
/// another will be sorted immediately after it.
941
fn _cmp_key_suffix(l: &(usize, &str), r: &(usize, &str)) -> Ordering {
×
942
    let (l_pos, l_string) = l;
×
943
    let (r_pos, r_string) = r;
×
944
    let mut l_iter = l_string.chars();
×
945
    let mut r_iter = r_string.chars();
×
946

947
    // Iterate strings in reverse order so that, if one string runs out before
948
    // non-matching characters are found, it is a suffix of the other.
949
    while let Some((l_char, r_char)) = l_iter.next_back().zip(r_iter.next_back()) {
×
950
        match l_char.cmp(&r_char) {
×
951
            Ordering::Equal => (),
952
            ord => return ord,
×
953
        };
954
    }
955

956
    match r_string.chars().count().cmp(&l_string.chars().count()) {
×
957
        Ordering::Equal => l_pos.cmp(r_pos),
×
958
        ord => ord,
×
959
    }
960
}
×
961

962
/// Determines the suffix-aware ordering of two strings in descending order of
963
/// length.
964
///
965
/// When used to sort strings, any string which is wholly contained within the
966
/// end of another will be sorted immediately after it. See
967
/// [`build_string_data`] for more details.
968
///
969
/// [`build_string_data`]: Writer::build_string_data
970
fn cmp_string_descending_suffix_aware(l: &&str, r: &&str) -> Ordering {
×
971
    let mut l_iter = l.chars();
×
972
    let mut r_iter = r.chars();
×
973

974
    // Iterate strings in reverse order so that, if one string runs out before
975
    // non-matching characters are found, it is a suffix of the other.
976
    while let Some((l_char, r_char)) = l_iter.next_back().zip(r_iter.next_back()) {
×
977
        match l_char.cmp(&r_char) {
×
978
            Ordering::Equal => (),
979
            ord => return ord,
×
980
        }
981
    }
982

983
    // Sort matching strings in descending order of length, such that a suffix
984
    // sorts after its containing string.
985
    r.chars().count().cmp(&l.chars().count())
×
986
}
×
987

988
/// Determines the suffix-aware ordering of two strings in ascending order of
989
/// length.
990
///
991
/// When used to sort strings, any string which is a suffix of another will be
992
/// sorted to the end. See [`build_string_data`] for more details.
993
///
994
/// [`build_string_data`]: Writer::build_string_data
995
fn cmp_string_ascending_suffix_aware(
×
996
    l: &(&&str, &Rc<RefCell<StringResourceData>>),
997
    r: &(&&str, &Rc<RefCell<StringResourceData>>),
998
) -> Ordering {
999
    let (l, l_data) = l;
×
1000
    let (r, r_data) = r;
×
1001
    // If one string is a suffix and the other is not, the suffix should be
1002
    // sorted after in order to guarantee that its containing string will be
1003
    // processed first.
1004
    match (
×
1005
        l_data.borrow().containing_string,
×
1006
        r_data.borrow().containing_string,
×
1007
    ) {
1008
        (Some(_), None) => return Ordering::Greater,
×
1009
        (None, Some(_)) => return Ordering::Less,
×
1010
        _ => (),
1011
    };
×
1012

1013
    // Sort longer strings later.
1014
    match l.chars().count().cmp(&r.chars().count()) {
×
1015
        Ordering::Equal => (),
1016
        ord => return ord,
×
1017
    };
1018

1019
    // Strings with more duplicates or matching suffixes are sorted earlier.
1020
    // This allows for maximizing savings in pool bundles. It is otherwise not
1021
    // relevant.
1022
    match r_data
×
1023
        .borrow()
1024
        .characters_saved
1025
        .cmp(&l_data.borrow().characters_saved)
×
1026
    {
1027
        Ordering::Equal => (),
1028
        ord => return ord,
×
1029
    };
×
1030

1031
    // All other things being equal, resort to lexical sort.
1032
    l.cmp(r)
×
1033
}
×
1034

1035
/// The `BinResBundle` struct is designed to mimic the in-memory layout of a
1036
/// binary resource bundle. It is used in constructing byte data for writing.
1037
struct BinResBundle<'a> {
1038
    /// The file header.
1039
    ///
1040
    /// As represented in the byte data, the header is zero-padded such that the
1041
    /// total size of the header in bytes is divisible by 16.
1042
    header: BinHeader,
1043

1044
    /// The resource descriptor for the root resource in the bundle. This is
1045
    /// considered the `0`th 32-bit value in the body for purposes of
1046
    /// resolving offsets.
1047
    root_descriptor: ResDescriptor,
1048

1049
    /// Details of the final layout of the bundle as well as attributes for
1050
    /// resolving resources.
1051
    ///
1052
    /// The index is present from [`FormatVersion::V1_1`] on.
1053
    index: BinIndex,
1054

1055
    /// A block of strings representing keys in table resources.
1056
    ///
1057
    /// Keys are stored as contiguous null-terminated 8-bit strings in the
1058
    /// core encoding of the [`CharsetFamily`] of the bundle.
1059
    keys: &'a [u8],
1060

1061
    /// A block of 16-bit data containing 16-bit resources.
1062
    ///
1063
    /// This block is present from [`FormatVersion::V2_0`] on.
1064
    ///
1065
    /// 16-bit resources consist of UTF-16 string resources and collections
1066
    /// which contain only 16-bit string resources. See
1067
    /// [`ResourceReprType::StringV2`], [`ResourceReprType::Array16`], and
1068
    /// [`ResourceReprType::Table16`] for details on representation.
1069
    data_16_bit: &'a [u8],
1070

1071
    /// A block of resource representations. See [`ResourceReprType`] for
1072
    /// details on the representation of resources.
1073
    resources: &'a [u8],
1074
}
1075

1076
impl TryFrom<BinResBundle<'_>> for Vec<u8> {
1077
    type Error = BinarySerializerError;
1078

1079
    fn try_from(value: BinResBundle<'_>) -> Result<Self, Self::Error> {
×
1080
        // Manually build the list of bytes to write to the output. There are
1081
        // crates which provide this functionality, but the writing of the body
1082
        // is sufficiently complex as to make the header the only part where
1083
        // an external crate would be convenient, and that's a matter of a small
1084
        // number of bytes, so on balance it's easier to just handle those here.
1085
        let mut bytes = Vec::new();
×
1086
        bytes.append(&mut Vec::<u8>::from(value.header));
×
1087

1088
        // Write the body.
1089
        bytes.extend_from_slice(&u32::from(value.root_descriptor).to_ne_bytes());
×
1090
        bytes.append(&mut Vec::<u8>::try_from(value.index)?);
×
1091
        bytes.extend_from_slice(value.keys);
×
1092
        bytes.extend_from_slice(value.data_16_bit);
×
1093
        bytes.extend_from_slice(value.resources);
×
1094

1095
        Ok(bytes)
×
1096
    }
×
1097
}
1098

1099
impl From<BinHeader> for Vec<u8> {
1100
    fn from(value: BinHeader) -> Self {
×
1101
        let mut bytes = Vec::with_capacity(value.size as usize);
×
1102

1103
        bytes.extend_from_slice(&value.size.to_ne_bytes());
×
1104
        bytes.extend_from_slice(&value.magic);
×
1105

1106
        let mut data_info_bytes = Vec::<u8>::from(value.repr_info);
×
1107
        bytes.append(&mut data_info_bytes);
×
1108

1109
        // Pad the header such that its total size is divisible by 16.
1110
        bytes.resize(PADDED_HEADER_SIZE, 0);
×
1111

1112
        bytes
×
1113
    }
×
1114
}
1115

1116
impl From<BinReprInfo> for Vec<u8> {
1117
    fn from(value: BinReprInfo) -> Self {
×
1118
        let mut bytes = Vec::with_capacity(value.size as usize);
×
1119

1120
        bytes.extend_from_slice(&value.size.to_ne_bytes());
×
1121
        bytes.extend_from_slice(&value.reserved_word.to_ne_bytes());
×
1122
        bytes.push(value.endianness.into());
×
1123
        bytes.push(value.charset_family.into());
×
1124
        bytes.push(value.size_of_char);
×
1125
        bytes.push(value.reserved_byte);
×
1126
        bytes.extend_from_slice(&value.data_format);
×
1127
        bytes.extend_from_slice(&<[u8; 4]>::from(value.format_version));
×
1128
        bytes.extend_from_slice(&value.data_version);
×
1129

1130
        bytes
×
1131
    }
×
1132
}
1133

1134
impl TryFrom<BinIndex> for Vec<u8> {
1135
    type Error = BinarySerializerError;
1136

1137
    fn try_from(value: BinIndex) -> Result<Self, Self::Error> {
×
1138
        let mut bytes =
1139
            Vec::with_capacity(value.field_count as usize * core::mem::size_of::<u32>());
×
1140

1141
        // Format version 1.0 did not include an index and so no bytes should be
1142
        // written.
1143
        if value.field_count >= 5 {
×
1144
            bytes.extend_from_slice(&value.field_count.to_ne_bytes());
×
1145
            bytes.extend_from_slice(&value.keys_end.to_ne_bytes());
×
1146
            bytes.extend_from_slice(&value.resources_end.to_ne_bytes());
×
1147
            bytes.extend_from_slice(&value.bundle_end.to_ne_bytes());
×
1148
            bytes.extend_from_slice(&value.largest_table_entry_count.to_ne_bytes());
×
1149
        }
1150

1151
        if value.field_count >= 6 {
×
1152
            let bundle_attributes =
1153
                value
×
1154
                    .bundle_attributes
1155
                    .ok_or(BinarySerializerError::unexpected(
×
1156
                        "no bundle attributes field provided",
1157
                    ))?;
×
1158

1159
            bytes.extend_from_slice(&bundle_attributes.to_ne_bytes());
×
1160
        }
1161

1162
        if value.field_count >= 7 {
×
1163
            let data_16_bit_end =
1164
                value
×
1165
                    .data_16_bit_end
1166
                    .ok_or(BinarySerializerError::unexpected(
×
1167
                        "no 16-bit data end offset provided",
1168
                    ))?;
×
1169

1170
            bytes.extend_from_slice(&data_16_bit_end.to_ne_bytes());
×
1171
        }
1172

1173
        if value.field_count >= 8 {
×
1174
            let pool_checksum = value
×
1175
                .pool_checksum
1176
                .ok_or(BinarySerializerError::unexpected(
×
1177
                    "no pool checksum provided",
1178
                ))?;
×
1179

1180
            bytes.extend_from_slice(&pool_checksum.to_ne_bytes());
×
1181
        }
1182

1183
        Ok(bytes)
×
1184
    }
×
1185
}
1186

1187
impl From<FormatVersion> for [u8; 4] {
1188
    fn from(value: FormatVersion) -> [u8; 4] {
×
1189
        match value {
×
1190
            FormatVersion::V1_0 => [1, 0, 0, 0],
×
1191
            FormatVersion::V1_1 => [1, 1, 0, 0],
×
1192
            FormatVersion::V1_2 => [1, 2, 0, 0],
×
1193
            FormatVersion::V1_3 => [1, 3, 0, 0],
×
1194
            FormatVersion::V2_0 => [2, 0, 0, 0],
×
1195
            FormatVersion::V3_0 => [3, 0, 0, 0],
×
1196
        }
1197
    }
×
1198
}
1199

1200
impl From<ResDescriptor> for u32 {
1201
    fn from(value: ResDescriptor) -> Self {
×
1202
        ((value.resource_type as u32) << 28) | value.value
×
1203
    }
×
1204
}
1205

1206
/// The `Error` type provides basic error handling for serialization of binary
1207
/// resource bundles.
1208
#[derive(Debug)]
×
1209
pub struct BinarySerializerError {
1210
    kind: ErrorKind,
×
1211
}
1212

1213
impl BinarySerializerError {
1214
    /// Creates a new error indicating that the input data contains a string
1215
    /// longer than a binary resource bundle can encode.
1216
    pub fn string_too_long(length: usize) -> Self {
×
1217
        Self {
×
1218
            kind: ErrorKind::StringTooLong(length),
×
1219
        }
1220
    }
×
1221

1222
    /// Creates a new error indicating that an unexpected error has occurred.
1223
    ///
1224
    /// Errors of this type should be programming errors within the writer,
1225
    /// rather than problems with input data.
1226
    pub fn unexpected(message: &'static str) -> Self {
×
1227
        Self {
×
1228
            kind: ErrorKind::Unexpected(message),
×
1229
        }
1230
    }
×
1231
}
1232

1233
impl fmt::Display for BinarySerializerError {
1234
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
1235
        match self.kind {
×
1236
            ErrorKind::StringTooLong(length) => write!(
×
1237
                f,
1238
                "Cannot serialize string of length {length}, maximum is 4,294,967,295 characters"
1239
            ),
1240
            ErrorKind::Unexpected(message) => write!(f, "Unexpected error: {message}"),
×
1241
        }
1242
    }
×
1243
}
1244

1245
#[derive(Debug)]
×
1246
enum ErrorKind {
1247
    StringTooLong(usize),
×
1248
    Unexpected(&'static str),
×
1249
}
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