Skip to main content

zerodds_dlrl_codegen/
csharp.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! C#-Codegen — DDS 1.4 §B.4 (analog C++ PSM, dotnet-Form).
5
6use alloc::format;
7use alloc::string::String;
8
9use crate::DlrlTypeInfo;
10
11/// Erzeugt eine `partial class` mit `[DlrlObject]`-Attribut.
12#[allow(clippy::format_collect)]
13#[must_use]
14pub fn generate_csharp_partial(info: &DlrlTypeInfo) -> String {
15    let cls = simple_name(&info.name);
16    let ns = namespace(&info.name);
17    let key_attrs: String = info
18        .keys
19        .iter()
20        .map(|k| format!("    [DlrlKey] public long {k} {{ get; set; }}\n"))
21        .collect();
22    let rel_attrs: String = info
23        .relations
24        .iter()
25        .map(|(rel, target)| {
26            let t = simple_name(target);
27            format!(
28                "    [DlrlRelation(typeof({t}))] public RefList<{t}> {rel} {{ get; }} = new RefList<{t}>();\n"
29            )
30        })
31        .collect();
32    let body = format!(
33        "[DlrlObject]\npublic partial class {cls} : ObjectRoot\n{{\n{key_attrs}{rel_attrs}}}\n"
34    );
35    if ns.is_empty() {
36        body
37    } else {
38        format!("namespace {ns}\n{{\n{body}}}\n")
39    }
40}
41
42/// Erzeugt zusaetzlich eine separate Object-Class (komplett, nicht
43/// `partial`) — fuer Caller, die das Type nicht user-anchor brauchen.
44#[allow(clippy::format_collect)]
45#[must_use]
46pub fn generate_csharp_object(info: &DlrlTypeInfo) -> String {
47    let cls = simple_name(&info.name);
48    let key_lines: String = info
49        .keys
50        .iter()
51        .map(|k| format!("    public long {k};\n"))
52        .collect();
53    format!(
54        "// Generated DLRL Object — Spec §B.6\n\
55[DlrlObject]\npublic class {cls} : ObjectRoot\n{{\n{key_lines}}}\n"
56    )
57}
58
59fn simple_name(scoped: &str) -> &str {
60    scoped.rsplit("::").next().unwrap_or(scoped)
61}
62
63fn namespace(scoped: &str) -> String {
64    let parts: alloc::vec::Vec<&str> = scoped.split("::").collect();
65    if parts.len() <= 1 {
66        return String::new();
67    }
68    parts[..parts.len() - 1].join(".")
69}
70
71#[cfg(test)]
72#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
73mod tests {
74    use super::*;
75
76    fn trade_info() -> DlrlTypeInfo {
77        DlrlTypeInfo {
78            name: "demo::Trade".into(),
79            keys: alloc::vec!["symbol".into()],
80            relations: alloc::vec![("quotes".into(), "demo::Quote".into())],
81        }
82    }
83
84    #[test]
85    fn csharp_partial_emits_namespace() {
86        let s = generate_csharp_partial(&trade_info());
87        assert!(s.contains("namespace demo"));
88        assert!(s.contains("[DlrlObject]"));
89        assert!(s.contains("partial class Trade : ObjectRoot"));
90        assert!(s.contains("[DlrlKey] public long symbol"));
91        assert!(s.contains("[DlrlRelation(typeof(Quote))]"));
92        assert!(s.contains("RefList<Quote> quotes"));
93    }
94
95    #[test]
96    fn csharp_partial_no_namespace_for_unscoped() {
97        let info = DlrlTypeInfo {
98            name: "Foo".into(),
99            ..DlrlTypeInfo::default()
100        };
101        let s = generate_csharp_partial(&info);
102        assert!(!s.contains("namespace"));
103        assert!(s.contains("class Foo : ObjectRoot"));
104    }
105
106    #[test]
107    fn csharp_object_emits_keys() {
108        let s = generate_csharp_object(&trade_info());
109        assert!(s.contains("public long symbol;"));
110    }
111
112    #[test]
113    fn namespace_for_multi_level() {
114        assert_eq!(namespace("a::b::C"), "a.b");
115        assert_eq!(namespace("X"), "");
116    }
117}