Skip to main content

zerodds_dlrl_codegen/
ts.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! TypeScript-Codegen — DDS-XRCE 1.0 + DDS-TS 1.0 (Annex Web-PSM).
5
6use alloc::format;
7use alloc::string::String;
8
9use crate::DlrlTypeInfo;
10
11/// Erzeugt ein TypeScript-Interface mit Key-Markern (`__dlrlKey: true`).
12#[allow(clippy::format_collect)]
13#[must_use]
14pub fn generate_ts_interface(info: &DlrlTypeInfo) -> String {
15    let cls = simple_name(&info.name);
16    let key_lines: String = info
17        .keys
18        .iter()
19        .map(|k| format!("  /** DLRL key field. */ {k}: number;\n"))
20        .collect();
21    let rel_lines: String = info
22        .relations
23        .iter()
24        .map(|(rel, target)| {
25            let t = simple_name(target);
26            format!("  /** DLRL relation → {t} */ {rel}: ReadonlyArray<{t}>;\n")
27        })
28        .collect();
29    format!(
30        "// Generated DLRL Interface — Spec §B.6 (TS PSM)\n\
31export interface {cls} {{\n{key_lines}{rel_lines}}}\n"
32    )
33}
34
35/// Erzeugt eine TypeScript-Class mit Home-Factory-Stub.
36#[allow(clippy::format_collect)]
37#[must_use]
38pub fn generate_ts_class(info: &DlrlTypeInfo) -> String {
39    let cls = simple_name(&info.name);
40    let key_lines: String = info
41        .keys
42        .iter()
43        .map(|k| format!("  {k}: number = 0;\n"))
44        .collect();
45    let rel_lines: String = info
46        .relations
47        .iter()
48        .map(|(rel, target)| {
49            let t = simple_name(target);
50            format!("  {rel}: {t}[] = [];\n")
51        })
52        .collect();
53    format!(
54        "// Generated DLRL Class — Spec §B.6 (TS PSM)\n\
55export class {cls}Impl implements {cls} {{\n{key_lines}{rel_lines}}}\n\n\
56export class {cls}Home {{\n  create(): {cls}Impl {{ return new {cls}Impl(); }}\n  destroy(_obj: {cls}Impl): void {{}}\n}}\n"
57    )
58}
59
60fn simple_name(scoped: &str) -> &str {
61    scoped.rsplit("::").next().unwrap_or(scoped)
62}
63
64#[cfg(test)]
65#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
66mod tests {
67    use super::*;
68
69    fn trade_info() -> DlrlTypeInfo {
70        DlrlTypeInfo {
71            name: "demo::Trade".into(),
72            keys: alloc::vec!["symbol".into()],
73            relations: alloc::vec![("quotes".into(), "demo::Quote".into())],
74        }
75    }
76
77    #[test]
78    fn ts_interface_emits_keys_and_relations() {
79        let s = generate_ts_interface(&trade_info());
80        assert!(s.contains("export interface Trade"));
81        assert!(s.contains("symbol: number;"));
82        assert!(s.contains("quotes: ReadonlyArray<Quote>;"));
83    }
84
85    #[test]
86    fn ts_class_emits_impl_and_home() {
87        let s = generate_ts_class(&trade_info());
88        assert!(s.contains("class TradeImpl implements Trade"));
89        assert!(s.contains("symbol: number = 0;"));
90        assert!(s.contains("quotes: Quote[] = [];"));
91        assert!(s.contains("class TradeHome"));
92        assert!(s.contains("create(): TradeImpl"));
93        assert!(s.contains("destroy(_obj: TradeImpl): void"));
94    }
95}