use std::collections::BTreeMap;
use structio::{
Options, Pretty, SkipNull, beve, json, to_beve, to_beve_with, to_string, to_string_with,
};
struct Register(u32);
impl json::Write for Register {
fn write<O: Options>(&self, w: &mut json::Writer<'_, O>) {
self.0.write(w);
}
}
impl beve::Write for Register {
fn write<O: Options>(&self, w: &mut beve::Writer<'_, O>) {
self.0.write(w);
}
}
struct Surface {
id: Register,
volts: f64,
}
structio::object!(write_only Surface { id, volts });
#[test]
fn a_field_type_needs_no_read_impl_and_no_default() {
let s = Surface {
id: Register(7),
volts: 3.25,
};
assert_eq!(to_string(&s), r#"{"id":7,"volts":3.25}"#);
assert_eq!(
structio::transcode::beve_to_json(&to_beve(&s)).unwrap(),
r#"{"id":7,"volts":3.25}"#
);
}
#[derive(Default)]
struct BothWays {
device_id: u32,
label: String,
reading: Option<f64>,
}
structio::object!(BothWays as "camelCase" {
#[required] device_id,
"tag" => label,
reading,
});
struct WriteHalf {
device_id: u32,
label: String,
reading: Option<f64>,
}
structio::object!(write_only WriteHalf as "camelCase" {
device_id,
"tag" => label,
reading,
});
fn twins() -> (BothWays, WriteHalf) {
(
BothWays {
device_id: 4,
label: "north".into(),
reading: None,
},
WriteHalf {
device_id: 4,
label: "north".into(),
reading: None,
},
)
}
#[test]
fn the_keys_the_case_rule_and_the_bytes_are_unchanged() {
let (both, half) = twins();
assert_eq!(to_string(&half), to_string(&both));
assert_eq!(
to_string(&half),
r#"{"deviceId":4,"tag":"north","reading":null}"#
);
assert_eq!(to_beve(&half), to_beve(&both));
}
#[test]
fn a_write_policy_reaches_it_the_same_way() {
let (both, half) = twins();
assert_eq!(
to_string_with::<Pretty, _>(&half),
to_string_with::<Pretty, _>(&both)
);
assert_eq!(
to_string_with::<SkipNull, _>(&half),
r#"{"deviceId":4,"tag":"north"}"#
);
assert_eq!(
to_beve_with::<SkipNull, _>(&half),
to_beve_with::<SkipNull, _>(&both)
);
}
struct Partial {
kept: u32,
#[allow(dead_code)]
dropped: Register,
}
structio::object!(write_only Partial { kept, .. });
#[test]
fn a_declaration_may_still_end_in_rest() {
let p = Partial {
kept: 1,
dropped: Register(9),
};
assert_eq!(to_string(&p), r#"{"kept":1}"#);
assert_eq!(
structio::transcode::beve_to_json(&to_beve(&p)).unwrap(),
r#"{"kept":1}"#
);
}
struct Tenths;
impl json::WriteAs<u32> for Tenths {
fn write<O: Options>(value: &u32, w: &mut json::Writer<'_, O>) {
json::Write::write(&(*value as f64 / 10.0), w);
}
}
impl beve::WriteAs<u32> for Tenths {
fn write<O: Options>(value: &u32, w: &mut beve::Writer<'_, O>) {
beve::Write::write(&(*value as f64 / 10.0), w);
}
}
struct Adapted {
scaled: u32,
}
structio::object!(write_only Adapted { scaled as Tenths });
#[test]
fn an_adapter_needs_only_its_write_half() {
assert_eq!(to_string(&Adapted { scaled: 25 }), r#"{"scaled":2.5}"#);
assert!(!to_beve(&Adapted { scaled: 25 }).is_empty());
}
#[derive(Default)]
struct PointBoth {
x: f64,
y: f64,
}
structio::array!(PointBoth [ f64; x, y ]);
struct PointHalf {
x: f64,
y: f64,
}
structio::array!(write_only PointHalf [ f64; x, y ]);
#[test]
fn a_positional_struct_keeps_its_typed_array() {
let (both, half) = (PointBoth { x: 1.5, y: -2.0 }, PointHalf { x: 1.5, y: -2.0 });
assert_eq!(to_string(&half), "[1.5,-2]");
assert_eq!(to_string(&half), to_string(&both));
assert_eq!(to_beve(&half), to_beve(&both));
}
struct Registers {
a: Register,
b: Register,
}
structio::array!(write_only Registers [ a, b ]);
#[test]
fn a_positional_element_needs_no_read_impl() {
let r = Registers {
a: Register(1),
b: Register(2),
};
assert_eq!(to_string(&r), "[1,2]");
}
#[derive(Default)]
enum LevelBoth {
#[default]
Info,
Warning,
}
structio::unit_enum!(LevelBoth as "snake_case" { Info, "WARN" => Warning });
enum LevelHalf {
Info,
Warning,
}
structio::unit_enum!(write_only LevelHalf as "snake_case" { Info, "WARN" => Warning });
#[test]
fn a_unit_enum_keeps_its_string_array_packing() {
assert_eq!(to_string(&LevelHalf::Warning), r#""WARN""#);
assert_eq!(to_string(&LevelHalf::Info), to_string(&LevelBoth::Info));
let half = vec![LevelHalf::Info, LevelHalf::Warning];
let both = vec![LevelBoth::Info, LevelBoth::Warning];
assert_eq!(to_beve(&half), to_beve(&both));
}
#[derive(Default)]
struct Payload {
mode: u8,
}
structio::object!(Payload { mode });
#[derive(Default)]
enum MsgBoth {
#[default]
Idle,
Config(Payload),
}
structio::tagged_enum!(MsgBoth { Idle, Config(_) });
enum MsgHalf {
Idle,
Config(Payload),
}
structio::tagged_enum!(write_only MsgHalf { Idle, Config(_) });
#[test]
fn an_externally_tagged_enum_narrows() {
assert_eq!(
to_string(&MsgHalf::Config(Payload { mode: 2 })),
r#"{"Config":{"mode":2}}"#
);
assert_eq!(to_string(&MsgHalf::Idle), to_string(&MsgBoth::Idle));
assert_eq!(
to_beve(&MsgHalf::Config(Payload { mode: 2 })),
to_beve(&MsgBoth::Config(Payload { mode: 2 }))
);
}
#[derive(Default)]
enum TaggedBoth {
#[default]
Idle,
Config(Payload),
}
structio::tagged_enum!(TaggedBoth as tag "kind" { Idle, Config(_) });
enum TaggedHalf {
Idle,
Config(Payload),
}
structio::tagged_enum!(write_only TaggedHalf as tag "kind" { Idle, Config(_) });
#[test]
fn an_internally_tagged_enum_narrows() {
assert_eq!(
to_string(&TaggedHalf::Config(Payload { mode: 2 })),
r#"{"kind":"Config","mode":2}"#
);
assert_eq!(
to_string(&TaggedHalf::Config(Payload { mode: 2 })),
to_string(&TaggedBoth::Config(Payload { mode: 2 }))
);
assert_eq!(
to_beve(&TaggedHalf::Config(Payload { mode: 2 })),
to_beve(&TaggedBoth::Config(Payload { mode: 2 }))
);
assert_eq!(to_string(&TaggedHalf::Idle), to_string(&TaggedBoth::Idle));
assert_eq!(to_beve(&TaggedHalf::Idle), to_beve(&TaggedBoth::Idle));
}
struct JsonOnly {
id: Register,
}
structio::json_object!(write_only JsonOnly { id });
struct BeveOnly<'a> {
payload: &'a [u8],
}
structio::beve_object!(write_only ['a] BeveOnly<'a> { payload });
#[test]
fn the_two_axes_narrow_independently() {
assert_eq!(to_string(&JsonOnly { id: Register(3) }), r#"{"id":3}"#);
assert!(
!to_beve(&BeveOnly {
payload: &[1, 2, 3]
})
.is_empty()
);
}
struct Sample<T> {
value: T,
}
structio::object!(write_only [T: structio::Write] Sample<T> { value });
#[test]
fn a_type_parameter_carries_the_write_bound_alone() {
assert_eq!(
to_string(&Sample {
value: Register(11)
}),
r#"{"value":11}"#
);
assert_eq!(to_string(&Sample { value: 2u8 }), r#"{"value":2}"#);
}
#[cfg(feature = "derive")]
#[derive(structio::Structio)]
#[structio(write_only, rename_all = "camelCase")]
struct Derived {
device_id: u32,
#[structio(rename = "tag")]
label: String,
reading: Option<f64>,
}
#[cfg(feature = "derive")]
#[derive(structio::Structio)]
#[structio(write_only)]
struct DerivedSample<T> {
value: T,
}
#[cfg(feature = "derive")]
#[test]
fn the_derive_expands_to_the_narrowed_declaration() {
let derived = Derived {
device_id: 4,
label: "north".into(),
reading: None,
};
let (both, _) = twins();
assert_eq!(to_string(&derived), to_string(&both));
assert_eq!(to_beve(&derived), to_beve(&both));
assert_eq!(
to_string(&DerivedSample {
value: Register(11)
}),
r#"{"value":11}"#
);
}
#[derive(Default)]
struct PairBoth {
a: u32,
b: u32,
}
structio::json_array!(PairBoth [ a, b ]);
struct PairJson {
a: u32,
b: u32,
}
structio::json_array!(write_only PairJson [ a, b ]);
#[derive(Default)]
struct PairBeveBoth {
a: u32,
b: u32,
}
structio::beve_array!(PairBeveBoth [ u32; a, b ]);
struct PairBeve {
a: u32,
b: u32,
}
structio::beve_array!(write_only PairBeve [ u32; a, b ]);
#[derive(Default)]
enum JsonMsgBoth {
#[default]
Idle,
Config(Payload),
}
structio::json_tagged_enum!(JsonMsgBoth as tag "kind" { Idle, Config(_) });
enum JsonMsgHalf {
Idle,
Config(Payload),
}
structio::json_tagged_enum!(write_only JsonMsgHalf as tag "kind" { Idle, Config(_) });
#[derive(Default)]
enum BeveMsgBoth {
#[default]
Idle,
Config(Payload),
}
structio::beve_tagged_enum!(BeveMsgBoth as "kebab-case" { Idle, Config(_) });
enum BeveMsgHalf {
Idle,
Config(Payload),
}
structio::beve_tagged_enum!(write_only BeveMsgHalf as "kebab-case" { Idle, Config(_) });
#[test]
fn every_one_format_macro_narrows_to_one_direction() {
assert_eq!(
to_string(&PairJson { a: 1, b: 2 }),
to_string(&PairBoth { a: 1, b: 2 })
);
assert_eq!(
to_beve(&PairBeve { a: 1, b: 2 }),
to_beve(&PairBeveBoth { a: 1, b: 2 })
);
assert_eq!(
to_string(&JsonMsgHalf::Config(Payload { mode: 1 })),
to_string(&JsonMsgBoth::Config(Payload { mode: 1 }))
);
assert_eq!(to_string(&JsonMsgHalf::Idle), to_string(&JsonMsgBoth::Idle));
assert_eq!(
to_beve(&BeveMsgHalf::Config(Payload { mode: 1 })),
to_beve(&BeveMsgBoth::Config(Payload { mode: 1 }))
);
assert_eq!(to_beve(&BeveMsgHalf::Idle), to_beve(&BeveMsgBoth::Idle));
}
struct Inner {
id: Register,
}
structio::object!(write_only Inner { id });
struct Outer {
inner: Inner,
many: Vec<Inner>,
maybe: Option<Inner>,
named: BTreeMap<String, Inner>,
}
structio::object!(write_only Outer { inner, many, maybe, named });
#[test]
fn a_narrowed_type_nests_in_containers_and_in_itself() {
let o = Outer {
inner: Inner { id: Register(1) },
many: vec![Inner { id: Register(2) }, Inner { id: Register(3) }],
maybe: None,
named: BTreeMap::from([("k".to_string(), Inner { id: Register(4) })]),
};
assert_eq!(
to_string(&o),
r#"{"inner":{"id":1},"many":[{"id":2},{"id":3}],"maybe":null,"named":{"k":{"id":4}}}"#
);
assert_eq!(
structio::transcode::beve_to_json(&to_beve(&o)).unwrap(),
to_string(&o)
);
assert_eq!(
structio::transcode::beve_to_json(&to_beve_with::<SkipNull, _>(&o)).unwrap(),
to_string_with::<SkipNull, _>(&o)
);
}
struct Slot<'a, T, const N: usize>
where
T: Clone,
{
label: &'a str,
value: T,
}
structio::object!(write_only ['a, T: Clone + structio::Write, const N: usize] Slot<'a, T, N> {
label, value
});
#[test]
fn a_lifetime_a_const_parameter_and_a_folded_bound() {
let s: Slot<'_, u8, 4> = Slot {
label: "x",
value: 2,
};
assert_eq!(to_string(&s), r#"{"label":"x","value":2}"#);
assert!(!to_beve(&s).is_empty());
}
#[cfg(feature = "derive")]
#[derive(structio::Structio)]
#[structio(write_only, array, element = "f64")]
struct DerivedPoint {
x: f64,
y: f64,
}
#[cfg(feature = "derive")]
#[derive(structio::Structio)]
#[structio(write_only, rename_all = "snake_case")]
enum DerivedLevel {
Info,
#[structio(rename = "WARN")]
Warning,
}
#[cfg(feature = "derive")]
#[derive(structio::Structio)]
#[structio(write_only, tag = "kind")]
enum DerivedMsg {
Idle,
Config(Payload),
}
#[cfg(feature = "derive")]
#[derive(structio::Structio)]
#[structio(write_only, json)]
struct DerivedJson<'a, T, const N: usize>
where
T: Clone,
{
label: &'a str,
value: T,
}
#[cfg(feature = "derive")]
#[test]
fn the_derive_narrows_every_shape_it_claims() {
assert_eq!(
to_beve(&DerivedPoint { x: 1.5, y: -2.0 }),
to_beve(&PointHalf { x: 1.5, y: -2.0 })
);
assert_eq!(to_string(&DerivedPoint { x: 1.5, y: -2.0 }), "[1.5,-2]");
assert_eq!(to_string(&DerivedLevel::Warning), r#""WARN""#);
assert_eq!(
to_beve(&vec![DerivedLevel::Info, DerivedLevel::Warning]),
to_beve(&vec![LevelHalf::Info, LevelHalf::Warning])
);
assert_eq!(
to_string(&DerivedMsg::Config(Payload { mode: 2 })),
r#"{"kind":"Config","mode":2}"#
);
assert_eq!(to_string(&DerivedMsg::Idle), to_string(&TaggedHalf::Idle));
let d: DerivedJson<'_, u8, 4> = DerivedJson {
label: "x",
value: 2,
};
assert_eq!(to_string(&d), r#"{"label":"x","value":2}"#);
}