zerodds_idl_cpp/
psm_cxx.rs1use core::fmt::Write;
31
32use crate::error::CppGenError;
33
34const TPL_CORE_HPP: &str = include_str!("../templates/dds-psm-cxx/core.hpp.tmpl");
36const TPL_REFERENCE_HPP: &str = include_str!("../templates/dds-psm-cxx/reference.hpp.tmpl");
37const TPL_EXCEPTIONS_HPP: &str = include_str!("../templates/dds-psm-cxx/exceptions.hpp.tmpl");
38const TPL_LISTENER_HPP: &str = include_str!("../templates/dds-psm-cxx/listener.hpp.tmpl");
39const TPL_CONDITION_HPP: &str = include_str!("../templates/dds-psm-cxx/condition.hpp.tmpl");
40
41pub fn emit_psm_cxx_includes(participant_name: &str) -> Result<String, CppGenError> {
55 if participant_name.is_empty() {
56 return Err(CppGenError::InvalidName {
57 name: participant_name.to_string(),
58 reason: "participant header name must not be empty".into(),
59 });
60 }
61 if participant_name.contains('/')
63 || participant_name.contains('\\')
64 || participant_name.contains("..")
65 {
66 return Err(CppGenError::InvalidName {
67 name: participant_name.to_string(),
68 reason: "participant header name must not contain path separators".into(),
69 });
70 }
71
72 let mut out = String::new();
73 writeln!(out, "// dds-psm-cxx-1.0 includes (generated).").map_err(fmt_err)?;
74 writeln!(out, "#include <cstdint>").map_err(fmt_err)?;
75 writeln!(out, "#include <memory>").map_err(fmt_err)?;
76 writeln!(out, "#include <string>").map_err(fmt_err)?;
77 writeln!(out, "#include <vector>").map_err(fmt_err)?;
78 writeln!(out, "#include <exception>").map_err(fmt_err)?;
79 writeln!(out, "#include <utility>").map_err(fmt_err)?;
80 writeln!(out, "#include <dds/core/core.hpp>").map_err(fmt_err)?;
81 writeln!(out, "#include <dds/core/reference.hpp>").map_err(fmt_err)?;
82 writeln!(out, "#include <dds/core/exceptions.hpp>").map_err(fmt_err)?;
83 writeln!(out, "#include <dds/core/listener.hpp>").map_err(fmt_err)?;
84 writeln!(out, "#include <dds/core/condition.hpp>").map_err(fmt_err)?;
85 writeln!(out, "#include \"{participant_name}.hpp\"").map_err(fmt_err)?;
86 Ok(out)
87}
88
89pub fn emit_reference_value_pattern(out: &mut String) -> Result<(), CppGenError> {
99 out.push_str(TPL_REFERENCE_HPP);
100 if !TPL_REFERENCE_HPP.ends_with('\n') {
101 out.push('\n');
102 }
103 Ok(())
104}
105
106pub fn emit_exception_hierarchy(out: &mut String) -> Result<(), CppGenError> {
111 out.push_str(TPL_EXCEPTIONS_HPP);
112 if !TPL_EXCEPTIONS_HPP.ends_with('\n') {
113 out.push('\n');
114 }
115 Ok(())
116}
117
118pub fn emit_listener_skeleton(out: &mut String) -> Result<(), CppGenError> {
126 out.push_str(TPL_LISTENER_HPP);
127 if !TPL_LISTENER_HPP.ends_with('\n') {
128 out.push('\n');
129 }
130 Ok(())
131}
132
133pub fn emit_condition_skeleton(out: &mut String) -> Result<(), CppGenError> {
138 out.push_str(TPL_CONDITION_HPP);
139 if !TPL_CONDITION_HPP.ends_with('\n') {
140 out.push('\n');
141 }
142 Ok(())
143}
144
145pub fn emit_core_basics(out: &mut String) -> Result<(), CppGenError> {
151 out.push_str(TPL_CORE_HPP);
152 if !TPL_CORE_HPP.ends_with('\n') {
153 out.push('\n');
154 }
155 Ok(())
156}
157
158pub fn emit_full_psm_cxx_skeleton() -> Result<String, CppGenError> {
165 let mut out = String::new();
166 writeln!(
167 out,
168 "// Generated dds-psm-cxx-1.0 skeleton (zerodds idl-cpp C5.2)."
169 )
170 .map_err(fmt_err)?;
171 writeln!(out, "#pragma once").map_err(fmt_err)?;
172 writeln!(out).map_err(fmt_err)?;
173 writeln!(out, "#include <cstdint>").map_err(fmt_err)?;
174 writeln!(out, "#include <memory>").map_err(fmt_err)?;
175 writeln!(out, "#include <string>").map_err(fmt_err)?;
176 writeln!(out, "#include <vector>").map_err(fmt_err)?;
177 writeln!(out, "#include <exception>").map_err(fmt_err)?;
178 writeln!(out, "#include <utility>").map_err(fmt_err)?;
179 writeln!(out).map_err(fmt_err)?;
180 emit_core_basics(&mut out)?;
181 emit_reference_value_pattern(&mut out)?;
182 emit_exception_hierarchy(&mut out)?;
183 emit_listener_skeleton(&mut out)?;
184 emit_condition_skeleton(&mut out)?;
185 Ok(out)
186}
187
188fn fmt_err(_: core::fmt::Error) -> CppGenError {
189 CppGenError::Internal("string formatting failed".into())
190}
191
192#[cfg(test)]
193mod tests {
194 #![allow(clippy::expect_used, clippy::panic)]
195 use super::*;
196
197 #[test]
198 fn includes_with_valid_name() {
199 let s = emit_psm_cxx_includes("MyParticipant").expect("ok");
200 assert!(s.contains("#include <dds/core/core.hpp>"));
201 assert!(s.contains("#include <dds/core/reference.hpp>"));
202 assert!(s.contains("#include \"MyParticipant.hpp\""));
203 }
204
205 #[test]
206 fn includes_rejects_empty() {
207 let res = emit_psm_cxx_includes("");
208 assert!(matches!(res, Err(CppGenError::InvalidName { .. })));
209 }
210
211 #[test]
212 fn includes_rejects_path_traversal() {
213 let res = emit_psm_cxx_includes("../etc/passwd");
214 assert!(matches!(res, Err(CppGenError::InvalidName { .. })));
215 let res2 = emit_psm_cxx_includes("a/b");
216 assert!(matches!(res2, Err(CppGenError::InvalidName { .. })));
217 let res3 = emit_psm_cxx_includes("a\\b");
218 assert!(matches!(res3, Err(CppGenError::InvalidName { .. })));
219 }
220
221 #[test]
222 fn reference_pattern_emits_reference_template() {
223 let mut s = String::new();
224 emit_reference_value_pattern(&mut s).expect("ok");
225 assert!(s.contains("namespace dds { namespace core {"));
226 assert!(s.contains("class Reference {"));
227 assert!(s.contains("class Value {"));
228 }
229
230 #[test]
231 fn exception_hierarchy_emits_dds_exception_classes() {
232 let mut s = String::new();
233 emit_exception_hierarchy(&mut s).expect("ok");
234 assert!(s.contains("class Exception"));
235 assert!(s.contains("class PreconditionNotMetError"));
236 assert!(s.contains("class NotEnabledError"));
237 assert!(s.contains("class OutOfResourcesError"));
238 assert!(s.contains("class IllegalOperationError"));
239 }
240
241 #[test]
242 fn listener_skeleton_has_13_callbacks() {
243 let mut s = String::new();
244 emit_listener_skeleton(&mut s).expect("ok");
245 let callbacks = [
247 "on_inconsistent_topic",
248 "on_sample_lost",
249 "on_sample_rejected",
250 "on_liveliness_changed",
251 "on_requested_deadline_missed",
252 "on_requested_incompatible_qos",
253 "on_offered_deadline_missed",
254 "on_offered_incompatible_qos",
255 "on_liveliness_lost",
256 "on_publication_matched",
257 "on_subscription_matched",
258 "on_data_available",
259 "on_data_on_readers",
260 ];
261 for cb in callbacks {
262 assert!(s.contains(cb), "missing callback: {cb}");
263 }
264 }
265
266 #[test]
267 fn condition_skeleton_has_waitset() {
268 let mut s = String::new();
269 emit_condition_skeleton(&mut s).expect("ok");
270 assert!(s.contains("class Condition"));
271 assert!(s.contains("class WaitSet"));
272 assert!(s.contains("class GuardCondition"));
273 assert!(s.contains("class StatusCondition"));
274 assert!(s.contains("class ReadCondition"));
275 }
276
277 #[test]
278 fn core_basics_define_time_duration_handle() {
279 let mut s = String::new();
280 emit_core_basics(&mut s).expect("ok");
281 assert!(s.contains("class Time"));
282 assert!(s.contains("class Duration"));
283 assert!(s.contains("class InstanceHandle"));
284 assert!(s.contains("Sample"));
285 }
286
287 #[test]
288 fn full_skeleton_combines_all_blocks() {
289 let s = emit_full_psm_cxx_skeleton().expect("ok");
290 assert!(s.contains("#pragma once"));
291 assert!(s.contains("class Time"));
292 assert!(s.contains("class Reference"));
293 assert!(s.contains("class Exception"));
294 assert!(s.contains("on_data_available"));
295 assert!(s.contains("class WaitSet"));
296 }
297
298 #[test]
299 fn full_skeleton_namespaces_are_dds_core() {
300 let s = emit_full_psm_cxx_skeleton().expect("ok");
301 let count = s.matches("namespace dds").count();
303 assert!(count >= 3, "expected >=3 namespace dds blocks, got {count}");
304 }
305}