Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Value::from and full code lines coverage #498

Merged
merged 10 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix issue [#321](https://github.com/ron-rs/ron/issues/321) and allow parsing UTF-8 identifiers ([#488](https://github.com/ron-rs/ron/pull/488))
- Breaking: Enforce that ron always writes valid UTF-8 ([#488](https://github.com/ron-rs/ron/pull/488))
- Fix parsing of struct/variant names starting in `None`, `Some`, `true`, or `false` ([#499](https://github.com/ron-rs/ron/pull/499))
- Add convenient `Value::from` impls ([#498](https://github.com/ron-rs/ron/pull/498))

## [0.8.1] - 2023-08-17

Expand Down
207 changes: 207 additions & 0 deletions src/de/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ struct EmptyStruct2 {}
#[derive(Debug, PartialEq, Deserialize)]
struct NewType(i32);

#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "")]
struct UnnamedNewType(i32);

#[derive(Debug, PartialEq, Deserialize)]
struct TupleStruct(f32, f32);

#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "")]
struct UnnamedTupleStruct(f32, f32);

#[derive(Clone, Copy, Debug, PartialEq, Deserialize)]
struct MyStruct {
x: f32,
Expand Down Expand Up @@ -56,8 +64,186 @@ fn test_struct() {
check_from_str_bytes_reader("NewType(42)", Ok(NewType(42)));
check_from_str_bytes_reader("(33)", Ok(NewType(33)));

check_from_str_bytes_reader::<NewType>(
"NewType",
Err(SpannedError {
code: Error::ExpectedNamedStructLike("NewType"),
position: Position { line: 1, col: 8 },
}),
);
check_from_str_bytes_reader::<UnnamedNewType>(
"",
Err(SpannedError {
code: Error::ExpectedStructLike,
position: Position { line: 1, col: 1 },
}),
);
check_from_str_bytes_reader("(33)", Ok(UnnamedNewType(33)));
check_from_str_bytes_reader::<UnnamedNewType>(
"Newtype",
Err(SpannedError {
code: Error::ExpectedNamedStructLike(""),
position: Position { line: 1, col: 8 },
}),
);

check_from_str_bytes_reader("TupleStruct(2,5,)", Ok(TupleStruct(2.0, 5.0)));
check_from_str_bytes_reader("(3,4)", Ok(TupleStruct(3.0, 4.0)));
check_from_str_bytes_reader::<TupleStruct>(
"",
Err(SpannedError {
code: Error::ExpectedNamedStructLike("TupleStruct"),
position: Position { line: 1, col: 1 },
}),
);
check_from_str_bytes_reader::<UnnamedTupleStruct>(
"TupleStruct(2,5,)",
Err(SpannedError {
code: Error::ExpectedNamedStructLike(""),
position: Position { line: 1, col: 12 },
}),
);
check_from_str_bytes_reader("(3,4)", Ok(UnnamedTupleStruct(3.0, 4.0)));
check_from_str_bytes_reader::<UnnamedTupleStruct>(
"",
Err(SpannedError {
code: Error::ExpectedStructLike,
position: Position { line: 1, col: 1 },
}),
);
}

#[test]
fn test_unclosed_limited_seq_struct() {
#[derive(Debug, PartialEq)]
struct LimitedStruct;

impl<'de> serde::Deserialize<'de> for LimitedStruct {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = LimitedStruct;

// GRCOV_EXCL_START
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_str("struct LimitedStruct")
}
// GRCOV_EXCL_STOP

fn visit_map<A: serde::de::MapAccess<'de>>(
self,
_map: A,
) -> Result<Self::Value, A::Error> {
Ok(LimitedStruct)
}
}

deserializer.deserialize_struct("LimitedStruct", &[], Visitor)
}
}

check_from_str_bytes_reader::<LimitedStruct>(
"(",
Err(SpannedError {
code: Error::ExpectedStructLikeEnd,
position: Position { line: 1, col: 2 },
}),
)
}

#[test]
fn test_unclosed_limited_seq() {
#[derive(Debug, PartialEq)]
struct LimitedSeq;

impl<'de> serde::Deserialize<'de> for LimitedSeq {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = LimitedSeq;

// GRCOV_EXCL_START
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_str("an empty sequence")
}
// GRCOV_EXCL_STOP

fn visit_seq<A: serde::de::SeqAccess<'de>>(
self,
_seq: A,
) -> Result<Self::Value, A::Error> {
Ok(LimitedSeq)
}
}

deserializer.deserialize_seq(Visitor)
}
}

check_from_str_bytes_reader::<LimitedSeq>(
"[",
Err(SpannedError {
code: Error::ExpectedArrayEnd,
position: Position { line: 1, col: 2 },
}),
);

assert_eq!(
crate::Value::from(vec![42]).into_rust::<LimitedSeq>(),
Err(Error::ExpectedDifferentLength {
expected: String::from("a sequence of length 0"),
found: 1
})
);
}

#[test]
fn test_unclosed_limited_map() {
#[derive(Debug, PartialEq)]
struct LimitedMap;

impl<'de> serde::Deserialize<'de> for LimitedMap {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = LimitedMap;

// GRCOV_EXCL_START
fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_str("an empty map")
}
// GRCOV_EXCL_STOP

fn visit_map<A: serde::de::MapAccess<'de>>(
self,
_map: A,
) -> Result<Self::Value, A::Error> {
Ok(LimitedMap)
}
}

deserializer.deserialize_map(Visitor)
}
}

check_from_str_bytes_reader::<LimitedMap>(
"{",
Err(SpannedError {
code: Error::ExpectedMapEnd,
position: Position { line: 1, col: 2 },
}),
);

assert_eq!(
crate::Value::Map([("a", 42)].into_iter().collect()).into_rust::<LimitedMap>(),
Err(Error::ExpectedDifferentLength {
expected: String::from("a map of length 0"),
found: 1
})
);
}

#[test]
Expand All @@ -70,6 +256,13 @@ fn test_option() {
fn test_enum() {
check_from_str_bytes_reader("A", Ok(MyEnum::A));
check_from_str_bytes_reader("B(true,)", Ok(MyEnum::B(true)));
check_from_str_bytes_reader::<MyEnum>(
"B",
Err(SpannedError {
code: Error::ExpectedStructLike,
position: Position { line: 1, col: 2 },
}),
);
check_from_str_bytes_reader("C(true,3.5,)", Ok(MyEnum::C(true, 3.5)));
check_from_str_bytes_reader("D(a:2,b:3,)", Ok(MyEnum::D { a: 2, b: 3 }));
}
Expand Down Expand Up @@ -206,10 +399,24 @@ fn untagged() {
enum Untagged {
U8(u8),
Bool(bool),
Value(crate::Value),
}

check_from_str_bytes_reader("true", Ok(Untagged::Bool(true)));
check_from_str_bytes_reader("8", Ok(Untagged::U8(8)));

// Check for a failure in Deserializer::check_struct_type
// - untagged enum and a leading identifier trigger the serde content enum path
// - serde content uses deserialize_any, which retriggers the struct type check
// - struct type check inside a serde content performs a full newtype check
// - newtype check fails on the unclosed struct
check_from_str_bytes_reader::<Untagged>(
"Value(()",
Err(crate::error::SpannedError {
code: crate::Error::Eof,
position: crate::error::Position { line: 1, col: 9 },
}),
);
}

#[test]
Expand Down
18 changes: 17 additions & 1 deletion src/de/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,12 @@ impl<'de> Visitor<'de> for ValueVisitor {
{
let mut res: Map = Map::new();

while let Some(entry) = map.next_entry()? {
#[cfg(feature = "indexmap")]
if let Some(cap) = map.size_hint() {
res.0.reserve_exact(cap);
}

while let Some(entry) = map.next_entry::<Value, Value>()? {
res.insert(entry.0, entry.1);
}

Expand Down Expand Up @@ -416,5 +421,16 @@ mod tests {
position: crate::error::Position { line: 1, col: 4 },
},
);

// Check for a failure in Deserializer::check_struct_type
// - opening brace triggers the struct type check
// - unclosed block comment fails the whitespace skip
assert_eq!(
"( /*".parse::<Value>().unwrap_err(),
crate::error::SpannedError {
code: crate::Error::UnclosedBlockComment,
position: crate::error::Position { line: 1, col: 5 },
},
);
}
}
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,10 +632,10 @@ mod tests {
);
check_error_message(
&Error::MissingStructField {
field: "b+c",
field: "",
outer: Some(String::from("S+T")),
},
"Unexpected missing field named `r#b+c` in `r#S+T`",
"Unexpected missing field named \"\"_[invalid identifier] in `r#S+T`",
);
check_error_message(
&Error::duplicate_field("a"),
Expand Down
13 changes: 9 additions & 4 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,9 +583,9 @@ impl<'a> Parser<'a> {
}

let mut braces = 1_usize;
let mut comma = false;
let mut more_than_one = false;

// Skip ahead to see if the value is followed by a comma
// Skip ahead to see if the value is followed by another value
while braces > 0 {
// Skip spurious braces in comments, strings, and characters
parser.skip_ws()?;
Expand All @@ -608,12 +608,13 @@ impl<'a> Parser<'a> {
} else if matches!(c, ')' | ']' | '}') {
braces -= 1;
} else if c == ',' && braces == 1 {
comma = true;
parser.skip_ws()?;
more_than_one = !parser.check_char(')');
break;
}
}

if comma {
if more_than_one {
Ok(StructType::Tuple)
} else {
Ok(StructType::NewtypeOrTuple)
Expand Down Expand Up @@ -658,6 +659,10 @@ impl<'a> Parser<'a> {
Err(_) => return Err(Error::ExpectedNamedStructLike(ident)),
};

if ident.is_empty() {
return Err(Error::ExpectedNamedStructLike(ident));
}

if found_ident != ident {
return Err(Error::ExpectedDifferentStructName {
expected: ident,
Expand Down
Loading
Loading