vox_codegen/targets/swift/
mod.rs1pub mod client;
13pub mod decode;
14pub mod encode;
15pub mod schema;
16pub mod server;
17pub mod types;
18pub mod wire;
19
20use vox_types::{MethodDescriptor, ServiceDescriptor};
21
22pub use client::generate_client;
23pub use schema::generate_schemas;
24pub use server::generate_server;
25pub use types::{collect_named_types, generate_named_types};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum SwiftBindings {
30 Client,
32 Server,
34 ClientAndServer,
36}
37
38pub fn generate_method_ids(methods: &[&MethodDescriptor]) -> String {
40 use crate::render::{fq_name, hex_u64};
41
42 let mut items = methods
43 .iter()
44 .map(|m| (fq_name(m), m.id.0))
45 .collect::<Vec<_>>();
46 items.sort_by(|a, b| a.0.cmp(&b.0));
47
48 let mut out = String::new();
49 out.push_str("// @generated by vox-codegen\n");
50 out.push_str("// This file defines canonical vox method IDs.\n\n");
51 out.push_str("public enum VoxMethodId {\n");
52 out.push_str(" public static let byName: [String: UInt64] = [\n");
53 for (name, id) in items {
54 out.push_str(&format!(" \"{name}\": {hex},\n", hex = hex_u64(id)));
55 }
56 out.push_str(" ]\n");
57 out.push_str("}\n");
58 out
59}
60
61pub fn generate_service(service: &ServiceDescriptor) -> String {
65 generate_service_with_bindings(service, SwiftBindings::ClientAndServer)
66}
67
68pub fn generate_service_with_bindings(
72 service: &ServiceDescriptor,
73 bindings: SwiftBindings,
74) -> String {
75 use crate::render::hex_u64;
76 use heck::{ToLowerCamelCase, ToUpperCamelCase};
77
78 let mut out = String::new();
79 out.push_str("// @generated by vox-codegen\n");
80 out.push_str("// DO NOT EDIT - regenerate with `cargo xtask codegen --swift`\n\n");
81 out.push_str("import Foundation\n");
82 out.push_str("import VoxRuntime\n\n");
83
84 let service_name = service.service_name.to_upper_camel_case();
85
86 out.push_str(&format!("// MARK: - {service_name} Method IDs\n\n"));
88 out.push_str(&format!("public enum {service_name}MethodId {{\n"));
89 for method in service.methods {
90 let method_name = method.method_name.to_lower_camel_case();
91 let id = crate::method_id(method);
92 out.push_str(&format!(
93 " public static let {method_name}: UInt64 = {hex}\n",
94 hex = hex_u64(id)
95 ));
96 }
97 out.push_str("}\n\n");
98
99 out.push_str(&format!("// MARK: - {service_name} Types\n\n"));
101 let named_types = collect_named_types(service);
102 out.push_str(&generate_named_types(&named_types));
103
104 match bindings {
105 SwiftBindings::Client => {
106 out.push_str(&format!("// MARK: - {service_name} Client\n\n"));
107 out.push_str(&generate_client(service));
108 }
109 SwiftBindings::Server => {
110 out.push_str(&format!("// MARK: - {service_name} Server\n\n"));
111 out.push_str(&generate_server(service));
112 }
113 SwiftBindings::ClientAndServer => {
114 out.push_str(&format!("// MARK: - {service_name} Client\n\n"));
115 out.push_str(&generate_client(service));
116
117 out.push_str(&format!("// MARK: - {service_name} Server\n\n"));
118 out.push_str(&generate_server(service));
119 }
120 }
121
122 out.push_str(&format!("// MARK: - {service_name} Schemas\n\n"));
124 out.push_str(&generate_schemas(service));
125
126 out
127}
128
129#[cfg(test)]
130mod tests {
131 use super::generate_service;
132 use vox::{Rx, Tx};
133 use vox_types::{MethodDescriptor, RetryPolicy, ServiceDescriptor, method_descriptor};
134
135 #[test]
136 fn generated_swift_emits_channel_schemas() {
137 let subscribe = method_descriptor::<(Tx<u32>, Rx<u32>), ()>(
138 "StreamSvc",
139 "subscribe",
140 &["output", "input"],
141 None,
142 );
143 let methods = Box::leak(vec![subscribe].into_boxed_slice());
144 let service = ServiceDescriptor {
145 service_name: "StreamSvc",
146 methods,
147 doc: None,
148 };
149
150 let generated = generate_service(&service);
151
152 assert!(
153 generated.contains(".tx(element: .u32)"),
154 "generated Swift should emit Tx channel schema:\n{generated}"
155 );
156 assert!(
157 generated.contains(".rx(element: .u32)"),
158 "generated Swift should emit Rx channel schema:\n{generated}"
159 );
160 }
161
162 #[test]
163 fn generated_swift_emits_retry_policy_for_client_and_dispatcher() {
164 let base = method_descriptor::<(u32,), ()>("RetrySvc", "rerun", &["value"], None);
165 let method = Box::leak(Box::new(MethodDescriptor {
166 id: base.id,
167 service_name: base.service_name,
168 method_name: base.method_name,
169 args_shape: base.args_shape,
170 args: base.args,
171 return_shape: base.return_shape,
172 retry: RetryPolicy::PERSIST_IDEM,
173 doc: None,
174 }));
175 let methods: &'static [&'static MethodDescriptor] =
176 Box::leak(vec![method as &'static MethodDescriptor].into_boxed_slice());
177 let service = ServiceDescriptor {
178 service_name: "RetrySvc",
179 methods,
180 doc: None,
181 };
182
183 let generated = generate_service(&service);
184
185 assert!(
186 generated.contains("retry: .persistIdem"),
187 "generated Swift client should pass retry policy:\n{generated}"
188 );
189 assert!(
190 generated.contains("public static func retryPolicy(methodId: UInt64) -> RetryPolicy"),
191 "generated Swift dispatcher should expose retry policy lookup:\n{generated}"
192 );
193 assert!(
194 generated.contains("return .persistIdem"),
195 "generated Swift dispatcher should return the method retry policy:\n{generated}"
196 );
197 }
198}