use std::str::FromStr;
use libfmp::{
codecs::{
DateOrDateTime, DynamicJson, DynamicObject, FiscalYear, IsoTimestamp,
NumberOrNumericString, OpaqueDateText, PercentageValue, TitleCaseBoolFlag, TrueFalseFlag,
UsDate, WireBool, YesNoFlag, YnFlag, empty_date, empty_or_null_date,
empty_or_null_date_or_datetime,
},
types::Date,
};
use serde::{Deserialize, Serialize};
#[test]
fn mixed_numeric_forms_round_trip_without_f64_coercion() {
let year_string: FiscalYear = serde_json::from_str(r#""2025""#).unwrap();
let year_integer: FiscalYear = serde_json::from_str("2025").unwrap();
assert_eq!(serde_json::to_string(&year_string).unwrap(), r#""2025""#);
assert_eq!(serde_json::to_string(&year_integer).unwrap(), "2025");
assert!(serde_json::from_str::<FiscalYear>("-2025").is_err());
assert!(serde_json::from_str::<FiscalYear>(r#""20.25""#).is_err());
let documented_string: NumberOrNumericString =
serde_json::from_str(r#""33644000000""#).unwrap();
let documented_number: NumberOrNumericString = serde_json::from_str("416161000000").unwrap();
assert_eq!(
serde_json::to_string(&documented_string).unwrap(),
r#""33644000000""#
);
assert_eq!(
serde_json::to_string(&documented_number).unwrap(),
"416161000000"
);
let beyond_javascript_safe_integer = "9007199254740993";
let exact: NumberOrNumericString =
serde_json::from_str(beyond_javascript_safe_integer).unwrap();
assert_eq!(
serde_json::to_string(&exact).unwrap(),
beyond_javascript_safe_integer
);
let beyond_u64 = "18446744073709551616";
let exact: NumberOrNumericString = serde_json::from_str(beyond_u64).unwrap();
assert_eq!(serde_json::to_string(&exact).unwrap(), beyond_u64);
}
#[test]
fn percentages_and_boolean_families_preserve_wire_kinds_and_spelling() {
for wire in ["0.4690516410716045", r#""0.10335""#, r#""97.26%""#] {
let value: PercentageValue = serde_json::from_str(wire).unwrap();
assert_eq!(serde_json::to_string(&value).unwrap(), wire);
}
assert_eq!(
serde_json::from_str::<WireBool>("true").unwrap(),
WireBool(true)
);
assert_eq!(
serde_json::from_str::<YnFlag>(r#""N""#).unwrap(),
YnFlag::False
);
assert_eq!(
serde_json::from_str::<YesNoFlag>(r#""Yes""#).unwrap(),
YesNoFlag::True
);
assert_eq!(
serde_json::from_str::<TrueFalseFlag>(r#""false""#).unwrap(),
TrueFalseFlag::False
);
assert_eq!(
serde_json::from_str::<TitleCaseBoolFlag>(r#""False""#).unwrap(),
TitleCaseBoolFlag::False
);
assert!(serde_json::from_str::<YnFlag>(r#""No""#).is_err());
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct OptionalTemporalFixture {
#[serde(default, with = "empty_or_null_date")]
declaration_date: Option<Date>,
#[serde(default, with = "empty_or_null_date_or_datetime")]
filing_date: Option<DateOrDateTime>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct RequiredEmptyDateFixture {
#[serde(with = "empty_date")]
date_of_first_sale: Option<Date>,
}
#[test]
fn temporal_contracts_are_strict_and_empty_or_null_aware() {
assert_eq!(
IsoTimestamp::from_str("2026-07-30T16:00:20.049Z")
.unwrap()
.to_string(),
"2026-07-30T16:00:20.049Z"
);
assert!(IsoTimestamp::from_str("2026-07-30 16:00:20").is_err());
assert_eq!(
UsDate::from_str("10-31-2026").unwrap().to_string(),
"10-31-2026"
);
assert!(UsDate::from_str("2026-10-31").is_err());
assert!(matches!(
DateOrDateTime::from_str("2023-11-13").unwrap(),
DateOrDateTime::Date(_)
));
assert!(matches!(
DateOrDateTime::from_str("2026-07-30 00:00:00").unwrap(),
DateOrDateTime::DateTime(_)
));
assert!(DateOrDateTime::from_str("2026-07-30T00:00:00Z").is_err());
for wire in [
r#"{"declaration_date":"","filing_date":null}"#,
r#"{"declaration_date":null,"filing_date":""}"#,
r#"{}"#,
] {
let fixture: OptionalTemporalFixture = serde_json::from_str(wire).unwrap();
assert_eq!(fixture.declaration_date, None);
assert_eq!(fixture.filing_date, None);
}
let partial = OpaqueDateText("--09-27".to_owned());
assert_eq!(serde_json::to_string(&partial).unwrap(), r#""--09-27""#);
}
#[test]
fn required_empty_date_preserves_its_sentinel_and_rejects_null_missing_or_malformed_values() {
let empty: RequiredEmptyDateFixture =
serde_json::from_str(r#"{"date_of_first_sale":""}"#).unwrap();
assert_eq!(empty.date_of_first_sale, None);
assert_eq!(
serde_json::to_string(&empty).unwrap(),
r#"{"date_of_first_sale":""}"#
);
let actual: RequiredEmptyDateFixture =
serde_json::from_str(r#"{"date_of_first_sale":"2014-02-14"}"#).unwrap();
assert_eq!(actual.date_of_first_sale.unwrap().to_string(), "2014-02-14");
assert_eq!(
serde_json::to_string(&actual).unwrap(),
r#"{"date_of_first_sale":"2014-02-14"}"#
);
for wire in [
r#"{"date_of_first_sale":null}"#,
r#"{}"#,
r#"{"date_of_first_sale":"02-14-2014"}"#,
r#"{"date_of_first_sale":"2014-02-30"}"#,
] {
assert!(serde_json::from_str::<RequiredEmptyDateFixture>(wire).is_err());
}
}
#[test]
fn dynamic_json_preserves_recursive_native_shape() {
let value: DynamicJson = serde_json::from_str(
r#"{"documenttype":"10-K","documentannualreport":"true","documentfiscalyearfocus":2025,"nested":[null,false,{"amount":"33644000000","exact":9007199254740993}]}"#,
)
.unwrap();
assert_eq!(value["documenttype"], "10-K");
assert_eq!(value["documentfiscalyearfocus"], 2025);
assert_eq!(value["nested"][2]["amount"], "33644000000");
assert_eq!(value["nested"][2]["exact"].to_string(), "9007199254740993");
}
#[test]
fn dynamic_object_preserves_semantics_without_treating_member_order_as_data() {
let wire = r#"{
"Issuer Defined Section": {
"heterogeneousCells": [null, false, "text", 184467440737095516160, -42, 0.125],
"nested": {"arbitrary key": [1, {"flag": true}]}
},
"another section": []
}"#;
let object: DynamicObject = serde_json::from_str(wire).unwrap();
assert_eq!(
object["Issuer Defined Section"]["heterogeneousCells"][3].to_string(),
"184467440737095516160"
);
assert_eq!(
object["Issuer Defined Section"]["nested"]["arbitrary key"][1]["flag"],
true
);
let encoded = serde_json::to_string(&object).unwrap();
let round_trip: DynamicObject = serde_json::from_str(&encoded).unwrap();
assert_eq!(round_trip, object);
assert_eq!(
round_trip["Issuer Defined Section"]["heterogeneousCells"][3].to_string(),
"184467440737095516160"
);
assert!(serde_json::from_str::<DynamicObject>("[]").is_err());
assert!(serde_json::from_str::<DynamicObject>("null").is_err());
}