1use core::cmp::Ordering;
4
5use crate::descriptor::cmp_lexical;
6use crate::error::{err, index, member, Error, ErrorCode, Result};
7use crate::json::{self, jcs_sorted, Value};
8use crate::lexical::{self, limits};
9use crate::model::{self, Arg, Cell, Scalar, Type};
10use crate::receipt::Receipt;
11use crate::schema;
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15enum SliceArg {
16 Value(Scalar),
18 Open,
20 Range { ty: Type, lo: String, hi: String },
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Hash)]
25enum Kind {
26 Cells { ns: String, fn_name: String, fn_hash: String, args: Vec<(String, SliceArg)> },
28 Versions { cell: Cell, from: String, to: String },
30}
31
32#[derive(Clone, Debug, PartialEq, Eq, Hash)]
41pub struct Slice {
42 kind: Kind,
43 canonical: Vec<u8>,
44 id: String,
45}
46
47impl Slice {
48 pub fn from_json(bytes: &[u8]) -> Result<Slice> {
53 Slice::from_value(&json::parse(bytes)?)
54 }
55
56 pub fn from_value(v: &Value) -> Result<Slice> {
58 json::check_value_depth(v)?;
59 slice_structure(v, "$")?;
60 slice_canonical(v, "$")
61 }
62
63 pub fn canonical(&self) -> Vec<u8> {
65 self.canonical.clone()
66 }
67
68 pub fn id(&self) -> &str {
70 &self.id
71 }
72
73 pub fn to_value(&self) -> Value {
75 match &self.kind {
76 Kind::Cells { ns, fn_name, fn_hash, args } => cells_value(ns, fn_name, fn_hash, args),
77 Kind::Versions { cell, from, to } => Value::Object(vec![
78 ("cell".into(), cell.to_value()),
79 ("from".into(), Value::String(from.clone())),
80 ("to".into(), Value::String(to.clone())),
81 ]),
82 }
83 }
84
85 pub fn function_name(&self) -> String {
88 match &self.kind {
89 Kind::Cells { ns, fn_name, .. } => format!("{ns}/{fn_name}"),
90 Kind::Versions { cell, .. } => cell.function_name(),
91 }
92 }
93
94 pub fn fn_hash(&self) -> &str {
96 match &self.kind {
97 Kind::Cells { fn_hash, .. } => fn_hash,
98 Kind::Versions { cell, .. } => &cell.fn_hash,
99 }
100 }
101
102 pub(crate) fn key_names(&self) -> Vec<String> {
105 match &self.kind {
106 Kind::Cells { args, .. } => {
107 let mut v: Vec<String> =
108 args.iter().filter(|(_, a)| !matches!(a, SliceArg::Value(_))).map(|(k, _)| k.clone()).collect();
109 v.sort();
110 v
111 }
112 Kind::Versions { .. } => Vec::new(),
113 }
114 }
115
116 pub fn is_version_slice(&self) -> bool {
118 matches!(self.kind, Kind::Versions { .. })
119 }
120
121 pub fn contains(&self, receipt: &Receipt) -> bool {
127 let c = receipt.cell();
128 match &self.kind {
129 Kind::Versions { cell, from, to } => {
130 c == cell && from.as_str() <= receipt.tx() && receipt.tx() <= to.as_str()
131 }
132 Kind::Cells { ns, fn_name, fn_hash, args } => {
133 if c.ns != *ns || c.fn_name != *fn_name || c.fn_hash != *fn_hash || c.args.len() != args.len() {
134 return false;
135 }
136 args.iter().all(|(name, a)| match (a, c.args.get(name)) {
137 (_, None | Some(Arg::Slice)) => false,
138 (SliceArg::Open, Some(Arg::Value(_))) => true,
139 (SliceArg::Value(want), Some(Arg::Value(got))) => want == got,
140 (SliceArg::Range { ty, lo, hi }, Some(Arg::Value(got))) => {
141 got.ty() == *ty
142 && cmp_lexical(*ty, lo, got.lexical()) != Ordering::Greater
143 && cmp_lexical(*ty, got.lexical(), hi) != Ordering::Greater
144 }
145 })
146 }
147 }
148 }
149}
150
151fn cells_value(ns: &str, fn_name: &str, fn_hash: &str, args: &[(String, SliceArg)]) -> Value {
152 let args = args
153 .iter()
154 .map(|(k, a)| {
155 let v = match a {
156 SliceArg::Value(s) => s.to_value(),
157 SliceArg::Open => Value::String("*".into()),
158 SliceArg::Range { ty, lo, hi } => Value::Object(vec![
159 ("hi".into(), Value::String(hi.clone())),
160 ("lo".into(), Value::String(lo.clone())),
161 ("type".into(), Value::String(ty.as_str().into())),
162 ]),
163 };
164 (k.clone(), v)
165 })
166 .collect();
167 Value::Object(vec![
168 ("args".into(), Value::Object(args)),
169 ("fn".into(), Value::String(fn_name.into())),
170 ("fn_hash".into(), Value::String(fn_hash.into())),
171 ("ns".into(), Value::String(ns.into())),
172 ])
173}
174
175fn is_version_slice(m: &[(String, Value)]) -> bool {
176 m.len() == 3 && ["cell", "from", "to"].iter().all(|k| schema::opt(m, k).is_some())
177}
178
179fn is_range(v: &Value) -> bool {
180 matches!(v, Value::Object(m) if m.len() == 3 && ["hi", "lo", "type"].iter().all(|k| schema::opt(m, k).is_some()))
181}
182
183fn slice_structure(v: &Value, path: &str) -> Result<()> {
184 let m = schema::object(v, path)?;
185 if is_version_slice(m) {
186 model::cell_structure(schema::get(m, "cell"), &member(path, "cell"))?;
187 schema::string(m, "from", path)?;
188 schema::string(m, "to", path)?;
189 return Ok(());
190 }
191 schema::exact(m, &["args", "fn", "fn_hash", "ns"], &[], path, ErrorCode::Schema)?;
192 let ap = member(path, "args");
193 for (k, a) in jcs_sorted(schema::object(schema::get(m, "args"), &ap)?) {
194 let p = member(&ap, k);
195 match a {
196 Value::String(s) if s == "*" => {}
197 Value::String(_) => return err(ErrorCode::Schema, &p, "an argument is a typed scalar, \"*\" or a range"),
198 r if is_range(r) => {
199 let rm = schema::object(r, &p)?;
200 for side in ["hi", "lo", "type"] {
201 schema::string(rm, side, &p)?;
202 }
203 }
204 other => model::scalar_structure(other, &p)?,
205 }
206 }
207 schema::string(m, "fn", path)?;
208 schema::string(m, "fn_hash", path)?;
209 schema::string(m, "ns", path)?;
210 Ok(())
211}
212
213fn slice_canonical(v: &Value, path: &str) -> Result<Slice> {
214 let m = schema::object(v, path)?;
215 let kind = if is_version_slice(m) {
216 let cp = member(path, "cell");
217 let cell = model::cell_canonical(schema::get(m, "cell"), &cp)?;
218 if let Some((k, _)) = cell.args.iter().find(|(_, a)| **a == Arg::Slice) {
219 return err(ErrorCode::Slice, &member(&member(&cp, "args"), k), "a version slice is of one cell");
220 }
221 let from = schema::string(m, "from", path)?;
222 lexical::check_instant(from, &member(path, "from"))?;
223 let to = schema::string(m, "to", path)?;
224 lexical::check_instant(to, &member(path, "to"))?;
225 Kind::Versions { cell, from: from.into(), to: to.into() }
226 } else {
227 let ap = member(path, "args");
228 let am = schema::object(schema::get(m, "args"), &ap)?;
229 if am.len() > limits::ARGS {
230 return err(ErrorCode::CanonLimit, &ap, "more than 32 arguments");
231 }
232 let mut args = Vec::new();
233 for (k, a) in jcs_sorted(am) {
234 let p = member(&ap, k);
235 lexical::check_arg_name(k, &p)?;
236 let arg = match a {
237 Value::String(s) if s == "*" => SliceArg::Open,
238 r if is_range(r) => range_canonical(r, &p)?,
239 other => SliceArg::Value(model::scalar_canonical(other, &p)?),
240 };
241 args.push((k.clone(), arg));
242 }
243 let f = schema::string(m, "fn", path)?;
244 lexical::check_name(f, &member(path, "fn"))?;
245 let h = schema::string(m, "fn_hash", path)?;
246 lexical::check_hex(h, 64, &member(path, "fn_hash"))?;
247 let ns = schema::string(m, "ns", path)?;
248 lexical::check_namespace(ns, &member(path, "ns"))?;
249 if ns.len() + 1 + f.len() > limits::FUNCTION_NAME {
250 return err(ErrorCode::CanonLimit, path, "function name longer than 512 characters");
251 }
252 if !args.iter().any(|(_, a)| !matches!(a, SliceArg::Value(_))) {
253 return err(ErrorCode::Slice, &ap, "a slice object has an open or ranged argument");
254 }
255 args.sort_by(|a, b| a.0.cmp(&b.0));
256 Kind::Cells { ns: ns.into(), fn_name: f.into(), fn_hash: h.into(), args }
257 };
258 let mut s = Slice { kind, canonical: Vec::new(), id: String::new() };
259 s.canonical = s.to_value().to_jcs();
260 s.id = crate::crypto::sha256_hex(&s.canonical);
261 Ok(s)
262}
263
264fn range_canonical(v: &Value, path: &str) -> Result<SliceArg> {
268 let m = schema::object(v, path)?;
269 let tp = member(path, "type");
270 let ty = Type::parse(schema::string(m, "type", path)?, &tp)?;
271 if matches!(ty, Type::Boolean | Type::Blob) {
272 return err(ErrorCode::CanonType, &tp, format!("a {} argument has no range", ty.as_str()));
273 }
274 let hi = schema::string(m, "hi", path)?;
275 model::check_lexical(ty, hi, &member(path, "hi"))?;
276 let lo = schema::string(m, "lo", path)?;
277 model::check_lexical(ty, lo, &member(path, "lo"))?;
278 Ok(SliceArg::Range { ty, lo: lo.into(), hi: hi.into() })
279}
280
281#[derive(Clone, Debug, PartialEq, Eq, Hash)]
286pub struct Manifest {
287 slice: Slice,
288 members: Vec<String>,
289 members_hash: String,
290}
291
292impl Manifest {
293 pub fn from_json(bytes: &[u8]) -> Result<Manifest> {
298 Manifest::from_value(&json::parse(bytes)?)
299 }
300
301 pub fn from_value(v: &Value) -> Result<Manifest> {
303 json::check_value_depth(v)?;
304 let m = schema::object(v, "$")?;
305 schema::exact(m, &["members", "slice"], &[], "$", ErrorCode::Schema)?;
306 let Value::Array(items) = schema::get(m, "members") else {
307 return err(ErrorCode::Schema, "$.members", "members is an array of receipt hashes");
308 };
309 if !items.iter().all(|i| matches!(i, Value::String(_))) {
310 return err(ErrorCode::Schema, "$.members", "members is an array of receipt hashes");
311 }
312 slice_structure(schema::get(m, "slice"), "$.slice")?;
313 let members: Vec<String> = items.iter().filter_map(Value::as_str).map(String::from).collect();
314 check_members(&members)?;
315 let slice = slice_canonical(schema::get(m, "slice"), "$.slice")?;
316 Ok(Manifest::unchecked(slice, members))
317 }
318
319 pub fn new(slice: Slice, members: Vec<String>) -> Result<Manifest> {
324 check_members(&members)?;
325 Ok(Manifest::unchecked(slice, members))
326 }
327
328 fn unchecked(slice: Slice, members: Vec<String>) -> Manifest {
329 let hash = crate::crypto::sha256_hex(&members_value(&members).to_jcs());
330 Manifest { slice, members, members_hash: hash }
331 }
332
333 pub fn slice(&self) -> &Slice {
335 &self.slice
336 }
337
338 pub fn members(&self) -> &[String] {
340 &self.members
341 }
342
343 pub fn members_hash(&self) -> &str {
345 &self.members_hash
346 }
347
348 pub fn entry(&self) -> String {
350 format!("slice:{}:{}", self.slice.id(), self.members_hash)
351 }
352
353 pub fn to_value(&self) -> Value {
355 Value::Object(vec![("members".into(), members_value(&self.members)), ("slice".into(), self.slice.to_value())])
356 }
357
358 pub fn canonical(&self) -> Vec<u8> {
360 self.to_value().to_jcs()
361 }
362}
363
364fn members_value(members: &[String]) -> Value {
365 Value::Array(members.iter().map(|h| Value::String(h.clone())).collect())
366}
367
368fn check_members(members: &[String]) -> Result<()> {
369 for (i, h) in members.iter().enumerate() {
370 lexical::check_hex(h, 64, &index("$.members", i))?;
371 }
372 if members.windows(2).any(|w| w[0] >= w[1]) {
373 return err(ErrorCode::CanonSet, "$.members", "entries must be strictly ascending");
374 }
375 if members.len() > limits::MEMBERS {
376 return err(ErrorCode::CanonLimit, "$.members", "more than 1048576 members");
377 }
378 Ok(())
379}
380
381pub(crate) fn nest(e: Error, prefix: &str) -> Error {
383 let inner = e.path().strip_prefix('$').unwrap_or(e.path()).to_string();
384 Error::new(e.code(), format!("{prefix}{inner}"), e.detail())
385}