use base64::Engine as _;
use std::collections::HashMap;
use bc_ur::prelude::*;
use indoc::indoc;
use dcbor_parse::{ parse_dcbor_item, ParseError };
fn roundtrip<T: Into<CBOR>>(value: T) {
let cbor = value.into();
let src = cbor.diagnostic();
match parse_dcbor_item(&src) {
Ok(result) => {
if result != cbor {
panic!("=== Expected ===\n{}\n\n=== Got ===\n{}", cbor, result);
}
}
Err(e) => panic!("{:?}", e),
}
}
#[test]
fn test_basic_types() {
roundtrip(true);
roundtrip(false);
roundtrip(CBOR::null());
roundtrip(10);
roundtrip(3.14);
roundtrip(f64::INFINITY);
roundtrip(f64::NEG_INFINITY);
roundtrip("Hello, world!");
}
fn hex_diagnostic(bytes: &[u8]) -> String {
let hex = hex::encode(bytes);
format!("h'{}'", hex)
}
fn base64_diagnostic(bytes: &[u8]) -> String {
format!("b64'{}'", base64::engine::general_purpose::STANDARD.encode(bytes))
}
#[test]
fn test_byte_string() {
let bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a];
let cbor = CBOR::to_byte_string(bytes.clone());
roundtrip(cbor.clone());
let hex = hex_diagnostic(&bytes);
assert_eq!(hex, "h'0102030405060708090a'");
let cbor2 = parse_dcbor_item(&hex).unwrap();
assert_eq!(cbor2, cbor);
let base64 = base64_diagnostic(&bytes);
assert_eq!(base64, "b64'AQIDBAUGBwgJCg=='");
let cbor3 = parse_dcbor_item(&base64).unwrap();
assert_eq!(cbor3, cbor);
}
#[test]
fn test_nan() {
let cbor = CBOR::from(f64::NAN);
let src = cbor.diagnostic();
assert_eq!(src, "NaN");
let cbor2 = parse_dcbor_item(&src).unwrap();
assert!(f64::try_from(cbor2).unwrap().is_nan());
}
#[test]
fn test_tagged() {
roundtrip(CBOR::to_tagged_value(1234, CBOR::to_byte_string(vec![0x01, 0x02, 0x03])));
roundtrip(CBOR::to_tagged_value(5678, "Hello, world!"));
roundtrip(CBOR::to_tagged_value(9012, true));
}
#[test]
fn test_array() {
let v: Vec<i32> = vec![];
roundtrip(v);
roundtrip(vec![1, 2, 3]);
roundtrip(vec![true.to_cbor(), false.to_cbor(), CBOR::null()]);
roundtrip(vec![CBOR::to_byte_string(vec![0x01, 0x02]).to_cbor(), "Hello".to_cbor()]);
roundtrip(vec![vec![1, 2], vec![3, 4]]);
}
#[test]
fn test_map() {
let m1: HashMap<String, i32> = HashMap::new();
roundtrip(m1);
let mut m2 = HashMap::new();
m2.insert("key1", 1);
m2.insert("key2", 2);
m2.insert("key3", 3);
roundtrip(m2);
let mut m3 = HashMap::new();
m3.insert(1, "value1");
m3.insert(2, "value2");
m3.insert(3, "value3");
roundtrip(m3.clone());
let mut m4 = HashMap::new();
m4.insert("key1", CBOR::to_byte_string(vec![0x01, 0x02]));
m4.insert("key2", "value2".to_cbor());
m4.insert("key3", m3.to_cbor());
roundtrip(m4);
}
#[test]
fn test_nested() {
let nested = vec![
CBOR::to_tagged_value(1234, CBOR::to_byte_string(vec![0x01, 0x02, 0x03])),
vec![1, 2, 3].to_cbor(),
HashMap::from([
("key1", "value1".to_cbor()),
("key2", vec![4, 5, 6].to_cbor()),
]).to_cbor()
];
roundtrip(nested);
}
#[test]
fn test_ur() {
dcbor::register_tags();
let date = dcbor::Date::from_ymd(2025, 5, 15);
let ur = date.ur_string();
assert_eq!(ur, "ur:date/cyisdadmlasgtapttl");
let date2 = dcbor::Date::from_ur_string(&ur).unwrap();
assert_eq!(date2, date);
let date_cbor = parse_dcbor_item(&ur).unwrap();
assert_eq!(date_cbor, date.to_cbor());
}
#[test]
fn test_named_tag() {
dcbor::register_tags();
let date_cbor = dcbor::Date::from_ymd(2025, 5, 15).to_cbor();
let date_diag = date_cbor.diagnostic().to_string().replace("1(", "date(");
let date_cbor2 = parse_dcbor_item(&date_diag).unwrap();
assert_eq!(date_cbor2, date_cbor);
}
#[test]
fn test_known_value() {
let v = known_values::IS_A;
let cbor = v.to_cbor();
let src = cbor.diagnostic();
assert_eq!(src, "40000(1)");
let cbor2 = parse_dcbor_item(&src).unwrap();
assert_eq!(cbor2, cbor);
let src2 = "'1'";
let cbor3 = parse_dcbor_item(src2).unwrap();
assert_eq!(cbor3, cbor);
let src3 = "'isA'";
let cbor4 = parse_dcbor_item(src3).unwrap();
assert_eq!(cbor4, cbor);
}
#[test]
fn test_unit_known_value() {
let v = known_values::UNIT;
let cbor = v.to_cbor();
let src = cbor.diagnostic();
assert_eq!(src, "40000(0)");
let cbor2 = parse_dcbor_item(&src).unwrap();
assert_eq!(cbor2, cbor);
let src2 = "'0'";
let cbor3 = parse_dcbor_item(&src2).unwrap();
assert_eq!(cbor3, cbor);
let src3 = "''";
let cbor4 = parse_dcbor_item(&src3).unwrap();
assert_eq!(cbor4, cbor);
let src4 = "Unit";
let cbor5 = parse_dcbor_item(&src4).unwrap();
assert_eq!(cbor5, cbor);
}
#[test]
fn test_errors() {
dcbor::register_tags();
fn check_error<F>(source: &str, expected: F) where F: Fn(&ParseError) -> bool {
let result = parse_dcbor_item(source);
let err = result.unwrap_err();
assert!(expected(&err), "Unexpected error for source `{}`: {:?}", source, err);
}
check_error("", |e| matches!(e, ParseError::EmptyInput));
check_error("[1, 2", |e| matches!(e, ParseError::UnexpectedEndOfInput));
check_error("[1, 2,\n3, 4,", |e| matches!(e, ParseError::UnexpectedEndOfInput));
check_error("1 1", |e| matches!(e, ParseError::ExtraData(_)));
check_error("(", |e| matches!(e, ParseError::UnexpectedToken(_, _)));
check_error("q", |e| matches!(e, ParseError::UnrecognizedToken(_)));
check_error("[1 2 3]", |e| matches!(e, ParseError::ExpectedComma(_)));
check_error("{1: 2, 3}", |e| matches!(e, ParseError::ExpectedColon(_)));
check_error("{1: 2 3: 4}", |e| matches!(e, ParseError::ExpectedComma(_)));
check_error("1([1, 2, 3]", |e| matches!(e, ParseError::UnmatchedParentheses(_)));
check_error("{1: 2, 3: 4", |e| matches!(e, ParseError::UnmatchedBraces(_)));
check_error("{1: 2, 3:}", |e| matches!(e, ParseError::ExpectedMapKey(_)));
check_error("20000000000000000000(1)", |e| matches!(e, ParseError::InvalidTagValue(_, _)));
check_error("foobar(1)", |e| matches!(e, ParseError::UnknownTagName(_, _)));
check_error("h'01020'", |e| matches!(e, ParseError::InvalidHexString(_)));
check_error("b64'AQIDBAUGBwgJCg'", |e| matches!(e, ParseError::InvalidBase64String(_)));
check_error("ur:foobar/cyisdadmlasgtapttl", |e| matches!(e, ParseError::UnknownUrType(_, _)));
check_error("ur:date/cyisdadmlasgtapttx", |e| matches!(e, ParseError::InvalidUr(_, _)));
check_error("'20000000000000000000'", |e| matches!(e, ParseError::InvalidKnownValue(_, _)));
check_error("'foobar'", |e| matches!(e, ParseError::UnknownKnownValueName(_, _)));
}
#[test]
fn test_whitespace() {
let src = indoc! {r#"
{
"Hello":
"World"
}
"#}.trim();
let result = parse_dcbor_item(&src).unwrap();
println!("{}", result.diagnostic());
}
#[test]
fn test_whitespace_2() {
let src = indoc! {r#"
{"Hello":
"World"}
"#}.trim();
let result = parse_dcbor_item(&src).unwrap();
println!("{}", result.diagnostic());
}
#[test]
fn test_inline_comments() {
let src = "/this is a comment/ [1, /ignore me/ 2, 3]";
let result = parse_dcbor_item(src).unwrap();
assert_eq!(result, vec![1, 2, 3].into());
}
#[test]
fn test_end_of_line_comments() {
let src = "[1, 2, 3] # this should be ignored";
let result = parse_dcbor_item(src).unwrap();
assert_eq!(result, vec![1, 2, 3].into());
}