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)]
345pub struct CellRef {
346 pub ns: String,
348 pub fn_name: String,
350 pub args: Args,
352 pub asof: Option<String>,
354}
355
356impl CellRef {
357 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 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 pub fn canonical(&self) -> Vec<u8> {
397 self.to_value().to_jcs()
398 }
399
400 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}