use super::register;
use crate::textfmt::{from_text, to_text};
use crate::Value;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use codec::Encode;
fn roundtrip<T: scale_info::TypeInfo + Encode + 'static>(val: &T, expected_text: &str) {
let encoded = val.encode();
let (ty_id, reg) = register(val);
let value = Value::new(&encoded, ty_id, ®);
let text = to_text(&value).expect("format failed");
assert_eq!(text, expected_text, "format mismatch");
let decoded = from_text(&text, ®, ty_id).expect("parse failed");
assert_eq!(decoded, encoded, "roundtrip mismatch for '{text}'");
}
#[test]
fn primitives() {
roundtrip(&42u8, "42");
roundtrip(&1000u16, "1000");
roundtrip(&123456u32, "123456");
roundtrip(&999u64, "999");
roundtrip(&0u128, "0");
roundtrip(&true, "true");
roundtrip(&false, "false");
roundtrip(&-1i8, "-1");
roundtrip(&-1000i16, "-1000");
roundtrip(&-42i32, "-42");
}
#[test]
fn strings() {
roundtrip(&String::from("hello"), "'hello'");
roundtrip(&String::from(""), "''");
roundtrip(&String::from("foo bar"), "'foo bar'");
roundtrip(&String::from("it's"), "'it''s'");
roundtrip(&String::from("'"), "''''");
}
#[test]
fn integer_overflow_rejected() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::U8]);
assert!(from_text("256", ®, 0).is_err());
assert!(from_text("0", ®, 0).is_ok());
assert!(from_text("255", ®, 0).is_ok());
let reg = Registry::new(alloc::vec![TypeDef::U16]);
assert!(from_text("65536", ®, 0).is_err());
assert!(from_text("65535", ®, 0).is_ok());
let reg = Registry::new(alloc::vec![TypeDef::I8]);
assert!(from_text("-129", ®, 0).is_err());
assert!(from_text("128", ®, 0).is_err());
assert!(from_text("-128", ®, 0).is_ok());
assert!(from_text("127", ®, 0).is_ok());
}
#[test]
fn recursive_type_depth_limit() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::StructNewType(0)]);
let err = from_text("42", ®, 0);
assert!(err.is_err());
let msg = alloc::format!("{}", err.unwrap_err());
assert!(msg.contains("depth"), "expected depth error, got: {msg}");
}
#[test]
fn trailing_input_truncated_in_error() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::U8]);
let long_trail = alloc::format!("1{}", "x".repeat(200));
let err = from_text(&long_trail, ®, 0).unwrap_err();
let msg = alloc::format!("{err}");
assert!(msg.len() < 120, "error too long, may echo raw input: {msg}");
assert!(msg.contains("..."), "should be truncated: {msg}");
}
#[test]
fn bool_rejects_prefix_match() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::Bool]);
assert!(from_text("truer", ®, 0).is_err());
assert!(from_text("falsehood", ®, 0).is_err());
assert!(from_text("true", ®, 0).is_ok());
assert!(from_text("false", ®, 0).is_ok());
}
#[test]
fn unterminated_string_rejected() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::Str]);
assert!(from_text("'hello", ®, 0).is_err());
assert!(from_text("'", ®, 0).is_err());
}
#[test]
fn odd_hex_rejected() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::Bytes]);
assert!(from_text("0xabc", ®, 0).is_err()); assert!(from_text("0xab", ®, 0).is_ok());
}
#[test]
fn invalid_variant_rejected() {
#[derive(Encode, scale_info::TypeInfo)]
enum AB {
A,
#[allow(dead_code)]
B,
}
let (ty_id, reg) = register(&AB::A);
assert!(from_text("AB::C", ®, ty_id).is_err());
assert!(from_text("Wrong::A", ®, ty_id).is_err());
assert!(from_text("AB::A", ®, ty_id).is_ok());
}
#[test]
fn wrong_field_name_rejected() {
#[derive(Encode, scale_info::TypeInfo)]
struct Foo {
bar: u32,
}
let (ty_id, reg) = register(&Foo { bar: 0 });
assert!(from_text("(baz:1)", ®, ty_id).is_err());
assert!(from_text("(bar:1)", ®, ty_id).is_ok());
}
#[test]
fn empty_input_for_non_unit_rejected() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::U32]);
assert!(from_text("", ®, 0).is_err());
}
#[test]
fn missing_sequence_close_rejected() {
use crate::registry::{Registry, TypeDef};
let reg = Registry::new(alloc::vec![TypeDef::Sequence(1), TypeDef::U8]);
assert!(from_text("..1;2;3", ®, 0).is_err()); assert!(from_text("..1;2;3.", ®, 0).is_ok());
}
#[test]
fn string_with_special_chars_roundtrip() {
roundtrip(&String::from("a;b"), "'a;b'");
roundtrip(&String::from("a:b"), "'a:b'");
roundtrip(&String::from("a(b)"), "'a(b)'");
roundtrip(&String::from("..x."), "'..x.'");
roundtrip(&String::from("Foo::Bar"), "'Foo::Bar'");
}
#[test]
fn display_impl() {
let encoded = 42u32.encode();
let (ty_id, reg) = register(&42u32);
let value = Value::new(&encoded, ty_id, ®);
assert_eq!(alloc::format!("{value}"), "42");
}
#[test]
fn tuples() {
roundtrip(&(1u8, 2u16), "(1;2)");
roundtrip(&(true, 42u32, String::from("x")), "(true;42;'x')");
}
#[test]
fn option_variants() {
roundtrip(&Some(42u32), "Option::Some(42)");
roundtrip(&Option::<u32>::None, "Option::None");
}
#[test]
fn sequences() {
roundtrip(&vec![1u16, 2, 3], "..1;2;3.");
roundtrip(&Vec::<u32>::new(), "...");
roundtrip(&vec![vec![1u16, 2], vec![3u16]], "....1;2.;..3..");
}
#[test]
fn bytes_hex() {
roundtrip(&vec![0x01u8, 0x02, 0xAB], "0x0102ab");
roundtrip(&Vec::<u8>::new(), "0x");
}
#[test]
fn arrays() {
roundtrip(&[10u8, 20, 30], "..10;20;30.");
}
#[test]
fn named_struct() {
#[derive(Encode, scale_info::TypeInfo)]
struct Point {
x: u32,
y: u32,
}
roundtrip(&Point { x: 1, y: 2 }, "(x:1;y:2)");
}
#[test]
fn nested_struct() {
#[derive(Encode, scale_info::TypeInfo)]
struct Inner {
a: u8,
b: bool,
}
#[derive(Encode, scale_info::TypeInfo)]
struct Outer {
name: String,
inner: Inner,
}
roundtrip(
&Outer {
name: String::from("test"),
inner: Inner { a: 1, b: true },
},
"(name:'test';inner:(a:1;b:true))",
);
}
#[test]
fn enum_variants() {
#[derive(Encode, scale_info::TypeInfo)]
enum Color {
Red,
Green,
Blue,
}
roundtrip(&Color::Red, "Color::Red");
roundtrip(&Color::Green, "Color::Green");
}
#[test]
fn enum_with_data() {
#[derive(Encode, scale_info::TypeInfo)]
enum Action {
Transfer { from: u32, to: u32, amount: u64 },
Approve(u32),
}
roundtrip(
&Action::Transfer {
from: 1,
to: 2,
amount: 1000,
},
"Action::Transfer(from:1;to:2;amount:1000)",
);
roundtrip(&Action::Approve(42), "Action::Approve(42)");
}
#[test]
fn struct_with_sequence() {
#[derive(Encode, scale_info::TypeInfo)]
struct Staker {
era: u32,
validators: Vec<u32>,
active: bool,
}
roundtrip(
&Staker {
era: 42,
validators: vec![1, 2, 3],
active: true,
},
"(era:42;validators:..1;2;3.;active:true)",
);
}
#[test]
fn complex_nested() {
#[derive(Encode, scale_info::TypeInfo)]
struct Nom {
who: u32,
value: u64,
}
#[derive(Encode, scale_info::TypeInfo)]
struct Exposure {
total: u64,
own: u64,
others: Vec<Nom>,
}
roundtrip(
&Exposure {
total: 1000,
own: 500,
others: vec![Nom { who: 1, value: 300 }, Nom { who: 2, value: 200 }],
},
"(total:1000;own:500;others:..(who:1;value:300);(who:2;value:200).)",
);
}
#[test]
fn map_type() {
let mut m = BTreeMap::new();
m.insert(1u32, 100u64);
m.insert(2, 200);
roundtrip(&m, "..(1;100);(2;200).");
}
#[test]
fn parse_then_format() {
#[derive(Encode, scale_info::TypeInfo)]
struct Pair {
a: u32,
b: bool,
}
let (ty_id, reg) = register(&Pair { a: 0, b: false });
let input = "(a:99;b:true)";
let bytes = from_text(input, ®, ty_id).unwrap();
let value = Value::new(&bytes, ty_id, ®);
let output = to_text(&value).unwrap();
assert_eq!(output, input);
}