1use std::collections::BTreeMap;
4
5use crate::error::{err, index, member, ErrorCode, Result};
6use crate::json::{self, jcs_sorted, Value};
7use crate::lexical::{self, limits};
8use crate::model::{self, Arg, Cell, Scalar, Type};
9use crate::schema;
10
11#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13pub enum RateSide {
14 Currency(String),
16 Arg(String),
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub enum Unit {
23 Literal(String),
25 Arg(String),
27 Rate {
29 of: RateSide,
31 per: RateSide,
33 },
34}
35
36impl Unit {
37 pub fn to_value(&self) -> Value {
39 fn arg(n: &str) -> Value {
40 Value::Object(vec![("arg".into(), Value::String(n.into()))])
41 }
42 fn side(s: &RateSide) -> Value {
43 match s {
44 RateSide::Currency(c) => Value::String(c.clone()),
45 RateSide::Arg(n) => arg(n),
46 }
47 }
48 match self {
49 Unit::Literal(s) => Value::String(s.clone()),
50 Unit::Arg(n) => arg(n),
51 Unit::Rate { of, per } => Value::Object(vec![("of".into(), side(of)), ("per".into(), side(per))]),
52 }
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq, Hash)]
58pub struct ArgDecl {
59 pub ty: Type,
61 pub unit: Option<Unit>,
63 pub valid: bool,
65}
66
67#[derive(Clone, Debug, PartialEq, Eq, Hash)]
69pub struct Returns {
70 pub ty: Type,
72 pub unit: Option<Unit>,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Hash)]
78pub struct Signature {
79 pub args: BTreeMap<String, ArgDecl>,
81 pub returns: Returns,
83}
84
85impl Signature {
86 pub fn to_value(&self) -> Value {
88 let args = self
89 .args
90 .iter()
91 .map(|(k, d)| {
92 let mut m = vec![("type".to_string(), Value::String(d.ty.as_str().into()))];
93 if let Some(u) = &d.unit {
94 m.push(("unit".into(), u.to_value()));
95 }
96 m.push(("valid".into(), Value::Bool(d.valid)));
97 (k.clone(), Value::Object(m))
98 })
99 .collect();
100 let returns = match &self.returns.unit {
101 Some(u) => Value::Object(vec![
102 ("type".into(), Value::String(self.returns.ty.as_str().into())),
103 ("unit".into(), u.to_value()),
104 ]),
105 None => Value::String(self.returns.ty.as_str().into()),
106 };
107 Value::Object(vec![("args".into(), Value::Object(args)), ("returns".into(), returns)])
108 }
109}
110
111#[derive(Clone, Debug, PartialEq, Eq, Hash)]
114#[non_exhaustive]
115pub enum Extent {
116 Values(Vec<String>),
120 Range {
123 lo: String,
125 hi: String,
127 },
128}
129
130impl Extent {
131 pub fn to_value(&self) -> Value {
133 match self {
134 Extent::Values(v) => Value::Array(v.iter().map(|s| Value::String(s.clone())).collect()),
135 Extent::Range { lo, hi } => {
136 Value::Object(vec![("hi".into(), Value::String(hi.clone())), ("lo".into(), Value::String(lo.clone()))])
137 }
138 }
139 }
140}
141
142#[derive(Clone, Debug, PartialEq, Eq, Hash)]
151pub struct Descriptor {
152 pub lang: String,
154 pub body: String,
156 pub inputs: Vec<String>,
158 pub signature: Signature,
160}
161
162impl Descriptor {
163 pub fn from_json(bytes: &[u8]) -> Result<Descriptor> {
167 Descriptor::from_value(&json::parse(bytes)?)
168 }
169
170 pub fn from_value(v: &Value) -> Result<Descriptor> {
173 json::check_value_depth(v)?;
174 descriptor_structure(v, false)?;
175 let f = descriptor_canonical(v)?;
176 Ok(Descriptor { lang: f.lang, body: f.body, inputs: f.inputs, signature: f.signature })
177 }
178
179 pub fn to_value(&self) -> Value {
181 descriptor_value(&self.lang, &self.body, &self.inputs, &self.signature, None, &BTreeMap::new())
182 }
183
184 pub fn canonical(&self) -> Vec<u8> {
186 self.to_value().to_jcs()
187 }
188
189 pub fn hash(&self) -> String {
191 crate::crypto::sha256_hex(&self.canonical())
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq, Hash)]
202#[non_exhaustive]
203pub struct FunctionDescriptor {
204 pub lang: String,
206 pub body: String,
208 pub inputs: Vec<String>,
210 pub signature: Signature,
213 pub order: Option<Vec<String>>,
216 pub extents: BTreeMap<String, Extent>,
218}
219
220impl FunctionDescriptor {
221 pub fn from_json(bytes: &[u8]) -> Result<FunctionDescriptor> {
225 FunctionDescriptor::from_value(&json::parse(bytes)?)
226 }
227
228 pub fn from_value(v: &Value) -> Result<FunctionDescriptor> {
230 json::check_value_depth(v)?;
231 descriptor_structure(v, true)?;
232 descriptor_canonical(v)
233 }
234
235 pub fn to_value(&self) -> Value {
237 descriptor_value(&self.lang, &self.body, &self.inputs, &self.signature, self.order.as_deref(), &self.extents)
238 }
239
240 pub fn canonical(&self) -> Vec<u8> {
242 self.to_value().to_jcs()
243 }
244
245 pub fn hash(&self) -> String {
247 crate::crypto::sha256_hex(&self.canonical())
248 }
249
250 pub fn to_descriptor(&self) -> Option<Descriptor> {
252 (self.order.is_none() && self.extents.is_empty()).then(|| Descriptor {
253 lang: self.lang.clone(),
254 body: self.body.clone(),
255 inputs: self.inputs.clone(),
256 signature: self.signature.clone(),
257 })
258 }
259
260 pub fn resolve_unit(&self, cell: &Cell) -> Result<Option<String>> {
263 resolve_returns(&self.signature.returns, cell)
264 }
265}
266
267impl From<Descriptor> for FunctionDescriptor {
268 fn from(d: Descriptor) -> FunctionDescriptor {
269 FunctionDescriptor {
270 lang: d.lang,
271 body: d.body,
272 inputs: d.inputs,
273 signature: d.signature,
274 order: None,
275 extents: BTreeMap::new(),
276 }
277 }
278}
279
280fn descriptor_value(
281 lang: &str,
282 body: &str,
283 inputs: &[String],
284 sig: &Signature,
285 order: Option<&[String]>,
286 extents: &BTreeMap<String, Extent>,
287) -> Value {
288 let mut sig_v = sig.to_value();
289 if let Value::Object(m) = &mut sig_v {
290 if let Some((_, Value::Object(args))) = m.iter_mut().find(|(k, _)| k == "args") {
291 for (name, decl) in args.iter_mut() {
292 if let (Some(e), Value::Object(dm)) = (extents.get(name), decl) {
293 dm.push(("extent".into(), e.to_value()));
294 }
295 }
296 }
297 if let Some(o) = order {
298 m.push(("order".into(), Value::Array(o.iter().map(|s| Value::String(s.clone())).collect())));
299 }
300 }
301 Value::Object(vec![
302 (
303 "code".into(),
304 Value::Object(vec![
305 ("body".into(), Value::String(body.into())),
306 ("lang".into(), Value::String(lang.into())),
307 ]),
308 ),
309 ("inputs".into(), Value::Array(inputs.iter().map(|s| Value::String(s.clone())).collect())),
310 ("signature".into(), sig_v),
311 ])
312}
313
314fn unit_shape(v: &Value, path: &str) -> Result<()> {
315 let bad = || err(ErrorCode::Schema, path, "a unit is a string, {\"arg\": name} or {\"of\", \"per\"}");
316 match v {
317 Value::String(_) => Ok(()),
318 Value::Object(m) if m.len() == 1 && m[0].0 == "arg" => match m[0].1 {
319 Value::String(_) => Ok(()),
320 _ => bad(),
321 },
322 Value::Object(m) if m.len() == 2 && schema::opt(m, "of").is_some() && schema::opt(m, "per").is_some() => {
323 for side in ["of", "per"] {
324 let ok = match schema::get(m, side) {
325 Value::String(_) => true,
326 Value::Object(a) => a.len() == 1 && a[0].0 == "arg" && matches!(a[0].1, Value::String(_)),
327 _ => false,
328 };
329 if !ok {
330 return err(
331 ErrorCode::Schema,
332 &member(path, side),
333 "a rate side is a currency code or {\"arg\": name}",
334 );
335 }
336 }
337 Ok(())
338 }
339 _ => bad(),
340 }
341}
342
343fn extent_shape(v: &Value, path: &str) -> Result<()> {
346 match v {
347 Value::Array(items) => {
348 if !items.iter().all(|i| matches!(i, Value::String(_))) {
349 return err(ErrorCode::Schema, path, "an extent array holds lexical values as strings");
350 }
351 Ok(())
352 }
353 Value::Object(m) => {
354 schema::exact(m, &["hi", "lo"], &[], path, ErrorCode::Schema)?;
355 schema::string(m, "hi", path)?;
356 schema::string(m, "lo", path)?;
357 Ok(())
358 }
359 _ => err(ErrorCode::Schema, path, "an extent is an array of values or a range {\"hi\", \"lo\"}"),
360 }
361}
362
363pub(crate) fn descriptor_structure(v: &Value, extended: bool) -> Result<()> {
366 let new_sig: &[&str] = if extended { &["order"] } else { &[] };
367 let new_arg: &[&str] = if extended { &["extent", "unit"] } else { &["unit"] };
368 let m = schema::object(v, "$")?;
369 schema::exact(m, &["code", "inputs", "signature"], &[], "$", ErrorCode::Schema)?;
370 let code = schema::object(schema::get(m, "code"), "$.code")?;
371 schema::exact(code, &["body", "lang"], &[], "$.code", ErrorCode::Schema)?;
372 schema::string(code, "body", "$.code")?;
373 schema::string(code, "lang", "$.code")?;
374 match schema::get(m, "inputs") {
375 Value::Array(items) if items.iter().all(|i| matches!(i, Value::String(_))) => {}
376 _ => return err(ErrorCode::Schema, "$.inputs", "inputs must be an array of strings"),
377 }
378 let sig = schema::object(schema::get(m, "signature"), "$.signature")?;
379 schema::exact(sig, &["args", "returns"], new_sig, "$.signature", ErrorCode::Schema)?;
380 let args = schema::object(schema::get(sig, "args"), "$.signature.args")?;
381 for (k, d) in jcs_sorted(args) {
382 let p = member("$.signature.args", k);
383 let dm = schema::object(d, &p)?;
384 schema::exact(dm, &["type", "valid"], new_arg, &p, ErrorCode::Schema)?;
385 if let Some(e) = schema::opt(dm, "extent") {
386 extent_shape(e, &member(&p, "extent"))?;
387 }
388 schema::string(dm, "type", &p)?;
389 if !matches!(schema::get(dm, "valid"), Value::Bool(_)) {
390 return err(ErrorCode::Schema, &member(&p, "valid"), "valid must be a boolean");
391 }
392 if let Some(u) = schema::opt(dm, "unit") {
393 unit_shape(u, &member(&p, "unit"))?;
394 }
395 }
396 if let Some(o) = schema::opt(sig, "order") {
397 if !matches!(o, Value::Array(items) if items.iter().all(|i| matches!(i, Value::String(_)))) {
398 return err(ErrorCode::Schema, "$.signature.order", "order is an array of argument names");
399 }
400 }
401 let rp = "$.signature.returns";
402 match schema::get(sig, "returns") {
403 Value::String(_) => {}
404 Value::Object(rm) => {
405 schema::exact(rm, &["type"], &["unit"], rp, ErrorCode::Schema)?;
406 schema::string(rm, "type", rp)?;
407 if let Some(u) = schema::opt(rm, "unit") {
408 unit_shape(u, &member(rp, "unit"))?;
409 }
410 }
411 _ => return err(ErrorCode::Schema, rp, "returns must be a type name or an object"),
412 }
413 Ok(())
414}
415
416fn unit_arg_ref(name: &str, args: &[(String, Value)], path: &str) -> Result<()> {
417 let is_string = schema::opt(args, name)
418 .and_then(Value::as_object)
419 .map(|d| schema::get(d, "type").as_str() == Some("string"))
420 .unwrap_or(false);
421 if !is_string {
422 return err(ErrorCode::CanonUnit, path, format!("a unit names a string argument of the signature: {name:?}"));
423 }
424 Ok(())
425}
426
427fn side_value(v: &Value) -> RateSide {
428 match v {
429 Value::String(s) => RateSide::Currency(s.clone()),
430 other => RateSide::Arg(other.get("arg").and_then(Value::as_str).unwrap_or("").into()),
431 }
432}
433
434fn unit_canonical(v: &Value, args: &[(String, Value)], path: &str) -> Result<Unit> {
436 match v {
437 Value::String(s) => {
438 if !lexical::is_literal_unit(s) {
439 return err(ErrorCode::CanonUnit, path, format!("not a unit listed in units.md: {s:?}"));
440 }
441 Ok(Unit::Literal(s.clone()))
442 }
443 Value::Object(m) if m.len() == 1 => {
444 let name = schema::get(m, "arg").as_str().unwrap_or("");
445 unit_arg_ref(name, args, path)?;
446 Ok(Unit::Arg(name.into()))
447 }
448 Value::Object(m) => {
449 for side in ["of", "per"] {
450 let sp = member(path, side);
451 match schema::get(m, side) {
452 Value::String(s) => {
453 if !lexical::is_currency(s) {
454 return err(ErrorCode::CanonUnit, &sp, format!("a rate side is a currency code: {s:?}"));
455 }
456 }
457 other => unit_arg_ref(other.get("arg").and_then(Value::as_str).unwrap_or(""), args, &sp)?,
458 }
459 }
460 Ok(Unit::Rate { of: side_value(schema::get(m, "of")), per: side_value(schema::get(m, "per")) })
461 }
462 _ => err(ErrorCode::Schema, path, "not a unit"),
463 }
464}
465
466pub(crate) fn cmp_lexical(ty: Type, a: &str, b: &str) -> core::cmp::Ordering {
470 match ty {
471 Type::Number => lexical::cmp_numbers(a, b),
472 Type::Boolean => (a == "true").cmp(&(b == "true")),
473 _ => a.cmp(b),
474 }
475}
476
477fn extent_canonical(v: &Value, ty: Type, path: &str) -> Result<Extent> {
481 if matches!(ty, Type::Boolean | Type::Blob) {
482 return err(ErrorCode::CanonType, path, format!("a {} argument has no extent", ty.as_str()));
483 }
484 match v {
485 Value::Array(items) => {
486 let values: Vec<&str> = items.iter().filter_map(Value::as_str).collect();
487 for (i, x) in values.iter().enumerate() {
488 model::check_lexical(ty, x, &index(path, i))?;
489 }
490 if values.windows(2).any(|w| cmp_lexical(ty, w[0], w[1]) != core::cmp::Ordering::Less) {
491 return err(ErrorCode::CanonSet, path, "extent values are strictly ascending in their type's order");
492 }
493 if values.len() > limits::EXTENT {
494 return err(ErrorCode::CanonLimit, path, "more than 4096 extent values");
495 }
496 Ok(Extent::Values(values.iter().map(|x| x.to_string()).collect()))
497 }
498 Value::Object(m) => {
499 if !matches!(ty, Type::Date | Type::Number) {
500 return err(ErrorCode::CanonType, path, "an extent range is for date and number arguments");
501 }
502 for side in ["hi", "lo"] {
503 let sp = member(path, side);
504 let x = schema::string(m, side, path)?;
505 model::check_lexical(ty, x, &sp)?;
506 if ty == Type::Number && !lexical::is_integer(x) {
507 return err(ErrorCode::CanonNumber, &sp, "an extent range over numbers has integer ends");
508 }
509 }
510 let (lo, hi) = (schema::string(m, "lo", path)?, schema::string(m, "hi", path)?);
511 if cmp_lexical(ty, lo, hi) == core::cmp::Ordering::Greater {
512 return err(ErrorCode::CanonSet, path, "an extent range has lo <= hi");
513 }
514 Ok(Extent::Range { lo: lo.into(), hi: hi.into() })
515 }
516 _ => err(ErrorCode::Schema, path, "not an extent"),
517 }
518}
519
520pub(crate) fn descriptor_canonical(v: &Value) -> Result<FunctionDescriptor> {
521 let m = schema::object(v, "$")?;
522 let code = schema::object(schema::get(m, "code"), "$.code")?;
523 let body = schema::string(code, "body", "$.code")?;
524 lexical::check_text(body, limits::BODY, true, "$.code.body")?;
525 let lang = schema::string(code, "lang", "$.code")?;
526 lexical::check_name(lang, "$.code.lang")?;
527
528 let items: Vec<&str> = match schema::get(m, "inputs") {
529 Value::Array(items) => items.iter().filter_map(Value::as_str).collect(),
530 _ => Vec::new(),
531 };
532 for (i, s) in items.iter().enumerate() {
533 lexical::check_function_name(s, &index("$.inputs", i))?;
534 }
535 if items.windows(2).any(|w| w[0] >= w[1]) {
536 return err(ErrorCode::CanonSet, "$.inputs", "entries must be strictly ascending");
537 }
538 if items.len() > limits::INPUTS {
539 return err(ErrorCode::CanonLimit, "$.inputs", "more than 4096 inputs");
540 }
541
542 let sig = schema::object(schema::get(m, "signature"), "$.signature")?;
543 let args = schema::object(schema::get(sig, "args"), "$.signature.args")?;
544 if args.len() > limits::ARGS {
545 return err(ErrorCode::CanonLimit, "$.signature.args", "more than 32 arguments");
546 }
547 let mut decls = BTreeMap::new();
548 let mut extents = BTreeMap::new();
549 for (k, d) in jcs_sorted(args) {
550 let p = member("$.signature.args", k);
551 lexical::check_arg_name(k, &p)?;
552 let dm = schema::object(d, &p)?;
553 let ty = Type::parse(schema::string(dm, "type", &p)?, &member(&p, "type"))?;
554 let unit = match (ty, schema::opt(dm, "unit")) {
555 (Type::Number, None) => return err(ErrorCode::CanonUnit, &p, "a number argument declares its unit"),
556 (Type::Number, Some(u)) => Some(unit_canonical(u, args, &member(&p, "unit"))?),
557 (_, Some(_)) => return err(ErrorCode::CanonUnit, &member(&p, "unit"), "only number arguments have a unit"),
558 (_, None) => None,
559 };
560 let valid = matches!(schema::get(dm, "valid"), Value::Bool(true));
561 if valid && !ty.may_be_valid_time() {
562 return err(ErrorCode::CanonType, &p, "a valid-time argument must be a date, instant or string");
563 }
564 if let Some(e) = schema::opt(dm, "extent") {
565 extents.insert(k.clone(), extent_canonical(e, ty, &member(&p, "extent"))?);
566 }
567 decls.insert(k.clone(), ArgDecl { ty, unit, valid });
568 }
569 let order = match schema::opt(sig, "order") {
570 Some(Value::Array(items)) => {
571 let names: Vec<&str> = items.iter().filter_map(Value::as_str).collect();
572 let mut sorted = names.clone();
573 sorted.sort_unstable();
574 sorted.dedup();
575 if sorted.len() != names.len()
576 || names.len() != decls.len()
577 || !names.iter().all(|n| decls.contains_key(*n))
578 {
579 return err(ErrorCode::CanonSet, "$.signature.order", "order lists every argument exactly once");
580 }
581 Some(names.iter().map(|n| n.to_string()).collect())
582 }
583 _ => None,
584 };
585
586 let rp = "$.signature.returns";
587 let returns = match schema::get(sig, "returns") {
588 Value::String(name) => {
589 let ty = Type::parse(name, rp)?;
590 if ty == Type::Number {
591 return err(
592 ErrorCode::CanonUnit,
593 rp,
594 "a number return is written {\"type\": \"number\", \"unit\": ...}",
595 );
596 }
597 Returns { ty, unit: None }
598 }
599 Value::Object(rm) => {
600 let ty = Type::parse(schema::string(rm, "type", rp)?, &member(rp, "type"))?;
601 if ty != Type::Number {
602 return err(ErrorCode::CanonUnit, rp, "only a number return is written as an object");
603 }
604 let Some(u) = schema::opt(rm, "unit") else {
605 return err(ErrorCode::CanonUnit, rp, "a number return declares its unit");
606 };
607 Returns { ty, unit: Some(unit_canonical(u, args, &member(rp, "unit"))?) }
608 }
609 _ => return err(ErrorCode::Schema, rp, "returns must be a type name or an object"),
610 };
611 Ok(FunctionDescriptor {
612 lang: lang.into(),
613 body: body.into(),
614 inputs: items.iter().map(|s| s.to_string()).collect(),
615 signature: Signature { args: decls, returns },
616 order,
617 extents,
618 })
619}
620
621pub fn resolved_unit(descriptor: &[u8], cell: &[u8]) -> Result<Option<String>> {
627 let cell_v = json::parse(cell)?;
628 let desc_v = json::parse(descriptor)?;
629 resolved_unit_value(&desc_v, &cell_v)
630}
631
632pub fn resolved_unit_value(descriptor: &Value, cell: &Value) -> Result<Option<String>> {
634 json::check_value_depth(descriptor)?;
635 json::check_value_depth(cell)?;
636 let cell = Cell::from_value(cell)?;
637 let hash = crate::crypto::sha256_hex(&descriptor.to_jcs());
638 if hash != cell.fn_hash {
639 return err(ErrorCode::FnHashMismatch, "$.descriptor", "the descriptor does not hash to cell.fn_hash");
640 }
641 let d = FunctionDescriptor::from_value(descriptor)?;
642 d.resolve_unit(&cell)
643}
644
645pub fn resolve_with(d: &Descriptor, cell: &Cell) -> Result<Option<String>> {
647 resolve_returns(&d.signature.returns, cell)
648}
649
650fn resolve_returns(returns: &Returns, cell: &Cell) -> Result<Option<String>> {
651 let Some(unit) = &returns.unit else { return Ok(None) };
652 let arg_value = |name: &str| -> Result<String> {
653 let p = member("$.cell.args", name);
654 match cell.args.get(name) {
655 None => err(
656 ErrorCode::ArgMissing,
657 &p,
658 format!("the unit refers to argument {name:?}, which the cell does not bind"),
659 ),
660 Some(Arg::Slice) => err(ErrorCode::Slice, &p, "a slice has no single unit"),
661 Some(Arg::Value(Scalar::String(s))) => Ok(s.clone()),
662 Some(Arg::Value(other)) => {
663 err(ErrorCode::ArgType, &p, format!("bound as {}, not string", other.ty().as_str()))
664 }
665 }
666 };
667 let side = |s: &RateSide| -> Result<String> {
668 match s {
669 RateSide::Currency(c) => Ok(c.clone()),
670 RateSide::Arg(n) => {
671 let v = arg_value(n)?;
672 if !lexical::is_currency(&v) {
673 return err(
674 ErrorCode::CanonUnit,
675 &member("$.cell.args", n),
676 format!("a rate side must be a currency code: {v:?}"),
677 );
678 }
679 Ok(v)
680 }
681 }
682 };
683 Ok(Some(match unit {
684 Unit::Literal(s) => s.clone(),
685 Unit::Arg(n) => {
686 let v = arg_value(n)?;
687 if !lexical::is_literal_unit(&v) {
688 return err(ErrorCode::CanonUnit, &member("$.cell.args", n), format!("not a unit: {v:?}"));
689 }
690 v
691 }
692 Unit::Rate { of, per } => {
693 let o = side(of)?;
694 let p = side(per)?;
695 format!("{o}/{p}")
696 }
697 }))
698}