pub mod client;
pub mod decode;
pub mod encode;
pub mod schema;
pub mod server;
pub mod types;
pub mod wire;
use vox_types::{MethodDescriptor, ServiceDescriptor};
pub use client::generate_client;
pub use encode::generate_named_type_encode_fns;
pub use schema::generate_schemas;
pub use server::generate_server;
pub use types::{collect_named_types, generate_named_types};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SwiftBindings {
Client,
Server,
ClientAndServer,
}
pub fn generate_method_ids(methods: &[&MethodDescriptor]) -> String {
use crate::render::{fq_name, hex_u64};
let mut items = methods
.iter()
.map(|m| (fq_name(m), m.id.0))
.collect::<Vec<_>>();
items.sort_by(|a, b| a.0.cmp(&b.0));
let mut out = String::new();
out.push_str("// @generated by vox-codegen\n");
out.push_str("// This file defines canonical vox method IDs.\n\n");
out.push_str("public enum VoxMethodId {\n");
out.push_str(" public static let byName: [String: UInt64] = [\n");
for (name, id) in items {
out.push_str(&format!(" \"{name}\": {hex},\n", hex = hex_u64(id)));
}
out.push_str(" ]\n");
out.push_str("}\n");
out
}
pub fn generate_service(service: &ServiceDescriptor) -> String {
generate_service_with_bindings(service, SwiftBindings::ClientAndServer)
}
pub fn generate_service_with_bindings(
service: &ServiceDescriptor,
bindings: SwiftBindings,
) -> String {
use crate::render::hex_u64;
use heck::{ToLowerCamelCase, ToUpperCamelCase};
let mut out = String::new();
out.push_str("// @generated by vox-codegen\n");
out.push_str("// DO NOT EDIT - regenerate with `cargo xtask codegen --swift`\n\n");
out.push_str("import Foundation\n");
out.push_str("@preconcurrency import NIOCore\n");
out.push_str("import VoxRuntime\n\n");
let service_name = service.service_name.to_upper_camel_case();
out.push_str(&format!("// MARK: - {service_name} Method IDs\n\n"));
out.push_str(&format!("public enum {service_name}MethodId {{\n"));
for method in service.methods {
let method_name = method.method_name.to_lower_camel_case();
let id = crate::method_id(method);
out.push_str(&format!(
" public static let {method_name}: UInt64 = {hex}\n",
hex = hex_u64(id)
));
}
out.push_str("}\n\n");
out.push_str(&format!("// MARK: - {service_name} Types\n\n"));
let named_types = collect_named_types(service);
out.push_str(&generate_named_types(&named_types));
out.push_str(&format!("// MARK: - {service_name} Encoders\n\n"));
out.push_str(&generate_named_type_encode_fns(&named_types));
match bindings {
SwiftBindings::Client => {
out.push_str(&format!("// MARK: - {service_name} Client\n\n"));
out.push_str(&generate_client(service));
}
SwiftBindings::Server => {
out.push_str(&format!("// MARK: - {service_name} Server\n\n"));
out.push_str(&generate_server(service));
}
SwiftBindings::ClientAndServer => {
out.push_str(&format!("// MARK: - {service_name} Client\n\n"));
out.push_str(&generate_client(service));
out.push_str(&format!("// MARK: - {service_name} Server\n\n"));
out.push_str(&generate_server(service));
}
}
out.push_str(&format!("// MARK: - {service_name} Schemas\n\n"));
out.push_str(&generate_schemas(service));
out
}
#[cfg(test)]
mod tests {
use super::generate_service;
use vox::{Rx, Tx};
use vox_types::{MethodDescriptor, RetryPolicy, ServiceDescriptor, method_descriptor};
#[test]
fn generated_swift_emits_channel_schemas() {
let subscribe = method_descriptor::<(Tx<u32>, Rx<u32>), ()>(
"StreamSvc",
"subscribe",
&["output", "input"],
None,
);
let methods = Box::leak(vec![subscribe].into_boxed_slice());
let service = ServiceDescriptor {
service_name: "StreamSvc",
methods,
doc: None,
};
let generated = generate_service(&service);
assert!(
generated.contains(".tx(element: .u32)"),
"generated Swift should emit Tx channel schema:\n{generated}"
);
assert!(
generated.contains(".rx(element: .u32)"),
"generated Swift should emit Rx channel schema:\n{generated}"
);
}
#[test]
fn generated_swift_emits_retry_policy_for_client_and_dispatcher() {
let base = method_descriptor::<(u32,), ()>("RetrySvc", "rerun", &["value"], None);
let method = Box::leak(Box::new(MethodDescriptor {
id: base.id,
service_name: base.service_name,
method_name: base.method_name,
args_shape: base.args_shape,
args: base.args,
return_shape: base.return_shape,
retry: RetryPolicy::PERSIST_IDEM,
doc: None,
}));
let methods: &'static [&'static MethodDescriptor] =
Box::leak(vec![method as &'static MethodDescriptor].into_boxed_slice());
let service = ServiceDescriptor {
service_name: "RetrySvc",
methods,
doc: None,
};
let generated = generate_service(&service);
assert!(
generated.contains("retry: .persistIdem"),
"generated Swift client should pass retry policy:\n{generated}"
);
assert!(
generated.contains("public func retryPolicy(methodId: UInt64) -> RetryPolicy"),
"generated Swift dispatcher should expose retry policy lookup:\n{generated}"
);
assert!(
generated.contains("return .persistIdem"),
"generated Swift dispatcher should return the method retry policy:\n{generated}"
);
}
}