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 for helper types.").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!(out, " /// Reset-on-read: sets all *_change fields to 0.").map_err(fmt_err)?;
355 writeln!(out, " void reset_changes() {{").map_err(fmt_err)?;
356 for f in s.fields {
357 if f.name.ends_with("_change") {
358 writeln!(out, " {}_ = 0;", f.name).map_err(fmt_err)?;
359 }
360 }
361 writeln!(out, " }}").map_err(fmt_err)?;
362 }
363
364 if !s.fields.is_empty() {
365 writeln!(out).map_err(fmt_err)?;
366 for f in s.fields {
368 writeln!(
369 out,
370 " const {ty}& {name}() const {{ return {name}_; }}",
371 ty = f.cpp_ty,
372 name = f.name
373 )
374 .map_err(fmt_err)?;
375 writeln!(
376 out,
377 " {ty}& {name}() {{ return {name}_; }}",
378 ty = f.cpp_ty,
379 name = f.name
380 )
381 .map_err(fmt_err)?;
382 writeln!(
383 out,
384 " void {name}(const {ty}& value) {{ {name}_ = value; }}",
385 ty = f.cpp_ty,
386 name = f.name
387 )
388 .map_err(fmt_err)?;
389 writeln!(
390 out,
391 " void {name}({ty}&& value) {{ {name}_ = std::move(value); }}",
392 ty = f.cpp_ty,
393 name = f.name
394 )
395 .map_err(fmt_err)?;
396 }
397
398 writeln!(out).map_err(fmt_err)?;
399 writeln!(out, "private:").map_err(fmt_err)?;
400 for f in s.fields {
401 writeln!(out, " {ty} {name}_{{}};", ty = f.cpp_ty, name = f.name)
402 .map_err(fmt_err)?;
403 }
404 }
405
406 writeln!(out, "}};").map_err(fmt_err)?;
407 writeln!(out).map_err(fmt_err)?;
408 Ok(())
409}
410
411fn fmt_err(_: core::fmt::Error) -> CppGenError {
412 CppGenError::Internal("string formatting failed".into())
413}
414
415#[cfg(test)]
416mod tests {
417 #![allow(clippy::expect_used, clippy::panic)]
418 use super::*;
419
420 fn render() -> String {
421 let mut s = String::new();
422 emit_status_header(&mut s).expect("emit");
423 s
424 }
425
426 #[test]
427 fn status_namespace_is_dds_core_status() {
428 let s = render();
429 assert!(s.contains("namespace dds { namespace core { namespace status {"));
430 assert!(
431 s.contains("}}} // namespace dds::core::status")
432 || s.contains("} } } // namespace dds::core::status")
433 );
434 }
435
436 #[test]
437 fn sample_lost_status_has_total_count_fields() {
438 let s = render();
439 assert!(s.contains("class SampleLostStatus {"));
440 assert!(s.contains("int32_t total_count_{};"));
441 assert!(s.contains("int32_t total_count_change_{};"));
442 }
443
444 #[test]
445 fn sample_rejected_status_has_last_reason() {
446 let s = render();
447 assert!(s.contains("class SampleRejectedStatus {"));
448 assert!(s.contains("SampleRejectedState"));
449 assert!(s.contains("last_instance_handle"));
450 }
451
452 #[test]
453 fn liveliness_changed_status_has_alive_and_not_alive() {
454 let s = render();
455 assert!(s.contains("class LivelinessChangedStatus {"));
456 assert!(s.contains("alive_count_{};"));
457 assert!(s.contains("not_alive_count_{};"));
458 assert!(s.contains("alive_count_change_{};"));
459 assert!(s.contains("not_alive_count_change_{};"));
460 }
461
462 #[test]
463 fn matched_status_has_current_count_pair() {
464 let s = render();
465 assert!(s.contains("class PublicationMatchedStatus {"));
466 assert!(s.contains("class SubscriptionMatchedStatus {"));
467 assert!(s.contains("current_count_{};"));
468 assert!(s.contains("current_count_change_{};"));
469 }
470
471 #[test]
472 fn incompatible_qos_status_has_policy_count_vector() {
473 let s = render();
474 assert!(s.contains("class RequestedIncompatibleQosStatus {"));
475 assert!(s.contains("class OfferedIncompatibleQosStatus {"));
476 assert!(s.contains("std::vector<::dds::core::status::QosPolicyCount> policies_{};"));
477 }
478
479 #[test]
480 fn marker_statuses_emit_empty_class() {
481 let s = render();
482 assert!(s.contains("class DataAvailableStatus {"));
483 assert!(s.contains("class DataOnReadersStatus {"));
484 assert!(s.contains("DataAvailableStatus() = default;"));
487 assert!(s.contains("DataOnReadersStatus() = default;"));
488 }
489
490 #[test]
491 fn reset_changes_resets_only_change_fields() {
492 let s = render();
493 let needle = "class InconsistentTopicStatus {";
495 let start = s.find(needle).expect("class header");
496 let after = &s[start..];
497 let reset_pos = after.find("reset_changes()").expect("reset method");
498 let reset_block = &after[reset_pos..];
499 let close = reset_block.find(" }").expect("end of method");
501 let body = &reset_block[..close];
502 assert!(body.contains("total_count_change_ = 0;"));
503 assert!(!body.contains("total_count_ = 0;"));
504 }
505
506 #[test]
507 fn all_thirteen_status_classes_emitted() {
508 let names = status_class_names();
509 assert_eq!(names.len(), 13);
510 let s = render();
511 for n in names {
512 let class_marker = format!("class {n} {{");
513 assert!(s.contains(&class_marker), "missing class: {n}");
514 }
515 }
516
517 #[test]
518 fn move_setter_variant_present() {
519 let s = render();
520 assert!(s.contains("void total_count(int32_t&& value)"));
522 assert!(s.contains("std::move(value);"));
523 }
524
525 #[test]
526 fn const_getter_returns_const_ref() {
527 let s = render();
528 assert!(s.contains("const int32_t& total_count() const"));
529 assert!(s.contains("int32_t& total_count() {"));
530 }
531
532 #[test]
533 fn copy_and_move_special_members_defaulted() {
534 let s = render();
535 assert!(s.contains("SampleLostStatus(const SampleLostStatus&) = default;"));
536 assert!(s.contains("SampleLostStatus(SampleLostStatus&&) noexcept = default;"));
537 }
538}