use cbor2::Value;
use crate::constants::{SECURITY_MULTIBASE, XSD_DATETIME};
use crate::{
DecodeOptions, EncodeOptions, Error, TypeTable, decode, decode_with_loader, encode,
encode_with_loader,
};
fn text(value: &str) -> Value {
Value::Text(value.to_owned())
}
fn map(entries: impl IntoIterator<Item = (Value, Value)>) -> Value {
Value::Map(entries.into_iter().collect())
}
fn array(items: impl IntoIterator<Item = Value>) -> Value {
Value::Array(items.into_iter().collect())
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn assert_semantic_eq(left: &Value, right: &Value) {
let left = cbor2::to_canonical_vec(left).unwrap();
let right = cbor2::to_canonical_vec(right).unwrap();
assert_eq!(left, right);
}
#[test]
fn encodes_empty_document_without_compression() {
let bytes = encode(&map([]), EncodeOptions::uncompressed()).unwrap();
assert_eq!(hex(&bytes), "d9cb1d8200a0");
}
#[test]
fn encodes_empty_document_with_default_table() {
let bytes = encode(
&map([]),
EncodeOptions {
registry_entry_id: 1,
type_table: None,
canonical: true,
},
)
.unwrap();
assert_eq!(hex(&bytes), "d9cb1d8201a0");
}
#[test]
fn encodes_multibyte_registry_id() {
let table = TypeTable::new();
let bytes = encode(&map([]), EncodeOptions::compressed(128, &table)).unwrap();
assert_eq!(hex(&bytes), "d9cb1d821880a0");
}
#[test]
fn rejects_missing_type_table() {
let error = encode(
&map([]),
EncodeOptions {
registry_entry_id: 2,
type_table: None,
canonical: true,
},
)
.unwrap_err();
assert_eq!(error, Error::NoTypeTable(2));
}
#[test]
fn round_trips_remote_context_and_multibase_table_value() {
let context_url = "urn:foo";
let context = map([(
text("@context"),
map([
(text("ex"), text("https://example.com/")),
(
text("foo"),
map([
(text("@id"), text("ex:foo")),
(text("@type"), text(SECURITY_MULTIBASE)),
]),
),
]),
)]);
let document = map([
(text("@context"), text(context_url)),
(text("foo"), text("MAQID")),
]);
let mut table = TypeTable::with_common_tables();
table.insert("context", context_url, 0x8000);
table.insert(SECURITY_MULTIBASE, "MAQID", 0x8001);
let loader = |url: &str| {
if url == context_url {
Ok(context.clone())
} else {
Err(Error::DocumentLoader(url.to_owned()))
}
};
let bytes =
encode_with_loader(&document, EncodeOptions::compressed(20, &table), loader).unwrap();
let decoded = decode_with_loader(
&bytes,
DecodeOptions {
type_table: Some(&table),
},
|url| {
if url == context_url {
Ok(context.clone())
} else {
Err(Error::DocumentLoader(url.to_owned()))
}
},
)
.unwrap();
assert_semantic_eq(&decoded, &document);
}
#[test]
fn round_trips_inline_context_url_and_datetime_codecs() {
let document = map([
(
text("@context"),
map([
(text("ex"), text("https://example.com/")),
(
text("homepage"),
map([
(text("@id"), text("ex:homepage")),
(text("@type"), text("@id")),
]),
),
(
text("created"),
map([
(text("@id"), text("ex:created")),
(text("@type"), text(XSD_DATETIME)),
]),
),
]),
),
(text("homepage"), text("https://example.com/alice")),
(text("created"), text("2021-04-09T20:38:55Z")),
]);
let table = TypeTable::new();
let bytes = encode(&document, EncodeOptions::compressed(1, &table)).unwrap();
let decoded = decode(&bytes, DecodeOptions { type_table: None }).unwrap();
assert_semantic_eq(&decoded, &document);
}
#[test]
fn round_trips_data_url_uuid_and_did_key() {
let document = map([
(
text("@context"),
map([
(text("ex"), text("https://example.com/")),
(text("id"), map([(text("@id"), text("@id"))])),
(
text("image"),
map([
(text("@id"), text("ex:image")),
(text("@type"), text("@id")),
]),
),
(
text("uuid"),
map([(text("@id"), text("ex:uuid")), (text("@type"), text("@id"))]),
),
]),
),
(text("id"), text("did:key:z6MkiTBz")),
(text("image"), text("data:text/plain;base64,AQID")),
(
text("uuid"),
text("urn:uuid:550e8400-e29b-41d4-a716-446655440000"),
),
]);
let table = TypeTable::new();
let bytes = encode(&document, EncodeOptions::compressed(1, &table)).unwrap();
let decoded = decode(&bytes, DecodeOptions::default()).unwrap();
assert_semantic_eq(&decoded, &document);
}
#[test]
fn decodes_uncompressed_payload() {
let document = map([(text("hello"), text("world"))]);
let bytes = encode(&document, EncodeOptions::uncompressed()).unwrap();
let decoded = decode(&bytes, DecodeOptions::default()).unwrap();
assert_semantic_eq(&decoded, &document);
}
#[test]
fn top_level_array_round_trips() {
let context = map([(text("name"), text("https://schema.org/name"))]);
let document = array([
map([
(text("@context"), context.clone()),
(text("name"), text("Alice")),
]),
map([(text("@context"), context), (text("name"), text("Bob"))]),
]);
let table = TypeTable::new();
let bytes = encode(&document, EncodeOptions::compressed(1, &table)).unwrap();
let decoded = decode(&bytes, DecodeOptions::default()).unwrap();
assert_eq!(decoded, document);
}