1use 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
56const 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#[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 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 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#[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 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#[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#[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
303pub 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 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#[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#[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
435fn 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
457fn 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
468fn 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 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
553fn 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}