use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt;
use crate::descriptor::Descriptor;
use crate::error::ErrorCode;
use crate::keys::Keys;
use crate::model::{Cell, Scalar};
use crate::receipt::Receipt;
use crate::slice::Manifest;
use crate::verify::{verify, Evaluation, Level, Resolver, VerifyResult};
#[rustfmt::skip]
mod data;
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Fixtures {
pub key_documents: Vec<(String, Vec<u8>)>,
pub descriptors: Vec<(String, Vec<u8>)>,
pub receipts: Vec<(String, Vec<u8>)>,
pub versions: Vec<Version>,
pub slices: SliceFixtures,
}
impl Fixtures {
pub fn keys(&self) -> Keys {
keys_of(&self.key_documents)
}
}
fn keys_of(docs: &[(String, Vec<u8>)]) -> Keys {
let mut keys = Keys::new();
for (host, body) in docs {
keys.insert_response(host, 200, Some(body));
}
keys
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SliceFixtures {
pub key_documents: Vec<(String, Vec<u8>)>,
pub descriptors: Vec<(String, Vec<u8>)>,
pub receipts: Vec<(String, Vec<u8>)>,
pub manifests: Vec<(String, Vec<u8>)>,
}
impl SliceFixtures {
pub fn keys(&self) -> Keys {
keys_of(&self.key_documents)
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Version {
pub cell_id: String,
pub tx: String,
pub receipt_hash: String,
pub value: Scalar,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum AnswerRule {
#[default]
TxAndAsof,
TxOnly,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Call {
LookupDescriptor(String),
LookupReceipt(String),
LookupManifest(String),
Evaluate {
cell_id: String,
asof: String,
},
}
pub struct Recording<'a> {
inner: &'a dyn Resolver,
calls: RefCell<Vec<Call>>,
}
impl<'a> Recording<'a> {
pub fn new(inner: &'a dyn Resolver) -> Recording<'a> {
Recording { inner, calls: RefCell::new(Vec::new()) }
}
pub fn calls(&self) -> Vec<Call> {
self.calls.borrow().clone()
}
pub fn take(&self) -> Vec<Call> {
self.calls.take()
}
}
impl fmt::Debug for Recording<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Recording").field("calls", &self.calls.borrow()).finish_non_exhaustive()
}
}
impl Resolver for Recording<'_> {
fn descriptor(&self, fn_hash: &str) -> Option<Cow<'_, [u8]>> {
self.calls.borrow_mut().push(Call::LookupDescriptor(fn_hash.to_string()));
self.inner.descriptor(fn_hash)
}
fn receipt(&self, hash: &str) -> Option<Cow<'_, [u8]>> {
self.calls.borrow_mut().push(Call::LookupReceipt(hash.to_string()));
self.inner.receipt(hash)
}
fn evaluate(&self, cell: &Cell, asof: &str) -> Option<Scalar> {
self.calls.borrow_mut().push(Call::Evaluate { cell_id: cell.id(), asof: asof.to_string() });
self.inner.evaluate(cell, asof)
}
fn manifest(&self, entry: &str) -> Option<Cow<'_, [u8]>> {
self.calls.borrow_mut().push(Call::LookupManifest(entry.to_string()));
self.inner.manifest(entry)
}
fn evaluation(&self, cell: &Cell, asof: &str) -> Option<Evaluation> {
self.calls.borrow_mut().push(Call::Evaluate { cell_id: cell.id(), asof: asof.to_string() });
self.inner.evaluation(cell, asof)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Report {
failures: Vec<String>,
}
impl Report {
pub fn failures(&self) -> &[String] {
&self.failures
}
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, line) in self.failures.iter().enumerate() {
if i > 0 {
f.write_str("\n")?;
}
f.write_str(line)?;
}
Ok(())
}
}
impl std::error::Error for Report {}
#[derive(Clone, Debug)]
struct Case {
name: &'static str,
bytes: Vec<u8>,
}
#[derive(Clone, Debug)]
pub struct Kit {
fixtures: Fixtures,
cases: Vec<Case>,
slice_cases: Vec<Case>,
time_aware: bool,
answer_rule: AnswerRule,
slices: bool,
setup: Vec<String>,
}
impl Default for Kit {
fn default() -> Kit {
Kit::new()
}
}
const NOT_JSON: &str = "not-json";
impl Kit {
pub fn new() -> Kit {
let mut setup = Vec::new();
let mut fx = Fixtures {
key_documents: Vec::new(),
descriptors: Vec::new(),
receipts: Vec::new(),
versions: Vec::new(),
slices: SliceFixtures {
key_documents: Vec::new(),
descriptors: Vec::new(),
receipts: Vec::new(),
manifests: Vec::new(),
},
};
let mut cases = Vec::new();
for (host, text) in data::KEY_DOCUMENTS {
fx.key_documents.push((host.to_string(), text.as_bytes().to_vec()));
}
let mut stored = Vec::new();
for (source, text) in data::DESCRIPTORS {
match Descriptor::from_json(text.as_bytes()) {
Ok(d) => {
if d.lang == "stored" {
stored.push(d.hash());
}
fx.descriptors.push((d.hash(), text.as_bytes().to_vec()));
}
Err(e) => setup.push(format!("setup: {source}: {e}")),
}
}
for (name, source, text) in data::RECEIPTS {
match Receipt::from_json(text.as_bytes()) {
Ok(r) => {
if stored.contains(&r.cell().fn_hash) {
fx.versions.push(Version {
cell_id: r.cell().id(),
tx: r.tx().to_string(),
receipt_hash: r.hash().to_string(),
value: r.value().clone(),
});
}
fx.receipts.push((r.hash().to_string(), text.as_bytes().to_vec()));
}
Err(e) => setup.push(format!("setup: {source}: {e}")),
}
cases.push(Case { name, bytes: text.as_bytes().to_vec() });
}
for (name, _, text) in data::PROBES {
cases.push(Case { name, bytes: text.as_bytes().to_vec() });
}
cases.push(Case { name: NOT_JSON, bytes: b"{".to_vec() });
fx.versions.sort_by(|a, b| a.cell_id.cmp(&b.cell_id).then_with(|| a.tx.cmp(&b.tx)));
let slice_cases = load_slices(&mut fx.slices, &mut setup);
Kit {
fixtures: fx,
cases,
slice_cases,
time_aware: false,
answer_rule: AnswerRule::default(),
slices: false,
setup,
}
}
pub fn slices(mut self, yes: bool) -> Kit {
self.slices = yes;
self
}
pub fn time_aware(mut self, yes: bool) -> Kit {
self.time_aware = yes;
self
}
pub fn answer_rule(mut self, rule: AnswerRule) -> Kit {
self.answer_rule = rule;
self
}
pub fn fixtures(&self) -> &Fixtures {
&self.fixtures
}
pub fn run<R: Resolver, F: FnOnce(&Fixtures) -> R>(&self, load: F) -> Result<(), Report> {
self.setup_ok()?;
let r = load(&self.fixtures);
self.check(&r)
}
pub fn check(&self, resolver: &dyn Resolver) -> Result<(), Report> {
self.setup_ok()?;
let mut failures = Vec::new();
let mut checks: Vec<Check> =
vec![Kit::check_lazy_calls, Kit::check_unavailable, Kit::check_bytes, Kit::check_asof];
if self.slices {
checks.push(Kit::check_slices);
}
for check in checks {
if let Err(mut r) = check(self, resolver) {
failures.append(&mut r.failures);
}
}
report(failures)
}
pub fn check_slices(&self, resolver: &dyn Resolver) -> Result<(), Report> {
self.setup_ok()?;
let mut f = Failures::new("slices");
let keys = self.fixtures.slices.keys();
let members = self.manifest_members();
for case in &self.slice_cases {
let Ok(r) = Receipt::from_json(&case.bytes) else {
f.push(case.name, "the fixture does not parse".to_string());
continue;
};
let rec = Recording::new(resolver);
let out = verify(&case.bytes, &keys, Some(&rec), None);
let calls = rec.take();
let want_code = match case.name {
"slice-manifest-missing" | "slice-member-missing" => Some(ErrorCode::InputUnavailable),
_ => None,
};
match want_code {
Some(code) => {
if out.ok || out.code != Some(code) {
f.push(case.name, format!("want {code}, got ok={} code={:?} {}", out.ok, out.code, out.path));
}
}
None => {
let passed = out.ok
&& ((out.level == Some(Level::Recomputed) && out.code.is_none())
|| (out.level == Some(Level::Signed) && out.code == Some(ErrorCode::EvalUnavailable)));
if !passed {
f.push(
case.name,
format!(
"want step 7.5 to pass (recomputed, or E_EVAL_UNAVAILABLE), got ok={} level={:?} code={:?} {} {}",
out.ok, out.level, out.code, out.path, out.detail
),
);
}
}
}
let want = expected_calls_with(&r, &out, &members);
let same = match out.code {
Some(ErrorCode::ValueMismatch | ErrorCode::InputSuperseded) => {
calls.starts_with(&want) && calls[want.len()..].iter().all(|c| matches!(c, Call::LookupReceipt(_)))
}
_ => calls == want,
};
if !same {
f.push(case.name, format!("asked {calls:?}, step 7 needs {want:?} (result {:?})", out.code));
}
if want_code.is_none() {
if let Some(Evaluation { lineage: Some(lineage), .. }) = resolver.evaluation(r.cell(), r.asof()) {
for e in &lineage {
let form = crate::lexical::check_input_entry(e, "").is_ok();
let backed = r.inputs().contains(e)
|| if e.starts_with("slice:") {
resolver.manifest(e).is_some()
} else {
resolver.receipt(e).is_some()
};
if !form || !backed {
f.push(case.name, format!("lineage entry {} is not an entry the resolver backs", shown(e)));
}
}
}
}
}
let sf = &self.fixtures.slices;
for (e, bytes) in &sf.manifests {
for _ in 0..2 {
match resolver.manifest(e) {
Some(b) if *b == **bytes => {}
Some(b) => f.push(
"bytes",
format!("manifest {e}: {} bytes differ from the {} loaded", b.len(), bytes.len()),
),
None => f.push("bytes", format!("manifest {e}: None, but it was loaded")),
}
}
}
for (h, bytes) in &sf.receipts {
match resolver.receipt(h) {
Some(b) if *b == **bytes => {}
Some(_) => f.push("bytes", format!("receipt {h}: the bytes differ from those loaded")),
None => f.push("bytes", format!("receipt {h}: None, but it was loaded")),
}
}
for (h, bytes) in &sf.descriptors {
match resolver.descriptor(h) {
Some(b) if *b == **bytes => {}
Some(_) => f.push("bytes", format!("descriptor {h}: the bytes differ from those loaded")),
None => f.push("bytes", format!("descriptor {h}: None, but it was loaded")),
}
}
let known = sf.manifests.first().map(|(e, _)| e.clone()).unwrap_or_default();
let missing: Vec<String> = self
.slice_cases
.iter()
.filter_map(|c| Receipt::from_json(&c.bytes).ok())
.flat_map(|r| r.inputs().to_vec())
.filter(|e| e.starts_with("slice:") && !sf.manifests.iter().any(|(x, _)| x == e))
.collect();
let mut probes = vec![
format!("slice:{}:{}", "0".repeat(64), "0".repeat(64)),
String::new(),
"slice:".into(),
known.to_uppercase(),
format!("{known}0"),
format!(" {known}"),
known.get(..134).unwrap_or_default().to_string(),
known.replacen("slice:", "SLICE:", 1),
"' OR '1'='1".into(),
"../../etc/passwd".into(),
"a".repeat(100_000),
];
probes.extend(sf.receipts.first().map(|(h, _)| h.clone()));
probes.extend(missing);
for p in &probes {
if resolver.manifest(p).is_some() {
f.push("lookup", format!("manifest({}) is not None", shown(p)));
}
}
f.finish()
}
fn manifest_members(&self) -> Vec<(String, Vec<String>)> {
self.fixtures
.slices
.manifests
.iter()
.filter_map(|(e, b)| Manifest::from_json(b).ok().map(|m| (e.clone(), m.members().to_vec())))
.collect()
}
pub fn check_lazy_calls(&self, resolver: &dyn Resolver) -> Result<(), Report> {
self.setup_ok()?;
let keys = self.fixtures.keys();
let mut f = Failures::new("lazy_calls");
for case in &self.cases {
let rec = Recording::new(resolver);
let without = verify(&case.bytes, &keys, None, None);
let out = verify(&case.bytes, &keys, Some(&rec), None);
let calls = rec.take();
if !without.ok {
if !calls.is_empty() {
f.push(
case.name,
format!("steps 1-6 fail ({:?}), yet the resolver was asked {calls:?}", without.code),
);
}
if out != without {
f.push(case.name, format!("a resolver changed a failure before step 7: {out:?}"));
}
continue;
}
match Receipt::from_json(&case.bytes) {
Ok(r) => {
let want = expected_calls(&r, &out);
let same = match out.code {
Some(ErrorCode::ValueMismatch | ErrorCode::InputSuperseded) => {
calls.starts_with(&want)
&& calls[want.len()..].iter().all(|c| matches!(c, Call::LookupReceipt(_)))
}
_ => calls == want,
};
if !same {
f.push(case.name, format!("asked {calls:?}, step 7 needs {want:?} (result {:?})", out.code));
}
}
Err(e) => f.push(case.name, format!("steps 1-6 passed but the receipt does not parse: {e}")),
}
}
for name in ["tampered-value", "duplicate-member", NOT_JSON] {
if let Some(case) = self.case(name) {
if verify(&case.bytes, &keys, None, None).ok {
f.push(name, "expected to fail before step 7".to_string());
}
}
}
f.finish()
}
pub fn check_unavailable(&self, resolver: &dyn Resolver) -> Result<(), Report> {
self.setup_ok()?;
let mut f = Failures::new("unavailable");
let keys = self.fixtures.keys();
self.expect(&mut f, resolver, &keys, "instant-no-valid-time", Want::Fails(ErrorCode::FnUnavailable));
self.expect(&mut f, resolver, &keys, "computed-two-inputs", Want::Fails(ErrorCode::InputUnavailable));
self.expect(&mut f, resolver, &keys, "first-input-missing", Want::Fails(ErrorCode::InputUnavailable));
self.expect(&mut f, resolver, &keys, "computed-with-lineage", Want::EvalUnavailable);
let unknown_fn = self.receipt_of("instant-no-valid-time").map(|r| r.cell().fn_hash.clone());
let known_receipt = self.fixtures.receipts.first().map(|(h, _)| h.clone()).unwrap_or_default();
let known_fn = self.fixtures.descriptors.first().map(|(h, _)| h.clone()).unwrap_or_default();
let mut probes: Vec<String> = vec![
"0".repeat(64),
"f".repeat(64),
String::new(),
"abc".into(),
"g".repeat(64),
"\0".into(),
format!("{known_receipt}\0"),
"../../etc/passwd".into(),
"' OR '1'='1".into(),
"%".into(),
"_".repeat(64),
"é".repeat(32),
"a".repeat(100_000),
known_receipt.to_uppercase(),
known_fn.to_uppercase(),
format!("{known_receipt}0"),
format!(" {known_receipt}"),
format!("{known_fn}\n"),
known_receipt.get(..63).unwrap_or_default().to_string(),
];
probes.extend(unknown_fn);
for name in ["computed-two-inputs", "first-input-missing"] {
if let Some(r) = self.receipt_of(name) {
probes.extend(r.inputs().iter().filter(|h| !self.has_receipt(h)).cloned());
}
}
for p in &probes {
if resolver.descriptor(p).is_some() {
f.push("lookup", format!("descriptor({}) is not None", shown(p)));
}
if resolver.receipt(p).is_some() {
f.push("lookup", format!("receipt({}) is not None", shown(p)));
}
}
if let Some(r) = self.receipt_of("timeline-1") {
for asof in ["", "not-an-instant", "\0", "2024-03-16", "' OR '1'='1", "2024-03-16T09:00:00Z"] {
let _ = resolver.evaluate(r.cell(), asof);
}
}
f.finish()
}
pub fn check_bytes(&self, resolver: &dyn Resolver) -> Result<(), Report> {
self.setup_ok()?;
let mut f = Failures::new("bytes");
for (h, bytes) in &self.fixtures.descriptors {
for _ in 0..2 {
match resolver.descriptor(h) {
Some(b) if *b == **bytes => {}
Some(b) => {
f.push("descriptor", format!("{h}: {} bytes differ from the {} loaded", b.len(), bytes.len()))
}
None => f.push("descriptor", format!("{h}: None, but it was loaded")),
}
}
}
for (h, bytes) in &self.fixtures.receipts {
for _ in 0..2 {
match resolver.receipt(h) {
Some(b) if *b == **bytes => {}
Some(b) => {
f.push("receipt", format!("{h}: {} bytes differ from the {} loaded", b.len(), bytes.len()))
}
None => f.push("receipt", format!("{h}: None, but it was loaded")),
}
}
}
f.finish()
}
pub fn check_asof(&self, resolver: &dyn Resolver) -> Result<(), Report> {
self.setup_ok()?;
let mut f = Failures::new("asof");
let keys = self.fixtures.keys();
for case in &self.cases {
let Ok(r) = Receipt::from_json(&case.bytes) else { continue };
let rec = Recording::new(resolver);
let _ = verify(&case.bytes, &keys, Some(&rec), None);
for call in rec.take() {
if let Call::Evaluate { cell_id, asof } = call {
if cell_id != r.cell().id() || asof != r.asof() {
f.push(case.name, format!("evaluate got ({cell_id}, {asof}), not the receipt's cell and asof"));
}
}
}
}
self.expect(&mut f, resolver, &keys, "stored-number", Want::Recomputed);
if !self.time_aware {
return f.finish();
}
for name in ["timeline-1", "timeline-1-later", "timeline-2"] {
self.expect(&mut f, resolver, &keys, name, Want::Recomputed);
}
self.expect(&mut f, resolver, &keys, "timeline-0", Want::EvalUnavailable);
let (Some(v1), Some(v2)) = (self.receipt_of("timeline-1"), self.receipt_of("timeline-2")) else {
f.push("timeline", "fixtures missing".to_string());
return f.finish();
};
let cell = v1.cell();
let points: Vec<(&str, Option<&Scalar>)> = match self.answer_rule {
AnswerRule::TxOnly => vec![
("0001-01-01T00:00:00.000000Z", None),
("2024-03-15T15:59:59.999999Z", None),
(v1.tx(), Some(v1.value())),
("2024-03-17T00:00:00.000000Z", Some(v1.value())),
("2024-03-18T09:59:59.999999Z", Some(v1.value())),
(v2.tx(), Some(v2.value())),
("9999-12-31T23:59:59.999999Z", Some(v2.value())),
],
AnswerRule::TxAndAsof => vec![
("0001-01-01T00:00:00.000000Z", None),
("2024-03-15T15:59:59.999999Z", None),
(v1.tx(), None),
("2024-03-16T08:59:59.999999Z", None),
(v1.asof(), Some(v1.value())),
("2024-03-17T00:00:00.000000Z", Some(v1.value())),
("2024-03-18T09:59:59.999999Z", Some(v1.value())),
(v2.tx(), Some(v2.value())),
("9999-12-31T23:59:59.999999Z", Some(v2.value())),
],
};
for (asof, want) in points {
let got = resolver.evaluate(cell, asof);
if got.as_ref() != want {
f.push("timeline", format!("evaluate as of {asof} gave {got:?}, want {want:?}"));
}
}
f.finish()
}
fn setup_ok(&self) -> Result<(), Report> {
if self.setup.is_empty() {
Ok(())
} else {
Err(Report { failures: self.setup.clone() })
}
}
fn case(&self, name: &str) -> Option<&Case> {
self.cases.iter().find(|c| c.name == name)
}
fn receipt_of(&self, name: &str) -> Option<Receipt> {
self.case(name).and_then(|c| Receipt::from_json(&c.bytes).ok())
}
fn has_receipt(&self, hash: &str) -> bool {
self.fixtures.receipts.iter().any(|(h, _)| h == hash)
}
fn expect(&self, f: &mut Failures, resolver: &dyn Resolver, keys: &Keys, name: &str, want: Want) {
let Some(case) = self.case(name) else {
f.push(name, "no such fixture".to_string());
return;
};
let out = verify(&case.bytes, keys, Some(resolver), None);
let good = match want {
Want::Recomputed => out.ok && out.level == Some(Level::Recomputed) && out.code.is_none(),
Want::EvalUnavailable => {
out.ok && out.level == Some(Level::Signed) && out.code == Some(ErrorCode::EvalUnavailable)
}
Want::Fails(code) => !out.ok && out.code == Some(code),
};
if !good {
f.push(
name,
format!(
"want {want:?}, got ok={} level={:?} code={:?} {} {}",
out.ok, out.level, out.code, out.path, out.detail
),
);
}
}
}
fn load_slices(sf: &mut SliceFixtures, setup: &mut Vec<String>) -> Vec<Case> {
let mut cases = Vec::new();
for (host, text) in data::SLICE_KEY_DOCUMENTS {
sf.key_documents.push((host.to_string(), text.as_bytes().to_vec()));
}
for (source, text) in data::SLICE_DESCRIPTORS {
match crate::descriptor::FunctionDescriptor::from_json(text.as_bytes()) {
Ok(d) => sf.descriptors.push((d.hash(), text.as_bytes().to_vec())),
Err(e) => setup.push(format!("setup: {source}: {e}")),
}
}
for (name, source, text) in data::SLICE_RECEIPTS {
match Receipt::from_json(text.as_bytes()) {
Ok(r) => sf.receipts.push((r.hash().to_string(), text.as_bytes().to_vec())),
Err(e) => setup.push(format!("setup: {source}: {e}")),
}
if *name != "member" {
cases.push(Case { name, bytes: text.as_bytes().to_vec() });
}
}
for (source, text) in data::MANIFESTS {
match Manifest::from_json(text.as_bytes()) {
Ok(m) => sf.manifests.push((m.entry(), text.as_bytes().to_vec())),
Err(e) => setup.push(format!("setup: {source}: {e}")),
}
}
for (name, _, text) in data::SLICE_PROBES {
cases.push(Case { name, bytes: text.as_bytes().to_vec() });
}
cases
}
type Check = fn(&Kit, &dyn Resolver) -> Result<(), Report>;
#[derive(Clone, Copy, Debug)]
enum Want {
Recomputed,
EvalUnavailable,
Fails(ErrorCode),
}
fn expected_calls(r: &Receipt, out: &VerifyResult) -> Vec<Call> {
expected_calls_with(r, out, &[])
}
fn entry_calls(e: &str, members: &[(String, Vec<String>)], take: Option<usize>) -> Vec<Call> {
if !e.starts_with("slice:") {
return vec![Call::LookupReceipt(e.to_string())];
}
let mut calls = vec![Call::LookupManifest(e.to_string())];
if let Some((_, ms)) = members.iter().find(|(x, _)| x == e) {
calls.extend(ms.iter().take(take.unwrap_or(ms.len())).map(|h| Call::LookupReceipt(h.clone())));
}
calls
}
fn expected_calls_with(r: &Receipt, out: &VerifyResult, members: &[(String, Vec<String>)]) -> Vec<Call> {
let mut calls = vec![Call::LookupDescriptor(r.cell().fn_hash.clone())];
let all_inputs = || r.inputs().iter().flat_map(|e| entry_calls(e, members, None));
let evaluate = Call::Evaluate { cell_id: r.cell().id(), asof: r.asof().to_string() };
match out.code {
None | Some(ErrorCode::ValueMismatch | ErrorCode::EvalUnavailable | ErrorCode::InputSuperseded) => {
calls.extend(all_inputs());
calls.push(evaluate);
}
Some(_) if out.path.starts_with("$.inputs.") => {
let rest = &out.path["$.inputs.".len()..];
let at = r.inputs().iter().position(|e| {
rest == e.as_str() || rest.starts_with(&format!("{e}.")) || rest.starts_with(&format!("{e}["))
});
let Some(i) = at else {
calls.extend(all_inputs());
return calls;
};
calls.extend(r.inputs()[..i].iter().flat_map(|e| entry_calls(e, members, None)));
let e = &r.inputs()[i];
let member = rest[e.len()..]
.strip_prefix(".members.")
.and_then(|h| members.iter().find(|(x, _)| x == e).and_then(|(_, ms)| ms.iter().position(|m| m == h)));
calls.extend(entry_calls(e, members, Some(member.map_or(0, |k| k + 1))));
}
Some(ErrorCode::TimeOrder) if out.path == "$.tx" => calls.extend(all_inputs()),
Some(_) => {}
}
calls
}
fn shown(s: &str) -> String {
if s.chars().count() > 80 {
format!("{:?}… ({} bytes)", s.chars().take(16).collect::<String>(), s.len())
} else {
format!("{s:?}")
}
}
struct Failures {
check: &'static str,
lines: Vec<String>,
}
impl Failures {
fn new(check: &'static str) -> Failures {
Failures { check, lines: Vec::new() }
}
fn push(&mut self, case: &str, what: String) {
self.lines.push(format!("{}: {case}: {what}", self.check));
}
fn finish(self) -> Result<(), Report> {
report(self.lines)
}
}
fn report(failures: Vec<String>) -> Result<(), Report> {
if failures.is_empty() {
Ok(())
} else {
Err(Report { failures })
}
}