use std::collections::{BTreeMap, HashMap};

use ron::{
    error::Position, error::Span, error::SpannedError, extensions::Extensions, ser::PrettyConfig,
    Error,
};
use serde::{Deserialize, Serialize};

#[test]
fn struct_names_inside_internally_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A { hi: 42 }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A { hi: 42 }
            },
            PrettyConfig::default().struct_names(true)
        ),
        Err(Err(SpannedError {
            code: Error::MissingStructField {
                field: "hi",
                outer: None
            },
            span: Span {
                start: Position { line: 7, col: 1 },
                end: Position { line: 7, col: 2 }
            }
        })),
    );
}

#[test]
fn struct_names_inside_adjacently_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A { hi: 42 }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A { hi: 42 }
            },
            PrettyConfig::default().struct_names(true)
        ),
        Ok(()),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(
            "AdjacentlyTagged(tag: B, content: B(ho: 24, a: A(hi: 42)))"
        ),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A { hi: 42 }
        }),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(
            "AdjacentlyTagged(content: B(ho: 24, a: A(hi: 42)), tag: B)"
        ),
        Err(SpannedError {
            code: Error::MissingStructField {
                field: "ho",
                outer: Some(String::from("AdjacentlyTagged"))
            },
            span: Span {
                start: Position { line: 1, col: 58 },
                end: Position { line: 1, col: 58 }
            }
        }),
    );
}

#[test]
fn struct_names_inside_untagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A { hi: 42 }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A { hi: 42 }
            },
            PrettyConfig::default().struct_names(true)
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 2 }
            }
        })),
    );
}

#[test]
fn struct_names_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B { a: A { hi: 42 } }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B { a: A { hi: 42 } }
            },
            PrettyConfig::default().struct_names(true)
        ),
        Err(Err(SpannedError {
            code: Error::MissingStructField {
                field: "hi",
                outer: None
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        })),
    );
}

#[test]
fn struct_names_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B { a: A { hi: 42 } }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B { a: A { hi: 42 } }
            },
            PrettyConfig::default().struct_names(true)
        ),
        Err(Err(SpannedError {
            code: Error::MissingStructField {
                field: "hi",
                outer: Some(String::from("C"))
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        })),
    );
}

#[test]
fn implicit_some_inside_internally_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Option<Option<()>>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A { hi: Some(Some(())) }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A { hi: Some(Some(())) }
            },
            PrettyConfig::default().extensions(Extensions::IMPLICIT_SOME)
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: B { ho: 24, a: A { hi: Some(Some(())) } } != B { ho: 24, a: A { hi: None } }"))))
    );
}

#[test]
fn implicit_some_inside_adjacently_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Unit;

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Option<Option<Unit>>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A {
                    hi: Some(Some(Unit))
                }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A {
                    hi: Some(Some(Unit))
                }
            },
            PrettyConfig::default().extensions(Extensions::IMPLICIT_SOME)
        ),
        Ok(()),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(
            "#![enable(implicit_some)] (tag: B, content: (ho: 24, a: A(hi: ())))"
        ),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A {
                hi: Some(Some(Unit))
            }
        }),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(
            "#![enable(implicit_some)] (content: (ho: 24, a: A(hi: ())), tag: B)"
        ),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A { hi: None } // THIS IS WRONG
        }),
    );
}

#[test]
fn implicit_some_inside_untagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Option<Option<()>>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A { hi: Some(Some(())) }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A { hi: Some(Some(())) }
            },
            PrettyConfig::default().extensions(Extensions::IMPLICIT_SOME)
        ),
        Err(Ok(Error::Message(String::from(
            "ROUNDTRIP error: B { ho: 24, a: A { hi: Some(Some(())) } } != B { ho: 24, a: A { hi: None } }"
        )))),
    );
}

#[test]
fn implicit_some_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Unit,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Option<Option<Untagged>>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A {
                        hi: Some(Some(Untagged::Unit))
                    }
                }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A { hi: Some(Some(Untagged::Unit)) }
                }
            },
            PrettyConfig::default().extensions(Extensions::IMPLICIT_SOME)
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: FlattenedStruct { ho: 24, a: B { a: A { hi: Some(Some(Unit)) } } } != FlattenedStruct { ho: 24, a: B { a: A { hi: None } } }"))))
    );
}

#[test]
fn implicit_some_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Option<Option<[u8; 0]>>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A { hi: Some(Some([])) }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("an empty array"),
                found: String::from("a unit value")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A { hi: Some(Some([])) }
                }
            },
            PrettyConfig::default().extensions(Extensions::IMPLICIT_SOME)
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: C { ho: 24, a: B { a: A { hi: Some(Some([])) } } } != C { ho: 24, a: B { a: A { hi: None } } }"))))
    );
}

#[test]
fn newtype_inside_internally_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A(i32);

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    // NOTE:
    // 1. ron is correctly collected into Content, newtype is a one-seq here
    // 2. newtype asks ContentDeserializer for newtype
    // 3. ContentDeserializer forwards any value to visit_newtype_struct
    //    https://github.com/serde-rs/serde/blob/8c4aad3a59515f7b779f764d5e16d6bae297ab7f/serde/src/private/de.rs#L1347-L1359

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B { ho: 24, a: A(42) },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a sequence")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 2 }
            }
        }))
    );
}

#[test]
fn newtype_inside_adjacently_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A(i32);

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B { ho: 24, a: A(42) },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(tag: B, content: (ho: 24, a: (42)))"),
        Ok(AdjacentlyTagged::B { ho: 24, a: A(42) }),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(content: (ho: 24, a: (42)), tag: B)"),
        Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a sequence")
            },
            span: Span {
                start: Position { line: 1, col: 36 },
                end: Position { line: 1, col: 36 }
            }
        })
    );
}

#[test]
fn newtype_inside_untagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A(i32);

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(&Untagged::B { ho: 24, a: A(42) }, PrettyConfig::default()),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 4, col: 1 },
                end: Position { line: 4, col: 2 }
            }
        }))
    );
}

#[test]
fn newtype_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A(i32);

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B { a: A(42) }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a sequence")
            },
            span: Span {
                start: Position { line: 4, col: 1 },
                end: Position { line: 4, col: 1 }
            }
        }))
    );
}

#[test]
fn newtype_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A(i32);

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B { a: A(42) }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a sequence")
            },
            span: Span {
                start: Position { line: 4, col: 1 },
                end: Position { line: 4, col: 1 }
            }
        }))
    );
}

#[test]
fn one_tuple_inside_unwrapped_newtype_variant_inside_internally_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum A {
        Newtype((i32,)),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A::Newtype((42,))
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A::Newtype((42,))
            },
            PrettyConfig::default().extensions(Extensions::UNWRAP_VARIANT_NEWTYPES)
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("a tuple of size 1"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 2 }
            }
        }))
    );
}

#[test]
fn one_tuple_inside_unwrapped_newtype_variant_inside_adjacently_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum A {
        Newtype([i32; 1]),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A::Newtype([42])
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A::Newtype([42])
            },
            PrettyConfig::default().extensions(Extensions::UNWRAP_VARIANT_NEWTYPES)
        ),
        Ok(())
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(
            "#![enable(unwrap_variant_newtypes)] (tag: B, content: (ho: 24, a: Newtype(42)))"
        ),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A::Newtype([42])
        }),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(
            "#![enable(unwrap_variant_newtypes)] (content: (ho: 24, a: Newtype(42)), tag: B)"
        ),
        Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("an array of length 1"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 1, col: 79 },
                end: Position { line: 1, col: 79 }
            }
        })
    );
}

#[test]
fn one_tuple_inside_unwrapped_newtype_variant_inside_untagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct OneTuple(i32, #[serde(skip)] ());

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: Option<OneTuple> },
    }

    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: Some(OneTuple(42, ()))
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: Some(OneTuple(42, ()))
            },
            PrettyConfig::default().extensions(Extensions::UNWRAP_VARIANT_NEWTYPES)
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 2 }
            }
        }))
    );
}

#[test]
fn one_tuple_inside_unwrapped_newtype_variant_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum A {
        Newtype([i32; 1]),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A::Newtype([42])
                }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A::Newtype([42])
                }
            },
            PrettyConfig::default().extensions(Extensions::UNWRAP_VARIANT_NEWTYPES)
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("an array of length 1"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn one_tuple_inside_unwrapped_newtype_variant_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum A {
        Newtype((i32,)),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A::Newtype((42,))
                }
            },
            PrettyConfig::default()
        ),
        Ok(()),
    );
    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A::Newtype((42,))
                }
            },
            PrettyConfig::default().extensions(Extensions::UNWRAP_VARIANT_NEWTYPES)
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("a tuple of size 1"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn one_tuple_variant_inside_internally_tagged() {
    // A tuple variant with just one element that is not a newtype variant
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum OneEnum {
        OneTuple(i32, #[serde(skip)] ()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: OneEnum,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A {
                    hi: OneEnum::OneTuple(42, ())
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("tuple variant"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 7, col: 1 },
                end: Position { line: 7, col: 2 }
            }
        }))
    );
}

#[test]
fn one_tuple_variant_inside_adjacently_tagged() {
    // A tuple variant with just one element that is not a newtype variant
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum OneEnum {
        OneTuple(i32, #[serde(skip)] ()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: OneEnum,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A {
                    hi: OneEnum::OneTuple(42, ())
                }
            },
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(tag: B, content: (ho: 24, a: (hi: OneTuple(42))))"),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A {
                hi: OneEnum::OneTuple(42, ())
            }
        }),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(content: (ho: 24, a: (hi: OneTuple(42))), tag: B)"),
        Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("tuple variant"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 1, col: 50 },
                end: Position { line: 1, col: 50 }
            }
        })
    );
}

#[test]
fn one_tuple_variant_inside_untagged() {
    // A tuple variant with just one element that is not a newtype variant
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum OneEnum {
        OneTuple(i32, #[serde(skip)] ()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: OneEnum,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A {
                    hi: OneEnum::OneTuple(42, ())
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 2 }
            }
        }))
    );
}

#[test]
fn one_tuple_variant_inside_flatten_struct() {
    // A tuple variant with just one element that is not a newtype variant
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum OneEnum {
        OneTuple(i32, #[serde(skip)] ()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: OneEnum,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A {
                        hi: OneEnum::OneTuple(42, ())
                    }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("tuple variant"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn one_tuple_variant_inside_flatten_struct_variant() {
    // A tuple variant with just one element that is not a newtype variant
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum OneEnum {
        OneTuple(i32, #[serde(skip)] ()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: OneEnum,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A {
                        hi: OneEnum::OneTuple(42, ())
                    }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("tuple variant"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn raw_value_inside_internally_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Box<ron::value::RawValue>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A {
                    hi: ron::value::RawValue::from_boxed_ron(String::from("42").into_boxed_str())
                        .unwrap(),
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any valid RON-value-string"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 7, col: 1 },
                end: Position { line: 7, col: 2 }
            }
        }))
    );
}

#[test]
fn raw_value_inside_adjacently_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Box<ron::value::RawValue>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A {
                    hi: ron::value::RawValue::from_boxed_ron(String::from("42").into_boxed_str())
                        .unwrap(),
                }
            },
            PrettyConfig::default()
        ),
        // adds an extra space
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: B { ho: 24, a: A { hi: RawValue(42) } } != B { ho: 24, a: A { hi: RawValue( 42) } }"))))
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(tag: B, content: (ho: 24, a: (hi:42)))"),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A {
                hi: ron::value::RawValue::from_boxed_ron(String::from("42").into_boxed_str())
                    .unwrap(),
            }
        }),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(content: (ho: 24, a: (hi:42)), tag: B)"),
        Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any valid RON-value-string"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 1, col: 39 },
                end: Position { line: 1, col: 39 }
            }
        })
    );
}

#[test]
fn raw_value_inside_untagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Box<ron::value::RawValue>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A {
                    hi: ron::value::RawValue::from_boxed_ron(String::from("42").into_boxed_str())
                        .unwrap(),
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 2 }
            }
        }))
    );
}

#[test]
fn raw_value_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Box<ron::value::RawValue>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A {
                        hi: ron::value::RawValue::from_boxed_ron(
                            String::from("42").into_boxed_str()
                        )
                        .unwrap(),
                    }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any valid RON-value-string"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn raw_value_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Box<ron::value::RawValue>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A {
                        hi: ron::value::RawValue::from_boxed_ron(
                            String::from("42").into_boxed_str()
                        )
                        .unwrap(),
                    }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any valid RON-value-string"),
                found: String::from("the unsigned integer `42`")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn unit_like_zero_length_inside_internally_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: [i32; 0],
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A { hi: [] }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("an empty array"),
                found: String::from("a unit value")
            },
            span: Span {
                start: Position { line: 7, col: 1 },
                end: Position { line: 7, col: 2 }
            }
        }))
    );
}

#[test]
fn unit_like_zero_length_inside_adjacently_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct TupleStruct();

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: TupleStruct,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A { hi: TupleStruct() }
            },
            PrettyConfig::default()
        ),
        Ok(()),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(tag: B, content: (ho: 24, a: (hi: ())))"),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A { hi: TupleStruct() }
        }),
    );
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>("(content: (ho: 24, a: (hi: ())), tag: B)"),
        Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("tuple struct TupleStruct"),
                found: String::from("a unit value")
            },
            span: Span {
                start: Position { line: 1, col: 40 },
                end: Position { line: 1, col: 40 }
            }
        })
    );
}

#[test]
fn unit_like_zero_length_inside_untagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Struct {}

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Struct,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: A },
    }

    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A { hi: Struct {} }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 2 }
            }
        }))
    );
}

#[test]
fn unit_like_zero_length_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum Enum {
        Tuple(),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Enum,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A { hi: Enum::Tuple() }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("tuple variant"),
                found: String::from("a unit value")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn unit_like_zero_length_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum Enum {
        Struct {},
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: Enum,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A {
                        hi: Enum::Struct {}
                    }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("struct variant"),
                found: String::from("a unit value")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn i128_inside_internally_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i128,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        B { ho: i32, a: A },
    }

    #[cfg(not(feature = "integer128"))]
    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A { hi: i128::MAX }
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("i128 is not supported"))))
    );
    #[cfg(feature = "integer128")]
    assert_eq!(
        check_roundtrip(
            &InternallyTagged::B {
                ho: 24,
                a: A { hi: i128::MAX }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any value"),
                found: format!("integer `{}` as u128", i128::MAX)
            },
            span: Span {
                start: Position { line: 5, col: 13 },
                end: Position { line: 5, col: 52 }
            }
        }))
    );
}

#[test]
fn u128_inside_adjacently_tagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: u128,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum AdjacentlyTagged {
        B { ho: i32, a: A },
    }

    #[cfg(not(feature = "integer128"))]
    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A { hi: u128::MAX }
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("u128 is not supported"))))
    );
    #[cfg(feature = "integer128")]
    assert_eq!(
        check_roundtrip(
            &AdjacentlyTagged::B {
                ho: 24,
                a: A { hi: u128::MAX }
            },
            PrettyConfig::default()
        ),
        Ok(()),
    );
    #[cfg(feature = "integer128")]
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(&format!(
            "(tag: B, content: (ho: 24, a: (hi: {})))",
            u128::MAX
        ),),
        Ok(AdjacentlyTagged::B {
            ho: 24,
            a: A { hi: u128::MAX }
        }),
    );
    #[cfg(feature = "integer128")]
    assert_eq!(
        ron::from_str::<AdjacentlyTagged>(&format!(
            "(content: (ho: 24, a: (hi: {})), tag: B)",
            u128::MAX
        ),),
        Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any value"),
                found: format!("integer `{}` as u128", u128::MAX)
            },
            span: Span {
                start: Position { line: 1, col: 28 },
                end: Position { line: 1, col: 67 }
            }
        }),
    );
}

#[test]
fn i128_inside_untagged() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i128,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        B { ho: i32, a: A },
    }

    #[cfg(not(feature = "integer128"))]
    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A { hi: i128::MIN }
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("i128 is not supported"))))
    );
    #[cfg(feature = "integer128")]
    assert_eq!(
        check_roundtrip(
            &Untagged::B {
                ho: 24,
                a: A { hi: i128::MIN }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any value"),
                found: format!("integer `{}` as i128", i128::MIN)
            },
            span: Span {
                start: Position { line: 4, col: 14 },
                end: Position { line: 4, col: 53 }
            }
        }))
    );
}

#[test]
fn u128_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: u128,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: B,
    }

    #[cfg(not(feature = "integer128"))]
    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A { hi: u128::MAX }
                }
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("u128 is not supported"))))
    );
    #[cfg(feature = "integer128")]
    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: B {
                    a: A { hi: u128::MAX }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any value"),
                found: format!("integer `{}` as u128", u128::MAX)
            },
            span: Span {
                start: Position { line: 4, col: 13 },
                end: Position { line: 4, col: 52 }
            }
        }))
    );
}

#[test]
fn i128_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct A {
        hi: i128,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        a: A,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: B,
        },
    }

    #[cfg(not(feature = "integer128"))]
    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A { hi: i128::MIN }
                }
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("i128 is not supported"))))
    );
    #[cfg(feature = "integer128")]
    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: B {
                    a: A { hi: i128::MIN }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("any value"),
                found: format!("integer `{}` as i128", i128::MIN)
            },
            span: Span {
                start: Position { line: 4, col: 14 },
                end: Position { line: 4, col: 53 }
            }
        }))
    );
}

#[test]
fn duplicate_key_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: [(String::from("ho"), 42)].into_iter().collect(),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::DuplicateStructField {
                field: "ho",
                outer: None
            },
            span: Span {
                start: Position { line: 3, col: 6 },
                end: Position { line: 3, col: 9 }
            }
        }))
    );
}

#[test]
fn duplicate_key_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct C {
        ho: i32,
        hi: bool,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct B {
        #[serde(flatten)]
        inner: C,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            inner: B,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                inner: B {
                    inner: C { ho: 42, hi: false }
                }
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::DuplicateStructField {
                field: "ho",
                outer: Some(String::from("A"))
            },
            span: Span {
                start: Position { line: 3, col: 6 },
                end: Position { line: 3, col: 9 }
            }
        }))
    );
}

#[test]
fn non_string_key_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<i32, bool>,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: [(1, true), (0, false)].into_iter().collect(),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::ExpectedString,
            span: Span {
                start: Position { line: 3, col: 5 },
                end: Position { line: 3, col: 5 }
            }
        }))
    );
}

#[test]
fn non_string_key_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<char, u8>,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: [('h', 0), ('i', 1)].into_iter().collect(),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::ExpectedString,
            span: Span {
                start: Position { line: 3, col: 5 },
                end: Position { line: 3, col: 5 }
            }
        }))
    );
}

#[test]
fn more_than_one_flatten_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Deep {
        #[serde(flatten)]
        other: HashMap<String, bool>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Inner {
        #[serde(flatten)]
        deep: Deep,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        hi: Inner,
        #[serde(flatten)]
        other: HashMap<String, bool>,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                hi: Inner {
                    deep: Deep {
                        other: HashMap::new(),
                    },
                },
                other: [(String::from("42"), true)].into_iter().collect(),
            },
            PrettyConfig::default()
        ),
        // both maps collect all the unknown keys
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: FlattenedStruct { ho: 24, hi: Inner { deep: Deep { other: {} } }, other: {\"42\": true} } != FlattenedStruct { ho: 24, hi: Inner { deep: Deep { other: {\"42\": true} } }, other: {\"42\": true} }"))))
    );
}

#[test]
fn more_than_one_flatten_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Deep {
        #[serde(flatten)]
        other: HashMap<String, bool>,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Inner {
        #[serde(flatten)]
        deep: Deep,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            hi: Inner,
            #[serde(flatten)]
            other: HashMap<String, bool>,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                hi: Inner {
                    deep: Deep {
                        other: [(String::from("24"), false)].into_iter().collect(),
                    },
                },
                other: HashMap::new(),
            },
            PrettyConfig::default()
        ),
        // both maps collect all the unknown keys
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: A { ho: 24, hi: Inner { deep: Deep { other: {\"24\": false} } }, other: {} } != A { ho: 24, hi: Inner { deep: Deep { other: {\"24\": false} } }, other: {\"24\": false} }"))))
    );
}

#[test]
fn flatten_struct_beside_map_inside_flatten_struct() {
    // The non-flattened struct must contain some flattened fields
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Flattened {
        hi: i32,
        #[serde(flatten)]
        flat: (),
    }

    // The non-flattened struct must be behind a level of flatten
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Inner {
        flat: Flattened,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        inner: Inner,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                inner: Inner {
                    flat: Flattened { hi: 42, flat: () },
                },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a map")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn flatten_struct_beside_map_inside_flatten_struct_variant() {
    // The non-flattened struct must contain some flattened fields
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Flattened {
        hi: i32,
        #[serde(flatten)]
        flat: (),
    }

    // The non-flattened struct must be behind a level of flatten
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Inner {
        flat: Flattened,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            inner: Inner,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                inner: Inner {
                    flat: Flattened { hi: 42, flat: () },
                },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a map")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn untagged_flatten_struct_variant_beside_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Flattened {
        Struct {
            hi: i32,
            #[serde(flatten)]
            flat: (),
        },
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        flat: Flattened,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Struct { hi: 42, flat: () },
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: FlattenedStruct { ho: 24, other: {}, flat: Struct { hi: 42, flat: () } } != FlattenedStruct { ho: 24, other: {\"hi\": 42}, flat: Struct { hi: 42, flat: () } }"))))
    );
}

#[test]
fn untagged_flatten_struct_variant_beside_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Flattened {
        Struct {
            hi: i32,
            #[serde(flatten)]
            flat: (),
        },
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            flat: Flattened,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Struct { hi: 42, flat: () },
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: A { ho: 24, other: {}, flat: Struct { hi: 42, flat: () } } != A { ho: 24, other: {\"hi\": 42}, flat: Struct { hi: 42, flat: () } }"))))
    );
}

#[test]
fn externally_tagged_newtype_variant_beside_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum Flattened {
        Newtype(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            flat: Flattened,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Newtype(()),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a unit value")
            },
            span: Span {
                start: Position { line: 4, col: 1 },
                end: Position { line: 4, col: 1 }
            }
        }))
    );
}

#[test]
fn externally_tagged_struct_variant_beside_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum Flattened {
        Struct { hi: i32 },
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        flat: Flattened,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Struct { hi: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a map")
            },
            span: Span {
                start: Position { line: 6, col: 1 },
                end: Position { line: 6, col: 1 }
            }
        }))
    );
}

#[test]
fn externally_tagged_tuple_variant_beside_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum Flattened {
        Tuple(i32, bool),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            flat: Flattened,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Tuple(42, true),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("a sequence")
            },
            span: Span {
                start: Position { line: 7, col: 1 },
                end: Position { line: 7, col: 1 }
            }
        }))
    );
}

#[test]
fn internally_tagged_unit_variant_beside_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum Flattened {
        Unit,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        flat: Flattened,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Unit,
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Unit\"")
            },
            span: Span {
                start: Position { line: 4, col: 1 },
                end: Position { line: 4, col: 1 }
            }
        }))
    );
}

#[test]
fn internally_tagged_newtype_variant_beside_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum Flattened {
        Newtype(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            flat: Flattened,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Newtype(()),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Newtype\"")
            },
            span: Span {
                start: Position { line: 4, col: 1 },
                end: Position { line: 4, col: 1 }
            }
        }))
    );
}

#[test]
fn internally_tagged_struct_variant_beside_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum Flattened {
        Struct { hi: i32 },
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        flat: Flattened,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Struct { hi: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Struct\"")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn adjacently_tagged_unit_variant_beside_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum Flattened {
        Unit,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        flat: Flattened,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Unit,
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Unit\"")
            },
            span: Span {
                start: Position { line: 4, col: 1 },
                end: Position { line: 4, col: 1 }
            }
        }))
    );
}

#[test]
fn adjacently_tagged_newtype_variant_beside_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum Flattened {
        Newtype(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            flat: Flattened,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Newtype(()),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Newtype\"")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn adjacently_tagged_struct_variant_beside_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum Flattened {
        Struct { hi: i32 },
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        flat: Flattened,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Struct { hi: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Struct\"")
            },
            span: Span {
                start: Position { line: 7, col: 1 },
                end: Position { line: 7, col: 1 }
            }
        }))
    );
}

#[test]
fn adjacently_tagged_tuple_variant_beside_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag", content = "content")]
    enum Flattened {
        Tuple(i32, bool),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            flat: Flattened,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened::Tuple(42, true),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Tuple\"")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn tagged_struct_beside_map_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    struct Flattened {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        other: HashMap<String, i32>,
        #[serde(flatten)]
        flat: Flattened,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened { hi: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Flattened\"")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn tagged_struct_beside_map_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    struct Flattened {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        A {
            ho: i32,
            #[serde(flatten)]
            other: HashMap<String, i32>,
            #[serde(flatten)]
            flat: Flattened,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::A {
                ho: 24,
                other: HashMap::new(),
                flat: Flattened { hi: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("i32"),
                found: String::from("the string \"Flattened\"")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn zero_length_untagged_tuple_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        A(),
    }

    assert_eq!(
        check_roundtrip(&Untagged::A(), PrettyConfig::default()),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 1, col: 1 },
                end: Position { line: 1, col: 3 }
            }
        }))
    );
}

#[test]
fn zero_length_untagged_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        A {},
    }

    assert_eq!(
        check_roundtrip(&Untagged::A {}, PrettyConfig::default()),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 1, col: 1 },
                end: Position { line: 1, col: 3 }
            }
        }))
    );
}

#[test]
fn unwrapped_one_element_untagged_tuple_variant() {
    // A tuple variant with just one element that is not a newtype variant
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        OneTuple(i32, #[serde(skip)] ()),
    }

    assert_eq!(
        check_roundtrip(&Untagged::OneTuple(42, ()), PrettyConfig::default()),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &Untagged::OneTuple(42, ()),
            PrettyConfig::default().extensions(Extensions::UNWRAP_VARIANT_NEWTYPES)
        ),
        Ok(())
    );
}

#[test]
fn unit_inside_untagged_newtype_variant_inside_internally_tagged_newtype_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Newtype(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        Newtype(Untagged),
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::Newtype(Untagged::Newtype(())),
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 3, col: 1 },
                end: Position { line: 3, col: 2 }
            }
        }))
    );
}

#[test]
fn unit_inside_untagged_newtype_variant_inside_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Newtype(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: Untagged,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: Untagged::Newtype(()),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 3, col: 1 },
                end: Position { line: 3, col: 1 }
            }
        }))
    );
}

#[test]
fn unit_struct_inside_untagged_newtype_variant_inside_internally_tagged_newtype_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Unit;

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Newtype(Unit),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        Newtype(Untagged),
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::Newtype(Untagged::Newtype(Unit)),
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 3, col: 1 },
                end: Position { line: 3, col: 2 }
            }
        }))
    );
}

#[test]
fn untagged_unit_variant_inside_internally_tagged_newtype_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Unit,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum FlattenedStructVariant {
        C {
            ho: i32,
            #[serde(flatten)]
            a: Untagged,
        },
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStructVariant::C {
                ho: 24,
                a: Untagged::Unit,
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 3, col: 1 },
                end: Position { line: 3, col: 1 }
            }
        }))
    );
}

#[test]
fn untagged_unit_variant_inside_flatten_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum Enum {
        Unit,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Unit,
        Newtype(Enum),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        Newtype(Untagged),
    }

    assert_eq!(
        check_roundtrip(
            &InternallyTagged::Newtype(Untagged::Newtype(Enum::Unit)),
            PrettyConfig::default()
        ),
        Ok(())
    );
    assert_eq!(
        check_roundtrip(
            &InternallyTagged::Newtype(Untagged::Unit),
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 3, col: 1 },
                end: Position { line: 3, col: 2 }
            }
        }))
    );
}

#[test]
fn unit_inside_internally_tagged_newtype_variant_inside_multi_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct AnotherFlattenedStruct {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        Newtype(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: InternallyTagged,
        #[serde(flatten)]
        b: AnotherFlattenedStruct,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: InternallyTagged::Newtype(()),
                b: AnotherFlattenedStruct { hi: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("unit"),
                found: String::from("a map")
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn untagged_unit_variant_inside_internally_tagged_newtype_variant_inside_multi_flatten_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Unit,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct AnotherFlattenedStruct {
        hi: i32,
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        Newtype(Untagged),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct FlattenedStruct {
        ho: i32,
        #[serde(flatten)]
        a: InternallyTagged,
        #[serde(flatten)]
        b: AnotherFlattenedStruct,
    }

    assert_eq!(
        check_roundtrip(
            &FlattenedStruct {
                ho: 24,
                a: InternallyTagged::Newtype(Untagged::Unit),
                b: AnotherFlattenedStruct { hi: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::Message(String::from(
                "data did not match any variant of untagged enum Untagged"
            )),
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn flattened_externally_tagged_newtype_variant_beside_flattened_intenally_tagged_enum() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum ExternallyTagged {
        Newtype(()),
        Other(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        Newtype(ExternallyTagged),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Flattened {
        #[serde(flatten)]
        a: InternallyTagged,
        #[serde(flatten)]
        b: ExternallyTagged,
    }

    assert_eq!(
        check_roundtrip(
            &Flattened {
                a: InternallyTagged::Newtype(ExternallyTagged::Other(())),
                b: ExternallyTagged::Newtype(()),
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("map with a single key"),
                found: String::from("a map"),
            },
            span: Span {
                start: Position { line: 5, col: 1 },
                end: Position { line: 5, col: 1 }
            }
        }))
    );
}

#[test]
fn flattened_externally_tagged_struct_variant_beside_flattened_intenally_tagged_enum() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    enum ExternallyTagged {
        Struct { a: i32 },
        Other(()),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(tag = "tag")]
    enum InternallyTagged {
        Newtype(ExternallyTagged),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Flattened {
        #[serde(flatten)]
        a: InternallyTagged,
        #[serde(flatten)]
        b: ExternallyTagged,
    }

    assert_eq!(
        check_roundtrip(
            &Flattened {
                a: InternallyTagged::Newtype(ExternallyTagged::Other(())),
                b: ExternallyTagged::Struct { a: 42 },
            },
            PrettyConfig::default()
        ),
        Err(Err(SpannedError {
            code: Error::InvalidValueForType {
                expected: String::from("map with a single key"),
                found: String::from("a map"),
            },
            span: Span {
                start: Position { line: 7, col: 1 },
                end: Position { line: 7, col: 1 }
            }
        }))
    );
}

#[test]
fn flattened_map_inside_option_beside_flattened_struct_variant() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Struct { a: i32 },
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Flattened {
        #[serde(flatten)]
        a: Untagged,
        #[serde(flatten)]
        b: Option<BTreeMap<String, i32>>,
    }

    assert_eq!(
        check_roundtrip(
            &Flattened {
                a: Untagged::Struct {
                    a: 42,
                },
                b: Some([(String::from("b"), 24)].into_iter().collect()),
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: Flattened { a: Struct { a: 42 }, b: Some({\"b\": 24}) } != Flattened { a: Struct { a: 42 }, b: Some({\"a\": 42, \"b\": 24}) }"))))
    );
}

#[test]
fn flattened_untagged_struct_beside_flattened_untagged_struct() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    #[serde(untagged)]
    enum Untagged {
        Struct { a: i32 },
        Other { b: i32 },
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Flattened {
        #[serde(flatten)]
        a: Untagged,
        #[serde(flatten)]
        b: Untagged,
    }

    assert_eq!(
        check_roundtrip(
            &Flattened {
                a: Untagged::Struct {
                    a: 42,
                },
                b: Untagged::Other {
                    b: 24,
                },
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: Flattened { a: Struct { a: 42 }, b: Other { b: 24 } } != Flattened { a: Struct { a: 42 }, b: Struct { a: 42 } }"))))
    );
}

#[test]
fn flattened_field_inside_flattened_struct_alongside_map() {
    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Units {
        a: i32,
        #[serde(flatten)]
        b: (),
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct Flattened {
        #[serde(flatten)]
        a: Units,
        #[serde(flatten)]
        b: BTreeMap<String, i32>,
    }

    assert_eq!(
        check_roundtrip(
            &Flattened {
                a: Units {
                    a: 42,
                    b: (),
                },
                b: [(String::from("c"), 24)].into_iter().collect(),
            },
            PrettyConfig::default()
        ),
        Err(Ok(Error::Message(String::from("ROUNDTRIP error: Flattened { a: Units { a: 42, b: () }, b: {\"c\": 24} } != Flattened { a: Units { a: 42, b: () }, b: {\"a\": 42, \"c\": 24} }"))))
    );
}

fn check_roundtrip<T: PartialEq + core::fmt::Debug + Serialize + serde::de::DeserializeOwned>(
    val: &T,
    config: PrettyConfig,
) -> Result<(), Result<Error, SpannedError>> {
    let ron = ron::ser::to_string_pretty(val, config).map_err(|err| Ok(err))?;
    println!("{ron}");
    let de = ron::from_str(&ron).map_err(|err| Err(err))?;
    if val == &de {
        Ok(())
    } else {
        Err(Ok(Error::Message(format!(
            "ROUNDTRIP error: {:?} != {:?}",
            val, de
        ))))
    }
}
