1use core::fmt::Write;
36
37use crate::error::CppGenError;
38
39#[derive(Debug, Clone, Copy)]
41struct StatusField {
42 name: &'static str,
44 cpp_ty: &'static str,
46}
47
48#[derive(Debug, Clone, Copy)]
50struct StatusSpec {
51 name: &'static str,
53 fields: &'static [StatusField],
55 spec_ref: &'static str,
57}
58
59const STATUSES: &[StatusSpec] = &[
61 StatusSpec {
62 name: "InconsistentTopicStatus",
63 spec_ref: "DDS 1.4 §2.2.4.1.4",
64 fields: &[
65 StatusField {
66 name: "total_count",
67 cpp_ty: "int32_t",
68 },
69 StatusField {
70 name: "total_count_change",
71 cpp_ty: "int32_t",
72 },
73 ],
74 },
75 StatusSpec {
76 name: "SampleLostStatus",
77 spec_ref: "DDS 1.4 §2.2.4.1.5",
78 fields: &[
79 StatusField {
80 name: "total_count",
81 cpp_ty: "int32_t",
82 },
83 StatusField {
84 name: "total_count_change",
85 cpp_ty: "int32_t",
86 },
87 ],
88 },
89 StatusSpec {
90 name: "SampleRejectedStatus",
91 spec_ref: "DDS 1.4 §2.2.4.1.6",
92 fields: &[
93 StatusField {
94 name: "total_count",
95 cpp_ty: "int32_t",
96 },
97 StatusField {
98 name: "total_count_change",
99 cpp_ty: "int32_t",
100 },
101 StatusField {
102 name: "last_reason",
103 cpp_ty: "::dds::core::status::SampleRejectedState",
104 },
105 StatusField {
106 name: "last_instance_handle",
107 cpp_ty: "::dds::core::InstanceHandle",
108 },
109 ],
110 },
111 StatusSpec {
112 name: "LivelinessChangedStatus",
113 spec_ref: "DDS 1.4 §2.2.4.1.7",
114 fields: &[
115 StatusField {
116 name: "alive_count",
117 cpp_ty: "int32_t",
118 },
119 StatusField {
120 name: "not_alive_count",
121 cpp_ty: "int32_t",
122 },
123 StatusField {
124 name: "alive_count_change",
125 cpp_ty: "int32_t",
126 },
127 StatusField {
128 name: "not_alive_count_change",
129 cpp_ty: "int32_t",
130 },
131 StatusField {
132 name: "last_publication_handle",
133 cpp_ty: "::dds::core::InstanceHandle",
134 },
135 ],
136 },
137 StatusSpec {
138 name: "RequestedDeadlineMissedStatus",
139 spec_ref: "DDS 1.4 §2.2.4.1.8",
140 fields: &[
141 StatusField {
142 name: "total_count",
143 cpp_ty: "int32_t",
144 },
145 StatusField {
146 name: "total_count_change",
147 cpp_ty: "int32_t",
148 },
149 StatusField {
150 name: "last_instance_handle",
151 cpp_ty: "::dds::core::InstanceHandle",
152 },
153 ],
154 },
155 StatusSpec {
156 name: "RequestedIncompatibleQosStatus",
157 spec_ref: "DDS 1.4 §2.2.4.1.9",
158 fields: &[
159 StatusField {
160 name: "total_count",
161 cpp_ty: "int32_t",
162 },
163 StatusField {
164 name: "total_count_change",
165 cpp_ty: "int32_t",
166 },
167 StatusField {
168 name: "last_policy_id",
169 cpp_ty: "::dds::core::policy::QosPolicyId",
170 },
171 StatusField {
172 name: "policies",
173 cpp_ty: "std::vector<::dds::core::status::QosPolicyCount>",
174 },
175 ],
176 },
177 StatusSpec {
178 name: "OfferedDeadlineMissedStatus",
179 spec_ref: "DDS 1.4 §2.2.4.1.10",
180 fields: &[
181 StatusField {
182 name: "total_count",
183 cpp_ty: "int32_t",
184 },
185 StatusField {
186 name: "total_count_change",
187 cpp_ty: "int32_t",
188 },
189 StatusField {
190 name: "last_instance_handle",
191 cpp_ty: "::dds::core::InstanceHandle",
192 },
193 ],
194 },
195 StatusSpec {
196 name: "OfferedIncompatibleQosStatus",
197 spec_ref: "DDS 1.4 §2.2.4.1.11",
198 fields: &[
199 StatusField {
200 name: "total_count",
201 cpp_ty: "int32_t",
202 },
203 StatusField {
204 name: "total_count_change",
205 cpp_ty: "int32_t",
206 },
207 StatusField {
208 name: "last_policy_id",
209 cpp_ty: "::dds::core::policy::QosPolicyId",
210 },
211 StatusField {
212 name: "policies",
213 cpp_ty: "std::vector<::dds::core::status::QosPolicyCount>",
214 },
215 ],
216 },
217 StatusSpec {
218 name: "LivelinessLostStatus",
219 spec_ref: "DDS 1.4 §2.2.4.1.12",
220 fields: &[
221 StatusField {
222 name: "total_count",
223 cpp_ty: "int32_t",
224 },
225 StatusField {
226 name: "total_count_change",
227 cpp_ty: "int32_t",
228 },
229 ],
230 },
231 StatusSpec {
232 name: "PublicationMatchedStatus",
233 spec_ref: "DDS 1.4 §2.2.4.1.13",
234 fields: &[
235 StatusField {
236 name: "total_count",
237 cpp_ty: "int32_t",
238 },
239 StatusField {
240 name: "total_count_change",
241 cpp_ty: "int32_t",
242 },
243 StatusField {
244 name: "current_count",
245 cpp_ty: "int32_t",
246 },
247 StatusField {
248 name: "current_count_change",
249 cpp_ty: "int32_t",
250 },
251 StatusField {
252 name: "last_subscription_handle",
253 cpp_ty: "::dds::core::InstanceHandle",
254 },
255 ],
256 },
257 StatusSpec {
258 name: "SubscriptionMatchedStatus",
259 spec_ref: "DDS 1.4 §2.2.4.1.14",
260 fields: &[
261 StatusField {
262 name: "total_count",
263 cpp_ty: "int32_t",
264 },
265 StatusField {
266 name: "total_count_change",
267 cpp_ty: "int32_t",
268 },
269 StatusField {
270 name: "current_count",
271 cpp_ty: "int32_t",
272 },
273 StatusField {
274 name: "current_count_change",
275 cpp_ty: "int32_t",
276 },
277 StatusField {
278 name: "last_publication_handle",
279 cpp_ty: "::dds::core::InstanceHandle",
280 },
281 ],
282 },
283 StatusSpec {
284 name: "DataAvailableStatus",
285 spec_ref: "DDS 1.4 §2.2.4.1.2 (marker)",
286 fields: &[],
287 },
288 StatusSpec {
289 name: "DataOnReadersStatus",
290 spec_ref: "DDS 1.4 §2.2.4.1.3 (marker)",
291 fields: &[],
292 },
293];
294
295pub fn emit_status_header(out: &mut String) -> Result<(), CppGenError> {
305 writeln!(
306 out,
307 "// Block-F: DDS-Status-Strukturen (Spec dds-1.4 §2.2.4.1)."
308 )
309 .map_err(fmt_err)?;
310 writeln!(
311 out,
312 "namespace dds {{ namespace core {{ namespace status {{"
313 )
314 .map_err(fmt_err)?;
315 writeln!(out).map_err(fmt_err)?;
316
317 writeln!(out, "// Forward-Declarations fuer Hilfstypen.").map_err(fmt_err)?;
320 writeln!(out, "enum class SampleRejectedState : int32_t;").map_err(fmt_err)?;
321 writeln!(out, "class QosPolicyCount;").map_err(fmt_err)?;
322 writeln!(out).map_err(fmt_err)?;
323
324 for s in STATUSES {
325 emit_status_class(out, s)?;
326 }
327
328 writeln!(out, "}} }} }} // namespace dds::core::status").map_err(fmt_err)?;
329 writeln!(out).map_err(fmt_err)?;
330 Ok(())
331}
332
333#[must_use]
335pub fn status_class_names() -> Vec<&'static str> {
336 STATUSES.iter().map(|s| s.name).collect()
337}
338
339fn emit_status_class(out: &mut String, s: &StatusSpec) -> Result<(), CppGenError> {
340 writeln!(out, "/// {} ({})", s.name, s.spec_ref).map_err(fmt_err)?;
341 writeln!(out, "class {} {{", s.name).map_err(fmt_err)?;
342 writeln!(out, "public:").map_err(fmt_err)?;
343 writeln!(out, " {}() = default;", s.name).map_err(fmt_err)?;
344 writeln!(out, " ~{}() = default;", s.name).map_err(fmt_err)?;
345 writeln!(out, " {0}(const {0}&) = default;", s.name).map_err(fmt_err)?;
346 writeln!(out, " {0}({0}&&) noexcept = default;", s.name).map_err(fmt_err)?;
347 writeln!(out, " {0}& operator=(const {0}&) = default;", s.name).map_err(fmt_err)?;
348 writeln!(out, " {0}& operator=({0}&&) noexcept = default;", s.name).map_err(fmt_err)?;
349
350 if !s.fields.is_empty() {
351 writeln!(out).map_err(fmt_err)?;
352 writeln!(
355 out,
356 " /// Reset-on-read: setzt alle *_change-Felder auf 0."
357 )
358 .map_err(fmt_err)?;
359 writeln!(out, " void reset_changes() {{").map_err(fmt_err)?;
360 for f in s.fields {
361 if f.name.ends_with("_change") {
362 writeln!(out, " {}_ = 0;", f.name).map_err(fmt_err)?;
363 }
364 }
365 writeln!(out, " }}").map_err(fmt_err)?;
366 }
367
368 if !s.fields.is_empty() {
369 writeln!(out).map_err(fmt_err)?;
370 for f in s.fields {
372 writeln!(
373 out,
374 " const {ty}& {name}() const {{ return {name}_; }}",
375 ty = f.cpp_ty,
376 name = f.name
377 )
378 .map_err(fmt_err)?;
379 writeln!(
380 out,
381 " {ty}& {name}() {{ return {name}_; }}",
382 ty = f.cpp_ty,
383 name = f.name
384 )
385 .map_err(fmt_err)?;
386 writeln!(
387 out,
388 " void {name}(const {ty}& value) {{ {name}_ = value; }}",
389 ty = f.cpp_ty,
390 name = f.name
391 )
392 .map_err(fmt_err)?;
393 writeln!(
394 out,
395 " void {name}({ty}&& value) {{ {name}_ = std::move(value); }}",
396 ty = f.cpp_ty,
397 name = f.name
398 )
399 .map_err(fmt_err)?;
400 }
401
402 writeln!(out).map_err(fmt_err)?;
403 writeln!(out, "private:").map_err(fmt_err)?;
404 for f in s.fields {
405 writeln!(out, " {ty} {name}_{{}};", ty = f.cpp_ty, name = f.name)
406 .map_err(fmt_err)?;
407 }
408 }
409
410 writeln!(out, "}};").map_err(fmt_err)?;
411 writeln!(out).map_err(fmt_err)?;
412 Ok(())
413}
414
415fn fmt_err(_: core::fmt::Error) -> CppGenError {
416 CppGenError::Internal("string formatting failed".into())
417}
418
419#[cfg(test)]
420mod tests {
421 #![allow(clippy::expect_used, clippy::panic)]
422 use super::*;
423
424 fn render() -> String {
425 let mut s = String::new();
426 emit_status_header(&mut s).expect("emit");
427 s
428 }
429
430 #[test]
431 fn status_namespace_is_dds_core_status() {
432 let s = render();
433 assert!(s.contains("namespace dds { namespace core { namespace status {"));
434 assert!(
435 s.contains("}}} // namespace dds::core::status")
436 || s.contains("} } } // namespace dds::core::status")
437 );
438 }
439
440 #[test]
441 fn sample_lost_status_has_total_count_fields() {
442 let s = render();
443 assert!(s.contains("class SampleLostStatus {"));
444 assert!(s.contains("int32_t total_count_{};"));
445 assert!(s.contains("int32_t total_count_change_{};"));
446 }
447
448 #[test]
449 fn sample_rejected_status_has_last_reason() {
450 let s = render();
451 assert!(s.contains("class SampleRejectedStatus {"));
452 assert!(s.contains("SampleRejectedState"));
453 assert!(s.contains("last_instance_handle"));
454 }
455
456 #[test]
457 fn liveliness_changed_status_has_alive_and_not_alive() {
458 let s = render();
459 assert!(s.contains("class LivelinessChangedStatus {"));
460 assert!(s.contains("alive_count_{};"));
461 assert!(s.contains("not_alive_count_{};"));
462 assert!(s.contains("alive_count_change_{};"));
463 assert!(s.contains("not_alive_count_change_{};"));
464 }
465
466 #[test]
467 fn matched_status_has_current_count_pair() {
468 let s = render();
469 assert!(s.contains("class PublicationMatchedStatus {"));
470 assert!(s.contains("class SubscriptionMatchedStatus {"));
471 assert!(s.contains("current_count_{};"));
472 assert!(s.contains("current_count_change_{};"));
473 }
474
475 #[test]
476 fn incompatible_qos_status_has_policy_count_vector() {
477 let s = render();
478 assert!(s.contains("class RequestedIncompatibleQosStatus {"));
479 assert!(s.contains("class OfferedIncompatibleQosStatus {"));
480 assert!(s.contains("std::vector<::dds::core::status::QosPolicyCount> policies_{};"));
481 }
482
483 #[test]
484 fn marker_statuses_emit_empty_class() {
485 let s = render();
486 assert!(s.contains("class DataAvailableStatus {"));
487 assert!(s.contains("class DataOnReadersStatus {"));
488 assert!(s.contains("DataAvailableStatus() = default;"));
491 assert!(s.contains("DataOnReadersStatus() = default;"));
492 }
493
494 #[test]
495 fn reset_changes_resets_only_change_fields() {
496 let s = render();
497 let needle = "class InconsistentTopicStatus {";
499 let start = s.find(needle).expect("class header");
500 let after = &s[start..];
501 let reset_pos = after.find("reset_changes()").expect("reset method");
502 let reset_block = &after[reset_pos..];
503 let close = reset_block.find(" }").expect("end of method");
505 let body = &reset_block[..close];
506 assert!(body.contains("total_count_change_ = 0;"));
507 assert!(!body.contains("total_count_ = 0;"));
508 }
509
510 #[test]
511 fn all_thirteen_status_classes_emitted() {
512 let names = status_class_names();
513 assert_eq!(names.len(), 13);
514 let s = render();
515 for n in names {
516 let class_marker = format!("class {n} {{");
517 assert!(s.contains(&class_marker), "missing class: {n}");
518 }
519 }
520
521 #[test]
522 fn move_setter_variant_present() {
523 let s = render();
524 assert!(s.contains("void total_count(int32_t&& value)"));
526 assert!(s.contains("std::move(value);"));
527 }
528
529 #[test]
530 fn const_getter_returns_const_ref() {
531 let s = render();
532 assert!(s.contains("const int32_t& total_count() const"));
533 assert!(s.contains("int32_t& total_count() {"));
534 }
535
536 #[test]
537 fn copy_and_move_special_members_defaulted() {
538 let s = render();
539 assert!(s.contains("SampleLostStatus(const SampleLostStatus&) = default;"));
540 assert!(s.contains("SampleLostStatus(SampleLostStatus&&) noexcept = default;"));
541 }
542}