use data_encoding::BASE32_NOPAD;
use fjall::UserKey;
use jacquard_common::types::crypto::{PublicKey, encode_uvarint};
use jacquard_common::types::string::Did;
use jacquard_common::types::tid::Tid;
use jacquard_common::{CowStr, IntoStatic};
use miette::{Context, IntoDiagnostic};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use smol_str::{SmolStr, format_smolstr};
use std::borrow::Cow;
use std::fmt::Display;
const S32_CHAR: &str = "234567abcdefghijklmnopqrstuvwxyz";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TrimmedDid<'s> {
Plc([u8; 15]),
Web(CowStr<'s>),
Other(CowStr<'s>),
}
pub(crate) const TAG_PLC: u8 = 1;
pub(crate) const TAG_WEB: u8 = 2;
impl<'s> TrimmedDid<'s> {
pub fn len(&self) -> usize {
match self {
TrimmedDid::Plc(_) => 16,
TrimmedDid::Web(s) => 1 + s.len(),
TrimmedDid::Other(s) => s.len(),
}
}
pub fn into_static(self) -> TrimmedDid<'static> {
match self {
TrimmedDid::Plc(bytes) => TrimmedDid::Plc(bytes),
TrimmedDid::Web(s) => TrimmedDid::Web(s.into_static()),
TrimmedDid::Other(s) => TrimmedDid::Other(s.into_static()),
}
}
pub fn from_did_str(value: &'s str) -> Self {
Self::from_trimmed_str(value)
}
fn from_trimmed_str(value: &'s str) -> Self {
let value = value.strip_prefix("did:").unwrap_or(value);
if let Some(rest) = value.strip_prefix("plc:")
&& rest.len() == 24
&& let Ok(bytes) = BASE32_NOPAD.decode(rest.to_ascii_uppercase().as_bytes())
&& let Ok(bytes) = bytes.try_into()
{
return TrimmedDid::Plc(bytes);
}
if let Some(rest) = value.strip_prefix("web:") {
return TrimmedDid::Web(CowStr::Borrowed(rest));
}
TrimmedDid::Other(CowStr::Borrowed(value))
}
pub fn to_did(&self) -> Did {
match self {
TrimmedDid::Plc(_) => {
let s = self.to_string();
Did::new_owned(format_smolstr!("did:{}", s)).expect("valid did from plc")
}
TrimmedDid::Web(s) => {
Did::new_owned(format_smolstr!("did:web:{}", s)).expect("valid did from web")
}
TrimmedDid::Other(s) => {
Did::new_owned(format_smolstr!("did:{}", s)).expect("valid did from other")
}
}
}
pub fn write_to_vec(&self, buf: &mut Vec<u8>) {
match self {
TrimmedDid::Plc(bytes) => {
buf.push(TAG_PLC);
buf.extend_from_slice(bytes);
}
TrimmedDid::Web(s) => {
buf.push(TAG_WEB);
buf.extend_from_slice(s.as_bytes());
}
TrimmedDid::Other(s) => buf.extend_from_slice(s.as_bytes()),
}
}
}
impl Display for TrimmedDid<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TrimmedDid::Plc(bytes) => {
f.write_str("plc:")?;
f.write_str(&BASE32_NOPAD.encode(bytes).to_ascii_lowercase())
}
TrimmedDid::Web(s) => {
f.write_str("web:")?;
f.write_str(s)
}
TrimmedDid::Other(s) => f.write_str(s),
}
}
}
impl<'a> From<&'a Did> for TrimmedDid<'a> {
fn from(did: &'a Did) -> Self {
Self::from_trimmed_str(did.as_str())
}
}
impl<'a> TryFrom<&'a [u8]> for TrimmedDid<'a> {
type Error = miette::Report;
fn try_from(value: &'a [u8]) -> miette::Result<Self> {
if value.is_empty() {
miette::bail!("empty did key");
}
match value[0] {
TAG_PLC => {
if value.len() == 16 {
let mut arr = [0u8; 15];
arr.copy_from_slice(&value[1..]);
return Ok(TrimmedDid::Plc(arr));
}
miette::bail!("invalid length for tagged plc did");
}
TAG_WEB => {
if let Ok(s) = std::str::from_utf8(&value[1..]) {
return Ok(TrimmedDid::Web(CowStr::Borrowed(s)));
}
miette::bail!("invalid utf8 for tagged web did");
}
_ => {
if let Ok(s) = std::str::from_utf8(value) {
return Ok(TrimmedDid::Other(CowStr::Borrowed(s)));
}
miette::bail!("invalid utf8 for other did");
}
}
}
}
impl<'a> From<TrimmedDid<'a>> for UserKey {
fn from(val: TrimmedDid<'a>) -> Self {
let mut vec = Vec::with_capacity(32);
val.write_to_vec(&mut vec);
UserKey::new(&vec)
}
}
impl<'a> From<&TrimmedDid<'a>> for UserKey {
fn from(val: &TrimmedDid<'a>) -> Self {
let mut vec = Vec::with_capacity(32);
val.write_to_vec(&mut vec);
UserKey::new(&vec)
}
}
impl Serialize for TrimmedDid<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut vec = Vec::with_capacity(32);
self.write_to_vec(&mut vec);
serializer.serialize_bytes(&vec)
}
}
impl<'de> Deserialize<'de> for TrimmedDid<'de> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct DidVisitor;
fn static_from_bytes<E>(value: &[u8]) -> Result<TrimmedDid<'static>, E>
where
E: serde::de::Error,
{
TrimmedDid::try_from(value)
.map(TrimmedDid::into_static)
.map_err(E::custom)
}
impl<'de> serde::de::Visitor<'de> for DidVisitor {
type Value = TrimmedDid<'de>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("bytes (tagged) or string (legacy)")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(TrimmedDid::from_trimmed_str(value).into_static())
}
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(TrimmedDid::from_trimmed_str(value))
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(TrimmedDid::from_trimmed_str(&value).into_static())
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
static_from_bytes(value)
}
fn visit_borrowed_bytes<E>(self, value: &'de [u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
TrimmedDid::try_from(value).map_err(E::custom)
}
fn visit_byte_buf<E>(self, value: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
static_from_bytes(&value)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0).min(2048));
while let Some(byte) = seq.next_element()? {
bytes.push(byte);
}
static_from_bytes(&bytes)
}
}
deserializer.deserialize_any(DidVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct DbTid(#[serde(with = "serde_bytes")] [u8; 8]);
impl DbTid {
pub fn new_from_bytes(bytes: [u8; 8]) -> Self {
Self(bytes)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_tid(self) -> Tid {
Tid::raw(self.to_smolstr())
}
pub fn add_micros(self, micros: u64) -> Self {
let raw = u64::from_be_bytes(self.0);
let next_micros = (raw >> 10).saturating_add(micros);
Self((next_micros << 10).to_be_bytes())
}
/// the inverse of the display form for any 8 bytes, not just valid tids.
/// `None` for anything display could not have printed
pub fn from_s32(s: &str) -> Option<Self> {
if s.len() != 13 {
return None;
}
// 13 digits hold 65 bits, so the checked math turns away a set 65th bit
s.chars()
.try_fold(0u64, |acc, c| {
let digit = S32_CHAR.find(c)? as u64;
acc.checked_mul(32)?.checked_add(digit)
})
.map(|i| Self(i.to_be_bytes()))
}
/// every replayed event calls this for its rev and most rkeys
fn to_smolstr(self) -> SmolStr {
let mut i = u64::from_be_bytes(self.0);
let mut out = [0; 13];
for c in out.iter_mut().rev() {
*c = S32_CHAR.as_bytes()[(i & 0x1F) as usize];
i >>= 5;
}
SmolStr::new_inline(std::str::from_utf8(&out).expect("s32 digits are ascii"))
}
}
pub fn s32decode(s: &str) -> u64 {
let mut i: usize = 0;
for c in s.chars() {
i = i * 32 + S32_CHAR.chars().position(|x| x == c).unwrap();
}
i as u64
}
impl From<&Tid> for DbTid {
fn from(tid: &Tid) -> Self {
DbTid(s32decode(tid.as_str()).to_be_bytes())
}
}
impl From<DbTid> for Tid {
fn from(val: DbTid) -> Self {
val.to_tid()
}
}
impl Display for DbTid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_smolstr())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
#[cfg_attr(not(feature = "indexer"), allow(dead_code))]
pub enum DbAction {
Create = 0,
Update = 1,
Delete = 2,
}
#[cfg_attr(not(feature = "indexer"), allow(dead_code))]
impl DbAction {
pub fn as_str(&self) -> &'static str {
match self {
DbAction::Create => "create",
DbAction::Update => "update",
DbAction::Delete => "delete",
}
}
}
impl<'a> TryFrom<&'a str> for DbAction {
type Error = miette::Report;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
match s {
"create" => Ok(DbAction::Create),
"update" => Ok(DbAction::Update),
"delete" => Ok(DbAction::Delete),
_ => miette::bail!("invalid action: {}", s),
}
}
}
impl Display for DbAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(untagged)]
#[cfg_attr(not(feature = "indexer"), allow(dead_code))]
pub enum DbRkey {
Tid(DbTid),
Str(SmolStr),
}
impl<'de> Deserialize<'de> for DbRkey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct DbRkeyVisitor;
fn tid_from_bytes<E>(value: &[u8]) -> Result<DbRkey, E>
where
E: serde::de::Error,
{
let bytes = value
.try_into()
.map_err(|_| E::invalid_length(value.len(), &"exactly 8 bytes"))?;
Ok(DbRkey::Tid(DbTid::new_from_bytes(bytes)))
}
impl<'de> serde::de::Visitor<'de> for DbRkeyVisitor {
type Value = DbRkey;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("an rkey string or an 8-byte tid")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(DbRkey::Str(SmolStr::from(value)))
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(DbRkey::Str(SmolStr::from(value)))
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
tid_from_bytes(value)
}
fn visit_byte_buf<E>(self, value: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
tid_from_bytes(&value)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut bytes = [0; 8];
for (index, byte) in bytes.iter_mut().enumerate() {
*byte = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(index, &self))?;
}
if seq.next_element::<serde::de::IgnoredAny>()?.is_some() {
return Err(serde::de::Error::invalid_length(9, &self));
}
Ok(DbRkey::Tid(DbTid::new_from_bytes(bytes)))
}
}
deserializer.deserialize_any(DbRkeyVisitor)
}
}
#[cfg_attr(not(feature = "indexer"), allow(dead_code))]
impl DbRkey {
pub fn new(s: &str) -> Self {
if let Ok(tid) = Tid::new(s) {
DbRkey::Tid(DbTid::from(&tid))
} else {
DbRkey::Str(SmolStr::from(s))
}
}
pub fn to_smolstr(&self) -> SmolStr {
match self {
DbRkey::Tid(tid) => tid.to_smolstr(),
DbRkey::Str(s) => s.clone(),
}
}
}
impl From<&str> for DbRkey {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for DbRkey {
fn from(s: String) -> Self {
Self::new(&s)
}
}
impl From<SmolStr> for DbRkey {
fn from(s: SmolStr) -> Self {
Self::new(s.as_str())
}
}
impl Display for DbRkey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_smolstr())
}
}
/// did:key:z... → raw multicodec public key bytes
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DidKey<'b>(
#[serde(borrow)]
#[serde(with = "serde_bytes")]
pub Cow<'b, [u8]>,
);
impl IntoStatic for DidKey<'_> {
type Output = DidKey<'static>;
fn into_static(self) -> Self::Output {
DidKey(Cow::Owned(self.0.into_owned()))
}
}
impl DidKey<'_> {
#[allow(dead_code)]
pub fn from_did_key(s: &str) -> miette::Result<Self> {
let multibase_str = s
.strip_prefix("did:key:")
.ok_or_else(|| miette::miette!("missing did:key: prefix in {s}"))?;
let (_base, bytes) = multibase::decode(multibase_str)
.into_diagnostic()
.wrap_err("invalid multibase in did:key")?;
Ok(Self(Cow::Owned(bytes)))
}
pub fn encode(&self) -> String {
multibase::encode(multibase::Base::Base58Btc, &self.0)
}
/// `None` when the multicodec prefix doesn't parse
pub fn to_public_key(&self) -> Option<PublicKey<'static>> {
use jacquard_common::types::crypto::{KeyCodec, decode_uvarint};
let (code, prefix) = decode_uvarint(&self.0)?;
let codec = match code {
0xED => KeyCodec::Ed25519,
0xE7 => KeyCodec::Secp256k1,
0x1200 => KeyCodec::P256,
other => KeyCodec::Unknown(other),
};
Some(PublicKey {
codec,
bytes: Cow::Owned(self.0[prefix..].to_vec()),
})
}
}
impl std::fmt::Display for DidKey<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "did:key:{}", self.encode())
}
}
impl From<PublicKey<'_>> for DidKey<'static> {
fn from(value: PublicKey<'_>) -> Self {
use jacquard_common::types::crypto::KeyCodec;
fn code_of(codec: KeyCodec) -> u64 {
match codec {
KeyCodec::Ed25519 => 0xED,
KeyCodec::Secp256k1 => 0xE7,
KeyCodec::P256 => 0x1200,
KeyCodec::Unknown(c) => c,
}
}
let mut bytes = Vec::with_capacity(8 + value.bytes.len());
bytes.append(&mut encode_uvarint(code_of(value.codec)));
bytes.extend_from_slice(&value.bytes);
Self(bytes.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use jacquard_common::types::tid::Tid;
#[test]
fn test_dbtid_roundtrip() {
let tid_str = "3jzfcijpj2z2a";
let tid = Tid::new(tid_str).unwrap();
let db_tid = DbTid::from(&tid);
assert_eq!(db_tid.to_tid().as_str(), tid_str);
let tid_str_2 = "2222222222222";
let tid = Tid::new(tid_str_2).unwrap();
let db_tid = DbTid::from(&tid);
assert_eq!(db_tid.to_tid().as_str(), tid_str_2);
}
#[test]
fn dbtid_display_parses_back_for_any_bytes() {
for bytes in [[0; 8], [0xff; 8], [0x7f, 0xff, 0, 1, 2, 3, 4, 5]] {
let tid = DbTid::new_from_bytes(bytes);
assert_eq!(DbTid::from_s32(&tid.to_string()), Some(tid));
}
// none of these can come out of display
for bad in [
"k222222222222",
"222222222222",
"22222222222222",
"222222222222!",
"",
] {
assert_eq!(DbTid::from_s32(bad), None, "{bad}");
}
}
#[test]
fn db_representation_conversions_are_lossless() {
for did in [
"did:plc:ewvi7nxzyoun6zhxrhs64oiz",
"did:web:example.com",
"did:example:opaque",
] {
let did = Did::new_owned(did).unwrap();
let trimmed = TrimmedDid::from(&did);
let mut bytes = Vec::new();
trimmed.write_to_vec(&mut bytes);
assert_eq!(TrimmedDid::try_from(bytes.as_slice()).unwrap(), trimmed);
assert_eq!(trimmed.to_did(), did);
assert_eq!(trimmed.to_string(), did.as_str().trim_start_matches("did:"));
assert_eq!(
UserKey::from(trimmed.clone()),
UserKey::from(&trimmed),
"owned and borrowed key conversions diverged for {did}"
);
assert_eq!(trimmed.clone().into_static(), trimmed);
}
for malformed in [
&[][..],
&[TAG_PLC][..],
&[TAG_PLC, 0][..],
&[TAG_WEB, 0xff][..],
&[0xff][..],
] {
assert!(TrimmedDid::try_from(malformed).is_err());
}
let tid = Tid::new("3jzfcijpj2z2a").unwrap();
let db_tid = DbTid::from(&tid);
assert_eq!(Tid::from(db_tid), tid);
assert_eq!(db_tid.to_string(), tid.as_str());
for value in ["self", "stinkpot", "3jzfcijpj2z2a"] {
let from_str = DbRkey::from(value);
assert_eq!(DbRkey::from(value.to_owned()), from_str);
assert_eq!(DbRkey::from(SmolStr::from(value)), from_str);
assert_eq!(from_str.to_string(), value);
}
use jacquard_common::types::crypto::KeyCodec;
for (codec, prefix) in [
(KeyCodec::Ed25519, &[0xed, 0x01][..]),
(KeyCodec::Secp256k1, &[0xe7, 0x01][..]),
(KeyCodec::P256, &[0x80, 0x24][..]),
(KeyCodec::Unknown(0x42), &[0x42][..]),
] {
let key = DidKey::from(PublicKey {
codec,
bytes: Cow::Borrowed(&[1, 2, 3]),
});
assert_eq!(key.0.strip_suffix(&[1, 2, 3]), Some(prefix));
assert_eq!(DidKey::from_did_key(&key.to_string()).unwrap(), key);
let back = key.to_public_key().unwrap();
assert_eq!((back.codec, back.bytes.as_ref()), (codec, &[1, 2, 3][..]));
}
}
#[test]
fn test_dbrkey() {
let tid_str = "3jzfcijpj2z2a";
let rkey = DbRkey::new(tid_str);
if let DbRkey::Tid(t) = rkey {
assert_eq!(t.to_tid().as_str(), tid_str);
} else {
panic!("expected tid");
}
let str_val = "self";
let rkey = DbRkey::new(str_val);
if let DbRkey::Str(s) = rkey {
assert_eq!(s, str_val);
} else {
panic!("expected str");
}
}
#[test]
fn dbrkey_msgpack_string_marker_preserves_eight_byte_rkey() {
let encoded = rmp_serde::to_vec(&DbRkey::Str("stinkpot".into())).unwrap();
assert_eq!(encoded, b"\xa8stinkpot");
let decoded: DbRkey = rmp_serde::from_slice(&encoded).unwrap();
assert_eq!(decoded, DbRkey::Str("stinkpot".into()));
assert_eq!(decoded.to_smolstr(), "stinkpot");
}
#[test]
fn dbrkey_msgpack_binary_marker_preserves_tid() {
let expected = DbRkey::new("3jzfcijpj2z2a");
let encoded = rmp_serde::to_vec(&expected).unwrap();
assert_eq!(encoded.get(..2), Some([0xc4, 8].as_slice()));
let decoded: DbRkey = rmp_serde::from_slice(&encoded).unwrap();
assert_eq!(decoded, expected);
assert_eq!(decoded.to_smolstr(), "3jzfcijpj2z2a");
}
#[test]
fn dbrkey_json_tid_sequence_stays_compatible() {
let expected = DbRkey::new("3jzfcijpj2z2a");
let encoded = serde_json::to_vec(&expected).unwrap();
assert_eq!(encoded.first(), Some(&b'['));
let decoded: DbRkey = serde_json::from_slice(&encoded).unwrap();
assert_eq!(decoded, expected);
}
#[test]
fn trimmed_did_accepts_legacy_msgpack_strings() {
for did in [
"did:plc:ewvi7nxzyoun6zhxrhs64oiz",
"did:web:example.com",
"did:example:opaque",
] {
let parsed = Did::new_owned(did).unwrap();
let expected = TrimmedDid::from(&parsed);
for legacy in [did, did.strip_prefix("did:").unwrap()] {
let encoded = rmp_serde::to_vec(legacy).unwrap();
let decoded: TrimmedDid<'_> = rmp_serde::from_slice(&encoded).unwrap();
assert_eq!(decoded, expected);
assert_eq!(decoded.to_did().as_str(), did);
}
}
}
#[test]
fn trimmed_did_binary_and_json_sequence_roundtrip() {
for (did, golden) in [
(
"did:plc:ewvi7nxzyoun6zhxrhs64oiz",
"c4100125aa8fb6f9c3a8df64f789e5ee3919",
),
("did:web:example.com", "c40c026578616d706c652e636f6d"),
("did:example:opaque", "c40e6578616d706c653a6f7061717565"),
] {
let parsed = Did::new_owned(did).unwrap();
let expected = TrimmedDid::from(&parsed);
let msgpack = rmp_serde::to_vec(&expected).unwrap();
assert_eq!(msgpack.first(), Some(&0xc4));
assert_eq!(crate::util::hex::encode(&msgpack), golden);
let from_msgpack: TrimmedDid<'_> = rmp_serde::from_slice(&msgpack).unwrap();
assert_eq!(from_msgpack, expected);
let json = serde_json::to_vec(&expected).unwrap();
assert_eq!(json.first(), Some(&b'['));
let from_json: TrimmedDid<'_> = serde_json::from_slice(&json).unwrap();
assert_eq!(from_json, expected);
}
}
#[test]
fn dbrkey_string_markers_preserve_every_utf8_length() {
let mut values = (0..=24)
.map(|length| "x".repeat(length))
.collect::<Vec<_>>();
values.extend(["stinkpot".to_owned(), "éééé".to_owned()]);
for value in values {
let expected = DbRkey::Str(value.clone().into());
let encoded = rmp_serde::to_vec(&expected).unwrap();
let decoded: DbRkey = rmp_serde::from_slice(&encoded).unwrap();
assert_eq!(decoded, expected, "failed for {value:?}");
}
}
#[test]
fn dbrkey_binary_marker_requires_exactly_eight_bytes() {
for length in [0, 1, 7, 9, 16] {
let encoded =
rmp_serde::to_vec(&serde_bytes::ByteBuf::from(vec![b'x'; length])).unwrap();
assert!(rmp_serde::from_slice::<DbRkey>(&encoded).is_err());
}
let encoded = rmp_serde::to_vec(&serde_bytes::ByteBuf::from(b"stinkpot".to_vec())).unwrap();
let decoded: DbRkey = rmp_serde::from_slice(&encoded).unwrap();
assert!(matches!(decoded, DbRkey::Tid(_)));
}
#[test]
fn dbrkey_json_sequence_requires_exactly_eight_bytes() {
for length in [0, 1, 7, 9, 16] {
let encoded = serde_json::to_vec(&vec![0_u8; length]).unwrap();
assert!(serde_json::from_slice::<DbRkey>(&encoded).is_err());
}
let decoded: DbRkey = serde_json::from_slice(b"[0,1,2,3,4,5,6,7]").unwrap();
assert_eq!(
decoded,
DbRkey::Tid(DbTid::new_from_bytes([0, 1, 2, 3, 4, 5, 6, 7]))
);
}
#[test]
fn db_action_and_did_key_representation_contracts() {
for (action, golden) in [
(DbAction::Create, "a6437265617465"),
(DbAction::Update, "a6557064617465"),
(DbAction::Delete, "a644656c657465"),
] {
let msgpack = rmp_serde::to_vec(&action).unwrap();
assert_eq!(crate::util::hex::encode(&msgpack), golden);
assert_eq!(rmp_serde::from_slice::<DbAction>(&msgpack).unwrap(), action);
assert_eq!(DbAction::try_from(action.as_str()).unwrap(), action);
}
let expected = DidKey(Cow::Borrowed(&[0xed, 0x01, 0x02, 0x03]));
let msgpack = rmp_serde::to_vec(&expected).unwrap();
assert_eq!(crate::util::hex::encode(&msgpack), "c404ed010203");
let decoded: DidKey<'_> = rmp_serde::from_slice(&msgpack).unwrap();
assert_eq!(decoded, expected);
let display = expected.to_string();
assert_eq!(DidKey::from_did_key(&display).unwrap(), expected);
}
}