Skip to main content

cbor_ld/
table.rs

1use cbor2::Value;
2use cbor2::value::Integer;
3use std::collections::BTreeMap;
4
5use crate::constants::{
6    CRYPTOSUITE_TYPED_TABLE, SECURITY_CRYPTO_SUITE, STRING_TABLE, XSD_BOOLEAN, XSD_DOUBLE,
7    XSD_INTEGER,
8};
9use crate::error::{Error, Result};
10
11/// A primitive value key used in a CBOR-LD type table.
12#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
13pub enum TableKey {
14    /// A text value.
15    Text(String),
16    /// An integer value.
17    Integer(i128),
18    /// A boolean value.
19    Bool(bool),
20    /// A null value.
21    Null,
22    /// A byte string value.
23    Bytes(Vec<u8>),
24}
25
26impl TableKey {
27    pub(crate) fn from_value(value: &Value) -> Result<Self> {
28        match value {
29            Value::Text(s) => Ok(Self::Text(s.clone())),
30            Value::Integer(n) => Ok(Self::Integer(i128::from(*n))),
31            Value::Bool(b) => Ok(Self::Bool(*b)),
32            Value::Null => Ok(Self::Null),
33            Value::Bytes(bytes) => Ok(Self::Bytes(bytes.clone())),
34            other => Err(Error::UnsupportedValue(format!(
35                "type table key must be a primitive value, got {other}"
36            ))),
37        }
38    }
39
40    pub(crate) fn to_value(&self) -> Value {
41        match self {
42            Self::Text(s) => Value::Text(s.clone()),
43            Self::Integer(n) => integer(*n),
44            Self::Bool(b) => Value::Bool(*b),
45            Self::Null => Value::Null,
46            Self::Bytes(bytes) => Value::Bytes(bytes.clone()),
47        }
48    }
49}
50
51impl From<&str> for TableKey {
52    fn from(value: &str) -> Self {
53        Self::Text(value.to_owned())
54    }
55}
56
57impl From<String> for TableKey {
58    fn from(value: String) -> Self {
59        Self::Text(value)
60    }
61}
62
63impl From<i64> for TableKey {
64    fn from(value: i64) -> Self {
65        Self::Integer(value.into())
66    }
67}
68
69impl From<u64> for TableKey {
70    fn from(value: u64) -> Self {
71        Self::Integer(value.into())
72    }
73}
74
75impl From<bool> for TableKey {
76    fn from(value: bool) -> Self {
77        Self::Bool(value)
78    }
79}
80
81/// A CBOR-LD registry type table.
82///
83/// Each top-level key is a value type, such as `context`, `url`, `none`, or a
84/// JSON-LD datatype IRI. Each inner table maps original values to compressed
85/// integer IDs.
86#[derive(Clone, Debug, Default, PartialEq, Eq)]
87pub struct TypeTable {
88    entries: BTreeMap<String, BTreeMap<TableKey, u64>>,
89}
90
91impl TypeTable {
92    /// Creates an empty type table. The core `context`, `url`, and `none`
93    /// tables are added automatically before processing.
94    pub fn new() -> Self {
95        Self::default()
96    }
97
98    /// Creates the static table material used by the JavaScript reference
99    /// implementation for its legacy/common registry examples.
100    pub fn with_common_tables() -> Self {
101        let mut table = Self::new();
102        for &(value, id) in STRING_TABLE {
103            table.insert("context", value, id);
104            table.insert("url", value, id);
105            table.insert("none", value, id);
106        }
107        for &(value, id) in CRYPTOSUITE_TYPED_TABLE {
108            table.insert(SECURITY_CRYPTO_SUITE, value, id);
109        }
110        table
111    }
112
113    /// Inserts a compressed value into a subtable.
114    pub fn insert(
115        &mut self,
116        table_type: impl Into<String>,
117        value: impl Into<TableKey>,
118        id: u64,
119    ) -> &mut Self {
120        self.entries
121            .entry(table_type.into())
122            .or_default()
123            .insert(value.into(), id);
124        self
125    }
126
127    /// Returns a subtable by type.
128    pub fn subtable(&self, table_type: &str) -> Option<&BTreeMap<TableKey, u64>> {
129        self.entries.get(table_type)
130    }
131
132    pub(crate) fn normalized(&self) -> Result<Self> {
133        let mut table = self.clone();
134        for unsupported in [XSD_INTEGER, XSD_DOUBLE, XSD_BOOLEAN] {
135            if table.entries.contains_key(unsupported) {
136                return Err(Error::UnsupportedLiteralType(unsupported.to_owned()));
137            }
138        }
139        for core in ["context", "url", "none"] {
140            table.entries.entry(core.to_owned()).or_default();
141        }
142        Ok(table)
143    }
144
145    pub(crate) fn reverse(&self) -> ReverseTypeTable {
146        let mut reverse = BTreeMap::new();
147        for (table_type, subtable) in &self.entries {
148            let mut inner = BTreeMap::new();
149            for (value, id) in subtable {
150                inner.insert(*id, value.clone());
151            }
152            reverse.insert(table_type.clone(), inner);
153        }
154        reverse
155    }
156}
157
158pub(crate) type ReverseTypeTable = BTreeMap<String, BTreeMap<u64, TableKey>>;
159
160fn integer(value: i128) -> Value {
161    Value::Integer(Integer::try_from(value).expect("integer value fits CBOR major type 0/1"))
162}