Skip to content

Commit 85a9fbf

Browse files
authored
Add support for CBOR sequences
Reviewed-on: #26
2 parents b32c831 + 84377ba commit 85a9fbf

6 files changed

Lines changed: 105 additions & 17 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ name = "_cbor_diag"
1515
crate-type = ["cdylib", "rlib"]
1616

1717
[dependencies]
18-
cbor-edn = { version = "0.0.8", default-features = false }
18+
cbor-edn = { version = "0.0.10", default-features = false }
1919
pyo3 = "0.28.0"
2020
# Not using the infer_signature feature as that is incompatible with pypy.
2121
#

doctest.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ src/lib.rs. They are manually extracted until I've figured out why `pytest
1212
{1: 'hello'}
1313
>>> cbor2.loads(diag2cbor("[1, spam'eggs']", to999=True))
1414
[1, CBORTag(999, ['spam', 'eggs'])]
15+
>>> diag2cbor("1, 2, 3", seq=True)
16+
b'\x01\x02\x03'
1517
>>> from cbor_diag import *
1618
>>> encoded = bytes.fromhex('a1016568656c6c6f')
1719
>>> cbor2diag(encoded)
@@ -23,5 +25,10 @@ src/lib.rs. They are manually extracted until I've figured out why `pytest
2325
'1(5)'
2426
>>> cbor2diag(cbor2.dumps([1, 2]), pretty=False)
2527
'[1,2]'
28+
>>> print(cbor2diag(b'\x01\x02\x03', seq=True))
29+
1,
30+
2,
31+
3
32+
<BLANKLINE>
2633
>>> cbor2diag(bytes.fromhex("d9 03e7 82 63 666f6f 63 626172"), from999=True)
2734
"foo'bar'"

python/cbor_diag/__init__.pyi

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ __all__ = [
77
"diag2cbor",
88
]
99

10-
def cbor2diag(encoded: bytes, *, pretty: builtins.bool = ..., from999: builtins.bool = ...) -> builtins.str:
10+
def cbor2diag(encoded: bytes, *, pretty: builtins.bool = ..., from999: builtins.bool = ..., seq: builtins.bool = ...) -> builtins.str:
1111
r"""
1212
Given a byte string containing encoded CBOR, produce some diagnostic notation.
1313
@@ -32,6 +32,15 @@ def cbor2diag(encoded: bytes, *, pretty: builtins.bool = ..., from999: builtins.
3232
>>> cbor2diag(cbor2.dumps([1, 2]), pretty=False)
3333
'[1,2]'
3434
35+
* With `seq=True`, [CBOR sequences](https://datatracker.ietf.org/doc/html/rfc8742)
36+
are tolerated:
37+
38+
>>> print(cbor2diag('\x01\x02\x03', seq=True))
39+
1,
40+
2,
41+
3
42+
<BLANKLINE>
43+
3544
* With ``from999=True``, CBOR tag 999 will be rendered as application oriented literal. Unlike
3645
other tags, this does not happen by default, as that tag is not intended to be used that way
3746
by default.
@@ -40,7 +49,7 @@ def cbor2diag(encoded: bytes, *, pretty: builtins.bool = ..., from999: builtins.
4049
"foo'bar'"
4150
"""
4251

43-
def diag2cbor(diagnostic: builtins.str, *, to999: builtins.bool = ...) -> bytes:
52+
def diag2cbor(diagnostic: builtins.str, *, to999: builtins.bool = ..., seq: builtins.bool = ...) -> bytes:
4453
r"""
4554
Given a string in CBOR diagnostic notation, produce its CBOR binary encoding.
4655
@@ -60,5 +69,11 @@ def diag2cbor(diagnostic: builtins.str, *, to999: builtins.bool = ...) -> bytes:
6069
6170
>>> cbor2.loads(diag2cbor("[1, spam'eggs']", to999=True))
6271
[1, CBORTag(999, ['spam', 'eggs'])]
72+
73+
* With `seq=True`, [CBOR sequences](https://datatracker.ietf.org/doc/html/rfc8742)
74+
are tolerated:
75+
76+
>>> diag2cbor("1, 2, 3", seq=True)
77+
'\x01\x02\x03'
6378
"""
6479

src/lib.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@ use pyo3_stub_gen::{define_stub_info_gatherer, derive::gen_stub_pyfunction};
2020
///
2121
/// >>> cbor2.loads(diag2cbor("[1, spam'eggs']", to999=True))
2222
/// [1, CBORTag(999, ['spam', 'eggs'])]
23+
///
24+
/// * With `seq=True`, [CBOR sequences](https://datatracker.ietf.org/doc/html/rfc8742)
25+
/// are tolerated:
26+
///
27+
/// >>> diag2cbor("1, 2, 3", seq=True)
28+
/// '\x01\x02\x03'
2329
#[gen_stub_pyfunction]
24-
#[pyfunction(signature = (diagnostic, *, to999=false))]
25-
fn diag2cbor(py: Python<'_>, diagnostic: &str, to999: bool) -> PyResult<Py<PyBytes>> {
26-
let mut data = cbor_edn::StandaloneItem::parse(diagnostic)
30+
#[pyfunction(signature = (diagnostic, *, to999=false, seq=false))]
31+
fn diag2cbor(py: Python<'_>, diagnostic: &str, to999: bool, seq: bool) -> PyResult<Py<PyBytes>> {
32+
let mut data = cbor_edn::Sequence::parse(diagnostic)
2733
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{}", e)))?;
2834

35+
check_sequence_expectation(&data, seq)?;
36+
2937
data.visit_application_literals(&mut cbor_edn::application::all_aol_to_item);
3038
if to999 {
3139
data.visit_application_literals(&mut cbor_edn::application::any_aol_to_tag999);
@@ -60,23 +68,36 @@ fn diag2cbor(py: Python<'_>, diagnostic: &str, to999: bool) -> PyResult<Py<PyByt
6068
/// >>> cbor2diag(cbor2.dumps([1, 2]), pretty=False)
6169
/// '[1,2]'
6270
///
71+
/// * With `seq=True`, [CBOR sequences](https://datatracker.ietf.org/doc/html/rfc8742)
72+
/// are tolerated:
73+
///
74+
/// >>> print(cbor2diag('\x01\x02\x03', seq=True))
75+
/// 1,
76+
/// 2,
77+
/// 3
78+
/// <BLANKLINE>
79+
///
6380
/// * With ``from999=True``, CBOR tag 999 will be rendered as application oriented literal. Unlike
6481
/// other tags, this does not happen by default, as that tag is not intended to be used that way
6582
/// by default.
6683
///
6784
/// >>> cbor2diag(bytes.fromhex("d9 03e7 82 63 666f6f 63 626172"), from999=True)
6885
/// "foo'bar'"
6986
#[gen_stub_pyfunction]
70-
#[pyfunction(signature = (encoded, *, pretty=true, from999=false))]
87+
#[pyfunction(signature = (encoded, *, pretty=true, from999=false, seq=false))]
7188
fn cbor2diag(
7289
_py: Python<'_>,
7390
// Staying generic for compatibility (we do still accept a [int]), but declare just bytes.
7491
#[gen_stub(override_type(type_repr = "bytes"))] encoded: &[u8],
7592
pretty: bool,
7693
from999: bool,
94+
seq: bool,
7795
) -> PyResult<String> {
78-
let mut parsed = cbor_edn::StandaloneItem::from_cbor(encoded)
96+
let mut parsed = cbor_edn::Sequence::from_cbor(encoded)
7997
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{}", e)))?;
98+
99+
check_sequence_expectation(&parsed, seq)?;
100+
80101
if pretty {
81102
parsed.visit_tag(&mut cbor_edn::application::all_tag_prettify);
82103
}
@@ -111,6 +132,24 @@ fn cbor2diag(
111132
Ok(parsed.serialize())
112133
}
113134

135+
/// "Raises" a ValueError if the sequence is really a sequence (and not really just a single item),
136+
/// unless `seq=true` (i.e., the user *asked* for sequence handling).
137+
fn check_sequence_expectation(data: &cbor_edn::Sequence, seq: bool) -> PyResult<()> {
138+
if seq {
139+
return Ok(());
140+
}
141+
142+
let count = data.items().count();
143+
if count == 1 {
144+
Ok(())
145+
} else {
146+
Err(pyo3::exceptions::PyValueError::new_err(format!(
147+
"Expected single item, found sequence of {}",
148+
count
149+
)))
150+
}
151+
}
152+
114153
/// This provides conversion functions between CBOR's diagnostic notation (EDN) and its binary
115154
/// representation.
116155
///

tests/test_seq.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import cbor2
2+
from cbor_diag import *
3+
4+
def test_empty_c2d():
5+
try:
6+
cbor2diag(b'')
7+
except ValueError:
8+
pass
9+
else:
10+
raise RuntimeError('Empty CBOR item should have raised an error.')
11+
12+
def test_empty_d2c():
13+
try:
14+
diag2cbor('/ just a comment /')
15+
except ValueError:
16+
pass
17+
else:
18+
raise RuntimeError('Empty CBOR item should have raised an error.')
19+
20+
def test_multiple_c2d():
21+
try:
22+
cbor2diag(b'\0\0')
23+
except ValueError:
24+
pass
25+
else:
26+
raise RuntimeError('Non-singular CBOR sequence should have raised an error.')
27+
28+
def test_multiple_d2c():
29+
try:
30+
diag2cbor('0, 0')
31+
except ValueError:
32+
pass
33+
else:
34+
raise RuntimeError('Non-singular CBOR sequence should have raised an error.')

0 commit comments

Comments
 (0)