use std::collections::BTreeMap;
use schemars::{JsonSchema, generate::SchemaSettings};
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::event::{EventBody, EventKind, EventPayload, EventRecord, EventSource};
use crate::file_name::FileName;
use crate::ids::{EventId, ImageId, PrintId};
use crate::image::ImageRef;
use crate::raw::RawBytes;
use crate::reported::{Range, Reported};
use crate::timestamp::Timestamp;
pub trait Sample: Sized {
fn sample_full() -> Self;
#[must_use]
fn sample_minimal() -> Self {
Self::sample_full()
}
#[must_use]
fn sample_alternates() -> Vec<Self> {
Vec::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct TypeContract {
pub name: &'static str,
schema: fn() -> Value,
full: fn() -> Value,
minimal: fn() -> Value,
round_trip: fn(Value) -> Result<Value, String>,
alternates: fn() -> Vec<Value>,
}
impl TypeContract {
#[must_use]
pub fn of<T: Sample + JsonSchema + Serialize + DeserializeOwned>(name: &'static str) -> Self {
Self {
name,
schema: schema_of::<T>,
full: || canonical(&T::sample_full()),
minimal: || canonical(&T::sample_minimal()),
round_trip: round_trip_as::<T>,
alternates: || T::sample_alternates().iter().map(canonical).collect(),
}
}
#[must_use]
pub fn schema(&self) -> Value {
(self.schema)()
}
#[must_use]
pub fn full(&self) -> Value {
(self.full)()
}
#[must_use]
pub fn minimal(&self) -> Value {
(self.minimal)()
}
#[must_use]
pub fn samples(&self) -> Vec<Value> {
let mut values = vec![self.full(), self.minimal()];
values.extend((self.alternates)());
values
}
pub fn round_trip(&self, value: Value) -> Result<Value, String> {
(self.round_trip)(value)
}
}
#[must_use]
pub fn schema_of<T: JsonSchema>() -> Value {
let generator = SchemaSettings::draft2020_12().into_generator();
serde_json::to_value(generator.into_root_schema_for::<T>())
.expect("a generated schema is JSON by construction")
}
pub const EVENT_KIND_MARKER: &str = "x-event-kind";
#[must_use]
pub fn event_schema_of<P: EventPayload>() -> Value {
let mut schema = schema_of::<P>();
schema
.as_object_mut()
.expect("a payload's schema is an object")
.insert(EVENT_KIND_MARKER.to_owned(), Value::from(P::KIND));
schema
}
fn emit<T: Serialize>(value: &T) -> Result<Value, String> {
serde_json::to_value(value).map_err(|error| error.to_string())
}
fn round_trip_as<T: Serialize + DeserializeOwned>(value: Value) -> Result<Value, String> {
let parsed: T = serde_json::from_value(value).map_err(|error| error.to_string())?;
emit(&parsed)
}
fn canonical<T: Serialize>(value: &T) -> Value {
emit(value).expect("a canonical sample serializes")
}
macro_rules! contract_of {
($ty:ty) => {
contract_of!($ty, stringify!($ty))
};
($ty:ty, $name:expr) => {
TypeContract::of::<$ty>($name)
};
}
macro_rules! declare {
($($ty:ty $(=> $name:literal)?),* $(,)?) => {
vec![$( contract_of!($ty $(, $name)?) ),*]
};
}
#[must_use]
pub fn declared() -> Vec<TypeContract> {
declare![
EventId,
EventBody,
EventKind,
EventRecord,
EventSource,
FileName,
ImageId,
ImageRef,
PrintId,
Range,
RawBytes,
Reported<f64> => "Reported",
Timestamp,
]
}
fn print_id() -> PrintId {
"0191f0a0-0000-7000-8000-000000000001"
.parse()
.expect("a fixed version 7 identifier")
}
fn event_id() -> EventId {
"0191f0a0-0000-7000-8000-000000000002"
.parse()
.expect("a fixed version 7 identifier")
}
fn image_id() -> ImageId {
"0191f0a0-0000-7000-8000-000000000003"
.parse()
.expect("a fixed version 7 identifier")
}
fn instant() -> Timestamp {
"2026-03-01T12:00:00Z"
.parse()
.expect("a fixed RFC 3339 instant")
}
impl Sample for PrintId {
fn sample_full() -> Self {
print_id()
}
}
impl Sample for EventId {
fn sample_full() -> Self {
event_id()
}
}
impl Sample for ImageId {
fn sample_full() -> Self {
image_id()
}
}
impl Sample for Timestamp {
fn sample_full() -> Self {
instant()
}
}
impl Sample for FileName {
fn sample_full() -> Self {
Self::new("benchy.gcode").expect("a name carrying no forbidden property")
}
}
impl Sample for RawBytes {
fn sample_full() -> Self {
Self::new(b"{\"event\":{}}".to_vec())
}
}
impl Sample for Range {
fn sample_full() -> Self {
Self::new(0.5, 1.5)
}
}
impl Sample for Reported<f64> {
fn sample_full() -> Self {
Self::new(1.0, Range::sample_full())
}
}
impl Sample for ImageRef {
fn sample_full() -> Self {
Self {
id: image_id(),
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_owned(),
}
}
}
impl Sample for EventSource {
fn sample_full() -> Self {
Self::new("sample_source")
}
}
impl Sample for EventKind {
fn sample_full() -> Self {
Self::new("sample_event").expect("a fixed kind name")
}
}
impl Sample for EventBody {
fn sample_full() -> Self {
Self {
kind: EventKind::sample_full(),
payload: serde_json::json!({ "detail": "a payload in the form its kind declares" }),
}
}
}
impl Sample for EventRecord {
fn sample_full() -> Self {
Self {
id: event_id(),
print_id: Some(print_id()),
source: EventSource::sample_full(),
received_at: instant(),
image: Some(ImageRef::sample_full()),
body: EventBody::sample_full(),
raw: Some(RawBytes::sample_full()),
}
}
fn sample_minimal() -> Self {
Self {
print_id: None,
image: None,
raw: None,
..Self::sample_full()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct WireField {
pub name: String,
pub descriptor: String,
pub required: bool,
}
fn referenced(reference: &str) -> String {
reference.rsplit('/').next().unwrap_or(reference).to_owned()
}
fn sole_arm(arms: &[Value]) -> Option<&Value> {
let mut kept = arms
.iter()
.filter(|arm| arm.get("type") != Some(&Value::from("null")));
let first = kept.next()?;
kept.next().is_none().then_some(first)
}
fn descriptor(schema: &Value) -> String {
if schema == &Value::Bool(true) {
return "any".to_owned();
}
if let Some(object) = schema.as_object()
&& object.keys().all(|key| key == "description")
{
return "any".to_owned();
}
if let Some(Value::String(reference)) = schema.get("$ref") {
return referenced(reference);
}
for key in ["anyOf", "oneOf"] {
if let Some(Value::Array(arms)) = schema.get(key) {
return match sole_arm(arms) {
Some(arm) => descriptor(arm),
None => "enum".to_owned(),
};
}
}
let kind = match schema.get("type") {
Some(Value::String(name)) => name.clone(),
Some(Value::Array(names)) => names
.iter()
.filter_map(Value::as_str)
.find(|name| *name != "null")
.unwrap_or("unknown")
.to_owned(),
_ => "unknown".to_owned(),
};
match kind.as_str() {
"array" => format!(
"array:{}",
schema
.get("items")
.map_or_else(|| "unknown".to_owned(), descriptor)
),
"object" => match (
schema.get("additionalProperties"),
schema.get("patternProperties"),
) {
(Some(value), _) if value.is_object() => format!("map:{}", descriptor(value)),
(_, Some(Value::Object(patterns))) if patterns.len() == 1 => {
let value = patterns.values().next().expect("one pattern");
format!("map:{}", descriptor(value))
}
_ => "object".to_owned(),
},
other => other.to_owned(),
}
}
fn required_names(schema: &Value) -> Vec<&str> {
schema
.get("required")
.and_then(Value::as_array)
.map(|names| names.iter().filter_map(Value::as_str).collect())
.unwrap_or_default()
}
fn collect(schema: &Value, into: &mut BTreeMap<String, WireField>) {
let required = required_names(schema);
let Some(Value::Object(properties)) = schema.get("properties") else {
return;
};
for (name, property) in properties {
into.insert(
name.clone(),
WireField {
name: name.clone(),
descriptor: descriptor(property),
required: required.contains(&name.as_str()),
},
);
}
}
#[must_use]
pub fn wire_fields(schema: &Value) -> Vec<WireField> {
let mut fields = BTreeMap::new();
collect(schema, &mut fields);
for key in ["anyOf", "oneOf", "allOf"] {
let Some(Value::Array(arms)) = schema.get(key) else {
continue;
};
let mut from_arms: BTreeMap<String, WireField> = BTreeMap::new();
for arm in arms {
let mut one = BTreeMap::new();
collect(arm, &mut one);
for (name, field) in one {
from_arms
.entry(name)
.and_modify(|held| {
held.required &= field.required;
if held.descriptor != field.descriptor {
"union".clone_into(&mut held.descriptor);
}
})
.or_insert(field);
}
}
fields.extend(from_arms);
}
fields.into_values().collect()
}