Skip to content

Commit

Permalink
Add encode error cases
Browse files Browse the repository at this point in the history
Also check the rmp-serde flatten issue
  • Loading branch information
YushiOMOTE committed Nov 21, 2020
1 parent 7157bfa commit d47bc07
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 28 deletions.
7 changes: 6 additions & 1 deletion perde-core/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ impl<'a> Serialize for WithSchema<'a> {
map.end()
}
Schema::Class(c) => {
let mut map = s.serialize_map(Some(c.ser_field_len))?;
let len = if c.flatten_dict.is_some() {
None
} else {
Some(c.ser_field_len)
};
let mut map = s.serialize_map(len)?;
serialize_fields(&self.object, &c.fields, &mut map)?;
map.end()
}
Expand Down
35 changes: 25 additions & 10 deletions perde-core/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use crate::{
use indexmap::IndexMap;
use std::collections::HashMap;

fn collect_members(mems: &IndexMap<String, FieldSchema>) -> (IndexMap<String, FieldSchema>, bool) {
fn collect_members(
mems: &IndexMap<String, FieldSchema>,
) -> (IndexMap<String, FieldSchema>, bool, usize) {
let mut has_flatten = false;
let mut skip_field_len = 0;

let mems = mems
.iter()
Expand All @@ -18,22 +21,28 @@ fn collect_members(mems: &IndexMap<String, FieldSchema>) -> (IndexMap<String, Fi
}
_ => {}
}
} else {
if field.attr.skip || field.attr.skip_serializing {
skip_field_len += 1;
}
}
let mut map = IndexMap::new();
map.insert(key.to_string(), field.clone());
map
})
.collect();

(mems, has_flatten)
(mems, has_flatten, skip_field_len)
}

fn collect_flatten_members(mems: &IndexMap<String, FieldSchema>) -> IndexMap<String, FieldSchema> {
let (mems, has_flatten) = collect_members(mems);
fn collect_flatten_members(
mems: &IndexMap<String, FieldSchema>,
) -> (IndexMap<String, FieldSchema>, usize) {
let (mems, has_flatten, skip_len) = collect_members(mems);
if has_flatten {
mems
(mems, skip_len)
} else {
IndexMap::new()
(IndexMap::new(), 0)
}
}

Expand Down Expand Up @@ -141,7 +150,7 @@ fn to_dataclass(p: &ObjectRef, attr: &Option<HashMap<&str, &ObjectRef>>) -> Resu
let fields = fields.get_tuple_iter()?;

let mut members = IndexMap::new();
let mut ser_field_len = 0;
let mut skip_field_len = 0;
let mut flatten_dict = None;

let missing = &import()?.missing;
Expand Down Expand Up @@ -174,8 +183,8 @@ fn to_dataclass(p: &ObjectRef, attr: &Option<HashMap<&str, &ObjectRef>>) -> Resu
)
};

if !fattr.skip && !fattr.skip_serializing {
ser_field_len += 1;
if (fattr.skip || fattr.skip_serializing) && !fattr.flatten {
skip_field_len += 1;
}

let schema = to_schema(ty.as_ref())?;
Expand All @@ -201,7 +210,13 @@ fn to_dataclass(p: &ObjectRef, attr: &Option<HashMap<&str, &ObjectRef>>) -> Resu

let name = p.name();
let class = p.owned();
let flatten_members = collect_flatten_members(&members);
let (flatten_members, flatten_skip_len) = collect_flatten_members(&members);

let ser_field_len = if flatten_members.is_empty() {
members.len() - skip_field_len
} else {
flatten_members.len() - flatten_skip_len
};

Ok(Schema::Class(Class::new(
class.into(),
Expand Down
16 changes: 5 additions & 11 deletions perde-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,12 @@

## Error testing

* Arguments error
* [ ] `dumps`
* [ ] `loads`
* [ ] `loads_as`
* Serialization error
* [ ] Parsing invalid objects.
* [ ] Serializing skipped enum variant.
* [ ] Serializing flatten struct in msgpack.
* Deserialization error
* [ ] Parsing broken data.
* [ ] Parsing valid format but doesn't match the specified type.
* [x] Arguments error
* [x] Serialization error
* [x] Deserialization error

## Known issues / Constraints

* Flatten for msgpack doesn't work due to [the issue](https://github.com/3Hren/msgpack-rust/issues/196) in `rmp-serde`.
* Flatten for nested classes works in perde. The Rust issue has been bypassed.
* Flatten for dict isn't yet supported in perde.
45 changes: 45 additions & 0 deletions perde-tests/gen/datagen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,28 @@ fn main() {

add!(TooManyMember { "hage".into(), "faa".into(), 33 });

#[derive(Serialize, Debug, new)]
struct SkipEnumError {
x: i64,
e: String,
}

add!(SkipEnumError { 3, "A".into() });

#[derive(Serialize, Debug, new)]
struct DictFlattenMsgpack {
x: String,
y: i64,
pp: String,
ppp: String,
pppp: String,
}

add!(DictFlattenMsgpack {
"hey".into(), -103223,
"q1".into(), "q2".into(), "q3".into()
});

#[derive(Serialize, Debug, new)]
struct Plain {
a: String,
Expand Down Expand Up @@ -268,6 +290,29 @@ fn main() {
}}
except "msgpack");

#[derive(Serialize, Debug, new)]
struct Flatten2 {
x: String,
a: i64,
b: i64,
}

add!(Flatten2 { "haa".into(), 11, 33 });

#[derive(Serialize, Debug, new)]
struct DictFlatten2 {
x: String,
y: i64,
pp: String,
ppp: String,
pppp: String,
}

add!(DictFlatten2 {
"hey".into(), -103223,
"q1".into(), "q2".into(), "q3".into()
});

#[derive(Serialize, Debug, new)]
struct DefaultConstruct {
a: String,
Expand Down
56 changes: 56 additions & 0 deletions perde-tests/tests/test_attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,59 @@ class DictFlatten:
z: Dict[str, str] = field(metadata={"perde_flatten": True})

m.repack_type(DictFlatten)


"""rust
#[derive(Serialize, Debug, new)]
struct Flatten2 {
x: String,
a: i64,
b: i64,
}
add!(Flatten2 { "haa".into(), 11, 33 });
"""


@pytest.mark.parametrize("m", FORMATS)
def test_flatten2(m):
@dataclass
class Flatten2Child:
a: int
b: int

@dataclass
class Flatten2:
x: str
y: Flatten2Child = field(metadata={"perde_flatten": True})

m.repack_type(Flatten2)


"""rust
#[derive(Serialize, Debug, new)]
struct DictFlatten2 {
x: String,
y: i64,
pp: String,
ppp: String,
pppp: String,
}
add!(DictFlatten2 {
"hey".into(), -103223,
"q1".into(), "q2".into(), "q3".into()
});
"""


# Hopefully support msgpack.
@pytest.mark.parametrize("m", FORMATS_EXCEPT("msgpack"))
def test_dict_flatten2(m):
@dataclass
class DictFlatten2:
x: str
y: int
z: Dict[str, str] = field(metadata={"perde_flatten": True})

m.repack_type(DictFlatten2)
102 changes: 96 additions & 6 deletions perde-tests/tests/test_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from dataclasses import dataclass, field
import pytest
import perde
import typing
from util import FORMATS, FORMATS_EXCEPT, FORMATS_ONLY


Expand Down Expand Up @@ -237,15 +238,15 @@ class TypeMismatch:
b: str

with pytest.raises(m.errtype) as e:
m.loads_as(TypeMismatch, m.data("DecodeError"))
m.loads_as(TypeMismatch, m.data("TypeMismatch"))

print(f"{m.name}: {e}")


"""rust
#[derive(Serialize, Debug, new)]
struct MissingMember {
a: String,
a: String,
}
add!(MissingMember { "hage".into() });
Expand All @@ -268,17 +269,17 @@ class MissingMember:
"""rust
#[derive(Serialize, Debug, new)]
struct TooManyMember {
a: String,
b: String,
c: i64,
a: String,
b: String,
c: i64,
}
add!(TooManyMember { "hage".into(), "faa".into(), 33 });
"""


@pytest.mark.parametrize("m", FORMATS)
def test_error_decode_missing_member(m):
def test_error_decode_too_many_member(m):
@perde.attr(deny_unknown_fields=True)
@dataclass
class TooManyMember:
Expand All @@ -289,3 +290,92 @@ class TooManyMember:
m.loads_as(TooManyMember, m.data("TooManyMember"))

print(f"{m.name}: {e}")


"""rust
#[derive(Serialize, Debug, new)]
struct SkipEnumError {
x: i64,
e: String,
}
add!(SkipEnumError { 3, "A".into() });
"""


@pytest.mark.parametrize("m", FORMATS)
def test_error_encode_skipped_enum(m):
class E(perde.Enum):
A = 1
B = 2, {"perde_skip": True}
C = 3

@dataclass
class SkipEnumError:
x: int
e: E

assert m.data("SkipEnumError") == m.dumps(SkipEnumError(3, E.A))

with pytest.raises(m.errtype) as e:
m.dumps(SkipEnumError(3, E.B))

assert e.value.args[
0] == 'variant `B` is marked as `skip` and cannot be serialized'

class E2(perde.Enum):
A = 1
B = 2, {"perde_skip_serializing": True}
C = 3

@dataclass
class SkipEnumError2:
x: int
e: E2

assert m.data("SkipEnumError") == m.dumps(SkipEnumError2(3, E2.A))

with pytest.raises(m.errtype) as e:
m.dumps(SkipEnumError(3, E2.B))

assert e.value.args[
0] == 'variant `B` is marked as `skip` and cannot be serialized'


"""rust
#[derive(Serialize, Debug, new)]
struct DictFlattenMsgpack {
x: String,
y: i64,
pp: String,
ppp: String,
pppp: String,
}
add!(DictFlattenMsgpack {
"hey".into(), -103223,
"q1".into(), "q2".into(), "q3".into()
});
"""


@pytest.mark.parametrize("m", FORMATS)
def test_error_dict_flatten_msgpack(m):
@dataclass
class DictFlattenMsgpack:
x: str
y: int
z: typing.Dict[str, str] = field(metadata={"perde_flatten": True})

d = DictFlattenMsgpack("hey", -103223, {
"pp": "q1",
"ppp": "q2",
"pppp": "q3"
})

if m.fmtname == "msgpack":
with pytest.raises(m.errtype) as e:
m.dumps(d)
print(e)
else:
assert m.dumps(d) == m.data("DictFlattenMsgpack")

0 comments on commit d47bc07

Please sign in to comment.