Skip to main content

zerodds_idl_cpp/
rpc.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! C6.1.D-cpp: DDS-RPC C++ PSM-Codegen.
4//!
5//! Spec-Referenz: OMG DDS-RPC 1.0 (formal/16-12-04), §10 — C++ PSM.
6//!
7//! Diese Schicht emittiert pro [`zerodds_rpc::ServiceDef`] vier Bausteine:
8//!
9//! * **Service-Interface** — abstrakte Klasse `<Service>` mit pro-Methode
10//!   sync + `_async`-Signaturen plus `<Service>HandlerInterface` fuer die
11//!   Server-Seite.
12//! * **Requester-Klasse** — typisierter `<Service>_Requester`-Wrapper, der
13//!   vom Generic [`dds::rpc::Requester<TIn, TOut>`]-Template erbt und pro
14//!   Methode eine `Future<TOut> name_async(TIn)` plus `TOut name(TIn)`-API
15//!   exportiert.
16//! * **Replier-Klasse** — typisierter `<Service>_Replier`-Wrapper mit
17//!   `dispatch_to_handler`, der einen [`<Service>HandlerInterface`] an
18//!   Methoden-Dispatch bindet.
19//! * **ServiceTraits** — `dds::rpc::ServiceTraits<<Service>>`-Spezialisierung
20//!   mit Topic-Names + Mapping-Variante.
21//!
22//! Die generischen Templates liegen unter
23//! `templates/dds-psm-cxx/rpc/{requester,replier,exception,service_traits}.hpp.tmpl`
24//! und werden via `include_str!` eingebettet.
25//!
26//! # Spec §10 Coverage
27//!
28//! | §10-Item | Coverage |
29//! |---|---|
30//! | §10.2 `dds::rpc`-Namespace | done — alle generierten Klassen liegen unter `dds::rpc::*`. |
31//! | §10.3 ServiceTraits | done — pro Service spezialisiert. |
32//! | §10.4 Requester-Template | done — Generic + typisierter Per-Service-Wrapper. |
33//! | §10.5 Replier-Template | done — Generic + typisierter Per-Service-Wrapper + HandlerInterface. |
34//! | §10.5 Operation-Naming | done — `<Service>_<method>_In/Out` direkt aus C6.1.B. |
35//! | §10.6 RemoteException | done — Hierarchie + Mapping pro IDL-Exception via [`emit_remote_exception_class`]. |
36//! | §10.7 Promise/Future | done — `dds::rpc::Future<T>` + `Promise<T>` als `std::future`-Wrapper. |
37//! | §10.7 Async-API | done — `<method>_async` liefert `Future<TOut>`. |
38//!
39//! # Bewusst nicht im Crate
40//!
41//! * Live-FFI-Anbindung an die Rust-RPC-Runtime — nur Quelltext.
42//! * Promise/Future-Optimierung (Skeleton ohne Listener-Style-API).
43//! * `attribute T name`-Mapping (§7.5.1.1.3) — keine Attribute-Slots im
44//!   aktuellen [`zerodds_rpc::ServiceDef`].
45//! * `g++`/`clang++`-Compile-Test — nicht Teil der CI.
46
47use core::fmt::Write;
48
49use zerodds_idl::ast::{
50    ExceptDecl, FloatingType, IntegerType, PrimitiveType, ScopedName, TypeSpec,
51};
52use zerodds_rpc::{MethodDef, ParamDef, ParamDirection, ServiceDef};
53
54use crate::error::CppGenError;
55
56/// Statische Templates (eingebettet via `include_str!`).
57const TPL_REQUESTER_HPP: &str = include_str!("../templates/dds-psm-cxx/rpc/requester.hpp.tmpl");
58const TPL_REPLIER_HPP: &str = include_str!("../templates/dds-psm-cxx/rpc/replier.hpp.tmpl");
59const TPL_EXCEPTION_HPP: &str = include_str!("../templates/dds-psm-cxx/rpc/exception.hpp.tmpl");
60const TPL_SERVICE_TRAITS_HPP: &str =
61    include_str!("../templates/dds-psm-cxx/rpc/service_traits.hpp.tmpl");
62
63// ---------------------------------------------------------------------------
64// Public API — Top-Level-Emitter
65// ---------------------------------------------------------------------------
66
67/// Emittiert das **Service-Interface** (abstrakte Klasse + HandlerInterface).
68///
69/// Output (vereinfacht):
70///
71/// ```cpp
72/// namespace dds { namespace rpc {
73///   class Calculator {
74///   public:
75///     virtual ~Calculator() = default;
76///     virtual int32_t add(int32_t a, int32_t b) = 0;
77///     virtual ::dds::rpc::Future<int32_t> add_async(int32_t a, int32_t b) = 0;
78///   };
79///   class CalculatorHandlerInterface {
80///   public:
81///     virtual ~CalculatorHandlerInterface() = default;
82///     virtual int32_t add(int32_t a, int32_t b) = 0;
83///   };
84/// } }
85/// ```
86#[must_use]
87pub fn emit_service_interface(svc: &ServiceDef) -> String {
88    let mut out = String::new();
89    let svc_name = &svc.name;
90    let _ = writeln!(
91        out,
92        "// zerodds-rpc-1.0 — Service-Interface fuer '{svc_name}' (Spec §10.4)."
93    );
94    let _ = writeln!(out, "namespace dds {{ namespace rpc {{");
95    let _ = writeln!(out);
96
97    // Abstract Service-Interface.
98    let _ = writeln!(out, "class {svc_name} {{");
99    let _ = writeln!(out, "public:");
100    let _ = writeln!(out, "    virtual ~{svc_name}() = default;");
101    for m in &svc.methods {
102        emit_doxygen_for_method(&mut out, m, "    ");
103        emit_sync_method_signature(&mut out, m, "    ");
104        if !m.oneway {
105            emit_async_method_signature(&mut out, m, "    ");
106        }
107    }
108    let _ = writeln!(out, "}};");
109    let _ = writeln!(out);
110
111    // HandlerInterface (Server-side).
112    let _ = writeln!(
113        out,
114        "/// Server-side Handler-Interface fuer '{svc_name}' (Spec §10.5)."
115    );
116    let _ = writeln!(out, "class {svc_name}HandlerInterface {{");
117    let _ = writeln!(out, "public:");
118    let _ = writeln!(out, "    virtual ~{svc_name}HandlerInterface() = default;");
119    for m in &svc.methods {
120        emit_doxygen_for_method(&mut out, m, "    ");
121        emit_sync_method_signature(&mut out, m, "    ");
122    }
123    let _ = writeln!(out, "}};");
124    let _ = writeln!(out);
125
126    let _ = writeln!(out, "}} }} // namespace dds::rpc");
127    out
128}
129
130/// Emittiert die typisierte **Requester-Klasse** fuer einen Service.
131///
132/// Erbt vom Generic `dds::rpc::Requester<TIn, TOut>` (Spec §10.4) und
133/// liefert pro Methode `Future<TOut> name_async(TIn)` + `TOut name(TIn)`.
134///
135/// Oneway-Methoden liefern `void` und kein Future (Spec §10.7
136/// "no reply for oneway operations").
137#[must_use]
138pub fn emit_requester_class(svc: &ServiceDef) -> String {
139    let mut out = String::new();
140    let svc_name = &svc.name;
141    let req_class = format!("{svc_name}_Requester");
142    let _ = writeln!(
143        out,
144        "// zerodds-rpc-1.0 — typisierter Requester fuer '{svc_name}' (Spec §10.4)."
145    );
146    let _ = writeln!(out, "namespace dds {{ namespace rpc {{");
147    let _ = writeln!(out);
148    let _ = writeln!(out, "class {req_class} {{");
149    let _ = writeln!(out, "public:");
150    let _ = writeln!(out, "    {req_class}() = default;");
151    let _ = writeln!(out, "    virtual ~{req_class}() = default;");
152    let _ = writeln!(out);
153    for m in &svc.methods {
154        emit_doxygen_for_method(&mut out, m, "    ");
155        if m.oneway {
156            // Oneway: fire-and-forget, kein Reply.
157            let _ = writeln!(out, "    /// Oneway — kein Reply, kein Future.");
158            emit_oneway_send_signature(&mut out, m, "    ");
159        } else {
160            emit_async_method_signature_pure(&mut out, m, "    ");
161            emit_sync_method_signature_inline(&mut out, m, "    ");
162        }
163        let _ = writeln!(out);
164    }
165    let _ = writeln!(out, "}};");
166    let _ = writeln!(out);
167    let _ = writeln!(out, "}} }} // namespace dds::rpc");
168    out
169}
170
171/// Emittiert die typisierte **Replier-Klasse** fuer einen Service.
172///
173/// Spec §10.5 — der Replier dispatcht eingehende Requests an den
174/// HandlerInterface-Implementor.
175#[must_use]
176pub fn emit_replier_class(svc: &ServiceDef) -> String {
177    let mut out = String::new();
178    let svc_name = &svc.name;
179    let rep_class = format!("{svc_name}_Replier");
180    let handler_iface = format!("{svc_name}HandlerInterface");
181    let _ = writeln!(
182        out,
183        "// zerodds-rpc-1.0 — typisierter Replier fuer '{svc_name}' (Spec §10.5)."
184    );
185    let _ = writeln!(out, "namespace dds {{ namespace rpc {{");
186    let _ = writeln!(out);
187    let _ = writeln!(out, "class {rep_class} {{");
188    let _ = writeln!(out, "public:");
189    let _ = writeln!(
190        out,
191        "    explicit {rep_class}(::dds::rpc::{handler_iface}* handler)"
192    );
193    let _ = writeln!(out, "        : handler_(handler) {{}}");
194    let _ = writeln!(out, "    virtual ~{rep_class}() = default;");
195    let _ = writeln!(out);
196    let _ = writeln!(
197        out,
198        "    /// Dispatcht eine eingegangene Anfrage anhand des Methoden-Tokens"
199    );
200    let _ = writeln!(
201        out,
202        "    /// an den registrierten Handler. Spec §10.5 — Operation-Naming."
203    );
204    let _ = writeln!(
205        out,
206        "    /// Skeleton: konkrete Wire-Deserialisierung erfolgt im Binding."
207    );
208    let _ = writeln!(
209        out,
210        "    void dispatch_to_handler(const std::string& method_name) {{"
211    );
212    if svc.methods.is_empty() {
213        let _ = writeln!(out, "        (void)method_name;");
214    } else {
215        for (i, m) in svc.methods.iter().enumerate() {
216            let kw = if i == 0 { "if" } else { "else if" };
217            let _ = writeln!(out, "        {kw} (method_name == \"{}\") {{", m.name);
218            if m.oneway {
219                let _ = writeln!(
220                    out,
221                    "            // Oneway: invoke handler, kein Reply (Spec §10.7)."
222                );
223                let args = handler_call_args(m);
224                let _ = writeln!(out, "            handler_->{}({args});", m.name);
225            } else {
226                let _ = writeln!(
227                    out,
228                    "            // Sync invoke; Promise-Set erfolgt im Caller (Skeleton)."
229                );
230                let args = handler_call_args(m);
231                if m.return_type.is_some() {
232                    let _ = writeln!(out, "            (void)handler_->{}({args});", m.name);
233                } else {
234                    let _ = writeln!(out, "            handler_->{}({args});", m.name);
235                }
236            }
237            let _ = writeln!(out, "        }}");
238        }
239        let _ = writeln!(out, "        else {{");
240        let _ = writeln!(
241            out,
242            "            throw ::dds::rpc::UnknownOperationError(method_name);"
243        );
244        let _ = writeln!(out, "        }}");
245    }
246    let _ = writeln!(out, "    }}");
247    let _ = writeln!(out);
248    let _ = writeln!(out, "private:");
249    let _ = writeln!(out, "    ::dds::rpc::{handler_iface}* handler_{{nullptr}};");
250    let _ = writeln!(out, "}};");
251    let _ = writeln!(out);
252    let _ = writeln!(out, "}} }} // namespace dds::rpc");
253    out
254}
255
256/// Emittiert die `ServiceTraits<<Service>>`-Spezialisierung (Spec §10.3).
257///
258/// Liefert `request_topic_name`, `reply_topic_name` und das
259/// [`ServiceMapping`](super) (Basic vs Enhanced).
260#[must_use]
261pub fn emit_service_traits(svc: &ServiceDef) -> String {
262    let mut out = String::new();
263    let svc_name = &svc.name;
264    let req_topic = format!("{svc_name}_Request");
265    let rep_topic = format!("{svc_name}_Reply");
266    let _ = writeln!(
267        out,
268        "// zerodds-rpc-1.0 — ServiceTraits-Spezialisierung fuer '{svc_name}' (Spec §10.3)."
269    );
270    let _ = writeln!(out, "namespace dds {{ namespace rpc {{");
271    let _ = writeln!(out);
272    let _ = writeln!(out, "template <>");
273    let _ = writeln!(out, "struct ServiceTraits<{svc_name}> {{");
274    let _ = writeln!(out, "    static constexpr bool is_specialized = true;");
275    let _ = writeln!(
276        out,
277        "    static constexpr const char* service_name = \"{svc_name}\";"
278    );
279    let _ = writeln!(
280        out,
281        "    static constexpr const char* request_topic_name = \"{req_topic}\";"
282    );
283    let _ = writeln!(
284        out,
285        "    static constexpr const char* reply_topic_name = \"{rep_topic}\";"
286    );
287    let _ = writeln!(
288        out,
289        "    static constexpr ServiceMapping mapping = ServiceMapping::Basic;"
290    );
291    let _ = writeln!(out, "    using requester_type = {svc_name}_Requester;");
292    let _ = writeln!(out, "    using replier_type = {svc_name}_Replier;");
293    let _ = writeln!(
294        out,
295        "    using handler_interface_type = {svc_name}HandlerInterface;"
296    );
297    let _ = writeln!(out, "}};");
298    let _ = writeln!(out);
299    let _ = writeln!(out, "}} }} // namespace dds::rpc");
300    out
301}
302
303/// Emittiert eine konkrete RemoteException-Subklasse aus einer IDL-
304/// `exception E { ... }`-Deklaration (Spec §10.6).
305///
306/// Die generierte Klasse erbt von `::dds::rpc::RemoteException` und
307/// liefert pro Member ein privates Feld + Getter/Setter.
308///
309/// # Errors
310/// * [`CppGenError::InvalidName`], wenn der Exception-Name oder ein Member-
311///   Name reserviert ist.
312/// * [`CppGenError::UnsupportedConstruct`], wenn ein Member einen
313///   nicht-unterstuetzten Typ verwendet (z.B. `any`/`fixed`).
314pub fn emit_remote_exception_class(except: &ExceptDecl) -> Result<String, CppGenError> {
315    crate::type_map::check_identifier(&except.name.text)?;
316    let mut out = String::new();
317    let name = &except.name.text;
318    let _ = writeln!(
319        out,
320        "// zerodds-rpc-1.0 — RemoteException-Subklasse '{name}' (Spec §10.6)."
321    );
322    let _ = writeln!(out, "namespace dds {{ namespace rpc {{");
323    let _ = writeln!(out);
324    let _ = writeln!(out, "class {name} : public ::dds::rpc::RemoteException {{");
325    let _ = writeln!(out, "public:");
326    let _ = writeln!(out, "    {name}() = default;");
327    let _ = writeln!(
328        out,
329        "    explicit {name}(std::string msg) : ::dds::rpc::RemoteException(std::move(msg)) {{}}"
330    );
331    let _ = writeln!(out, "    ~{name}() override = default;");
332    let _ = writeln!(out);
333
334    // Body: members.
335    if !except.members.is_empty() {
336        let _ = writeln!(out, "private:");
337        for m in &except.members {
338            for decl in &m.declarators {
339                let cpp_ty = idl_typespec_to_cpp(&m.type_spec)?;
340                let mname = decl.name();
341                crate::type_map::check_identifier(&mname.text)?;
342                let _ = writeln!(out, "    {cpp_ty} {}_{{}};", mname.text);
343            }
344        }
345        let _ = writeln!(out);
346        let _ = writeln!(out, "public:");
347        for m in &except.members {
348            for decl in &m.declarators {
349                let cpp_ty = idl_typespec_to_cpp(&m.type_spec)?;
350                let mn = &decl.name().text;
351                let _ = writeln!(
352                    out,
353                    "    const {cpp_ty}& {mn}() const noexcept {{ return {mn}_; }}"
354                );
355                let _ = writeln!(out, "    void {mn}(const {cpp_ty}& v) {{ {mn}_ = v; }}");
356            }
357        }
358    }
359    let _ = writeln!(out, "}};");
360    let _ = writeln!(out);
361    let _ = writeln!(out, "}} }} // namespace dds::rpc");
362    Ok(out)
363}
364
365/// Emittiert die generischen RPC-Header-Templates (Requester/Replier/
366/// Exception/ServiceTraits) als zusammengefuegten Header-Block.
367///
368/// Wird typischerweise einmal pro Compilation-Unit emittiert; die
369/// per-Service-Header (siehe [`emit_service_full_header`]) ziehen diesen
370/// Block per `#include` ein.
371#[must_use]
372pub fn emit_rpc_runtime_headers() -> String {
373    let mut out = String::new();
374    let _ = writeln!(
375        out,
376        "// Generated zerodds-rpc-1.0 runtime headers (zerodds idl-cpp C6.1.D-cpp)."
377    );
378    let _ = writeln!(out, "#pragma once");
379    let _ = writeln!(out);
380    let _ = writeln!(out, "#include <cstdint>");
381    let _ = writeln!(out, "#include <future>");
382    let _ = writeln!(out, "#include <memory>");
383    let _ = writeln!(out, "#include <string>");
384    let _ = writeln!(out, "#include <utility>");
385    let _ = writeln!(out, "#include <exception>");
386    let _ = writeln!(out, "#include <dds/core/exceptions.hpp>");
387    let _ = writeln!(out);
388    out.push_str(TPL_EXCEPTION_HPP);
389    if !TPL_EXCEPTION_HPP.ends_with('\n') {
390        out.push('\n');
391    }
392    out.push_str(TPL_SERVICE_TRAITS_HPP);
393    if !TPL_SERVICE_TRAITS_HPP.ends_with('\n') {
394        out.push('\n');
395    }
396    out.push_str(TPL_REQUESTER_HPP);
397    if !TPL_REQUESTER_HPP.ends_with('\n') {
398        out.push('\n');
399    }
400    out.push_str(TPL_REPLIER_HPP);
401    if !TPL_REPLIER_HPP.ends_with('\n') {
402        out.push('\n');
403    }
404    out
405}
406
407/// Emittiert einen vollstaendigen RPC-Service-Header
408/// (Interface + Requester + Replier + ServiceTraits) fuer einen einzelnen
409/// Service.
410///
411/// Reihenfolge folgt der Header-Konvention aus dds-psm-cxx — zuerst die
412/// Top-Level-Service-Klasse, dann die Endpoint-Wrapper, am Ende das
413/// `ServiceTraits`-Template (Spec §10.3 verlangt vollstaendige Sicht-
414/// barkeit der typisierten Wrapper, bevor das Trait spezialisiert wird).
415#[must_use]
416pub fn emit_service_full_header(svc: &ServiceDef) -> String {
417    let mut out = String::new();
418    let _ = writeln!(
419        out,
420        "// Generated zerodds-rpc-1.0 service header for '{}' (zerodds idl-cpp C6.1.D-cpp).",
421        svc.name
422    );
423    let _ = writeln!(out, "#pragma once");
424    let _ = writeln!(out);
425    out.push_str(&emit_service_interface(svc));
426    out.push('\n');
427    out.push_str(&emit_requester_class(svc));
428    out.push('\n');
429    out.push_str(&emit_replier_class(svc));
430    out.push('\n');
431    out.push_str(&emit_service_traits(svc));
432    out
433}
434
435// ---------------------------------------------------------------------------
436// Method-Signature-Helpers
437// ---------------------------------------------------------------------------
438
439fn return_type_str(m: &MethodDef) -> String {
440    if m.oneway {
441        "void".to_string()
442    } else if let Some(ret) = &m.return_type {
443        idl_typespec_to_cpp(ret).unwrap_or_else(|_| "void".to_string())
444    } else {
445        "void".to_string()
446    }
447}
448
449fn method_param_list(m: &MethodDef) -> String {
450    let mut parts: Vec<String> = Vec::new();
451    for p in &m.params {
452        parts.push(format_param_decl(p));
453    }
454    parts.join(", ")
455}
456
457/// Liefert die Parameter so, dass `inout` und `out` als nicht-const-Referenz
458/// emittiert werden (klassisches IDL→C++-Mapping fuer veraenderliche
459/// Parameter, siehe DDS-RPC §10.4 + IDL4-CPP §7.6.7).
460fn format_param_decl(p: &ParamDef) -> String {
461    let ty = idl_typespec_to_cpp(&p.type_ref).unwrap_or_else(|_| "void".to_string());
462    match p.direction {
463        ParamDirection::In => format!("const {ty}& {}", p.name),
464        ParamDirection::Out | ParamDirection::InOut => format!("{ty}& {}", p.name),
465    }
466}
467
468/// Argument-Liste fuer Handler-Calls — gleiche Param-Namen, ohne Typ.
469fn handler_call_args(m: &MethodDef) -> String {
470    m.params
471        .iter()
472        .map(|p| p.name.clone())
473        .collect::<Vec<_>>()
474        .join(", ")
475}
476
477fn emit_sync_method_signature(out: &mut String, m: &MethodDef, indent: &str) {
478    let ret = return_type_str(m);
479    let params = method_param_list(m);
480    let _ = writeln!(out, "{indent}virtual {ret} {}({params}) = 0;", m.name);
481}
482
483fn emit_async_method_signature(out: &mut String, m: &MethodDef, indent: &str) {
484    let ret = return_type_str(m);
485    let params = method_param_list(m);
486    let _ = writeln!(
487        out,
488        "{indent}virtual ::dds::rpc::Future<{ret}> {}_async({params}) = 0;",
489        m.name
490    );
491}
492
493fn emit_async_method_signature_pure(out: &mut String, m: &MethodDef, indent: &str) {
494    let ret = return_type_str(m);
495    let params = method_param_list(m);
496    let _ = writeln!(
497        out,
498        "{indent}virtual ::dds::rpc::Future<{ret}> {}_async({params}) {{",
499        m.name
500    );
501    let _ = writeln!(out, "{indent}    ::dds::rpc::Promise<{ret}> _promise{{}};");
502    // Argumente "verwenden" (Skeleton, kein realer Send).
503    for p in &m.params {
504        let _ = writeln!(out, "{indent}    (void){};", p.name);
505    }
506    let _ = writeln!(out, "{indent}    return _promise.get_future();");
507    let _ = writeln!(out, "{indent}}}");
508}
509
510fn emit_sync_method_signature_inline(out: &mut String, m: &MethodDef, indent: &str) {
511    let ret = return_type_str(m);
512    let params = method_param_list(m);
513    let args = handler_call_args(m);
514    let _ = writeln!(out, "{indent}virtual {ret} {}({params}) {{", m.name);
515    if ret == "void" {
516        let _ = writeln!(out, "{indent}    {}_async({args}).get();", m.name);
517    } else {
518        let _ = writeln!(out, "{indent}    return {}_async({args}).get();", m.name);
519    }
520    let _ = writeln!(out, "{indent}}}");
521}
522
523fn emit_oneway_send_signature(out: &mut String, m: &MethodDef, indent: &str) {
524    let params = method_param_list(m);
525    let _ = writeln!(out, "{indent}virtual void {}({params}) {{", m.name);
526    for p in &m.params {
527        let _ = writeln!(out, "{indent}    (void){};", p.name);
528    }
529    let _ = writeln!(out, "{indent}    // oneway: fire-and-forget (Spec §10.7).");
530    let _ = writeln!(out, "{indent}}}");
531}
532
533fn emit_doxygen_for_method(out: &mut String, m: &MethodDef, indent: &str) {
534    let _ = writeln!(out, "{indent}/// Operation '{}'.", m.name);
535    if m.oneway {
536        let _ = writeln!(out, "{indent}/// @oneway — kein Reply (Spec §10.7).");
537    }
538    if !m.params.is_empty() {
539        for p in &m.params {
540            let dir = match p.direction {
541                ParamDirection::In => "in",
542                ParamDirection::Out => "out",
543                ParamDirection::InOut => "inout",
544            };
545            let _ = writeln!(out, "{indent}/// @param {} ({dir})", p.name);
546        }
547    }
548    if let Some(_r) = &m.return_type {
549        let _ = writeln!(out, "{indent}/// @return Reply-Wert.");
550    }
551}
552
553// ---------------------------------------------------------------------------
554// IDL → C++-Type-Mapping (lokaler Subset von emitter::typespec_to_cpp).
555// ---------------------------------------------------------------------------
556
557/// zerodds-lint: recursion-depth 16
558///
559/// Sequenz/Array-Verschachtelung: IDL-Spec erlaubt theoretisch beliebige
560/// Tiefe, in der Praxis sind 4-8 Ebenen das Maximum (z.B. `sequence<sequence<
561/// sequence<long>>>`). Cap auf 16 schuetzt vor pathologischen Eingaben.
562fn idl_typespec_to_cpp(ts: &TypeSpec) -> Result<String, CppGenError> {
563    match ts {
564        TypeSpec::Primitive(p) => Ok(primitive_str(*p).to_string()),
565        TypeSpec::Scoped(s) => Ok(scoped_to_cpp(s)),
566        TypeSpec::Sequence(s) => {
567            let inner = idl_typespec_to_cpp(&s.elem)?;
568            Ok(format!("std::vector<{inner}>"))
569        }
570        TypeSpec::String(s) => {
571            if s.wide {
572                Ok("std::wstring".into())
573            } else {
574                Ok("std::string".into())
575            }
576        }
577        TypeSpec::Map(m) => {
578            let k = idl_typespec_to_cpp(&m.key)?;
579            let v = idl_typespec_to_cpp(&m.value)?;
580            Ok(format!("std::map<{k}, {v}>"))
581        }
582        TypeSpec::Fixed(_) => Err(CppGenError::UnsupportedConstruct {
583            construct: "fixed".into(),
584            context: None,
585        }),
586        TypeSpec::Any => Err(CppGenError::UnsupportedConstruct {
587            construct: "any".into(),
588            context: None,
589        }),
590    }
591}
592
593fn primitive_str(p: PrimitiveType) -> &'static str {
594    match p {
595        PrimitiveType::Boolean => "bool",
596        PrimitiveType::Octet => "uint8_t",
597        PrimitiveType::Char => "char",
598        PrimitiveType::WideChar => "wchar_t",
599        PrimitiveType::Integer(i) => integer_str(i),
600        PrimitiveType::Floating(f) => floating_str(f),
601    }
602}
603
604fn integer_str(i: IntegerType) -> &'static str {
605    match i {
606        IntegerType::Short | IntegerType::Int16 => "int16_t",
607        IntegerType::Long | IntegerType::Int32 => "int32_t",
608        IntegerType::LongLong | IntegerType::Int64 => "int64_t",
609        IntegerType::UShort | IntegerType::UInt16 => "uint16_t",
610        IntegerType::ULong | IntegerType::UInt32 => "uint32_t",
611        IntegerType::ULongLong | IntegerType::UInt64 => "uint64_t",
612        IntegerType::Int8 => "int8_t",
613        IntegerType::UInt8 => "uint8_t",
614    }
615}
616
617fn floating_str(f: FloatingType) -> &'static str {
618    match f {
619        FloatingType::Float => "float",
620        FloatingType::Double => "double",
621        FloatingType::LongDouble => "long double",
622    }
623}
624
625fn scoped_to_cpp(s: &ScopedName) -> String {
626    let parts: Vec<String> = s.parts.iter().map(|p| p.text.clone()).collect();
627    let joined = parts.join("::");
628    if s.absolute {
629        format!("::{joined}")
630    } else {
631        joined
632    }
633}
634
635#[cfg(test)]
636#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
637mod tests {
638    use super::*;
639    use zerodds_idl::ast::{IntegerType, PrimitiveType, StringType, TypeSpec};
640    use zerodds_idl::errors::Span;
641    use zerodds_rpc::{MethodDef, ParamDef, ParamDirection, ServiceDef};
642
643    fn long_t() -> TypeSpec {
644        TypeSpec::Primitive(PrimitiveType::Integer(IntegerType::Long))
645    }
646
647    fn string_t() -> TypeSpec {
648        TypeSpec::String(StringType {
649            wide: false,
650            bound: None,
651            span: Span::SYNTHETIC,
652        })
653    }
654
655    fn calc_service() -> ServiceDef {
656        ServiceDef {
657            name: "Calculator".into(),
658            methods: vec![MethodDef {
659                name: "add".into(),
660                params: vec![
661                    ParamDef {
662                        name: "a".into(),
663                        direction: ParamDirection::In,
664                        type_ref: long_t(),
665                    },
666                    ParamDef {
667                        name: "b".into(),
668                        direction: ParamDirection::In,
669                        type_ref: long_t(),
670                    },
671                ],
672                return_type: Some(long_t()),
673                oneway: false,
674            }],
675        }
676    }
677
678    #[test]
679    fn service_interface_contains_class_and_async() {
680        let s = emit_service_interface(&calc_service());
681        assert!(s.contains("class Calculator {"));
682        assert!(s.contains("virtual int32_t add("));
683        assert!(s.contains("::dds::rpc::Future<int32_t> add_async"));
684    }
685
686    #[test]
687    fn requester_class_emits_topic_wrapper() {
688        let s = emit_requester_class(&calc_service());
689        assert!(s.contains("class Calculator_Requester"));
690        assert!(s.contains("add_async"));
691        assert!(s.contains("Promise<int32_t>"));
692    }
693
694    #[test]
695    fn replier_class_dispatches_by_name() {
696        let s = emit_replier_class(&calc_service());
697        assert!(s.contains("class Calculator_Replier"));
698        assert!(s.contains("if (method_name == \"add\")"));
699        assert!(s.contains("UnknownOperationError"));
700    }
701
702    #[test]
703    fn service_traits_specialization_carries_topic_names() {
704        let s = emit_service_traits(&calc_service());
705        assert!(s.contains("struct ServiceTraits<Calculator>"));
706        assert!(s.contains("\"Calculator_Request\""));
707        assert!(s.contains("\"Calculator_Reply\""));
708    }
709
710    #[test]
711    fn full_header_combines_all_blocks() {
712        let s = emit_service_full_header(&calc_service());
713        assert!(s.contains("#pragma once"));
714        assert!(s.contains("class Calculator {"));
715        assert!(s.contains("class Calculator_Requester"));
716        assert!(s.contains("class Calculator_Replier"));
717        assert!(s.contains("ServiceTraits<Calculator>"));
718    }
719
720    #[test]
721    fn runtime_headers_include_all_templates() {
722        let s = emit_rpc_runtime_headers();
723        assert!(s.contains("class RemoteException"));
724        assert!(s.contains("class Requester"));
725        assert!(s.contains("class Replier"));
726        assert!(s.contains("ServiceTraits"));
727    }
728
729    #[test]
730    fn primitive_str_covers_all_branches() {
731        assert_eq!(primitive_str(PrimitiveType::Boolean), "bool");
732        assert_eq!(primitive_str(PrimitiveType::Octet), "uint8_t");
733        assert_eq!(primitive_str(PrimitiveType::Char), "char");
734        assert_eq!(primitive_str(PrimitiveType::WideChar), "wchar_t");
735    }
736
737    #[test]
738    fn integer_str_short_long() {
739        assert_eq!(integer_str(IntegerType::Short), "int16_t");
740        assert_eq!(integer_str(IntegerType::ULongLong), "uint64_t");
741    }
742
743    #[test]
744    fn idl_typespec_string_wide_vs_narrow() {
745        let s = idl_typespec_to_cpp(&string_t()).unwrap();
746        assert_eq!(s, "std::string");
747    }
748
749    #[test]
750    fn idl_typespec_any_is_rejected() {
751        let res = idl_typespec_to_cpp(&TypeSpec::Any);
752        assert!(matches!(res, Err(CppGenError::UnsupportedConstruct { .. })));
753    }
754}