use substrate_data::{Value, binary, text};
use crate::fault::fault_json;
pub(crate) use crate::fault::{Fault, Found};
use crate::hex::to_hex;
use crate::json::Json;
use crate::render::value_json;
use crate::schemas::shape_text;
use crate::{store, v1};
const INLINE_MAX: usize = 256;
#[derive(Debug, Clone)]
pub(crate) struct Frame {
pub(crate) writer: Reading,
pub(crate) bytes: Vec<u8>,
pub(crate) v2: Option<Vec<u8>>,
pub(crate) also: Vec<Reading>,
}
impl Frame {
pub(crate) fn new(name: &str, writer: Reading, now: Vec<u8>, also: Vec<Reading>) -> Frame {
let legacy = store::legacy(&format!("frames/{name}.frame")).filter(|old| *old != now);
Frame {
writer,
bytes: legacy.unwrap_or_else(|| now.clone()),
v2: None,
also,
}
.with_now(now)
}
fn with_now(mut self, now: Vec<u8>) -> Frame {
if self.bytes != now {
self.v2 = Some(now);
}
self
}
}
#[derive(Debug, Clone)]
pub(crate) struct Reading {
pub(crate) schema: &'static str,
pub(crate) value: Value,
}
#[derive(Debug, Clone)]
pub(crate) struct Io {
pub(crate) bytes: Vec<u8>,
pub(crate) messages: Vec<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub(crate) struct Case {
pub(crate) name: String,
pub(crate) description: String,
pub(crate) text: Option<String>,
pub(crate) value: Option<Value>,
pub(crate) binary: Option<Vec<u8>>,
pub(crate) binary_v2: Option<Vec<u8>>,
pub(crate) frame: Option<Frame>,
pub(crate) io: Option<Io>,
pub(crate) fault: Option<Fault>,
}
impl Case {
pub(crate) fn new(name: &str, description: &str) -> Case {
Case {
name: name.to_owned(),
description: description.to_owned(),
text: None,
value: None,
binary: None,
binary_v2: None,
frame: None,
io: None,
fault: None,
}
}
#[must_use]
pub(crate) fn text(mut self, t: &str) -> Case {
self.text = Some(t.to_owned());
self
}
#[must_use]
pub(crate) fn value(mut self, v: Value) -> Case {
self.value = Some(v);
self
}
#[must_use]
pub(crate) fn binary(mut self, b: Vec<u8>) -> Case {
self.binary = Some(b);
self
}
#[must_use]
pub(crate) fn legacy_binary(mut self) -> Case {
self.binary = store::legacy(&format!("binary/{}.sdat", self.name));
self
}
#[must_use]
pub(crate) fn binary_v2(mut self, b: Vec<u8>) -> Case {
self.binary_v2 = Some(b);
self
}
#[must_use]
pub(crate) fn all_forms(self, v: Value) -> Case {
let t = text::to_string(&v);
let old = v1::encode(&v);
let new = binary::encode(&v);
self.text(&t).binary(old).binary_v2(new).value(v)
}
#[must_use]
pub(crate) fn frame(mut self, f: Frame) -> Case {
self.frame = Some(f);
self
}
#[must_use]
pub(crate) fn io(mut self, i: Io) -> Case {
self.io = Some(i);
self
}
#[must_use]
pub(crate) fn fault(mut self, f: Fault) -> Case {
self.fault = Some(f);
self
}
pub(crate) fn to_json(&self) -> Json {
let mut out = vec![
("name".to_owned(), Json::text(&self.name)),
("description".to_owned(), Json::text(&self.description)),
];
self.put_value_forms(&mut out);
if let Some(b) = &self.binary {
put_bytes("binary", b, &format!("binary/{}.sdat", self.name), &mut out);
}
if let Some(b) = &self.binary_v2 {
let file = format!("binary_v2/{}.sdat", self.name);
put_bytes("binary_v2", b, &file, &mut out);
}
if let Some(f) = &self.frame {
out.push(("frame".to_owned(), frame_json(f, &self.name)));
}
if let Some(i) = &self.io {
out.push(("io".to_owned(), io_json(i, &self.name)));
}
if let Some(f) = &self.fault {
out.push(("error".to_owned(), fault_json(f)));
}
Json::Block(out)
}
fn put_value_forms(&self, out: &mut Vec<(String, Json)>) {
if let Some(t) = &self.text {
out.push(("text".to_owned(), Json::text(t)));
let writes = self
.value
.as_ref()
.is_some_and(|v| text::to_string(v) == *t);
out.push(("text_is_written".to_owned(), Json::Bool(writes)));
}
if let Some(v) = &self.value {
out.push(("value".to_owned(), value_json(v)));
}
if let (Some(v), Some(b)) = (&self.value, &self.binary) {
let encodes = v1::encode(v) == *b;
out.push(("binary_is_encoded".to_owned(), Json::Bool(encodes)));
}
if let (Some(v), Some(b)) = (&self.value, &self.binary_v2) {
let encodes = binary::encode(v) == *b;
out.push(("binary_v2_is_encoded".to_owned(), Json::Bool(encodes)));
}
if let (Some(old), Some(new)) = (&self.binary, &self.binary_v2) {
let exact = binary::upgrade(old).is_ok_and(|upgraded| upgraded == *new);
out.push(("binary_upgrades".to_owned(), Json::Bool(exact)));
}
}
pub(crate) fn files(&self) -> Vec<(String, Vec<u8>)> {
let mut files = Vec::new();
if let Some(b) = &self.binary {
files.push((format!("binary/{}.sdat", self.name), b.clone()));
}
if let Some(b) = &self.binary_v2 {
files.push((format!("binary_v2/{}.sdat", self.name), b.clone()));
}
if let Some(f) = &self.frame {
files.push((format!("frames/{}.frame", self.name), f.bytes.clone()));
if let Some(v2) = &f.v2 {
files.push((format!("frames_v2/{}.frame", self.name), v2.clone()));
}
}
if let Some(i) = &self.io {
files.push((format!("io/{}.stream", self.name), i.bytes.clone()));
}
files
}
}
fn put_bytes(key: &str, bytes: &[u8], file: &str, out: &mut Vec<(String, Json)>) {
if bytes.len() <= INLINE_MAX {
out.push((key.to_owned(), Json::text(to_hex(bytes))));
}
out.push((format!("{key}_len"), Json::num(bytes.len())));
out.push((format!("{key}_file"), Json::text(file)));
}
fn reading_json(r: &Reading) -> Vec<(String, Json)> {
vec![
("schema".to_owned(), Json::text(r.schema)),
("shape".to_owned(), Json::text(shape_text(r.schema))),
("value".to_owned(), value_json(&r.value)),
]
}
fn frame_json(f: &Frame, name: &str) -> Json {
let mut out = reading_json(&f.writer);
put_bytes("bytes", &f.bytes, &format!("frames/{name}.frame"), &mut out);
if let Some(v2) = &f.v2 {
put_bytes("bytes_v2", v2, &format!("frames_v2/{name}.frame"), &mut out);
}
if !f.also.is_empty() {
let also = f.also.iter().map(|r| Json::Obj(reading_json(r))).collect();
out.push(("also_read_by".to_owned(), Json::Arr(also)));
}
Json::Block(out)
}
fn io_json(i: &Io, name: &str) -> Json {
let mut out = Vec::new();
put_bytes("bytes", &i.bytes, &format!("io/{name}.stream"), &mut out);
let messages = i.messages.iter().map(|m| Json::text(to_hex(m))).collect();
out.push(("messages".to_owned(), Json::Arr(messages)));
out.push(("end".to_owned(), Json::text("clean")));
Json::Block(out)
}