cbor-ld 0.1.0

CBOR-LD 1.0 processor built on cbor2 with semantic compression, JSON-LD context processing, type tables and deterministic CBOR output.
Documentation
use cbor2::Value;
use cbor2::value::Integer;
use std::collections::BTreeMap;

use crate::constants::{
    CRYPTOSUITE_TYPED_TABLE, SECURITY_CRYPTO_SUITE, STRING_TABLE, XSD_BOOLEAN, XSD_DOUBLE,
    XSD_INTEGER,
};
use crate::error::{Error, Result};

/// A primitive value key used in a CBOR-LD type table.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TableKey {
    /// A text value.
    Text(String),
    /// An integer value.
    Integer(i128),
    /// A boolean value.
    Bool(bool),
    /// A null value.
    Null,
    /// A byte string value.
    Bytes(Vec<u8>),
}

impl TableKey {
    pub(crate) fn from_value(value: &Value) -> Result<Self> {
        match value {
            Value::Text(s) => Ok(Self::Text(s.clone())),
            Value::Integer(n) => Ok(Self::Integer(i128::from(*n))),
            Value::Bool(b) => Ok(Self::Bool(*b)),
            Value::Null => Ok(Self::Null),
            Value::Bytes(bytes) => Ok(Self::Bytes(bytes.clone())),
            other => Err(Error::UnsupportedValue(format!(
                "type table key must be a primitive value, got {other}"
            ))),
        }
    }

    pub(crate) fn to_value(&self) -> Value {
        match self {
            Self::Text(s) => Value::Text(s.clone()),
            Self::Integer(n) => integer(*n),
            Self::Bool(b) => Value::Bool(*b),
            Self::Null => Value::Null,
            Self::Bytes(bytes) => Value::Bytes(bytes.clone()),
        }
    }
}

impl From<&str> for TableKey {
    fn from(value: &str) -> Self {
        Self::Text(value.to_owned())
    }
}

impl From<String> for TableKey {
    fn from(value: String) -> Self {
        Self::Text(value)
    }
}

impl From<i64> for TableKey {
    fn from(value: i64) -> Self {
        Self::Integer(value.into())
    }
}

impl From<u64> for TableKey {
    fn from(value: u64) -> Self {
        Self::Integer(value.into())
    }
}

impl From<bool> for TableKey {
    fn from(value: bool) -> Self {
        Self::Bool(value)
    }
}

/// A CBOR-LD registry type table.
///
/// Each top-level key is a value type, such as `context`, `url`, `none`, or a
/// JSON-LD datatype IRI. Each inner table maps original values to compressed
/// integer IDs.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TypeTable {
    entries: BTreeMap<String, BTreeMap<TableKey, u64>>,
}

impl TypeTable {
    /// Creates an empty type table. The core `context`, `url`, and `none`
    /// tables are added automatically before processing.
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates the static table material used by the JavaScript reference
    /// implementation for its legacy/common registry examples.
    pub fn with_common_tables() -> Self {
        let mut table = Self::new();
        for &(value, id) in STRING_TABLE {
            table.insert("context", value, id);
            table.insert("url", value, id);
            table.insert("none", value, id);
        }
        for &(value, id) in CRYPTOSUITE_TYPED_TABLE {
            table.insert(SECURITY_CRYPTO_SUITE, value, id);
        }
        table
    }

    /// Inserts a compressed value into a subtable.
    pub fn insert(
        &mut self,
        table_type: impl Into<String>,
        value: impl Into<TableKey>,
        id: u64,
    ) -> &mut Self {
        self.entries
            .entry(table_type.into())
            .or_default()
            .insert(value.into(), id);
        self
    }

    /// Returns a subtable by type.
    pub fn subtable(&self, table_type: &str) -> Option<&BTreeMap<TableKey, u64>> {
        self.entries.get(table_type)
    }

    pub(crate) fn normalized(&self) -> Result<Self> {
        let mut table = self.clone();
        for unsupported in [XSD_INTEGER, XSD_DOUBLE, XSD_BOOLEAN] {
            if table.entries.contains_key(unsupported) {
                return Err(Error::UnsupportedLiteralType(unsupported.to_owned()));
            }
        }
        for core in ["context", "url", "none"] {
            table.entries.entry(core.to_owned()).or_default();
        }
        Ok(table)
    }

    pub(crate) fn reverse(&self) -> ReverseTypeTable {
        let mut reverse = BTreeMap::new();
        for (table_type, subtable) in &self.entries {
            let mut inner = BTreeMap::new();
            for (value, id) in subtable {
                inner.insert(*id, value.clone());
            }
            reverse.insert(table_type.clone(), inner);
        }
        reverse
    }
}

pub(crate) type ReverseTypeTable = BTreeMap<String, BTreeMap<u64, TableKey>>;

fn integer(value: i128) -> Value {
    Value::Integer(Integer::try_from(value).expect("integer value fits CBOR major type 0/1"))
}