use schemars::{JsonSchema, schema_for};
use serde_json::Value;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WireFormat {
pub root: String,
pub definitions: Vec<(String, String)>,
}
impl WireFormat {
pub fn render(&self) -> String {
let mut out = String::new();
out.push_str(&self.root);
for (name, body) in &self.definitions {
out.push_str("\n\n");
out.push_str(name);
out.push_str(":\n ");
out.push_str(&body.replace('\n', "\n "));
}
out
}
}
pub fn wire_format_for<T: JsonSchema>() -> WireFormat {
let schema = schema_for!(T);
let root_value: Value = schema.into();
project_root(&root_value)
}
pub fn project_root(root_value: &Value) -> WireFormat {
let defs: BTreeMap<String, Value> = root_value
.get("$defs")
.and_then(|v| v.as_object())
.map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
let mut projector = Projector {
defs: &defs,
emitted: BTreeMap::new(),
emission_order: Vec::new(),
visiting: BTreeSet::new(),
};
let root = projector.project(root_value);
let mut definitions = Vec::new();
for name in &projector.emission_order {
if let Some(body) = projector.emitted.remove(name) {
definitions.push((name.clone(), body));
}
}
WireFormat { root, definitions }
}
struct Projector<'a> {
defs: &'a BTreeMap<String, Value>,
emitted: BTreeMap<String, String>,
emission_order: Vec<String>,
visiting: BTreeSet<String>,
}
impl Projector<'_> {
fn project(&mut self, schema: &Value) -> String {
if let Some(refstr) = schema.get("$ref").and_then(|v| v.as_str()) {
return self.project_ref(refstr);
}
if let Some(variants) = schema.get("oneOf").and_then(|v| v.as_array()) {
return self.project_union(variants);
}
if let Some(variants) = schema.get("anyOf").and_then(|v| v.as_array()) {
return self.project_union(variants);
}
if let Some(values) = schema.get("enum").and_then(|v| v.as_array()) {
return project_enum_literals(values);
}
if let Some(c) = schema.get("const") {
return format_literal(c);
}
if let Some(t) = schema.get("type") {
return self.project_typed(schema, t);
}
"any".to_string()
}
fn project_typed(&mut self, schema: &Value, t: &Value) -> String {
match t {
Value::String(s) => self.project_typed_str(schema, s),
Value::Array(arr) => {
let non_null: Vec<&str> = arr
.iter()
.filter_map(|v| v.as_str())
.filter(|s| *s != "null")
.collect();
let has_null = arr.iter().any(|v| v.as_str() == Some("null"));
match (non_null.as_slice(), has_null) {
([single], true) => {
let mut synth = schema.clone();
if let Some(obj) = synth.as_object_mut() {
obj.insert("type".to_string(), Value::String((*single).into()));
}
let inner = self.project(&synth);
format!("{inner}?")
}
_ => {
non_null.join("|")
}
}
}
_ => "any".to_string(),
}
}
fn project_typed_str(&mut self, schema: &Value, t: &str) -> String {
match t {
"object" => self.project_object(schema),
"array" => {
if let Some(prefix) = schema.get("prefixItems").and_then(|v| v.as_array()) {
let slots: Vec<String> = prefix.iter().map(|s| self.project(s)).collect();
return format!("[{}]", slots.join(", "));
}
let item = schema
.get("items")
.map(|v| self.project(v))
.unwrap_or_else(|| "any".to_string());
format!("{item}[]")
}
"string" => "string".to_string(),
"integer" => "int".to_string(),
"number" => "float".to_string(),
"boolean" => "bool".to_string(),
"null" => "null".to_string(),
_ => "any".to_string(),
}
}
fn project_object(&mut self, schema: &Value) -> String {
let props = schema
.get("properties")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let required: BTreeSet<&str> = schema
.get("required")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
.unwrap_or_default();
if props.is_empty() {
return "{}".to_string();
}
let mut parts: Vec<String> = Vec::with_capacity(props.len());
for (name, sub) in &props {
let inner = self.project(sub);
let is_optional_field = !required.contains(name.as_str());
let (rendered_inner, inner_was_nullable) = match inner.strip_suffix('?') {
Some(stripped) => (stripped.to_string(), true),
None => (inner, false),
};
let suffix_on_value = if !is_optional_field && inner_was_nullable {
"?" } else {
""
};
let suffix_on_name = if is_optional_field { "?" } else { "" };
parts.push(format!(
"{name}{suffix_on_name}: {rendered_inner}{suffix_on_value}"
));
}
format!("{{ {} }}", parts.join(", "))
}
fn project_ref(&mut self, refstr: &str) -> String {
let name = refstr.rsplit('/').next().unwrap_or(refstr).to_string();
if self.emitted.contains_key(&name) || self.visiting.contains(&name) {
return name;
}
let target = match self.defs.get(&name) {
Some(s) => s.clone(),
None => return name, };
self.visiting.insert(name.clone());
self.emission_order.push(name.clone());
let body = self.project(&target);
self.emitted.insert(name.clone(), body);
self.visiting.remove(&name);
name
}
fn project_union(&mut self, variants: &[Value]) -> String {
let mut tag_field: Option<String> = None;
let mut tag_values: Vec<String> = Vec::new();
let mut tag_extractable = true;
for v in variants {
let Some(props) = v.get("properties").and_then(|p| p.as_object()) else {
tag_extractable = false;
break;
};
let mut found_tag = None;
for (pname, pschema) in props {
if let Some(c) = pschema.get("const").and_then(|c| c.as_str()) {
if found_tag.is_some() {
tag_extractable = false;
break;
}
found_tag = Some((pname.clone(), c.to_string()));
}
}
match found_tag {
Some((field, value)) => {
if let Some(existing) = &tag_field
&& existing != &field
{
tag_extractable = false;
break;
}
tag_field = Some(field);
tag_values.push(value);
}
None => {
tag_extractable = false;
break;
}
}
}
if tag_extractable && let Some(tag) = tag_field {
let mut mapping_lines: Vec<String> = Vec::with_capacity(variants.len());
for (i, v) in variants.iter().enumerate() {
let body = self.project(v);
let tag_value = tag_values
.get(i)
.cloned()
.unwrap_or_else(|| format!("variant{i}"));
mapping_lines.push(format!(" \"{tag_value}\" → {body}"));
}
let values_joined = tag_values
.iter()
.map(|s| format!("\"{s}\""))
.collect::<Vec<_>>()
.join("|");
return format!(
"{{ {tag}: {values_joined}, …per-variant fields:\n{}\n }}",
mapping_lines.join("\n")
);
}
let parts: Vec<String> = variants.iter().map(|v| self.project(v)).collect();
parts.join("|")
}
}
fn project_enum_literals(values: &[Value]) -> String {
let strs: Vec<String> = values
.iter()
.map(|v| match v {
Value::String(s) => format!("\"{s}\""),
other => other.to_string(),
})
.collect();
strs.join("|")
}
fn format_literal(c: &Value) -> String {
match c {
Value::String(s) => format!("\"{s}\""),
other => other.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(JsonSchema)]
#[expect(
dead_code,
reason = "JsonSchema-only fixture: fields are read by `schemars`'s derive at compile time, not by Rust code"
)]
struct Flat {
count: i64,
ratio: f64,
name: String,
ok: bool,
}
#[test]
fn flat_primitives() {
let wf = wire_format_for::<Flat>();
assert!(wf.root.contains("count: int"), "got `{}`", wf.root);
assert!(wf.root.contains("ratio: float"));
assert!(wf.root.contains("name: string"));
assert!(wf.root.contains("ok: bool"));
assert!(wf.definitions.is_empty(), "flat type needs no defs");
}
#[derive(JsonSchema)]
#[expect(
dead_code,
reason = "JsonSchema-only fixture: fields are read by `schemars`'s derive at compile time, not by Rust code"
)]
struct WithOption {
always: i64,
sometimes: Option<String>,
}
#[test]
fn option_becomes_question_mark() {
let wf = wire_format_for::<WithOption>();
assert!(wf.root.contains("always: int"));
assert!(wf.root.contains("sometimes?: string"), "got `{}`", wf.root);
}
#[derive(JsonSchema)]
#[expect(
dead_code,
reason = "JsonSchema-only fixture: fields are read by `schemars`'s derive at compile time, not by Rust code"
)]
struct WithVec {
rows: Vec<i64>,
labels: Vec<String>,
}
#[test]
fn vec_becomes_brackets() {
let wf = wire_format_for::<WithVec>();
assert!(wf.root.contains("rows: int[]"), "got `{}`", wf.root);
assert!(wf.root.contains("labels: string[]"));
}
#[derive(JsonSchema)]
#[expect(
dead_code,
reason = "JsonSchema-only fixture: fields are read by `schemars`'s derive at compile time, not by Rust code"
)]
struct Inner {
a: i64,
b: String,
}
#[derive(JsonSchema)]
#[expect(
dead_code,
reason = "JsonSchema-only fixture: fields are read by `schemars`'s derive at compile time, not by Rust code"
)]
struct Outer {
single: Inner,
many: Vec<Inner>,
}
#[test]
fn nested_struct_emitted_as_definition() -> anyhow::Result<()> {
let wf = wire_format_for::<Outer>();
assert!(wf.root.contains("single: Inner"), "got `{}`", wf.root);
assert!(wf.root.contains("many: Inner[]"));
let inner_def = wf
.definitions
.iter()
.find(|(n, _)| n == "Inner")
.map(|(_, body)| body.clone())
.ok_or_else(|| anyhow::anyhow!("expected Inner definition"))?;
assert!(inner_def.contains("a: int"), "got `{inner_def}`");
assert!(inner_def.contains("b: string"));
Ok(())
}
#[test]
fn render_assembles_root_and_definitions() {
let wf = wire_format_for::<Outer>();
let r = wf.render();
assert!(r.starts_with('{'), "root first; got:\n{r}");
assert!(r.contains("\n\nInner:\n "), "definition prefix; got:\n{r}");
}
#[derive(serde::Serialize, JsonSchema)]
#[serde(tag = "type", rename_all = "lowercase")]
#[expect(
dead_code,
reason = "JsonSchema-only fixture: fields are read by `schemars`'s derive at compile time, not by Rust code"
)]
enum Tagged {
Kernel { name: String },
Memcpy { bytes: i64 },
}
#[test]
fn tagged_union_collapses_to_type_field() -> anyhow::Result<()> {
#[derive(JsonSchema)]
#[expect(
dead_code,
reason = "JsonSchema-only fixture: fields are read by `schemars`'s derive at compile time, not by Rust code"
)]
struct Holder {
event: Tagged,
}
let wf = wire_format_for::<Holder>();
assert!(wf.root.contains("event: Tagged"), "got `{}`", wf.root);
let tagged_def = wf
.definitions
.iter()
.find(|(n, _)| n == "Tagged")
.map(|(_, b)| b.clone())
.ok_or_else(|| anyhow::anyhow!("expected Tagged definition"))?;
assert!(
tagged_def.contains("type:") && tagged_def.contains("\"kernel\""),
"got `{tagged_def}`"
);
assert!(tagged_def.contains("\"memcpy\""));
Ok(())
}
}