roam_codegen/targets/swift/
mod.rs1pub mod client;
13pub mod decode;
14pub mod encode;
15pub mod schema;
16pub mod server;
17pub mod types;
18
19use roam_schema::{MethodDetail, ServiceDetail};
20
21pub use client::generate_client;
22pub use schema::generate_schemas;
23pub use server::generate_server;
24pub use types::{collect_named_types, generate_named_types};
25
26pub fn generate_method_ids(methods: &[MethodDetail]) -> String {
28 use crate::render::{fq_name, hex_u64};
29
30 let mut items = methods
31 .iter()
32 .map(|m| (fq_name(m), crate::method_id(m)))
33 .collect::<Vec<_>>();
34 items.sort_by(|a, b| a.0.cmp(&b.0));
35
36 let mut out = String::new();
37 out.push_str("// @generated by roam-codegen\n");
38 out.push_str("// This file defines canonical roam method IDs.\n\n");
39 out.push_str("public enum RoamMethodId {\n");
40 out.push_str(" public static let byName: [String: UInt64] = [\n");
41 for (name, id) in items {
42 out.push_str(&format!(" \"{name}\": {hex},\n", hex = hex_u64(id)));
43 }
44 out.push_str(" ]\n");
45 out.push_str("}\n");
46 out
47}
48
49pub fn generate_service(service: &ServiceDetail) -> String {
53 use crate::render::hex_u64;
54 use heck::{ToLowerCamelCase, ToUpperCamelCase};
55
56 let mut out = String::new();
57 out.push_str("// @generated by roam-codegen\n");
58 out.push_str("// DO NOT EDIT - regenerate with `cargo xtask codegen --swift`\n\n");
59 out.push_str("import Foundation\n");
60 out.push_str("import RoamRuntime\n\n");
61
62 let service_name = service.name.to_upper_camel_case();
63
64 out.push_str(&format!("// MARK: - {service_name} Method IDs\n\n"));
66 out.push_str(&format!("public enum {service_name}MethodId {{\n"));
67 for method in &service.methods {
68 let method_name = method.method_name.to_lower_camel_case();
69 let id = crate::method_id(method);
70 out.push_str(&format!(
71 " public static let {method_name}: UInt64 = {hex}\n",
72 hex = hex_u64(id)
73 ));
74 }
75 out.push_str("}\n\n");
76
77 out.push_str(&format!("// MARK: - {service_name} Types\n\n"));
79 let named_types = collect_named_types(service);
80 out.push_str(&generate_named_types(&named_types));
81
82 out.push_str(&format!("// MARK: - {service_name} Client\n\n"));
84 out.push_str(&generate_client(service));
85
86 out.push_str(&format!("// MARK: - {service_name} Server\n\n"));
87 out.push_str(&generate_server(service));
88
89 let has_streaming = service.methods.iter().any(|m| {
91 use roam_schema::{is_rx, is_tx};
92 m.args.iter().any(|a| is_tx(a.ty) || is_rx(a.ty))
93 || is_tx(m.return_type)
94 || is_rx(m.return_type)
95 });
96
97 if has_streaming {
98 out.push_str(&format!("// MARK: - {service_name} Schemas\n\n"));
99 out.push_str(&generate_schemas(service));
100 }
101
102 out
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use facet::Facet;
109 use roam_schema::{ArgDetail, MethodDetail, ServiceDetail};
110 use std::borrow::Cow;
111
112 fn sample_service() -> ServiceDetail {
113 ServiceDetail {
114 name: Cow::Borrowed("Echo"),
115 doc: Some(Cow::Borrowed("Simple echo service")),
116 methods: vec![MethodDetail {
117 service_name: Cow::Borrowed("Echo"),
118 method_name: Cow::Borrowed("echo"),
119 args: vec![ArgDetail {
120 name: Cow::Borrowed("message"),
121 ty: <String as Facet>::SHAPE,
122 }],
123 return_type: <String as Facet>::SHAPE,
124 doc: Some(Cow::Borrowed("Echo back the message")),
125 }],
126 }
127 }
128
129 #[test]
130 fn test_generate_service_contains_protocols() {
131 let service = sample_service();
132 let code = generate_service(&service);
133
134 assert!(code.contains("protocol EchoCaller"));
135 assert!(code.contains("protocol EchoHandler"));
136 assert!(code.contains("EchoClient"));
137 assert!(code.contains("EchoDispatcher"));
138 assert!(code.contains("EchoMethodId"));
139 }
140}