use substrate_data::{Value, binary, text};
use crate::hex::to_hex;
use crate::json::Json;
use crate::render::value_json;
use crate::schemas::shape_text;
const INLINE_MAX: usize = 256;
#[derive(Debug, Clone)]
pub(crate) struct Frame {
pub(crate) writer: Reading,
pub(crate) bytes: Vec<u8>,
pub(crate) also: Vec<Reading>,
}
#[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) enum Fault {
Text {
input: String,
line: u32,
col: u32,
message: String,
},
Schema {
schema: &'static str,
input: String,
found: Vec<Found>,
},
Binary {
input: Vec<u8>,
kind: &'static str,
offset: usize,
message: String,
code: String,
},
Frame {
schema: &'static str,
input: Vec<u8>,
},
Io {
input: Vec<u8>,
kind: &'static str,
frames_before: usize,
},
}
#[derive(Debug, Clone)]
pub(crate) struct Found {
pub(crate) code: String,
pub(crate) line: u32,
pub(crate) col: u32,
pub(crate) path: String,
pub(crate) message: String,
}
#[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) 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,
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 all_forms(self, v: Value) -> Case {
let t = text::to_string(&v);
let b = binary::encode(&v);
self.text(&t).binary(b).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(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 = binary::encode(v) == *b;
out.push(("binary_is_encoded".to_owned(), Json::Bool(encodes)));
}
}
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(f) = &self.frame {
files.push((format!("frames/{}.frame", self.name), f.bytes.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 !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)
}
fn fault_json(f: &Fault) -> Json {
let entries = match f {
Fault::Text {
input,
line,
col,
message,
} => text_fault(input, *line, *col, message),
Fault::Schema {
schema,
input,
found,
} => schema_fault(schema, input, found),
Fault::Binary {
input,
kind,
offset,
message,
code,
} => binary_fault(input, kind, *offset, message, code),
Fault::Frame { schema, input } => vec![
("form", Json::text("frame")),
("schema", Json::text(*schema)),
("shape", Json::text(shape_text(schema))),
("input", Json::text(to_hex(input))),
],
Fault::Io {
input,
kind,
frames_before,
} => vec![
("form", Json::text("io")),
("input", Json::text(to_hex(input))),
("frames_before", Json::num(*frames_before)),
("kind", Json::text(*kind)),
],
};
Json::Block(
entries
.into_iter()
.map(|(k, v)| (k.to_owned(), v))
.collect(),
)
}
type Entries = Vec<(&'static str, Json)>;
fn text_fault(input: &str, line: u32, col: u32, message: &str) -> Entries {
vec![
("form", Json::text("text")),
("input", Json::text(input)),
("code", Json::text("SD0001")),
("line", Json::Num(u64::from(line))),
("col", Json::Num(u64::from(col))),
("message", Json::text(message)),
]
}
fn schema_fault(schema: &str, input: &str, found: &[Found]) -> Entries {
vec![
("form", Json::text("text_schema")),
("schema", Json::text(schema)),
("input", Json::text(input)),
(
"diagnostics",
Json::Arr(found.iter().map(found_json).collect()),
),
]
}
fn binary_fault(input: &[u8], kind: &str, offset: usize, message: &str, code: &str) -> Entries {
vec![
("form", Json::text("binary")),
("input", Json::text(to_hex(input))),
("kind", Json::text(kind)),
("offset", Json::num(offset)),
("message", Json::text(message)),
("schema_code", Json::text(code)),
]
}
fn found_json(d: &Found) -> Json {
Json::obj([
("code", Json::text(&d.code)),
("line", Json::Num(u64::from(d.line))),
("col", Json::Num(u64::from(d.col))),
("path", Json::text(&d.path)),
("message", Json::text(&d.message)),
])
}