use facet_core::Shape;
use vox_types::{ServiceDescriptor, ShapeKind, classify_shape, is_rx, is_tx};
use crate::render::hex_u64;
fn ok_shape(return_shape: &'static Shape) -> &'static Shape {
match classify_shape(return_shape) {
ShapeKind::Result { ok, .. } => ok,
_ => return_shape,
}
}
fn root_id(shape: &'static Shape) -> u64 {
phon_codegen::Module::from_shapes(&[shape])
.expect("derive phon schema")
.roots[0]
.id
.as_u64()
}
fn schema_closure_hex(
shape: &'static Shape,
auxiliary_roots: &[(String, &'static Shape)],
) -> String {
let auxiliary_roots: Vec<(&str, &'static Shape)> = auxiliary_roots
.iter()
.map(|(role, shape)| (role.as_str(), *shape))
.collect();
let bytes = if auxiliary_roots.is_empty() {
vox_phon::schema_bytes_for_shape(shape).expect("phon schema bytes")
} else {
vox_phon::schema_bytes_for_shape_with_auxiliary_roots(shape, &auxiliary_roots)
.expect("phon schema bytes")
};
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{b:02x}"));
}
s
}
fn channel_auxiliary_roots(method: &vox_types::MethodDescriptor) -> Vec<(String, &'static Shape)> {
method
.args
.iter()
.enumerate()
.filter_map(|(index, arg)| {
let direction = if is_tx(arg.shape) {
"tx"
} else if is_rx(arg.shape) {
"rx"
} else {
return None;
};
let element = arg
.channel_element
.expect("Tx/Rx arg must carry its channel element shape");
Some((format!("channel.arg.{index}.{direction}.element"), element))
})
.collect()
}
pub fn generate_phon_service(service: &ServiceDescriptor) -> String {
let name = lower_camel(service.service_name);
let mut roots: Vec<&'static Shape> = Vec::new();
for m in service.methods {
roots.push(m.args_shape);
roots.push(m.response_wire_shape);
for arg in m.args {
if let Some(element) = arg.channel_element {
roots.push(element);
}
}
}
let module = phon_codegen::Module::from_shapes(&roots).expect("derive service phon module");
let mut out = String::new();
out.push_str(
"// phon schemas for this service (registry + per-method roots + channel metadata).\n",
);
out.push_str(
"import { Registry, schemaFromBytes, hexToBytes } from \"@bearcove/phon-schema\";\n",
);
out.push_str("import type { Primitive } from \"@bearcove/phon-schema\";\n\n");
out.push_str(&phon_codegen::typescript::render_registry(
&module,
&format!("{name}Registry"),
));
out.push('\n');
out.push_str(&format!(
"export const {name}Methods: {{ [methodId: string]: import(\"@bearcove/vox-core\").PhonMethodSchemas }} = {{\n"
));
for m in service.methods {
let method_id = crate::method_id(m);
let args_root = root_id(m.args_shape);
let ok_root = root_id(ok_shape(m.return_shape));
let response_root = root_id(m.response_wire_shape);
let channel_auxiliary_roots = channel_auxiliary_roots(m);
let closure = schema_closure_hex(m.args_shape, &channel_auxiliary_roots);
let response_closure = schema_closure_hex(m.response_wire_shape, &[]);
out.push_str(&format!(" \"{}\": {{\n", hex_u64(method_id)));
out.push_str(&format!(" argsRoot: {}n,\n", hex_u64(args_root)));
out.push_str(&format!(" argsSchemaClosure: \"{closure}\",\n"));
out.push_str(&format!(" okRoot: {}n,\n", hex_u64(ok_root)));
out.push_str(&format!(" responseRoot: {}n,\n", hex_u64(response_root)));
out.push_str(&format!(
" responseSchemaClosure: \"{response_closure}\",\n"
));
out.push_str(" channels: [");
let mut first = true;
for (i, arg) in m.args.iter().enumerate() {
let dir = if is_tx(arg.shape) {
Some("tx")
} else if is_rx(arg.shape) {
Some("rx")
} else {
None
};
if let Some(dir) = dir {
let element = arg
.channel_element
.expect("Tx/Rx arg must carry its channel element shape");
if !first {
out.push_str(", ");
}
first = false;
out.push_str(&format!(
"{{ index: {i}, direction: \"{dir}\", elementRoot: {}n }}",
hex_u64(root_id(element))
));
}
}
out.push_str("],\n");
out.push_str(" },\n");
}
out.push_str("};\n");
out
}
fn lower_camel(s: &str) -> String {
use heck::ToLowerCamelCase;
s.to_lower_camel_case()
}