Skip to main content

zerodds_corba_rust/
interface_emit.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! IDL `interface` → Rust trait + Stub + Skeleton.
4//!
5//! Mapping:
6//! - `interface I { ... };` → `pub trait I { fn op(...) -> Result<...>; }`
7//!   plus `pub struct IStub { ... }` (Client) und `pub fn dispatch_<i>(...)`
8//!   (Server-Skeleton).
9//! - `attribute T x` → trait-method `fn x(&self) -> ...` und (wenn nicht
10//!   readonly) `fn set_x(&mut self, value: T) -> ...`.
11//! - `op(in T x, out T y, inout T z)` → trait-method mit
12//!   `&self`/`&mut self`-Receiver, `in`-Params als value, `out`/`inout`
13//!   als `&mut`.
14//! - `oneway op(...)` → trait-method ohne Reply-Body.
15//! - `raises (E1, E2)` → method-Return-Type ist Result<T, CorbaException>.
16
17extern crate alloc;
18use zerodds_idl::ast::types::{Export, InterfaceDef, OpDecl, ParamAttribute, ParamDecl, TypeSpec};
19
20use crate::error::Result;
21
22/// Emittiert ein vollstaendiges Rust-Trait + Stub + Skeleton-Dispatch
23/// fuer eine IDL-Interface-Definition.
24pub fn emit_interface(
25    out: &mut String,
26    i: &InterfaceDef,
27    registry: &crate::emitter::InterfaceRegistry<'_>,
28) -> Result<()> {
29    emit_interface_trait(out, i)?;
30    out.push('\n');
31    emit_interface_stub(out, i, registry)?;
32    out.push('\n');
33    emit_interface_skeleton(out, i)?;
34    Ok(())
35}
36
37fn emit_interface_trait(out: &mut String, i: &InterfaceDef) -> Result<()> {
38    let name = &i.name.text;
39    out.push_str("/// Generated by `zerodds-corba-rust` from IDL interface.\n");
40    // §3.5 raises: Exception-Enum pro Interface emittieren (basiert auf
41    // raises-Annotations aller Operations).
42    emit_interface_exceptions_enum(out, i)?;
43    // §6.1 Repository-ID als const im Trait — via Spec-konformen
44    // Builder aus `zerodds-corba-codegen::build_repository_id`
45    // (CORBA 3.3 §10.7.3.1 Format `IDL:<scoped-name>:<major>.<minor>`).
46    let repo_id = zerodds_corba_codegen::build_repository_id(&[], name, 1, 0);
47    let mut bases_iter = i.bases.iter().map(|s| {
48        s.parts
49            .iter()
50            .map(|p| p.text.as_str())
51            .collect::<Vec<_>>()
52            .join("::")
53    });
54    let bases_clause = if let Some(first) = bases_iter.next() {
55        let mut s = format!(": {first} + ::core::marker::Send + ::core::marker::Sync");
56        for base in bases_iter {
57            s.push_str(&format!(" + {base}"));
58        }
59        s
60    } else {
61        ": ::core::marker::Send + ::core::marker::Sync".to_string()
62    };
63    // Repository-ID als freier pub-const (nicht als trait-const, damit
64    // das Trait dyn-compatible bleibt — Skeleton-Dispatch verwendet
65    // `&dyn {name}`).
66    let repo_const = name.to_ascii_uppercase();
67    out.push_str(&format!(
68        "/// CORBA Repository-ID fuer `{name}` (Spec §10.7.3.1).\npub const {repo_const}_REPOSITORY_ID: &str = \"{repo_id}\";\n"
69    ));
70    out.push_str(&format!("pub trait {name}{bases_clause} {{\n"));
71    for export in &i.exports {
72        emit_export_trait_method(out, export)?;
73    }
74    out.push_str("}\n");
75    Ok(())
76}
77
78/// Emittiert ein Exception-Enum pro Interface basierend auf
79/// allen `raises`-Listen aller Operations. Spec §3.5.
80fn emit_interface_exceptions_enum(out: &mut String, i: &InterfaceDef) -> Result<()> {
81    let name = &i.name.text;
82    let mut all_raises: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
83    for export in &i.exports {
84        if let Export::Op(op) = export {
85            for raise in &op.raises {
86                let r = raise
87                    .parts
88                    .iter()
89                    .map(|p| p.text.as_str())
90                    .collect::<Vec<_>>()
91                    .join("::");
92                all_raises.insert(r);
93            }
94        }
95    }
96    if all_raises.is_empty() {
97        return Ok(());
98    }
99    out.push_str(&format!(
100        "/// Exception-Enum fuer `{name}` — vereinigt alle in `raises`-Listen referenzierten User-Exceptions plus System-Exception (Spec §3.5).\n"
101    ));
102    out.push_str("#[derive(Debug, Clone)]\n");
103    out.push_str(&format!("pub enum {name}Error {{\n"));
104    out.push_str(
105        "    /// CORBA-System-Exception (BAD_OPERATION, MARSHAL, OBJECT_NOT_EXIST, ...).\n",
106    );
107    out.push_str("    System(zerodds_corba_rust::CorbaException),\n");
108    for raise in &all_raises {
109        let variant = raise.split("::").last().unwrap_or(raise);
110        out.push_str(&format!(
111            "    /// User-Exception `{raise}` (raised by `{name}`-Operations).\n"
112        ));
113        out.push_str(&format!("    {variant}({raise}),\n"));
114    }
115    out.push_str("}\n\n");
116    Ok(())
117}
118
119fn emit_export_trait_method(out: &mut String, export: &Export) -> Result<()> {
120    match export {
121        Export::Op(op) => emit_op_trait_method(out, op),
122        Export::Attr(attr) => emit_attr_trait_method(out, attr),
123        // Const/Type/Except in Interface — werden in idl-rust separat
124        // als type-decls emittiert. Hier no-op.
125        _ => Ok(()),
126    }
127}
128
129/// Public-Wrapper fuer valuetype-Re-Use.
130pub fn emit_op_trait_method_pub(out: &mut String, op: &OpDecl) -> Result<()> {
131    emit_op_trait_method(out, op)
132}
133
134/// Public-Wrapper fuer valuetype-Re-Use.
135pub fn emit_attr_trait_method_pub(
136    out: &mut String,
137    attr: &zerodds_idl::ast::types::AttrDecl,
138) -> Result<()> {
139    emit_attr_trait_method(out, attr)
140}
141
142fn emit_op_trait_method(out: &mut String, op: &OpDecl) -> Result<()> {
143    let method = &op.name.text;
144    let receiver = if op_is_state_changing(op) {
145        "&mut self"
146    } else {
147        "&self"
148    };
149    let params_str = render_params(&op.params)?;
150    let return_str = render_return(&op.return_type, &op.raises)?;
151    let comma = if params_str.is_empty() { "" } else { ", " };
152    out.push_str(&format!(
153        "    /// IDL operation `{method}`.\n    fn {method}({receiver}{comma}{params_str}) -> {return_str};\n"
154    ));
155    Ok(())
156}
157
158fn emit_attr_trait_method(
159    out: &mut String,
160    attr: &zerodds_idl::ast::types::AttrDecl,
161) -> Result<()> {
162    let name = &attr.name.text;
163    let ty = zerodds_idl_rust::type_map::rust_type_for(&attr.type_spec)?;
164
165    out.push_str(&format!("    /// IDL attribute `{name}` getter.\n"));
166    out.push_str(&format!(
167        "    fn {name}(&self) -> ::core::result::Result<{ty}, zerodds_corba_rust::CorbaException>;\n"
168    ));
169    if !attr.readonly {
170        out.push_str(&format!("    /// IDL attribute `{name}` setter.\n"));
171        out.push_str(&format!(
172            "    fn set_{name}(&mut self, value: {ty}) -> ::core::result::Result<(), zerodds_corba_rust::CorbaException>;\n"
173        ));
174    }
175    Ok(())
176}
177
178fn op_is_state_changing(op: &OpDecl) -> bool {
179    op.params
180        .iter()
181        .any(|p| matches!(p.attribute, ParamAttribute::Out | ParamAttribute::InOut))
182}
183
184fn render_params(params: &[ParamDecl]) -> Result<String> {
185    let mut out = String::new();
186    for (idx, p) in params.iter().enumerate() {
187        if idx > 0 {
188            out.push_str(", ");
189        }
190        let ty = zerodds_idl_rust::type_map::rust_type_for(&p.type_spec)?;
191        let name = &p.name.text;
192        match p.attribute {
193            ParamAttribute::In => {
194                out.push_str(&format!("{name}: {ty}"));
195            }
196            ParamAttribute::Out | ParamAttribute::InOut => {
197                out.push_str(&format!("{name}: &mut {ty}"));
198            }
199        }
200    }
201    Ok(out)
202}
203
204fn render_return(
205    return_type: &Option<TypeSpec>,
206    _raises: &[zerodds_idl::ast::types::ScopedName],
207) -> Result<String> {
208    let inner = match return_type {
209        None => "()".to_string(),
210        Some(ts) => zerodds_idl_rust::type_map::rust_type_for(ts)?,
211    };
212    // Alle Operations koennen CORBA-System-Exceptions werfen, plus
213    // ggf. die in `raises` aufgelisteten User-Exceptions. Wir mappen
214    // einheitlich auf `Result<T, CorbaException>`. Spezifische
215    // User-Exception-Typen sind out-of-scope-Phase-1 (Phase 2:
216    // generierte Exception-Enum pro Interface).
217    Ok(format!(
218        "::core::result::Result<{inner}, zerodds_corba_rust::CorbaException>"
219    ))
220}
221
222fn emit_interface_stub(
223    out: &mut String,
224    i: &InterfaceDef,
225    registry: &crate::emitter::InterfaceRegistry<'_>,
226) -> Result<()> {
227    let name = &i.name.text;
228    let stub_name = format!("{name}Stub");
229    out.push_str(&format!(
230        "/// Client-Stub fuer `{name}` (sendet GIOP-Requests an einen Remote-Object).\n"
231    ));
232    out.push_str(&format!("pub struct {stub_name} {{\n"));
233    out.push_str("    /// Object-Reference (IOR) des Remote-Servants.\n");
234    out.push_str("    pub object_ref: zerodds_corba_rust::ObjectReference,\n");
235    out.push_str("}\n\n");
236
237    out.push_str(&format!("impl {stub_name} {{\n"));
238    out.push_str("    /// Konstruiert einen Stub mit einer ObjectReference.\n");
239    out.push_str("    #[must_use]\n");
240    out.push_str("    pub fn new(object_ref: zerodds_corba_rust::ObjectReference) -> Self {\n");
241    out.push_str("        Self { object_ref }\n");
242    out.push_str("    }\n");
243    out.push_str("}\n\n");
244
245    // Implement the trait via GIOP-marshalling stubs.
246    out.push_str(&format!("impl {name} for {stub_name} {{\n"));
247    for export in &i.exports {
248        emit_export_stub_impl(out, name, export)?;
249    }
250    out.push_str("}\n");
251
252    // §3.6 Inheritance: emit `impl Base for {stub_name}` fuer alle
253    // transitiv erreichbaren Basen, damit das Trait-Inheritance-Bound
254    // (`pub trait Derived: Base`) am Stub erfuellt ist.
255    let mut visited = alloc::collections::BTreeSet::new();
256    for base in &i.bases {
257        emit_base_impls_recursive(out, &stub_name, base, registry, &mut visited)?;
258    }
259    Ok(())
260}
261
262/// zerodds-lint: recursion-depth 16
263fn emit_base_impls_recursive(
264    out: &mut String,
265    stub_name: &str,
266    base_path: &zerodds_idl::ast::types::ScopedName,
267    registry: &crate::emitter::InterfaceRegistry<'_>,
268    visited: &mut alloc::collections::BTreeSet<String>,
269) -> Result<()> {
270    let base_simple = base_path
271        .parts
272        .last()
273        .map(|p| p.text.clone())
274        .unwrap_or_default();
275    if base_simple.is_empty() || !visited.insert(base_simple.clone()) {
276        return Ok(());
277    }
278    let Some(base_def) = registry.get(&base_simple) else {
279        // Base nicht im selben Spec-File — Phase-2-Cross-File-Resolution.
280        return Ok(());
281    };
282    let base_full_path = base_path
283        .parts
284        .iter()
285        .map(|p| p.text.as_str())
286        .collect::<Vec<_>>()
287        .join("::");
288    out.push_str(&format!(
289        "\n/// Base-Inheritance: `{base_simple}`-Methoden auf `{stub_name}` (Spec §3.6).\n"
290    ));
291    out.push_str(&format!("impl {base_full_path} for {stub_name} {{\n"));
292    for export in &base_def.exports {
293        emit_export_stub_impl(out, &base_simple, export)?;
294    }
295    out.push_str("}\n");
296    for grandbase in &base_def.bases {
297        emit_base_impls_recursive(out, stub_name, grandbase, registry, visited)?;
298    }
299    Ok(())
300}
301
302fn emit_export_stub_impl(out: &mut String, _iface: &str, export: &Export) -> Result<()> {
303    match export {
304        Export::Op(op) => emit_op_stub_impl(out, op),
305        Export::Attr(attr) => emit_attr_stub_impl(out, attr),
306        _ => Ok(()),
307    }
308}
309
310fn emit_op_stub_impl(out: &mut String, op: &OpDecl) -> Result<()> {
311    let method = &op.name.text;
312    let receiver = if op_is_state_changing(op) {
313        "&mut self"
314    } else {
315        "&self"
316    };
317    let params_str = render_params(&op.params)?;
318    let return_str = render_return(&op.return_type, &op.raises)?;
319    let comma = if params_str.is_empty() { "" } else { ", " };
320
321    out.push_str(&format!(
322        "    fn {method}({receiver}{comma}{params_str}) -> {return_str} {{\n"
323    ));
324    out.push_str(&format!(
325        "        // GIOP-Request: operation_name = \"{method}\".\n"
326    ));
327    out.push_str(
328        "        // Phase-2: encode in_params + send via corba-iiop, await reply, decode.\n",
329    );
330    out.push_str(
331        "        ::core::result::Result::Err(zerodds_corba_rust::CorbaException::SystemException {\n",
332    );
333    out.push_str("            minor: 0,\n");
334    out.push_str("            message: \"corba-rust stub: GIOP marshalling not yet wired\",\n");
335    out.push_str("        })\n");
336    out.push_str("    }\n");
337    Ok(())
338}
339
340fn emit_attr_stub_impl(out: &mut String, attr: &zerodds_idl::ast::types::AttrDecl) -> Result<()> {
341    let name = &attr.name.text;
342    let ty = zerodds_idl_rust::type_map::rust_type_for(&attr.type_spec)?;
343
344    // Getter
345    out.push_str(&format!(
346        "    fn {name}(&self) -> ::core::result::Result<{ty}, zerodds_corba_rust::CorbaException> {{\n"
347    ));
348    out.push_str(
349        "        ::core::result::Result::Err(zerodds_corba_rust::CorbaException::SystemException {\n",
350    );
351    out.push_str("            minor: 0,\n");
352    out.push_str(&format!(
353        "            message: \"corba-rust stub: attribute `{name}` getter not yet wired\",\n"
354    ));
355    out.push_str("        })\n");
356    out.push_str("    }\n");
357
358    if !attr.readonly {
359        out.push_str(&format!(
360            "    fn set_{name}(&mut self, value: {ty}) -> ::core::result::Result<(), zerodds_corba_rust::CorbaException> {{\n"
361        ));
362        out.push_str("        let _ = value;\n");
363        out.push_str(
364            "        ::core::result::Result::Err(zerodds_corba_rust::CorbaException::SystemException {\n",
365        );
366        out.push_str("            minor: 0,\n");
367        out.push_str(&format!(
368            "            message: \"corba-rust stub: attribute `{name}` setter not yet wired\",\n"
369        ));
370        out.push_str("        })\n");
371        out.push_str("    }\n");
372    }
373    Ok(())
374}
375
376fn emit_interface_skeleton(out: &mut String, i: &InterfaceDef) -> Result<()> {
377    let name = &i.name.text;
378    out.push_str(&format!("/// Server-Skeleton-Dispatch fuer `{name}`.\n"));
379    out.push_str("/// Wird vom POA aufgerufen wenn ein GIOP-Request mit Target-Operation\n");
380    out.push_str(&format!(
381        "/// `{name}` empfangen wird. Dispatched die Operation an den Servant.\n"
382    ));
383    let name_lower = name.to_lowercase();
384    out.push_str(&format!(
385        "pub fn dispatch_{name_lower}(servant: &dyn {name}, operation: &str, _payload: &[u8]) -> zerodds_corba_rust::SkeletonResult {{\n"
386    ));
387    out.push_str("    match operation {\n");
388    for export in &i.exports {
389        emit_export_skeleton_arm(out, export);
390    }
391    out.push_str("        _ => zerodds_corba_rust::SkeletonResult::BadOperation,\n");
392    out.push_str("    }\n");
393    out.push_str("}\n");
394    Ok(())
395}
396
397fn emit_export_skeleton_arm(out: &mut String, export: &Export) {
398    match export {
399        Export::Op(op) => {
400            let method = &op.name.text;
401            out.push_str(&format!(
402                "        \"{method}\" => {{\n            // Phase-2: GIOP-Body decoden, servant.{method}(...) aufrufen, Reply encoden.\n            let _ = servant;\n            zerodds_corba_rust::SkeletonResult::NotYetWired\n        }}\n"
403            ));
404        }
405        Export::Attr(attr) => {
406            let name = &attr.name.text;
407            out.push_str(&format!(
408                "        \"_get_{name}\" => {{\n            let _ = servant;\n            zerodds_corba_rust::SkeletonResult::NotYetWired\n        }}\n"
409            ));
410            if !attr.readonly {
411                out.push_str(&format!(
412                    "        \"_set_{name}\" => {{\n            let _ = servant;\n            zerodds_corba_rust::SkeletonResult::NotYetWired\n        }}\n"
413                ));
414            }
415        }
416        _ => {}
417    }
418}