Skip to main content

zerodds_idl_csharp/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Error types for the IDL โ†’ C# codegen.
4
5use core::fmt;
6
7/// Top-level error of the C# code generator.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum CsGenError {
10    /// The IDL construct is not supported in the current foundation
11    /// scope (C5.3-a). `construct` is a short label (e.g.
12    /// `"interface"`, `"valuetype"`, `"fixed"`, `"any"`, `"map"`,
13    /// `"bitset"`, `"bitmask"`).
14    UnsupportedConstruct {
15        /// Name of the unsupported construct.
16        construct: String,
17        /// Optional: identifier name (type name or member name).
18        context: Option<String>,
19    },
20    /// Identifier collides with a reserved C# keyword.
21    /// Unlike C++, C# does not reject but escapes with an
22    /// `@` prefix (spec behavior, ยง6 IDL4-CS mapping).
23    /// This error only occurs if the name itself would still be
24    /// invalid after escaping (empty string, double `@`, ...).
25    InvalidName {
26        /// The invalid identifier.
27        name: String,
28        /// Reason for the rejection.
29        reason: String,
30    },
31    /// Inheritance cycle in the struct graph (self-reference or
32    /// indirect loop). Detected before emission.
33    InheritanceCycle {
34        /// Type name involved in the cycle.
35        type_name: String,
36    },
37    /// The generated output is internally inconsistent (should not
38    /// happen โ€” bug indicator).
39    Internal(String),
40}
41
42impl fmt::Display for CsGenError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::UnsupportedConstruct { construct, context } => match context {
46                Some(ctx) => write!(
47                    f,
48                    "unsupported IDL construct '{construct}' in '{ctx}' (C5.3-a Foundation scope)",
49                ),
50                None => write!(
51                    f,
52                    "unsupported IDL construct '{construct}' (C5.3-a Foundation scope)",
53                ),
54            },
55            Self::InvalidName { name, reason } => {
56                write!(f, "invalid identifier '{name}': {reason}")
57            }
58            Self::InheritanceCycle { type_name } => {
59                write!(f, "inheritance cycle detected at type '{type_name}'")
60            }
61            Self::Internal(msg) => write!(f, "internal codegen error: {msg}"),
62        }
63    }
64}
65
66impl std::error::Error for CsGenError {}
67
68#[cfg(test)]
69mod tests {
70    #![allow(clippy::expect_used, clippy::panic)]
71    use super::*;
72
73    #[test]
74    fn unsupported_display_has_context() {
75        let e = CsGenError::UnsupportedConstruct {
76            construct: "interface".into(),
77            context: Some("Foo".into()),
78        };
79        let s = format!("{e}");
80        assert!(s.contains("interface"));
81        assert!(s.contains("Foo"));
82    }
83
84    #[test]
85    fn unsupported_display_without_context() {
86        let e = CsGenError::UnsupportedConstruct {
87            construct: "any".into(),
88            context: None,
89        };
90        let s = format!("{e}");
91        assert!(s.contains("any"));
92    }
93
94    #[test]
95    fn invalid_name_display() {
96        let e = CsGenError::InvalidName {
97            name: "@@".into(),
98            reason: "double-escape not allowed".into(),
99        };
100        assert!(format!("{e}").contains("double-escape"));
101    }
102
103    #[test]
104    fn inheritance_cycle_display() {
105        let e = CsGenError::InheritanceCycle {
106            type_name: "Loop".into(),
107        };
108        assert!(format!("{e}").contains("Loop"));
109    }
110
111    #[test]
112    fn internal_display() {
113        let e = CsGenError::Internal("oops".into());
114        assert!(format!("{e}").contains("oops"));
115    }
116
117    #[test]
118    fn errors_are_clonable_and_eq() {
119        let e1 = CsGenError::Internal("x".into());
120        let e2 = e1.clone();
121        assert_eq!(e1, e2);
122    }
123}