roam_codegen/targets/swift/
mod.rs

1//! Swift code generation for roam services.
2//!
3//! This module generates Swift client and server code from service definitions.
4//! The generated code includes:
5//! - Type definitions for all named types (structs, enums)
6//! - Caller protocol and client implementation for making RPC calls
7//! - Handler protocol for implementing services
8//! - Dispatcher for routing incoming calls
9//! - Encoding/decoding logic for all types
10//! - Runtime schema information for streaming channel binding
11
12pub 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
26/// Generate method IDs as a Swift enum.
27pub 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
49/// Generate a complete Swift module for a service.
50///
51/// This is the main entry point for Swift code generation.
52pub 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    // Generate method IDs enum
65    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    // Generate named types
78    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    // Generate protocols and implementations
83    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    // Generate schemas if streaming is used
90    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}