1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Type {
13 Number,
15 String,
17 Boolean,
19 Date,
21 Instant,
23 Blob,
25}
26
27impl Type {
28 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 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 pub fn may_be_valid_time(self) -> bool {
62 matches!(self, Type::Date | Type::Instant | Type::String)
63 }
64}
65
66#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub enum Scalar {
69 Number(String),
71 String(String),
73 Boolean(bool),
75 Date(String),
77 Instant(String),
79 Blob(String),
81}
82
83impl Scalar {
84 pub fn from_lexical(ty: Type, text: &str) -> Result<Scalar> {
87 check_lexical(ty, text, "$")?;
88 Ok(unchecked(ty, text))
89 }
90
91 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 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 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 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 pub fn canonical(&self) -> Vec<u8> {
130 self.to_value().to_jcs()
131 }
132}
133
134pub(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
146pub(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
174pub(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#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
192pub enum Arg {
193 Value(Scalar),
195 Slice,
197}
198
199impl Arg {
200 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 pub fn scalar(&self) -> Option<&Scalar> {
210 match self {
211 Arg::Value(s) => Some(s),
212 Arg::Slice => None,
213 }
214 }
215}
216
217pub 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
264pub struct Cell {
265 pub ns: String,
267 pub fn_name: String,
269 pub fn_hash: String,
271 pub args: Args,
273}
274
275impl Cell {
276 pub fn from_json(bytes: &[u8]) -> Result<Cell> {
278 let v = json::parse(bytes)?;
279 Cell::from_value(&v)
280 }
281
282 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 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 pub fn canonical(&self) -> Vec<u8> {
301 self.to_value().to_jcs()
302 }
303
304 pub fn id(&self) -> String {
306 crate::crypto::sha256_hex(&self.canonical())
307 }
308
309 pub fn is_slice(&self) -> bool {
311 self.args.values().any(|a| *a == Arg::Slice)
312 }
313
314 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
349pub struct CellRef {
350 pub ns: String,
352 pub fn_name: String,
354 pub args: Args,
356 pub asof: Option<String>,
358}
359
360impl CellRef {
361 pub fn from_json(bytes: &[u8]) -> Result<CellRef> {
363 let v = json::parse(bytes)?;
364 let m = schema::object(&v, "$")?;
365 schema::exact(m, &["args", "fn", "ns"], &["asof"], "$", ErrorCode::Schema)?;
366 args_structure(schema::get(m, "args"), "$.args")?;
367 schema::string(m, "fn", "$")?;
368 schema::string(m, "ns", "$")?;
369 if schema::opt(m, "asof").is_some() {
370 schema::string(m, "asof", "$")?;
371 }
372 let args = args_canonical(schema::get(m, "args"), "$.args")?;
373 let asof = match schema::opt(m, "asof") {
374 Some(Value::String(s)) => {
375 lexical::check_instant(s, "$.asof")?;
376 Some(s.clone())
377 }
378 _ => None,
379 };
380 let f = schema::string(m, "fn", "$")?;
381 lexical::check_name(f, "$.fn")?;
382 let ns = schema::string(m, "ns", "$")?;
383 lexical::check_namespace(ns, "$.ns")?;
384 check_fn_length(ns, f)?;
385 Ok(CellRef { ns: ns.into(), fn_name: f.into(), args, asof })
386 }
387
388 pub fn to_value(&self) -> Value {
390 let mut m = vec![("args".to_string(), args_to_value(&self.args))];
391 if let Some(a) = &self.asof {
392 m.push(("asof".into(), Value::String(a.clone())));
393 }
394 m.push(("fn".into(), Value::String(self.fn_name.clone())));
395 m.push(("ns".into(), Value::String(self.ns.clone())));
396 Value::Object(m)
397 }
398
399 pub fn canonical(&self) -> Vec<u8> {
401 self.to_value().to_jcs()
402 }
403
404 pub fn resolve(&self, fn_hash: &str) -> Result<Cell> {
406 lexical::check_hex(fn_hash, 64, "$.fn_hash")?;
407 Ok(Cell {
408 ns: self.ns.clone(),
409 fn_name: self.fn_name.clone(),
410 fn_hash: fn_hash.into(),
411 args: self.args.clone(),
412 })
413 }
414}
415
416#[derive(Clone, Debug, PartialEq, Eq, Hash)]
420#[non_exhaustive]
421pub struct Reference {
422 pub ns: String,
424 pub fn_name: String,
426 pub args: Args,
428 pub fn_hash: Option<String>,
430 pub asof: Option<String>,
432}
433
434impl Reference {
435 pub fn from_json(bytes: &[u8]) -> Result<Reference> {
439 let v = json::parse(bytes)?;
440 json::check_value_depth(&v)?;
441 let m = schema::object(&v, "$")?;
442 schema::exact(m, &["args", "fn", "ns"], &["asof", "fn_hash"], "$", ErrorCode::Schema)?;
443 args_structure(schema::get(m, "args"), "$.args")?;
444 schema::string(m, "fn", "$")?;
445 schema::string(m, "ns", "$")?;
446 for o in ["asof", "fn_hash"] {
447 if schema::opt(m, o).is_some() {
448 schema::string(m, o, "$")?;
449 }
450 }
451 let args = args_canonical(schema::get(m, "args"), "$.args")?;
452 let asof = match schema::opt(m, "asof") {
453 Some(Value::String(s)) => {
454 lexical::check_instant(s, "$.asof")?;
455 Some(s.clone())
456 }
457 _ => None,
458 };
459 let f = schema::string(m, "fn", "$")?;
460 lexical::check_name(f, "$.fn")?;
461 let fn_hash = match schema::opt(m, "fn_hash") {
462 Some(Value::String(h)) => {
463 lexical::check_hex(h, 64, "$.fn_hash")?;
464 Some(h.clone())
465 }
466 _ => None,
467 };
468 let ns = schema::string(m, "ns", "$")?;
469 lexical::check_namespace(ns, "$.ns")?;
470 check_fn_length(ns, f)?;
471 Ok(Reference { ns: ns.into(), fn_name: f.into(), args, fn_hash, asof })
472 }
473
474 pub fn new(ns: impl Into<String>, fn_name: impl Into<String>, args: Args) -> Reference {
478 Reference { ns: ns.into(), fn_name: fn_name.into(), args, fn_hash: None, asof: None }
479 }
480
481 pub fn to_value(&self) -> Value {
483 let mut m = vec![("args".to_string(), args_to_value(&self.args))];
484 if let Some(a) = &self.asof {
485 m.push(("asof".into(), Value::String(a.clone())));
486 }
487 m.push(("fn".into(), Value::String(self.fn_name.clone())));
488 if let Some(h) = &self.fn_hash {
489 m.push(("fn_hash".into(), Value::String(h.clone())));
490 }
491 m.push(("ns".into(), Value::String(self.ns.clone())));
492 Value::Object(m)
493 }
494
495 pub fn canonical(&self) -> Vec<u8> {
498 self.to_value().to_jcs()
499 }
500
501 pub fn cell(&self) -> Option<Cell> {
504 let h = self.fn_hash.as_ref()?;
505 Some(Cell { ns: self.ns.clone(), fn_name: self.fn_name.clone(), fn_hash: h.clone(), args: self.args.clone() })
506 }
507
508 pub fn resolve(&self, fn_hash: &str) -> Result<Cell> {
511 lexical::check_hex(fn_hash, 64, "$.fn_hash")?;
512 if self.fn_hash.as_deref().is_some_and(|p| p != fn_hash) {
513 return err(ErrorCode::FnHashMismatch, "$.fn_hash", "the reference is pinned to another function");
514 }
515 Ok(Cell {
516 ns: self.ns.clone(),
517 fn_name: self.fn_name.clone(),
518 fn_hash: fn_hash.into(),
519 args: self.args.clone(),
520 })
521 }
522
523 pub fn to_cell_ref(&self) -> Option<CellRef> {
525 self.fn_hash.is_none().then(|| CellRef {
526 ns: self.ns.clone(),
527 fn_name: self.fn_name.clone(),
528 args: self.args.clone(),
529 asof: self.asof.clone(),
530 })
531 }
532}
533
534impl From<CellRef> for Reference {
535 fn from(r: CellRef) -> Reference {
536 Reference { ns: r.ns, fn_name: r.fn_name, args: r.args, fn_hash: None, asof: r.asof }
537 }
538}