keyvalues-serde 0.2.4

(De)serialize VDF text with serde
Documentation
use std::{collections::HashMap, fmt};

use crate::utils::{read_asset_file, BoxedResult, Container};

use insta::assert_snapshot;
use keyvalues_serde::{error::Result, from_str, to_string};
use serde::Deserialize;

// Helper macro that generates the boilerplate for snapshotting a deserialization error backed by a
// file
macro_rules! test_snapshot_de {
    ($func_name:ident, $de_ty:ty, $file_name:expr $(,)?) => {
        #[test]
        fn $func_name() -> BoxedResult<()> {
            let vdf_text = read_asset_file($file_name)?;
            snapshot_de_err::<$de_ty>(stringify!($func_name), &vdf_text);

            Ok(())
        }
    };
}

fn snapshot_de_err<'a, T: Deserialize<'a> + fmt::Debug>(snapshot_name: &str, vdf_text: &'a str) {
    let result: Result<T> = from_str(vdf_text);
    let err = result.unwrap_err();
    assert_snapshot!(snapshot_name, err.to_string());
}

test_snapshot_de!(
    str_when_wanting_obj,
    Container<HashMap<String, String>>,
    "string_container.vdf",
);

test_snapshot_de!(
    str_when_wanting_top_level_obj,
    Container<String>,
    "top_level_string.vdf",
);

test_snapshot_de!(obj_when_wanting_str, Container<String>, "obj_container.vdf");

#[test]
fn incorrect_seq_length() -> BoxedResult<()> {
    let name_base = "incorrect_seq_length";
    let vdf_len_one = read_asset_file("string_container.vdf")?;
    let name = format!("{}-one_expecting_two", name_base);
    snapshot_de_err::<Container<(String, String)>>(&name, &vdf_len_one);

    let vdf_len_two = read_asset_file("sequence_string_double.vdf")?;
    let name = format!("{}-two_expecting_one", name_base);
    snapshot_de_err::<Container<(String,)>>(&name, &vdf_len_two);
    let name = format!("{}-two_expecting_three", name_base);
    snapshot_de_err::<Container<(String, String, String)>>(&name, &vdf_len_two);

    Ok(())
}

#[test]
fn parsing_obj_as_sequence() {
    let text = r#""Blah" {}"#;
    assert!(from_str::<Vec<String>>(text).is_err());
}

#[derive(Deserialize, Debug)]
#[allow(dead_code)] // used for snapshotting
pub struct Pair {
    pub first: String,
    pub second: String,
}

test_snapshot_de!(wants_too_many_members, Pair, "string_container.vdf",);

const INVALID_BOOL_TEXT: &str = r#"
"Container"
{
    "inner" "2"
}
"#;

#[test]
fn invalid_bool() {
    snapshot_de_err::<Container<bool>>("invalid_bool", INVALID_BOOL_TEXT);
}

#[test]
fn invalid_int() {
    let res: Result<Container<i32>> = from_str(ZERO_LEN_CHAR_TEXT);
    insta::assert_snapshot!(res.unwrap_err(), @"Tried parsing an invalid number");
}

#[test]
fn invalid_float() {
    let res: Result<Container<f32>> = from_str(ZERO_LEN_CHAR_TEXT);
    insta::assert_snapshot!(res.unwrap_err(), @"Tried parsing an invalid number");
}

const ZERO_LEN_CHAR_TEXT: &str = r#"
"Container"
{
    "inner" ""
}
"#;

const TWO_LEN_CHAR_TEXT: &str = r#"
"Container"
{
    "inner" "ab"
}
"#;

#[test]
fn invalid_chars() {
    let name_base = "invalid_chars";
    snapshot_de_err::<Container<char>>(&format!("{}-zero_len", name_base), ZERO_LEN_CHAR_TEXT);
    snapshot_de_err::<Container<char>>(&format!("{}-two_len", name_base), TWO_LEN_CHAR_TEXT);
}

#[test]
fn non_finite_float() {
    let err = to_string(&Container::new(f32::INFINITY)).unwrap_err();
    insta::assert_snapshot!(err, @"Only finite f32 values are allowed. Instead got: inf");
}