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

agronholm / cbor2 / 27145849196

08 Jun 2026 02:47PM UTC coverage: 94.898% (+0.009%) from 94.889%
27145849196

Pull #314

github

web-flow
Merge b4a5b04a6 into 7541d39a6
Pull Request #314: measure text strings by byte length in maybe_stringref

7 of 7 new or added lines in 1 file covered. (100.0%)

14 existing lines in 1 file now uncovered.

2362 of 2489 relevant lines covered (94.9%)

27138.13 hits per line

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

95.51
/rust/encoder.rs
1
use crate::types::{
2
    CBOREncodeError, CBOREncodeValueError, CBORSimpleValue, CBORTag, UndefinedType,
3
};
4
use crate::utils::PyImportable;
5
use bigdecimal::BigDecimal;
6
use half::f16;
7
use num_bigint::BigInt;
8
use pyo3::exceptions::{PyLookupError, PyRuntimeError, PyTypeError, PyValueError};
9
use pyo3::prelude::*;
10
use pyo3::sync::PyOnceLock;
11
use pyo3::types::{
12
    PyBool, PyByteArray, PyBytes, PyCFunction, PyComplex, PyDict, PyFloat, PyFrozenSet, PyInt,
13
    PyList, PyMapping, PyNone, PySequence, PySet, PyString, PyTuple, PyType,
14
};
15
use pyo3::{IntoPyObjectExt, Py, PyAny, intern, pyclass};
16
use std::collections::HashMap;
17
use std::mem::swap;
18

19
type EncoderFn = fn(&Bound<CBOREncoder>, &Bound<PyAny>) -> PyResult<()>;
20
type EncoderLookupVec = Vec<(Py<PyType>, EncoderFn)>;
21

22
static DATETIME_COMBINE_FUNC: PyImportable = PyImportable::new("datetime", "datetime.combine");
23
static ID_FUNC: PyImportable = PyImportable::new("builtins", "id");
24
static TZINFO_TYPE: PyImportable = PyImportable::new("datetime", "tzinfo");
25
static SORTED_FUNC: PyImportable = PyImportable::new("builtins", "sorted");
26
static ZERO_TIME: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
27
static STDLIB_ENCODERS: PyOnceLock<EncoderLookupVec> = PyOnceLock::new();
28

29
/// Wrap the given encoder function to gracefully handle cyclic data
30
/// structures.
31
///
32
/// If value sharing is enabled, this marks the given value shared in the
33
/// datastream on the first call. If the value has already been passed to this
34
/// method, a reference marker is instead written to the data stream and the
35
/// wrapped function is not called.
36
///
37
/// If value sharing is disabled, only infinite recursion protection is done.
38
#[pyfunction]
39
#[pyo3(signature = (wraps, /))]
40
pub fn shareable_encoder<'py>(
12✔
41
    py: Python<'py>,
12✔
42
    wraps: &Bound<'py, PyAny>,
12✔
43
) -> PyResult<Bound<'py, PyCFunction>> {
12✔
44
    // `wraps` is the original Python function
45
    let wraps = wraps.clone().unbind();
12✔
46
    PyCFunction::new_closure(
12✔
47
        py,
12✔
48
        None, // no module
12✔
49
        None, // no qualified name override
12✔
50
        move |args: &Bound<'_, PyTuple>, _kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<()> {
36✔
51
            let py = args.py();
36✔
52
            let encoder = args.get_item(0)?.cast_into::<CBOREncoder>()?;
36✔
53
            let value = args.get_item(1)?;
36✔
54
            CBOREncoder::encode_shared(&encoder, &value, || {
36✔
55
                wraps.call1(py, (&encoder, &value)).map(|_| ())
24✔
56
            })
24✔
57
        },
36✔
58
    )
59
}
12✔
60

61
/// The CBOREncoder class implements a fully featured CBOR encoder with several extensions for
62
/// handling shared references, big integers, rational numbers, and so on. Typically, the class is
63
/// not used directly, but the dump() and dumps() functions are called to indirectly construct and
64
/// use the class.
65
///
66
/// When the class is constructed manually, the main entry points are :meth:`encode` and
67
/// :meth:`encode_to_bytes`.
68
///
69
/// :param fp:
70
///     the file to write to (any file-like object opened for writing in binary mode)
71
/// :param datetime_as_timestamp:
72
///     set to :data:`True` to serialize datetimes as UNIX timestamps (this makes datetimes
73
///     more concise on the wire, but loses the timezone information)
74
/// :param timezone:
75
///     the default timezone to use for serializing naive datetimes; if this is not
76
///     specified naive datetimes will throw a :exc:`ValueError` when encoding is
77
///     attempted
78
/// :param value_sharing:
79
///     set to :data:`True` to allow more efficient serializing of repeated values
80
///     and, more importantly, cyclic data structures, at the cost of extra
81
///     line overhead
82
/// :param encoders:
83
///     An optional mapping for overriding the encoding for select Python types.
84
///     Each key in this mapping should be a Python type object, and the value a callable
85
///     that takes two arguments: the encoder object and the object to encode.
86
/// :param default:
87
///     a callable that is called by the encoder with two arguments (the encoder
88
///     instance and the value being encoded) when no suitable encoder has been found,
89
///     and should use the methods on the encoder to encode any objects it wants to add
90
///     to the data stream
91
/// :param canonical:
92
///     when :data:`True`, use "canonical" CBOR representation; this typically involves
93
///     sorting maps, sets, etc. into a pre-determined order ensuring that
94
///     serializations are comparable without decoding
95
/// :param date_as_datetime:
96
///     set to :data:`True` to serialize date objects as datetimes (CBOR tag 0), which was
97
///     the default behavior in previous releases (cbor2 <= 4.1.2).
98
/// :param string_referencing:
99
///     set to :data:`True` to allow more efficient serializing of repeated string values
100
/// :param indefinite_containers:
101
///     encode containers as indefinite (use stop code instead of specifying length)
102
#[pyclass(module = "cbor2")]
103
pub struct CBOREncoder {
104
    fp: Option<Py<PyAny>>,
105

106
    #[pyo3(get)]
107
    datetime_as_timestamp: bool,
108

109
    timezone: Option<Py<PyAny>>,
110

111
    #[pyo3(get)]
112
    value_sharing: bool,
113

114
    default: Option<Py<PyAny>>,
115

116
    #[pyo3(get)]
117
    canonical: bool,
118

119
    #[pyo3(get)]
120
    date_as_datetime: bool,
121

122
    #[pyo3(get)]
123
    string_referencing: bool,
124

125
    #[pyo3(get)]
126
    string_namespacing: bool,
127

128
    #[pyo3(get)]
129
    indefinite_containers: bool,
130

131
    encoders: Option<Py<PyMapping>>,
132
    write_method: Option<Py<PyAny>>,
133
    pub buffer: Vec<u8>,
134
    shared_containers: HashMap<usize, (Py<PyAny>, Option<Py<PyInt>>)>,
135
    string_references: HashMap<String, usize>,
136
    bytes_references: HashMap<Vec<u8>, usize>,
137
    encode_depth: usize,
138
}
139

140
const MAX_BUFFER_SIZE: usize = 4096;
141

142
impl CBOREncoder {
143
    pub fn new_internal(
3,108✔
144
        fp: Option<&Bound<'_, PyAny>>,
3,108✔
145
        datetime_as_timestamp: bool,
3,108✔
146
        timezone: Option<&Bound<'_, PyAny>>,
3,108✔
147
        value_sharing: bool,
3,108✔
148
        encoders: Option<&Bound<'_, PyMapping>>,
3,108✔
149
        default: Option<&Bound<'_, PyAny>>,
3,108✔
150
        canonical: bool,
3,108✔
151
        date_as_datetime: bool,
3,108✔
152
        string_referencing: bool,
3,108✔
153
        indefinite_containers: bool,
3,108✔
154
    ) -> PyResult<Self> {
3,108✔
155
        let mut instance = Self {
3,108✔
156
            fp: None,
3,108✔
157
            datetime_as_timestamp,
3,108✔
158
            timezone: None,
3,108✔
159
            value_sharing,
3,108✔
160
            default: None,
3,108✔
161
            canonical,
3,108✔
162
            date_as_datetime,
3,108✔
163
            string_referencing,
3,108✔
164
            string_namespacing: string_referencing,
3,108✔
165
            indefinite_containers,
3,108✔
166
            encoders: encoders.map(|e| e.clone().unbind()),
3,108✔
167
            write_method: None,
3,108✔
168
            buffer: Vec::new(),
3,108✔
169
            shared_containers: HashMap::new(),
3,108✔
170
            string_references: HashMap::new(),
3,108✔
171
            bytes_references: HashMap::new(),
3,108✔
172
            encode_depth: 0,
173
        };
174
        if let Some(fp) = fp {
3,108✔
175
            instance.set_fp(fp)?;
204✔
176
        }
2,904✔
177
        instance.set_timezone(timezone)?;
3,084✔
178
        instance.set_default(default)?;
3,084✔
179
        Ok(instance)
3,084✔
180
    }
3,108✔
181

182
    fn encode_shared(
2,840✔
183
        slf: &Bound<'_, Self>,
2,840✔
184
        obj: &Bound<'_, PyAny>,
2,840✔
185
        f: impl FnOnce() -> PyResult<()>,
2,840✔
186
    ) -> PyResult<()> {
2,840✔
187
        let py = slf.py();
2,840✔
188
        let value_sharing = slf.borrow().value_sharing;
2,840✔
189
        let value_id = ID_FUNC.get(py)?.call1((obj,))?.extract::<usize>()?;
2,840✔
190

191
        let mut this = slf.borrow_mut();
2,840✔
192
        let option = this.shared_containers.get(&value_id);
2,840✔
193
        match option {
96✔
194
            None => {
195
                if value_sharing {
2,744✔
196
                    // Mark the container as shareable
197
                    let next_index = PyInt::new(py, this.shared_containers.len()).unbind();
156✔
198
                    this.shared_containers.insert(
156✔
199
                        value_id,
156✔
200
                        (obj.clone().unbind(), Some(next_index.clone_ref(py))),
156✔
201
                    );
202
                    this.encode_length(py, 6, Some(28))?;
156✔
203
                    drop(this);
156✔
204
                    f().map(|_| ())
156✔
205
                } else {
206
                    this.shared_containers
2,588✔
207
                        .insert(value_id, (obj.clone().unbind(), None));
2,588✔
208
                    drop(this);
2,588✔
209
                    let result = f();
2,588✔
210
                    slf.borrow_mut().shared_containers.remove(&value_id);
2,588✔
211
                    result.map(|_| ())
2,588✔
212
                }
213
            }
214
            Some((_, None)) => Err(CBOREncodeValueError::new_err(
24✔
215
                "cyclic data structure detected",
24✔
216
            )),
24✔
217
            Some((_, Some(index))) => {
72✔
218
                // Generate a reference to the previous index instead of
219
                // encoding this again
220
                let value = index.clone_ref(py);
72✔
221
                this.encode_length(py, 6, Some(29))?;
72✔
222
                drop(this);
72✔
223
                Self::encode_int(slf, value.bind(py))
72✔
224
            }
225
        }
226
    }
2,840✔
227

228
    /// Call the given function with value sharing disabled in the encoder.
229
    fn disable_value_sharing<T>(slf: &Bound<'_, Self>, f: impl FnOnce() -> T) -> T {
64✔
230
        let mut this = slf.borrow_mut();
64✔
231
        let old_value_sharing = this.value_sharing;
64✔
232
        this.value_sharing = false;
64✔
233
        drop(this);
64✔
234
        let result = f();
64✔
235
        slf.borrow_mut().value_sharing = old_value_sharing;
64✔
236
        result
64✔
237
    }
64✔
238

239
    /// Call the given function with string namespacing disabled in the encoder.
240
    fn disable_string_namespacing<T>(slf: &Bound<'_, Self>, f: impl FnOnce() -> T) -> T {
2,804✔
241
        let mut this = slf.borrow_mut();
2,804✔
242
        let old_string_namespacing = this.string_namespacing;
2,804✔
243
        this.string_namespacing = false;
2,804✔
244
        drop(this);
2,804✔
245
        let result = f();
2,804✔
246
        slf.borrow_mut().string_namespacing = old_string_namespacing;
2,804✔
247
        result
2,804✔
248
    }
2,804✔
249

250
    /// Call the given function with string referencing disabled in the encoder.
251
    fn disable_string_referencing<T>(slf: &Bound<'_, Self>, f: impl FnOnce() -> T) -> T {
312✔
252
        let mut this = slf.borrow_mut();
312✔
253
        let old_string_referencing = this.string_referencing;
312✔
254
        this.string_referencing = false;
312✔
255
        drop(this);
312✔
256
        let result = f();
312✔
257
        slf.borrow_mut().string_referencing = old_string_referencing;
312✔
258
        result
312✔
259
    }
312✔
260

261
    fn encode_container(
2,804✔
262
        slf: &Bound<'_, Self>,
2,804✔
263
        obj: &Bound<'_, PyAny>,
2,804✔
264
        f: impl FnOnce() -> PyResult<()>,
2,804✔
265
    ) -> PyResult<()> {
2,804✔
266
        if slf.borrow().string_namespacing {
2,804✔
267
            // Create a new string reference domain
268
            slf.borrow_mut().encode_length(slf.py(), 6, Some(256))?;
96✔
269
        }
2,708✔
270

271
        Self::disable_string_namespacing(slf, || Self::encode_shared(slf, obj, f))
2,804✔
272
    }
2,804✔
273

274
    fn write_internal(&mut self, py: Python<'_>, mut data: Vec<u8>) -> PyResult<()> {
11,808✔
275
        if data.len() > MAX_BUFFER_SIZE {
11,808✔
276
            self.flush(py)?;
×
277
        }
11,808✔
278
        self.buffer.append(&mut data);
11,808✔
279
        self.maybe_flush(py)
11,808✔
280
    }
11,808✔
281

282
    fn write_byte(&mut self, py: Python<'_>, data: u8) -> PyResult<()> {
15,948✔
283
        self.buffer.push(data);
15,948✔
284
        self.maybe_flush(py)
15,948✔
285
    }
15,948✔
286

287
    fn flush(&mut self, py: Python<'_>) -> PyResult<()> {
3,360✔
288
        if let Some(write_method) = &self.write_method {
3,360✔
289
            write_method.call1(py, (&*self.buffer,))?;
120✔
290
            self.buffer.clear();
120✔
291
        }
3,240✔
292
        Ok(())
3,360✔
293
    }
3,360✔
294

295
    fn maybe_flush(&mut self, py: Python<'_>) -> PyResult<()> {
27,756✔
296
        if self.encode_depth == 0 || (self.fp.is_some() && self.buffer.len() >= MAX_BUFFER_SIZE) {
27,756✔
297
            self.flush(py)
72✔
298
        } else {
299
            Ok(())
27,684✔
300
        }
301
    }
27,756✔
302

303
    fn maybe_stringref(slf: &Bound<'_, Self>, value: &Bound<'_, PyAny>) -> PyResult<bool> {
636✔
304
        let py = slf.py();
636✔
305
        let mut this = slf.borrow_mut();
636✔
306
        let (index, is_string, length) = if let Ok(py_string) = value.cast::<PyString>() {
636✔
307
            let string: String = py_string.extract()?;
624✔
308
            // The threshold compares against the string's encoded size, which for text is the
309
            // number of UTF-8 bytes, not the number of code points
310
            (
624✔
311
                this.string_references.get(&string).copied(),
624✔
312
                true,
624✔
313
                string.len(),
624✔
314
            )
624✔
315
        } else {
316
            let bytes: Vec<u8> = value.cast::<PyBytes>()?.extract()?;
12✔
317
            let length = bytes.len();
12✔
318
            (this.bytes_references.get(&bytes).copied(), false, length)
12✔
319
        };
320
        match index {
636✔
321
            Some(index) => {
108✔
322
                drop(this);
108✔
323
                Self::encode_semantic(slf, 25, PyInt::new(py, index).as_any())?;
108✔
324
                Ok(true)
108✔
325
            }
326
            None => {
327
                let next_index = this.string_references.len() + this.bytes_references.len();
528✔
328
                let is_referenced = match next_index {
528✔
329
                    ..24 => length >= 3,
528✔
UNCOV
330
                    24..256 => length >= 4,
×
331
                    256..65536 => length >= 5,
×
332
                    65536..=4294967295 => length >= 7,
×
333
                    _ => length >= 11,
×
334
                };
335

336
                if is_referenced {
528✔
337
                    if is_string {
216✔
338
                        this.string_references.insert(value.extract()?, next_index);
204✔
339
                    } else {
340
                        this.bytes_references.insert(value.extract()?, next_index);
12✔
341
                    }
342
                }
312✔
343

344
                Ok(false)
528✔
345
            }
346
        }
347
    }
636✔
348
}
349

UNCOV
350
#[pymethods]
×
351
impl CBOREncoder {
352
    #[new]
353
    #[pyo3(signature = (
354
        fp,
355
        *,
356
        datetime_as_timestamp = false,
357
        timezone = None,
358
        value_sharing = false,
359
        encoders = None,
360
        default = None,
361
        canonical = false,
362
        date_as_datetime = false,
363
        string_referencing = false,
364
        indefinite_containers = false
365
    ))]
366
    pub fn new(
204✔
367
        fp: &Bound<'_, PyAny>,
204✔
368
        datetime_as_timestamp: bool,
204✔
369
        timezone: Option<&Bound<'_, PyAny>>,
204✔
370
        value_sharing: bool,
204✔
371
        encoders: Option<&Bound<'_, PyMapping>>,
204✔
372
        default: Option<&Bound<'_, PyAny>>,
204✔
373
        canonical: bool,
204✔
374
        date_as_datetime: bool,
204✔
375
        string_referencing: bool,
204✔
376
        indefinite_containers: bool,
204✔
377
    ) -> PyResult<Self> {
204✔
378
        CBOREncoder::new_internal(
204✔
379
            Some(fp),
204✔
380
            datetime_as_timestamp,
204✔
381
            timezone,
204✔
382
            value_sharing,
204✔
383
            encoders,
204✔
384
            default,
204✔
385
            canonical,
204✔
386
            date_as_datetime,
204✔
387
            string_referencing,
204✔
388
            indefinite_containers,
204✔
389
        )
390
    }
204✔
391

392
    #[getter]
UNCOV
393
    fn fp(&self, py: Python<'_>) -> Option<Py<PyAny>> {
×
394
        self.fp.as_ref().map(|fp| fp.clone_ref(py))
×
395
    }
×
396

397
    #[setter]
398
    fn set_fp(&mut self, fp: &Bound<'_, PyAny>) -> PyResult<()> {
216✔
399
        let result = fp.call_method0("writable");
216✔
400
        if let Ok(writable) = &result
216✔
401
            && writable.is_truthy()?
204✔
402
        {
403
            // Before replacing the file pointer, flush any pending writes and clear state
404
            if let Some(existing_fp) = &self.fp
192✔
405
                && !fp.is(existing_fp)
12✔
406
            {
407
                self.flush(fp.py())?;
12✔
408
                self.shared_containers.clear();
12✔
409
                self.string_references.clear();
12✔
410
                self.bytes_references.clear();
12✔
411
            }
180✔
412

413
            self.write_method = Some(fp.getattr("write")?.unbind());
192✔
414
            self.fp = Some(fp.clone().unbind());
192✔
415
            Ok(())
192✔
416
        } else {
417
            let exc = PyValueError::new_err("fp must be a writable file-like object");
24✔
418
            exc.set_cause(fp.py(), result.err());
24✔
419
            Err(exc)
24✔
420
        }
421
    }
216✔
422

423
    #[getter]
424
    fn timezone(&self, py: Python<'_>) -> Option<Py<PyAny>> {
12✔
425
        self.timezone
12✔
426
            .as_ref()
12✔
427
            .map(|timezone| timezone.clone_ref(py))
12✔
428
    }
12✔
429

430
    #[setter]
431
    fn set_timezone(&mut self, timezone: Option<&Bound<'_, PyAny>>) -> PyResult<()> {
3,096✔
432
        if let Some(timezone) = timezone {
3,096✔
433
            let py = timezone.py();
108✔
434
            if !timezone.is_instance(&TZINFO_TYPE.get(py)?)? {
108✔
435
                return Err(PyErr::new::<PyTypeError, _>(
12✔
436
                    "timezone must be a tzinfo object",
12✔
437
                ));
12✔
438
            }
96✔
439

440
            self.timezone = Some(timezone.clone().unbind());
96✔
441
        } else {
2,988✔
442
            self.timezone = None;
2,988✔
443
        }
2,988✔
444
        Ok(())
3,084✔
445
    }
3,096✔
446

447
    #[getter]
448
    fn default(&self, py: Python<'_>) -> Option<Py<PyAny>> {
12✔
449
        self.default.as_ref().map(|default| default.clone_ref(py))
12✔
450
    }
12✔
451

452
    #[setter]
453
    fn set_default(&mut self, default: Option<&Bound<'_, PyAny>>) -> PyResult<()> {
3,096✔
454
        if let Some(default) = default {
3,096✔
455
            if !default.is_callable() {
60✔
456
                return Err(PyErr::new::<PyTypeError, _>("default must be callable"));
12✔
457
            }
48✔
458

459
            self.default = Some(default.clone().unbind());
48✔
460
        } else {
3,036✔
461
            self.default = None;
3,036✔
462
        }
3,036✔
463
        Ok(())
3,084✔
464
    }
3,096✔
465

466
    /// Write bytes to the data stream.
467
    ///
468
    /// :param buf: the bytes to write
469
    /// :returns: the number of bytes written
470
    ///
471
    /// .. note:: During the encoding of an object, this method may write the given bytes to the
472
    ///    internal buffer without flushing to the actual output stream. When called outside the
473
    ///    encoding process, it is equivalent to ``encoder.fp.write(...)``.
474
    #[pyo3(signature = (buf, /))]
475
    fn write<'py>(&mut self, py: Python<'py>, buf: Vec<u8>) -> PyResult<usize> {
12✔
476
        if self.encode_depth == 0 {
12✔
477
            if self.write_method.is_none() {
12✔
UNCOV
478
                return Err(PyRuntimeError::new_err("fp not set"));
×
479
            }
12✔
480

481
            assert_eq!(self.buffer.len(), 0, "The buffer should have been empty");
12✔
482
            let write = self.write_method.as_ref().unwrap();
12✔
483
            write.bind(py).call1((&buf,))?.extract()
12✔
484
        } else {
UNCOV
485
            let buf_len = buf.len();
×
486
            self.write_internal(py, buf)?;
×
487
            Ok(buf_len)
×
488
        }
489
    }
12✔
490

491
    fn encode_value(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
15,992✔
492
        // Look up the Python type object of the object to be encoded
493
        let py = slf.py();
15,992✔
494
        let this = slf.borrow();
15,992✔
495

496
        if let Some(encoders) = &this.encoders {
15,992✔
UNCOV
497
            match encoders.bind(py).get_item(obj.get_type()) {
×
498
                Ok(encoder) => {
×
499
                    drop(this);
×
500
                    return encoder.call1((slf, obj)).map(|_| ());
×
501
                }
UNCOV
502
                Err(e) if e.is_instance_of::<PyLookupError>(py) => {}
×
503
                Err(e) => return Err(e),
×
504
            }
505
        }
15,992✔
506

507
        // Look up the type in the encoders dict, and if no encoder callback was found, check for
508
        // special types. If all else fails, fall back to the default encoder callback, if one was
509
        // provided. Otherwise, raise CBOREncoderError.
510
        drop(this);
15,992✔
511
        if let Ok(obj) = obj.cast::<PyBytes>() {
15,992✔
512
            Self::encode_bytes(slf, obj)
956✔
513
        } else if let Ok(obj) = obj.cast::<PyString>() {
15,036✔
514
            Self::encode_string(slf, obj)
2,372✔
515
        } else if let Ok(obj) = obj.cast::<PyBool>() {
12,664✔
516
            Self::encode_bool(slf, obj)
248✔
517
        } else if let Ok(obj) = obj.cast::<PyInt>() {
12,416✔
518
            Self::encode_int(slf, obj)
1,160✔
519
        } else if let Ok(obj) = obj.cast::<PyFloat>() {
11,256✔
520
            Self::encode_float(slf, obj)
6,008✔
521
        } else if let Ok(obj) = obj.cast::<PyComplex>() {
5,248✔
522
            Self::encode_complex(slf, obj)
72✔
523
        } else if let Ok(obj) = obj.cast::<PyByteArray>() {
5,176✔
524
            Self::encode_bytearray(slf, obj)
100✔
525
        } else if obj.is_none() {
5,076✔
526
            Self::encode_none(slf)
532✔
527
        } else if obj.is_exact_instance_of::<UndefinedType>() {
4,544✔
528
            Self::encode_undefined(slf)
12✔
529
        } else if let Ok(map) = obj.cast::<PyMapping>() {
4,532✔
530
            Self::encode_map(slf, map)
1,428✔
531
        } else if let Ok(sequence) = obj.cast::<PySequence>() {
3,104✔
532
            Self::encode_array(slf, sequence)
1,376✔
533
        } else if let Ok(sequence) = obj.cast::<PySet>() {
1,728✔
534
            Self::encode_set(slf, sequence)
100✔
535
        } else if let Ok(sequence) = obj.cast::<PyFrozenSet>() {
1,628✔
536
            Self::encode_frozenset(slf, sequence)
120✔
537
        } else if let Ok(simple_value) = obj.cast::<CBORSimpleValue>() {
1,508✔
538
            Self::encode_simple_value(slf, simple_value)
60✔
539
        } else if let Ok(tag) = obj.cast::<CBORTag>() {
1,448✔
540
            let tag = tag.borrow();
108✔
541
            Self::encode_semantic(slf, tag.tag, tag.value.bind(py))
108✔
542
        } else {
543
            let obj_type = obj.get_type();
1,340✔
544
            let stdlib_encoders =
1,340✔
545
                STDLIB_ENCODERS.get_or_try_init(py, || -> PyResult<EncoderLookupVec> {
1,340✔
546
                    Ok(vec![
12✔
547
                        (
548
                            py.import("datetime")?
12✔
549
                                .getattr("datetime")?
12✔
550
                                .cast_into()?
12✔
551
                                .unbind(),
12✔
552
                            CBOREncoder::encode_datetime,
12✔
553
                        ),
554
                        (
555
                            py.import("datetime")?
12✔
556
                                .getattr("date")?
12✔
557
                                .cast_into()?
12✔
558
                                .unbind(),
12✔
559
                            CBOREncoder::encode_date,
12✔
560
                        ),
561
                        (
562
                            py.import("decimal")?
12✔
563
                                .getattr("Decimal")?
12✔
564
                                .cast_into()?
12✔
565
                                .unbind(),
12✔
566
                            CBOREncoder::encode_decimal,
12✔
567
                        ),
568
                        (
569
                            py.import("fractions")?
12✔
570
                                .getattr("Fraction")?
12✔
571
                                .cast_into()?
12✔
572
                                .unbind(),
12✔
573
                            CBOREncoder::encode_rational,
12✔
574
                        ),
575
                        (
576
                            py.import("uuid")?.getattr("UUID")?.cast_into()?.unbind(),
12✔
577
                            CBOREncoder::encode_uuid,
12✔
578
                        ),
579
                        (
580
                            py.import("re")?.getattr("Pattern")?.cast_into()?.unbind(),
12✔
581
                            CBOREncoder::encode_regexp,
12✔
582
                        ),
583
                        (
584
                            py.import("ipaddress")?
12✔
585
                                .getattr("IPv4Address")?
12✔
586
                                .cast_into()?
12✔
587
                                .unbind(),
12✔
588
                            CBOREncoder::encode_ipv4_address,
12✔
589
                        ),
590
                        (
591
                            py.import("ipaddress")?
12✔
592
                                .getattr("IPv4Network")?
12✔
593
                                .cast_into()?
12✔
594
                                .unbind(),
12✔
595
                            CBOREncoder::encode_ipv4_network,
12✔
596
                        ),
597
                        (
598
                            py.import("ipaddress")?
12✔
599
                                .getattr("IPv4Interface")?
12✔
600
                                .cast_into()?
12✔
601
                                .unbind(),
12✔
602
                            CBOREncoder::encode_ipv4_interface,
12✔
603
                        ),
604
                        (
605
                            py.import("ipaddress")?
12✔
606
                                .getattr("IPv6Address")?
12✔
607
                                .cast_into()?
12✔
608
                                .unbind(),
12✔
609
                            CBOREncoder::encode_ipv6_address,
12✔
610
                        ),
611
                        (
612
                            py.import("ipaddress")?
12✔
613
                                .getattr("IPv6Network")?
12✔
614
                                .cast_into()?
12✔
615
                                .unbind(),
12✔
616
                            CBOREncoder::encode_ipv6_network,
12✔
617
                        ),
618
                        (
619
                            py.import("ipaddress")?
12✔
620
                                .getattr("IPv6Interface")?
12✔
621
                                .cast_into()?
12✔
622
                                .unbind(),
12✔
623
                            CBOREncoder::encode_ipv6_interface,
12✔
624
                        ),
625
                        (
626
                            py.import("email.mime.text")?
12✔
627
                                .getattr("MIMEText")?
12✔
628
                                .cast_into()?
12✔
629
                                .unbind(),
12✔
630
                            CBOREncoder::encode_mime,
12✔
631
                        ),
632
                    ])
633
                })?;
12✔
634
            for (pytype, callback) in stdlib_encoders {
5,944✔
635
                if obj_type.is(pytype) {
5,944✔
636
                    return callback(slf, obj);
1,244✔
637
                }
4,700✔
638
            }
639

640
            let default = slf.borrow().default.as_ref().map(|d| d.clone_ref(py));
96✔
641
            if let Some(default) = default {
96✔
642
                default.call1(py, (slf, obj)).map(|_| ())
84✔
643
            } else {
644
                Err(CBOREncodeError::new_err(format!(
12✔
645
                    "cannot encode type {obj_type}"
12✔
646
                )))
12✔
647
            }
648
        }
649
    }
15,992✔
650

651
    /// Encode the given object using CBOR.
652
    ///
653
    /// :param obj: the object to encode
654
    #[pyo3(signature = (obj, /))]
655
    pub fn encode(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
4,924✔
656
        slf.borrow_mut().encode_depth += 1;
4,924✔
657

658
        Self::encode_value(slf, obj)?;
4,924✔
659

660
        let mut this = slf.borrow_mut();
4,876✔
661
        this.encode_depth -= 1;
4,876✔
662
        if this.encode_depth == 0 {
4,876✔
663
            this.flush(slf.py())?;
2,916✔
664
            this.shared_containers.clear();
2,916✔
665
            this.string_references.clear();
2,916✔
666
            this.bytes_references.clear();
2,916✔
667
        }
1,960✔
668
        Ok(())
4,876✔
669
    }
4,924✔
670

671
    /// Encode the given object to a byte buffer and return its value as bytes.
672
    ///
673
    /// This method was intended to be used from the ``default`` hook when an
674
    /// object needs to be encoded separately from the rest but while still
675
    /// taking advantage of the shared value registry.
676
    ///
677
    /// :param obj: the object to encode
678
    #[pyo3(signature = (obj, /))]
679
    pub fn encode_to_bytes<'py>(
360✔
680
        slf: &Bound<'py, Self>,
360✔
681
        obj: &Bound<'py, PyAny>,
360✔
682
    ) -> PyResult<Vec<u8>> {
360✔
683
        let py = slf.py();
360✔
684
        let mut this = slf.borrow_mut();
360✔
685
        let mut write_method: Option<Py<PyAny>> = None;
360✔
686
        let mut buffer: Vec<u8> = Vec::new();
360✔
687
        swap(&mut this.write_method, &mut write_method);
360✔
688
        swap(&mut this.buffer, &mut buffer);
360✔
689
        drop(this);
360✔
690

691
        let result = Self::encode(slf, obj);
360✔
692

693
        this = slf.borrow_mut();
360✔
694
        this.flush(py)?;
360✔
695
        swap(&mut this.write_method, &mut write_method);
360✔
696
        swap(&mut this.buffer, &mut buffer);
360✔
697
        result.map(|_| buffer)
360✔
698
    }
360✔
699

700
    /// Takes a key and calculates the length of its optimal byte
701
    /// representation, along with the representation itself.
702
    /// This is used as the sorting key in CBOR's canonical representations.
703
    fn encode_sortable_key<'py>(
312✔
704
        slf: &Bound<'py, Self>,
312✔
705
        key: &Bound<'py, PyAny>,
312✔
706
    ) -> PyResult<(usize, Bound<'py, PyAny>)> {
312✔
707
        Self::disable_string_referencing(slf, || {
312✔
708
            let encoded = Self::encode_to_bytes(slf, key)?;
312✔
709
            let py_bytes = PyBytes::new(slf.py(), encoded.as_slice());
312✔
710
            Ok((encoded.len(), py_bytes.into_any()))
312✔
711
        })
312✔
712
    }
312✔
713

714
    /// Takes a (key, value) tuple and calculates the length of its optimal byte
715
    /// representation, along with the representation itself.
716
    /// This is used as the sorting key in CBOR's canonical representations.
717
    ///
718
    /// :param item: a (key, value) tuple
719
    fn encode_sortable_item<'py>(
216✔
720
        slf: &Bound<'py, Self>,
216✔
721
        item: &Bound<'py, PyTuple>,
216✔
722
    ) -> PyResult<(usize, Bound<'py, PyAny>)> {
216✔
723
        let key = item.get_item(0)?;
216✔
724
        Self::encode_sortable_key(slf, &key)
216✔
725
    }
216✔
726

727
    fn encode_length(
9,184✔
728
        &mut self,
9,184✔
729
        py: Python<'_>,
9,184✔
730
        major_tag: u8,
9,184✔
731
        length: Option<u64>,
9,184✔
732
    ) -> PyResult<()> {
9,184✔
733
        let major_tag = major_tag << 5;
9,184✔
734
        match length {
9,184✔
735
            Some(len) => match len {
9,112✔
736
                ..24 => self.write_byte(py, major_tag | len as u8),
9,112✔
737
                24..256 => {
2,244✔
738
                    self.write_byte(py, major_tag | 24)?;
1,368✔
739
                    self.write_internal(py, (len as u8).to_be_bytes().to_vec())
1,368✔
740
                }
741
                256..65536 => {
876✔
742
                    self.write_byte(py, major_tag | 25)?;
616✔
743
                    self.write_internal(py, (len as u16).to_be_bytes().to_vec())
616✔
744
                }
745
                65536..=4294967295 => {
260✔
746
                    self.write_byte(py, major_tag | 26)?;
132✔
747
                    self.write_internal(py, (len as u32).to_be_bytes().to_vec())
132✔
748
                }
749
                _ => {
750
                    self.write_byte(py, major_tag | 27)?;
128✔
751
                    self.write_internal(py, len.to_be_bytes().to_vec())
128✔
752
                }
753
            },
754
            None => {
755
                // Indefinite
756
                self.write_byte(py, major_tag | 31)
72✔
757
            }
758
        }
759
    }
9,184✔
760

761
    fn encode_string(slf: &Bound<'_, Self>, obj: &Bound<'_, PyString>) -> PyResult<()> {
2,372✔
762
        let py = slf.py();
2,372✔
763
        let string_referencing = slf.borrow().string_referencing;
2,372✔
764

765
        // If string referencing is enabled, check if this string already has an index,
766
        // and emit a string reference instead if it does
767
        if string_referencing && Self::maybe_stringref(slf, obj)? {
2,372✔
768
            return Ok(());
108✔
769
        }
2,264✔
770

771
        let mut this = slf.borrow_mut();
2,264✔
772
        let encoded = obj.to_str()?.as_bytes();
2,264✔
773
        this.encode_length(py, 3, Some(encoded.len() as u64))?;
2,264✔
774
        this.write_internal(py, encoded.to_vec())
2,264✔
775
    }
2,372✔
776

777
    fn encode_bytes(slf: &Bound<'_, Self>, obj: &Bound<'_, PyBytes>) -> PyResult<()> {
956✔
778
        let py = slf.py();
956✔
779
        let string_referencing = slf.borrow().string_referencing;
956✔
780

781
        // If string referencing is enabled, check if this string already has an index,
782
        // and emit a string reference instead if it does
783
        if string_referencing && Self::maybe_stringref(slf, obj)? {
956✔
UNCOV
784
            return Ok(());
×
785
        }
956✔
786

787
        let mut this = slf.borrow_mut();
956✔
788
        let bytes = obj.as_bytes();
956✔
789
        this.encode_length(py, 2, Some(bytes.len() as u64))?;
956✔
790
        this.write_internal(py, bytes.to_vec())
956✔
791
    }
956✔
792

793
    fn encode_bytearray(slf: &Bound<'_, Self>, obj: &Bound<'_, PyByteArray>) -> PyResult<()> {
100✔
794
        let py = slf.py();
100✔
795
        let mut this = slf.borrow_mut();
100✔
796
        this.encode_length(py, 2, Some(obj.len() as u64))?;
100✔
797
        this.write_internal(py, obj.to_vec())
100✔
798
    }
100✔
799

800
    fn encode_array(slf: &Bound<'_, Self>, obj: &Bound<'_, PySequence>) -> PyResult<()> {
1,376✔
801
        Self::encode_container(slf, obj, || {
1,376✔
802
            let indefinite_containers = slf.borrow().indefinite_containers;
1,316✔
803
            slf.borrow_mut().encode_length(
1,316✔
804
                slf.py(),
1,316✔
805
                4,
806
                if !indefinite_containers {
1,316✔
807
                    Some(obj.len()? as u64)
1,292✔
808
                } else {
809
                    None
24✔
810
                },
UNCOV
811
            )?;
×
812

813
            for value in obj.try_iter()? {
7,884✔
814
                Self::encode_value(slf, &value?)?;
7,884✔
815
            }
816

817
            if indefinite_containers {
1,304✔
818
                Self::encode_break(slf)?;
24✔
819
            }
1,280✔
820
            Ok(())
1,304✔
821
        })
1,316✔
822
    }
1,376✔
823

824
    fn encode_map(slf: &Bound<'_, Self>, obj: &Bound<'_, PyMapping>) -> PyResult<()> {
1,428✔
825
        Self::encode_container(slf, obj, || {
1,428✔
826
            let py = slf.py();
1,404✔
827
            let indefinite_containers = slf.borrow().indefinite_containers;
1,404✔
828
            slf.borrow_mut().encode_length(
1,404✔
829
                py,
1,404✔
830
                5,
831
                if !indefinite_containers {
1,404✔
832
                    Some(obj.len()? as u64)
1,380✔
833
                } else {
834
                    None
24✔
835
                },
UNCOV
836
            )?;
×
837

838
            let mut iterator = obj.call_method0("items")?.try_iter()?;
1,404✔
839
            if slf.borrow().canonical {
1,404✔
840
                // Reorder keys according to Canonical CBOR specification where they're sorted
841
                // by the length of the CBOR encoded value first, and only then by the lexical order
842
                let kwargs = PyDict::new(py);
120✔
843
                kwargs.set_item("key", slf.getattr("encode_sortable_item")?)?;
120✔
844
                iterator = SORTED_FUNC
120✔
845
                    .get(py)?
120✔
846
                    .call((iterator,), Some(&kwargs))?
120✔
847
                    .try_iter()?;
120✔
848
            }
1,284✔
849
            for item in iterator {
1,592✔
850
                let (key, value): (Bound<'_, PyAny>, Bound<'_, PyAny>) = item?.extract()?;
1,592✔
851
                Self::encode_value(slf, &key)?;
1,592✔
852
                Self::encode_value(slf, &value)?;
1,592✔
853
            }
854

855
            if indefinite_containers {
1,392✔
856
                Self::encode_break(slf)?
24✔
857
            }
1,368✔
858
            Ok(())
1,392✔
859
        })
1,404✔
860
    }
1,428✔
861

862
    fn encode_break(slf: &Bound<'_, Self>) -> PyResult<()> {
60✔
863
        // Break stop code for indefinite containers
864
        slf.borrow_mut().write_byte(slf.py(), 0xff)
60✔
865
    }
60✔
866

867
    fn encode_int(slf: &Bound<'_, Self>, obj: &Bound<'_, PyInt>) -> PyResult<()> {
1,232✔
868
        let py = slf.py();
1,232✔
869
        if obj.ge(18446744073709551616_i128)? {
1,232✔
870
            let (_, payload) = obj.extract::<BigInt>()?.to_bytes_be();
12✔
871
            let py_payload = PyBytes::new(py, &payload);
12✔
872
            Self::encode_semantic(slf, 2, py_payload.as_any())
12✔
873
        } else if obj.lt(-18446744073709551616_i128)? {
1,220✔
874
            let mut value = obj.extract::<BigInt>()?;
12✔
875
            value = -value - 1;
12✔
876
            let (_, payload) = value.to_bytes_be();
12✔
877
            let py_payload = PyBytes::new(py, &payload);
12✔
878
            Self::encode_semantic(slf, 3, py_payload.as_any())
12✔
879
        } else if obj.ge(0)? {
1,208✔
880
            let value: u64 = obj.extract()?;
948✔
881
            slf.borrow_mut().encode_length(py, 0, Some(value))
948✔
882
        } else {
883
            let value = obj.add(1)?.abs()?.extract::<u64>()?;
260✔
884
            slf.borrow_mut().encode_length(py, 1, Some(value))
260✔
885
        }
886
    }
1,232✔
887

888
    fn encode_bool(slf: &Bound<'_, Self>, obj: &Bound<'_, PyBool>) -> PyResult<()> {
248✔
889
        slf.borrow_mut()
248✔
890
            .write_byte(slf.py(), if obj.is_true() { b'\xf5' } else { b'\xf4' })
248✔
891
    }
248✔
892

893
    fn encode_none(slf: &Bound<'_, Self>) -> PyResult<()> {
532✔
894
        slf.borrow_mut().write_byte(slf.py(), b'\xf6')
532✔
895
    }
532✔
896

897
    fn encode_undefined(slf: &Bound<'_, Self>) -> PyResult<()> {
12✔
898
        slf.borrow_mut().write_byte(slf.py(), b'\xf7')
12✔
899
    }
12✔
900

901
    /// Encode a value with a semantic tag.
902
    ///
903
    /// :param tag: a numeric tag value
904
    /// :param value: the object to be encoded
905
    fn encode_semantic(slf: &Bound<'_, Self>, tag: u64, value: &Bound<'_, PyAny>) -> PyResult<()> {
1,552✔
906
        let old_string_referencing = slf.borrow().string_referencing;
1,552✔
907
        if tag == 256 {
1,552✔
UNCOV
908
            let mut this = slf.borrow_mut();
×
909
            this.string_referencing = true;
×
910

×
911
            // TODO: move the string/bytestring references here temporarily
×
912
        }
1,552✔
913
        let mut result = slf.borrow_mut().encode_length(slf.py(), 6, Some(tag));
1,552✔
914
        if result.is_ok() {
1,552✔
915
            result = Self::encode(slf, value);
1,552✔
916
        }
1,552✔
917
        slf.borrow_mut().string_referencing = old_string_referencing;
1,552✔
918
        // TODO: restore the string/bytestring references to the instance
919
        result
1,552✔
920
    }
1,552✔
921

922
    fn encode_set(slf: &Bound<'_, Self>, obj: &Bound<'_, PySet>) -> PyResult<()> {
100✔
923
        // Semantic tag 258
924
        if slf.borrow().canonical {
100✔
925
            let py = slf.py();
12✔
926
            let kwargs = PyDict::new(py);
12✔
927
            kwargs.set_item("key", slf.getattr("encode_sortable_key")?)?;
12✔
928
            let list = SORTED_FUNC.get(py)?.call((obj,), Some(&kwargs))?;
12✔
929
            Self::encode_semantic(slf, 258, list.as_any())
12✔
930
        } else {
931
            let tuple = PyTuple::new(slf.py(), obj)?;
88✔
932
            Self::encode_semantic(slf, 258, tuple.as_any())
88✔
933
        }
934
    }
100✔
935

936
    fn encode_frozenset(slf: &Bound<'_, Self>, obj: &Bound<'_, PyFrozenSet>) -> PyResult<()> {
120✔
937
        // Semantic tag 258
938
        if slf.borrow().canonical {
120✔
939
            let py = slf.py();
12✔
940
            let kwargs = PyDict::new(py);
12✔
941
            kwargs.set_item("key", slf.getattr("encode_sortable_key")?)?;
12✔
942
            let list = SORTED_FUNC.get(py)?.call((obj,), Some(&kwargs))?;
12✔
943
            Self::encode_semantic(slf, 258, list.as_any())
12✔
944
        } else {
945
            let tuple = PyTuple::new(slf.py(), obj)?;
108✔
946
            Self::encode_semantic(slf, 258, tuple.as_any())
108✔
947
        }
948
    }
120✔
949

950
    //
951
    // Semantic decoders (major tag 6)
952
    //
953

954
    fn encode_datetime(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
436✔
955
        let py = slf.py();
436✔
956

957
        let inner_encode_datetime = |aware_datetime: &Bound<'_, PyAny>| -> PyResult<()> {
436✔
958
            let datetime_as_timestamp = slf.borrow().datetime_as_timestamp;
424✔
959
            match datetime_as_timestamp {
424✔
960
                false => {
961
                    let formatted = aware_datetime
388✔
962
                        .call_method0(intern!(py, "isoformat"))?
388✔
963
                        .call_method1(
388✔
964
                            intern!(py, "replace"),
388✔
965
                            (intern!(py, "+00:00"), intern!(py, "Z")),
388✔
UNCOV
966
                        )?;
×
967
                    Self::encode_semantic(slf, 0, formatted.as_any())
388✔
968
                }
969
                true => {
970
                    let py_timestamp = aware_datetime.call_method0(intern!(py, "timestamp"))?;
36✔
971

972
                    // If the timestamp can be converted to an integer without loss, encode that
973
                    // integer instead
974
                    let timestamp_float: f64 = py_timestamp.extract()?;
36✔
975
                    let timestamp_int: u32 = timestamp_float as u32;
36✔
976
                    let arg: Bound<'_, PyAny> = if timestamp_int as f64 == timestamp_float {
36✔
977
                        PyInt::new(py, timestamp_int).into_any()
24✔
978
                    } else {
979
                        py_timestamp
12✔
980
                    };
981
                    Self::encode_semantic(slf, 1, &arg)
36✔
982
                }
983
            }
984
        };
424✔
985

986
        if obj.getattr("tzinfo")?.is_none() {
436✔
987
            // value is a naive datetime (no time zone)
988
            let timezone = slf.borrow().timezone.as_ref().map(|tz| tz.clone_ref(py));
36✔
989
            match timezone {
36✔
990
                Some(timezone) => {
24✔
991
                    let kwargs = PyDict::new(py);
24✔
992
                    kwargs.set_item("tzinfo", timezone)?;
24✔
993
                    let value = obj.call_method("replace", (), Some(&kwargs))?;
24✔
994
                    inner_encode_datetime(&value)
24✔
995
                }
996
                None => Err(CBOREncodeError::new_err(
12✔
997
                    "naive datetime encountered and no default timezone has been set",
12✔
998
                )),
12✔
999
            }
1000
        } else {
1001
            inner_encode_datetime(obj)
400✔
1002
        }
1003
    }
436✔
1004

1005
    fn encode_date(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
36✔
1006
        // Semantic tag 100
1007
        let (date_as_datetime, datetime_as_timestamp) = {
36✔
1008
            let this = slf.borrow();
36✔
1009
            (this.date_as_datetime, this.datetime_as_timestamp)
36✔
1010
        };
36✔
1011

1012
        if date_as_datetime {
36✔
1013
            // Encode a datetime with a zeroed-out time portion
1014
            let py = slf.py();
12✔
1015
            let time_zero = ZERO_TIME.get_or_try_init(py, || {
12✔
1016
                Ok::<_, PyErr>(py.import("datetime")?.getattr("time")?.call0()?.unbind())
12✔
1017
            })?;
12✔
1018
            let value = DATETIME_COMBINE_FUNC.get(py)?.call1((obj, time_zero))?;
12✔
1019
            Self::encode_datetime(slf, &value)
12✔
1020
        } else if datetime_as_timestamp {
24✔
1021
            // Encode a date as a number of days since the Unix epoch
1022
            // The baseline has to be adjusted as date.toordinal() returns the number of days from
1023
            // the beginning of the ISO calendar
1024
            let days_since_epoch: i32 = obj.call_method0("toordinal")?.extract()?;
12✔
1025
            let adjusted_delta = PyInt::new(slf.py(), days_since_epoch - 719163);
12✔
1026
            Self::encode_semantic(slf, 100, &adjusted_delta)
12✔
1027
        } else {
1028
            let datestring = obj.call_method0("isoformat")?;
12✔
1029
            Self::encode_semantic(slf, 1004, &datestring)
12✔
1030
        }
1031
    }
36✔
1032

1033
    fn encode_rational(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
64✔
1034
        // Semantic tag 30
1035
        let numerator = obj.getattr("numerator")?;
64✔
1036
        let denominator = obj.getattr("denominator")?;
64✔
1037
        Self::disable_value_sharing(slf, || {
64✔
1038
            let tuple = PyTuple::new(slf.py(), &[numerator, denominator])?;
64✔
1039
            Self::encode_semantic(slf, 30, &tuple)
64✔
1040
        })
64✔
1041
    }
64✔
1042

1043
    fn encode_regexp(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
12✔
1044
        // Semantic tag 35
1045
        let pattern = obj.getattr("pattern")?;
12✔
1046
        Self::encode_semantic(slf, 35, pattern.str()?.as_any())
12✔
1047
    }
12✔
1048

1049
    fn encode_mime(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
12✔
1050
        // Semantic tag 36
1051
        let string = obj.call_method0("as_string")?;
12✔
1052
        Self::encode_semantic(slf, 36, &string)
12✔
1053
    }
12✔
1054

1055
    fn encode_uuid(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
164✔
1056
        // Semantic tag 37
1057
        let bytes = obj.getattr("bytes")?;
164✔
1058
        Self::encode_semantic(slf, 37, &bytes)
164✔
1059
    }
164✔
1060

1061
    fn encode_decimal(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
288✔
1062
        if obj.call_method0("is_nan")?.is_truthy()? {
288✔
1063
            slf.borrow_mut()
12✔
1064
                .write_internal(slf.py(), vec![0xf9, 0x7e, 0x00])
12✔
1065
        } else if obj.call_method0("is_infinite")?.is_truthy()? {
276✔
1066
            let signed = obj.call_method0("is_signed")?.is_truthy()?;
200✔
1067
            let middle = if signed { 0xfc } else { 0x7c };
200✔
1068
            slf.borrow_mut()
200✔
1069
                .write_internal(slf.py(), vec![0xf9, middle, 0x00])
200✔
1070
        } else {
1071
            let py = slf.py();
76✔
1072
            let decimal: BigDecimal = obj.extract()?;
76✔
1073
            let (digits, exp) = decimal.as_bigint_and_exponent();
76✔
1074
            let py_exp = (-exp).into_bound_py_any(py)?;
76✔
1075
            let py_digits = digits.into_bound_py_any(py)?;
76✔
1076
            let parts = PyTuple::new(py, &[py_exp, py_digits])?;
76✔
1077
            Self::encode_semantic(slf, 4, &parts)
76✔
1078
        }
1079
    }
288✔
1080

1081
    fn encode_ipv4_address(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
136✔
1082
        // Semantic tag 52
1083
        Self::encode_semantic(slf, 52, &obj.getattr("packed")?)
136✔
1084
    }
136✔
1085

1086
    fn encode_ipv4_network(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
12✔
1087
        // Semantic tag 52
1088
        let packed_addr = obj
12✔
1089
            .getattr("network_address")?
12✔
1090
            .getattr("packed")?
12✔
1091
            .call_method1("rstrip", (b"\x00",))?;
12✔
1092
        let prefixlen = obj.getattr("prefixlen")?;
12✔
1093
        let elements = PyTuple::new(slf.py(), &[prefixlen, packed_addr])?;
12✔
1094
        Self::encode_semantic(slf, 52, &elements)
12✔
1095
    }
12✔
1096

1097
    fn encode_ipv4_interface(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
12✔
1098
        // Semantic tag 52
1099
        let packed_addr = obj.getattr("packed")?;
12✔
1100
        let prefixlen = obj.getattr("network")?.getattr("prefixlen")?;
12✔
1101
        let elements = PyTuple::new(slf.py(), [packed_addr, prefixlen])?;
12✔
1102
        Self::encode_semantic(slf, 52, &elements)
12✔
1103
    }
12✔
1104

1105
    fn encode_ipv6_address(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
60✔
1106
        // Semantic tag 54
1107
        let packed_addr = obj.getattr("packed")?;
60✔
1108
        let scope_id = obj.getattr("scope_id")?;
60✔
1109
        if scope_id.is_none() {
60✔
1110
            Self::encode_semantic(slf, 54, &obj.getattr("packed")?)
60✔
1111
        } else {
1112
            // Scoped (addr, prefixlen, scope ID)
UNCOV
1113
            let scope_id = scope_id.str()?;
×
1114
            let none = PyNone::get(slf.py());
×
1115
            let elements = PyTuple::new(
×
1116
                slf.py(),
×
1117
                [&packed_addr, &none, &scope_id.encode_utf8()?.into_any()],
×
1118
            )?;
×
1119
            Self::encode_semantic(slf, 54, &elements)
×
1120
        }
1121
    }
60✔
1122

1123
    fn encode_ipv6_network(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
12✔
1124
        // Semantic tag 54
1125
        let py = slf.py();
12✔
1126
        let packed_addr = obj
12✔
1127
            .getattr("network_address")?
12✔
1128
            .getattr("packed")?
12✔
1129
            .call_method1("rstrip", (b"\x00",))?;
12✔
1130
        let prefixlen = obj.getattr("prefixlen")?;
12✔
1131
        let elements = PyTuple::new(py, [prefixlen, packed_addr])?;
12✔
1132
        Self::encode_semantic(slf, 54, &elements)
12✔
1133
    }
12✔
1134

1135
    fn encode_ipv6_interface(slf: &Bound<'_, Self>, obj: &Bound<'_, PyAny>) -> PyResult<()> {
12✔
1136
        // Semantic tag 54
1137
        let packed_addr = obj.getattr("packed")?;
12✔
1138
        let prefixlen = obj.getattr("network")?.getattr("prefixlen")?;
12✔
1139
        let scope_id = obj.getattr("scope_id")?;
12✔
1140
        let elements = PyList::new(slf.py(), [packed_addr, prefixlen])?;
12✔
1141
        if !scope_id.is_none() {
12✔
1142
            elements.append(scope_id.cast_into::<PyString>()?.encode_utf8()?)?;
12✔
UNCOV
1143
        }
×
1144
        Self::encode_semantic(slf, 54, &elements)
12✔
1145
    }
12✔
1146

1147
    //
1148
    // Special encoders (major tag 7)
1149
    //
1150

1151
    fn encode_simple_value(
60✔
1152
        slf: &Bound<'_, Self>,
60✔
1153
        obj: &Bound<'_, CBORSimpleValue>,
60✔
1154
    ) -> PyResult<()> {
60✔
1155
        let py = slf.py();
60✔
1156
        let value = obj.get().0;
60✔
1157
        if value < 24 {
60✔
1158
            slf.borrow_mut().write_byte(py, 0xe0 | value)
36✔
1159
        } else {
1160
            slf.borrow_mut().write_internal(py, vec![0xf8, value])
24✔
1161
        }
1162
    }
60✔
1163

1164
    fn encode_float(slf: &Bound<'_, Self>, obj: &Bound<'_, PyFloat>) -> PyResult<()> {
6,008✔
1165
        let py = slf.py();
6,008✔
1166
        let value = obj.extract::<f64>()?;
6,008✔
1167
        if value.is_nan() {
6,008✔
1168
            slf.borrow_mut().write_internal(py, vec![0xf9, 0x7e, 0x00])
36✔
1169
        } else if value.is_infinite() {
5,972✔
1170
            let middle = if value.is_sign_positive() { 0x7c } else { 0xfc };
96✔
1171
            slf.borrow_mut()
96✔
1172
                .write_internal(py, vec![0xf9, middle, 0x00])
96✔
1173
        } else {
1174
            if slf.borrow().canonical {
5,876✔
1175
                // Find the shortest form that did not lose precision with the cast
1176
                let value_32 = value as f32;
84✔
1177
                if value_32 as f64 == value {
84✔
1178
                    let value_16 = f16::from_f32(value_32);
60✔
1179
                    return if value_16.to_f32() == value_32 {
60✔
1180
                        slf.borrow_mut().write_byte(py, 0xf9)?;
24✔
1181
                        slf.borrow_mut()
24✔
1182
                            .write_internal(py, value_16.to_be_bytes().to_vec())
24✔
1183
                    } else {
1184
                        slf.borrow_mut().write_byte(py, 0xfa)?;
36✔
1185
                        slf.borrow_mut()
36✔
1186
                            .write_internal(py, value_32.to_be_bytes().to_vec())
36✔
1187
                    };
1188
                }
24✔
1189
            }
5,792✔
1190
            slf.borrow_mut().write_byte(py, 0xfb)?;
5,816✔
1191
            slf.borrow_mut()
5,816✔
1192
                .write_internal(py, value.to_be_bytes().to_vec())
5,816✔
1193
        }
1194
    }
6,008✔
1195

1196
    fn encode_complex(slf: &Bound<'_, Self>, obj: &Bound<'_, PyComplex>) -> PyResult<()> {
72✔
1197
        let tuple = PyTuple::new(slf.py(), [obj.real(), obj.imag()])?;
72✔
1198
        Self::encode_semantic(slf, 43000, tuple.as_any())
72✔
1199
    }
72✔
1200
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc