Skip to main content

zlink_idl/type/
mod.rs

1//! Type definitions for Varlink IDL.
2
3mod type_ref;
4pub use type_ref::TypeRef;
5
6use core::fmt;
7
8use super::{EnumVariant, Field, List};
9
10/// Represents a type in Varlink IDL.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Type<'a> {
13    /// Boolean type.
14    Bool,
15    /// 64-bit signed integer.
16    Int,
17    /// 64-bit floating point.
18    Float,
19    /// UTF-8 string.
20    String,
21    /// Foreign untyped object.
22    ForeignObject,
23    /// Any JSON value (systemd extension).
24    Any,
25    /// Optional/nullable type.
26    Optional(TypeRef<'a>),
27    /// Array type.
28    Array(TypeRef<'a>),
29    /// Map type with string keys.
30    Map(TypeRef<'a>),
31    /// Custom named type reference.
32    Custom(&'a str),
33    /// Inline enum type.
34    Enum(List<'a, EnumVariant<'a>>),
35    /// Inline struct type.
36    Object(List<'a, Field<'a>>),
37}
38
39impl<'a> Type<'a> {
40    /// The child type if this type is optional.
41    pub const fn as_optional(&self) -> Option<&TypeRef<'a>> {
42        match self {
43            Type::Optional(optional) => Some(optional),
44            _ => None,
45        }
46    }
47
48    /// The array element type if this type is an array.
49    pub const fn as_array(&self) -> Option<&TypeRef<'a>> {
50        match self {
51            Type::Array(array) => Some(array),
52            _ => None,
53        }
54    }
55
56    /// The map value type if this type is a map.
57    pub const fn as_map(&self) -> Option<&TypeRef<'a>> {
58        match self {
59            Type::Map(map) => Some(map),
60            _ => None,
61        }
62    }
63
64    /// The custom type name if this type is a custom type.
65    pub const fn as_custom(&self) -> Option<&'a str> {
66        match self {
67            Type::Custom(custom) => Some(custom),
68            _ => None,
69        }
70    }
71
72    /// The enum variants if this type is an enum.
73    pub const fn as_enum(&self) -> Option<&List<'a, EnumVariant<'a>>> {
74        match self {
75            Type::Enum(variants) => Some(variants),
76            _ => None,
77        }
78    }
79
80    /// The object fields if this type is an object.
81    pub const fn as_object(&self) -> Option<&List<'a, Field<'a>>> {
82        match self {
83            Type::Object(fields) => Some(fields),
84            _ => None,
85        }
86    }
87}
88
89impl<'a> fmt::Display for Type<'a> {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            Type::Bool => write!(f, "bool"),
93            Type::Int => write!(f, "int"),
94            Type::Float => write!(f, "float"),
95            Type::String => write!(f, "string"),
96            Type::ForeignObject => write!(f, "object"),
97            Type::Any => write!(f, "any"),
98            Type::Optional(optional) => write!(f, "?{optional}"),
99            Type::Array(array) => write!(f, "[]{array}"),
100            Type::Map(map) => write!(f, "[string]{map}"),
101            Type::Custom(name) => write!(f, "{name}"),
102            Type::Enum(variants) => {
103                // Check if any variant has comments to determine formatting
104                let has_variant_comments = variants.iter().any(|v| v.has_comments());
105
106                if has_variant_comments {
107                    // Multi-line format when any variant has comments
108                    writeln!(f, "(")?;
109                    for variant in variants.iter() {
110                        // Write comments first
111                        for comment in variant.comments() {
112                            writeln!(f, "\t{}", comment)?;
113                        }
114                        // Then write the variant name
115                        writeln!(f, "\t{}", variant.name())?;
116                    }
117                    write!(f, ")")
118                } else {
119                    // Single-line format when no variants have comments
120                    write!(f, "(")?;
121                    let mut first = true;
122                    for variant in variants.iter() {
123                        if !first {
124                            write!(f, ", ")?;
125                        }
126                        first = false;
127                        write!(f, "{}", variant)?;
128                    }
129                    write!(f, ")")
130                }
131            }
132            Type::Object(fields) => {
133                write!(f, "(")?;
134                let mut first = true;
135                for field in fields.iter() {
136                    if !first {
137                        write!(f, ", ")?;
138                    }
139                    first = false;
140                    write!(f, "{field}")?;
141                }
142                write!(f, ")")
143            }
144        }
145    }
146}
147
148impl<'a> PartialEq<TypeRef<'a>> for Type<'a> {
149    fn eq(&self, other: &TypeRef<'a>) -> bool {
150        self == other.inner()
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use alloc::{string::ToString, vec};
157
158    use super::*;
159    use crate::{Comment, EnumVariant};
160
161    #[test]
162    fn type_names() {
163        use core::fmt::Write;
164        let mut buf = String::new();
165
166        buf.clear();
167        write!(buf, "{}", Type::Bool).unwrap();
168        assert_eq!(buf, "bool");
169
170        buf.clear();
171        write!(buf, "{}", Type::Int).unwrap();
172        assert_eq!(buf, "int");
173
174        buf.clear();
175        write!(buf, "{}", Type::Float).unwrap();
176        assert_eq!(buf, "float");
177
178        buf.clear();
179        write!(buf, "{}", Type::String).unwrap();
180        assert_eq!(buf, "string");
181
182        buf.clear();
183        write!(buf, "{}", Type::ForeignObject).unwrap();
184        assert_eq!(buf, "object");
185
186        buf.clear();
187        write!(buf, "{}", Type::Any).unwrap();
188        assert_eq!(buf, "any");
189    }
190
191    #[test]
192    fn complex_type_names() {
193        // Test with const-friendly borrowed variants
194        const INT_TYPE: Type<'static> = Type::Int;
195        const STRING_TYPE: Type<'static> = Type::String;
196        const BOOL_TYPE: Type<'static> = Type::Bool;
197
198        use core::fmt::Write;
199        let mut buf = String::new();
200
201        buf.clear();
202        write!(buf, "{}", Type::Optional(TypeRef::new(&INT_TYPE))).unwrap();
203        assert_eq!(buf, "?int");
204
205        buf.clear();
206        write!(buf, "{}", Type::Array(TypeRef::new(&STRING_TYPE))).unwrap();
207        assert_eq!(buf, "[]string");
208
209        buf.clear();
210        write!(buf, "{}", Type::Map(TypeRef::new(&BOOL_TYPE))).unwrap();
211        assert_eq!(buf, "[string]bool");
212
213        // Test with owned variants
214        assert_eq!(
215            Type::Optional(TypeRef::new_owned(Type::Int)).to_string(),
216            "?int"
217        );
218        assert_eq!(
219            Type::Array(TypeRef::new_owned(Type::String)).to_string(),
220            "[]string"
221        );
222
223        // Test complex nested types
224        let nested_type = Type::Array(TypeRef::new_owned(Type::Optional(TypeRef::new_owned(
225            Type::String,
226        ))));
227        assert_eq!(nested_type.to_string(), "[]?string");
228
229        // Test inline enum
230        let enum_type = Type::Enum(List::from(vec![
231            EnumVariant::new("one", &[]),
232            EnumVariant::new("two", &[]),
233            EnumVariant::new("three", &[]),
234        ]));
235        assert_eq!(enum_type.to_string(), "(one, two, three)");
236
237        // Test inline struct
238        let struct_type = Type::Object(List::from(vec![
239            Field::new("first", &Type::Int, &[]),
240            Field::new("second", &Type::String, &[]),
241        ]));
242        assert_eq!(struct_type.to_string(), "(first: int, second: string)");
243    }
244
245    #[test]
246    fn inline_enum_formatting_with_and_without_comments() {
247        use core::fmt::Write;
248
249        // Test single-line format when no variants have comments
250        let var1 = EnumVariant::new("red", &[]);
251        let var2 = EnumVariant::new("green", &[]);
252        let var3 = EnumVariant::new("blue", &[]);
253        let variants_no_comments = [&var1, &var2, &var3];
254        let enum_no_comments = Type::Enum(List::from(&variants_no_comments[..]));
255
256        let mut buf = String::new();
257        write!(buf, "{}", enum_no_comments).unwrap();
258        assert_eq!(buf, "(red, green, blue)");
259
260        // Test multi-line format when any variant has comments
261        let comment_refs = [&Comment::new("Primary color")];
262        let var_with_comment = EnumVariant::new("red", &comment_refs);
263        let var_without_comment1 = EnumVariant::new("green", &[]);
264        let var_without_comment2 = EnumVariant::new("blue", &[]);
265        let variants_with_comments = [
266            &var_with_comment,
267            &var_without_comment1,
268            &var_without_comment2,
269        ];
270        let enum_with_comments = Type::Enum(List::from(&variants_with_comments[..]));
271
272        let mut buf = String::new();
273        write!(buf, "{}", enum_with_comments).unwrap();
274        assert_eq!(buf, "(\n\t# Primary color\n\tred\n\tgreen\n\tblue\n)");
275    }
276}