Skip to main content

datumesh_core/
model.rs

1//! Typed scalars (3, 4.1), argument maps (4.2), cell references (4.3) and cells (4.4).
2
3use std::collections::BTreeMap;
4
5use crate::error::{err, member, ErrorCode, Result};
6use crate::json::{self, jcs_sorted, Value};
7use crate::lexical::{self, limits};
8use crate::schema;
9
10/// One of the six scalar types (3).
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Type {
13    /// An exact decimal written as a string (3.1).
14    Number,
15    /// An NFC string of Unicode 15.1 code points (3.5).
16    String,
17    /// `true` or `false`.
18    Boolean,
19    /// `YYYY-MM-DD` (3.2).
20    Date,
21    /// `YYYY-MM-DDTHH:MM:SS.ffffffZ` (3.3).
22    Instant,
23    /// `<sha256>:<length>:<media type>` (3.7).
24    Blob,
25}
26
27impl Type {
28    /// The type name as written in JSON.
29    pub const fn as_str(self) -> &'static str {
30        match self {
31            Type::Number => "number",
32            Type::String => "string",
33            Type::Boolean => "boolean",
34            Type::Date => "date",
35            Type::Instant => "instant",
36            Type::Blob => "blob",
37        }
38    }
39
40    /// The type of a type name; `None` outside the six.
41    pub fn from_name(name: &str) -> Option<Type> {
42        Some(match name {
43            "number" => Type::Number,
44            "string" => Type::String,
45            "boolean" => Type::Boolean,
46            "date" => Type::Date,
47            "instant" => Type::Instant,
48            "blob" => Type::Blob,
49            _ => return None,
50        })
51    }
52
53    pub(crate) fn parse(name: &str, path: &str) -> Result<Type> {
54        match Type::from_name(name) {
55            Some(t) => Ok(t),
56            None => err(ErrorCode::CanonType, path, format!("unknown type {name:?}")),
57        }
58    }
59
60    /// Whether a valid-time argument may have this type (date, instant or string).
61    pub fn may_be_valid_time(self) -> bool {
62        matches!(self, Type::Date | Type::Instant | Type::String)
63    }
64}
65
66/// A typed scalar value in its one canonical lexical form.
67#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub enum Scalar {
69    /// A canonical decimal string.
70    Number(String),
71    /// A valid string value.
72    String(String),
73    /// A boolean.
74    Boolean(bool),
75    /// A calendar date.
76    Date(String),
77    /// A UTC instant with microseconds.
78    Instant(String),
79    /// A blob reference.
80    Blob(String),
81}
82
83impl Scalar {
84    /// Builds a scalar of type `ty` from its lexical form, checking it (3). For a boolean,
85    /// `text` must be `true` or `false` (`E_CANON_TYPE` otherwise).
86    pub fn from_lexical(ty: Type, text: &str) -> Result<Scalar> {
87        check_lexical(ty, text, "$")?;
88        Ok(unchecked(ty, text))
89    }
90
91    /// Parses a typed scalar from JSON text (4.1): strict JSON, structure, canonical form.
92    pub fn from_json(bytes: &[u8]) -> Result<Scalar> {
93        let v = json::parse(bytes)?;
94        scalar_structure(&v, "$")?;
95        scalar_canonical(&v, "$")
96    }
97
98    /// The scalar's type.
99    pub fn ty(&self) -> Type {
100        match self {
101            Scalar::Number(_) => Type::Number,
102            Scalar::String(_) => Type::String,
103            Scalar::Boolean(_) => Type::Boolean,
104            Scalar::Date(_) => Type::Date,
105            Scalar::Instant(_) => Type::Instant,
106            Scalar::Blob(_) => Type::Blob,
107        }
108    }
109
110    /// The lexical form (`true`/`false` for booleans).
111    pub fn lexical(&self) -> &str {
112        match self {
113            Scalar::Number(s) | Scalar::String(s) | Scalar::Date(s) | Scalar::Instant(s) | Scalar::Blob(s) => s,
114            Scalar::Boolean(true) => "true",
115            Scalar::Boolean(false) => "false",
116        }
117    }
118
119    /// The JSON form `{"type": …, "value": …}`.
120    pub fn to_value(&self) -> Value {
121        let value = match self {
122            Scalar::Boolean(b) => Value::Bool(*b),
123            other => Value::String(other.lexical().into()),
124        };
125        Value::Object(vec![("type".into(), Value::String(self.ty().as_str().into())), ("value".into(), value)])
126    }
127
128    /// The canonical bytes (6.2) of the typed scalar.
129    pub fn canonical(&self) -> Vec<u8> {
130        self.to_value().to_jcs()
131    }
132}
133
134/// A scalar from a lexical form already checked against `ty`.
135pub(crate) fn unchecked(ty: Type, text: &str) -> Scalar {
136    match ty {
137        Type::Number => Scalar::Number(text.into()),
138        Type::String => Scalar::String(text.into()),
139        Type::Date => Scalar::Date(text.into()),
140        Type::Instant => Scalar::Instant(text.into()),
141        Type::Blob => Scalar::Blob(text.into()),
142        Type::Boolean => Scalar::Boolean(text == "true"),
143    }
144}
145
146/// Checks a lexical form against its type.
147pub(crate) fn check_lexical(ty: Type, text: &str, path: &str) -> Result<()> {
148    match ty {
149        Type::Number => lexical::check_number(text, path),
150        Type::String => lexical::check_string(text, path),
151        Type::Date => lexical::check_date(text, path),
152        Type::Instant => lexical::check_instant(text, path),
153        Type::Blob => lexical::check_blob(text, path),
154        Type::Boolean => {
155            if text == "true" || text == "false" {
156                Ok(())
157            } else {
158                err(ErrorCode::CanonType, path, format!("a boolean is true or false, not {text:?}"))
159            }
160        }
161    }
162}
163
164pub(crate) fn scalar_structure(v: &Value, path: &str) -> Result<()> {
165    let m = schema::object(v, path)?;
166    schema::exact(m, &["type", "value"], &[], path, ErrorCode::Schema)?;
167    schema::string(m, "type", path)?;
168    match schema::get(m, "value") {
169        Value::String(_) | Value::Bool(_) => Ok(()),
170        _ => err(ErrorCode::Schema, &member(path, "value"), "value must be a string or a boolean"),
171    }
172}
173
174/// Canonical check of a structurally valid typed scalar.
175pub(crate) fn scalar_canonical(v: &Value, path: &str) -> Result<Scalar> {
176    let m = schema::object(v, path)?;
177    let ty = Type::parse(schema::string(m, "type", path)?, &member(path, "type"))?;
178    let vp = member(path, "value");
179    match (ty, schema::get(m, "value")) {
180        (Type::Boolean, Value::Bool(b)) => Ok(Scalar::Boolean(*b)),
181        (Type::Boolean, _) => err(ErrorCode::CanonType, &vp, "boolean value must be JSON true or false"),
182        (t, Value::String(s)) => {
183            check_lexical(t, s, &vp)?;
184            Ok(unchecked(t, s))
185        }
186        (t, _) => err(ErrorCode::CanonType, &vp, format!("{} value must be a JSON string", t.as_str())),
187    }
188}
189
190/// An argument: a typed scalar, or the slice marker `"*"` (4.2).
191#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
192pub enum Arg {
193    /// A bound value.
194    Value(Scalar),
195    /// The slice marker: the argument is open.
196    Slice,
197}
198
199impl Arg {
200    /// The JSON form: a typed scalar or `"*"`.
201    pub fn to_value(&self) -> Value {
202        match self {
203            Arg::Value(s) => s.to_value(),
204            Arg::Slice => Value::String("*".into()),
205        }
206    }
207
208    /// The scalar, if bound.
209    pub fn scalar(&self) -> Option<&Scalar> {
210        match self {
211            Arg::Value(s) => Some(s),
212            Arg::Slice => None,
213        }
214    }
215}
216
217/// An argument map, keyed by argument name (byte order equals JCS order for names).
218pub type Args = BTreeMap<String, Arg>;
219
220pub(crate) fn args_to_value(args: &Args) -> Value {
221    Value::Object(args.iter().map(|(k, a)| (k.clone(), a.to_value())).collect())
222}
223
224pub(crate) fn args_structure(v: &Value, path: &str) -> Result<()> {
225    let m = schema::object(v, path)?;
226    for (k, a) in jcs_sorted(m) {
227        let p = member(path, k);
228        match a {
229            Value::String(s) if s == "*" => {}
230            Value::String(_) => return err(ErrorCode::Schema, &p, "an argument is a typed scalar or \"*\""),
231            other => scalar_structure(other, &p)?,
232        }
233    }
234    Ok(())
235}
236
237pub(crate) fn args_canonical(v: &Value, path: &str) -> Result<Args> {
238    let m = schema::object(v, path)?;
239    if m.len() > limits::ARGS {
240        return err(ErrorCode::CanonLimit, path, "more than 32 arguments");
241    }
242    let mut out = Args::new();
243    for (k, a) in jcs_sorted(m) {
244        let p = member(path, k);
245        lexical::check_arg_name(k, &p)?;
246        let arg = match a {
247            Value::String(s) if s == "*" => Arg::Slice,
248            other => Arg::Value(scalar_canonical(other, &p)?),
249        };
250        out.insert(k.clone(), arg);
251    }
252    Ok(out)
253}
254
255fn check_fn_length(ns: &str, f: &str) -> Result<()> {
256    if ns.len() + 1 + f.len() > limits::FUNCTION_NAME {
257        return err(ErrorCode::CanonLimit, "$", "function name longer than 512 characters");
258    }
259    Ok(())
260}
261
262/// A cell (4.4): a function, pinned by hash, applied to arguments. May denote a slice.
263#[derive(Clone, Debug, PartialEq, Eq, Hash)]
264pub struct Cell {
265    /// The namespace, e.g. `rates.example.org`.
266    pub ns: String,
267    /// The function name within the namespace.
268    pub fn_name: String,
269    /// The function hash, 64 lowercase hex digits.
270    pub fn_hash: String,
271    /// The arguments.
272    pub args: Args,
273}
274
275impl Cell {
276    /// Parses a cell from JSON text: strict JSON, structure, canonical form. Slices allowed.
277    pub fn from_json(bytes: &[u8]) -> Result<Cell> {
278        let v = json::parse(bytes)?;
279        Cell::from_value(&v)
280    }
281
282    /// Checks a parsed JSON value as a cell.
283    pub fn from_value(v: &Value) -> Result<Cell> {
284        json::check_value_depth(v)?;
285        cell_structure(v, "$")?;
286        cell_canonical(v, "$")
287    }
288
289    /// The JSON form.
290    pub fn to_value(&self) -> Value {
291        Value::Object(vec![
292            ("args".into(), args_to_value(&self.args)),
293            ("fn".into(), Value::String(self.fn_name.clone())),
294            ("fn_hash".into(), Value::String(self.fn_hash.clone())),
295            ("ns".into(), Value::String(self.ns.clone())),
296        ])
297    }
298
299    /// The canonical bytes (6.2).
300    pub fn canonical(&self) -> Vec<u8> {
301        self.to_value().to_jcs()
302    }
303
304    /// The cell id: SHA-256 of the canonical bytes, lowercase hex.
305    pub fn id(&self) -> String {
306        crate::crypto::sha256_hex(&self.canonical())
307    }
308
309    /// Whether any argument is the slice marker.
310    pub fn is_slice(&self) -> bool {
311        self.args.values().any(|a| *a == Arg::Slice)
312    }
313
314    /// The fully qualified function name `<ns>/<fn>`.
315    pub fn function_name(&self) -> String {
316        format!("{}/{}", self.ns, self.fn_name)
317    }
318}
319
320pub(crate) fn cell_structure(v: &Value, path: &str) -> Result<()> {
321    let m = schema::object(v, path)?;
322    schema::exact(m, &["args", "fn", "fn_hash", "ns"], &[], path, ErrorCode::Schema)?;
323    args_structure(schema::get(m, "args"), &member(path, "args"))?;
324    schema::string(m, "fn", path)?;
325    schema::string(m, "fn_hash", path)?;
326    schema::string(m, "ns", path)?;
327    Ok(())
328}
329
330pub(crate) fn cell_canonical(v: &Value, path: &str) -> Result<Cell> {
331    let m = schema::object(v, path)?;
332    let args = args_canonical(schema::get(m, "args"), &member(path, "args"))?;
333    let f = schema::string(m, "fn", path)?;
334    lexical::check_name(f, &member(path, "fn"))?;
335    let h = schema::string(m, "fn_hash", path)?;
336    lexical::check_hex(h, 64, &member(path, "fn_hash"))?;
337    let ns = schema::string(m, "ns", path)?;
338    lexical::check_namespace(ns, &member(path, "ns"))?;
339    check_fn_length(ns, f).map_err(|e| crate::error::Error::new(e.code(), path, e.detail()))?;
340    Ok(Cell { ns: ns.into(), fn_name: f.into(), fn_hash: h.into(), args })
341}
342
343/// A cell reference (4.3): what a cell URI denotes before the function name is resolved.
344#[derive(Clone, Debug, PartialEq, Eq, Hash)]
345pub struct CellRef {
346    /// The namespace.
347    pub ns: String,
348    /// The function name within the namespace.
349    pub fn_name: String,
350    /// The arguments, typed by the function's signature.
351    pub args: Args,
352    /// The transaction time asked for; `None` means now.
353    pub asof: Option<String>,
354}
355
356impl CellRef {
357    /// Parses a cell reference from JSON text: strict JSON, structure, canonical form.
358    pub fn from_json(bytes: &[u8]) -> Result<CellRef> {
359        let v = json::parse(bytes)?;
360        let m = schema::object(&v, "$")?;
361        schema::exact(m, &["args", "fn", "ns"], &["asof"], "$", ErrorCode::Schema)?;
362        args_structure(schema::get(m, "args"), "$.args")?;
363        schema::string(m, "fn", "$")?;
364        schema::string(m, "ns", "$")?;
365        if schema::opt(m, "asof").is_some() {
366            schema::string(m, "asof", "$")?;
367        }
368        let args = args_canonical(schema::get(m, "args"), "$.args")?;
369        let asof = match schema::opt(m, "asof") {
370            Some(Value::String(s)) => {
371                lexical::check_instant(s, "$.asof")?;
372                Some(s.clone())
373            }
374            _ => None,
375        };
376        let f = schema::string(m, "fn", "$")?;
377        lexical::check_name(f, "$.fn")?;
378        let ns = schema::string(m, "ns", "$")?;
379        lexical::check_namespace(ns, "$.ns")?;
380        check_fn_length(ns, f)?;
381        Ok(CellRef { ns: ns.into(), fn_name: f.into(), args, asof })
382    }
383
384    /// The JSON form.
385    pub fn to_value(&self) -> Value {
386        let mut m = vec![("args".to_string(), args_to_value(&self.args))];
387        if let Some(a) = &self.asof {
388            m.push(("asof".into(), Value::String(a.clone())));
389        }
390        m.push(("fn".into(), Value::String(self.fn_name.clone())));
391        m.push(("ns".into(), Value::String(self.ns.clone())));
392        Value::Object(m)
393    }
394
395    /// The canonical bytes (6.2).
396    pub fn canonical(&self) -> Vec<u8> {
397        self.to_value().to_jcs()
398    }
399
400    /// The cell this reference denotes once its function name has resolved to `fn_hash`.
401    pub fn resolve(&self, fn_hash: &str) -> Result<Cell> {
402        lexical::check_hex(fn_hash, 64, "$.fn_hash")?;
403        Ok(Cell {
404            ns: self.ns.clone(),
405            fn_name: self.fn_name.clone(),
406            fn_hash: fn_hash.into(),
407            args: self.args.clone(),
408        })
409    }
410}