Skip to main content

zlink_core/idl/
interface.rs

1//! Interface definitions for Varlink IDL.
2
3use core::fmt;
4
5use alloc::vec::Vec;
6
7#[cfg(feature = "idl-parse")]
8use crate::Error;
9
10use super::List;
11
12/// A Varlink interface definition.
13#[derive(Debug, Clone, Eq)]
14pub struct Interface<'a> {
15    /// The name of the interface in reverse-domain notation.
16    name: &'a str,
17    /// The methods of the interface.
18    methods: List<'a, super::Method<'a>>,
19    /// The custom types of the interface.
20    custom_types: List<'a, super::CustomType<'a>>,
21    /// The errors of the interface.
22    errors: List<'a, super::Error<'a>>,
23    /// The comments associated with this interface.
24    comments: List<'a, super::Comment<'a>>,
25}
26
27impl<'a> Interface<'a> {
28    /// Creates a new interface with the given name, borrowed collections, and comments.
29    pub const fn new(
30        name: &'a str,
31        methods: &'a [&'a super::Method<'a>],
32        custom_types: &'a [&'a super::CustomType<'a>],
33        errors: &'a [&'a super::Error<'a>],
34        comments: &'a [&'a super::Comment<'a>],
35    ) -> Self {
36        Self {
37            name,
38            methods: List::Borrowed(methods),
39            custom_types: List::Borrowed(custom_types),
40            errors: List::Borrowed(errors),
41            comments: List::Borrowed(comments),
42        }
43    }
44
45    /// Creates a new interface with the given name, owned collections, and comments.
46    pub fn new_owned(
47        name: &'a str,
48        methods: Vec<super::Method<'a>>,
49        custom_types: Vec<super::CustomType<'a>>,
50        errors: Vec<super::Error<'a>>,
51        comments: Vec<super::Comment<'a>>,
52    ) -> Self {
53        Self {
54            name,
55            methods: List::Owned(methods),
56            custom_types: List::Owned(custom_types),
57            errors: List::Owned(errors),
58            comments: List::from(comments),
59        }
60    }
61
62    /// Returns the name of the interface.
63    pub fn name(&self) -> &'a str {
64        self.name
65    }
66
67    /// Returns an iterator over the methods of the interface.
68    pub fn methods(&self) -> impl Iterator<Item = &super::Method<'a>> {
69        self.methods.iter()
70    }
71
72    /// Returns an iterator over the custom types of the interface.
73    pub fn custom_types(&self) -> impl Iterator<Item = &super::CustomType<'a>> {
74        self.custom_types.iter()
75    }
76
77    /// Returns an iterator over the errors of the interface.
78    pub fn errors(&self) -> impl Iterator<Item = &super::Error<'a>> {
79        self.errors.iter()
80    }
81
82    /// Returns an iterator over the comments associated with this interface.
83    pub fn comments(&self) -> impl Iterator<Item = &super::Comment<'a>> {
84        self.comments.iter()
85    }
86
87    /// Returns true if the interface has no members.
88    pub fn is_empty(&self) -> bool {
89        self.methods.is_empty() && self.custom_types.is_empty() && self.errors.is_empty()
90    }
91}
92
93impl<'a> fmt::Display for Interface<'a> {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        // Comments first
96        for comment in self.comments.iter() {
97            writeln!(f, "{comment}")?;
98        }
99        write!(f, "interface {}", self.name)?;
100        for custom_type in self.custom_types.iter() {
101            write!(f, "\n\n{custom_type}")?;
102        }
103        for method in self.methods.iter() {
104            write!(f, "\n\n{method}")?;
105        }
106        for error in self.errors.iter() {
107            write!(f, "\n\n{error}")?;
108        }
109        Ok(())
110    }
111}
112
113#[cfg(feature = "idl-parse")]
114impl<'a> TryFrom<&'a str> for Interface<'a> {
115    type Error = Error;
116
117    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
118        super::parse::parse_interface(value)
119    }
120}
121
122impl PartialEq for Interface<'_> {
123    fn eq(&self, other: &Self) -> bool {
124        self.name == other.name
125            && self.custom_types == other.custom_types
126            && self.methods == other.methods
127            && self.errors == other.errors
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::idl::{Error, Field, Method, Parameter, Type};
135    use alloc::string::String;
136
137    #[test]
138    fn org_varlink_service_interface() {
139        use crate::idl::TypeRef;
140
141        // Build the org.varlink.service interface as our test case
142        let interfaces_type = Type::Array(TypeRef::new(&Type::String));
143        let get_info_outputs = [
144            &Parameter::new("vendor", &Type::String, &[]),
145            &Parameter::new("product", &Type::String, &[]),
146            &Parameter::new("version", &Type::String, &[]),
147            &Parameter::new("url", &Type::String, &[]),
148            &Parameter::new("interfaces", &interfaces_type, &[]),
149        ];
150        let get_info = Method::new("GetInfo", &[], &get_info_outputs, &[]);
151
152        let get_interface_desc_inputs = [&Parameter::new("interface", &Type::String, &[])];
153        let get_interface_desc_outputs = [&Parameter::new("description", &Type::String, &[])];
154        let get_interface_desc = Method::new(
155            "GetInterfaceDescription",
156            &get_interface_desc_inputs,
157            &get_interface_desc_outputs,
158            &[],
159        );
160
161        let interface_not_found_fields = [&Field::new("interface", &Type::String, &[])];
162        let interface_not_found = Error::new("InterfaceNotFound", &interface_not_found_fields, &[]);
163
164        let method_not_found_fields = [&Field::new("method", &Type::String, &[])];
165        let method_not_found = Error::new("MethodNotFound", &method_not_found_fields, &[]);
166
167        let method_not_impl_fields = [&Field::new("method", &Type::String, &[])];
168        let method_not_impl = Error::new("MethodNotImplemented", &method_not_impl_fields, &[]);
169
170        let invalid_param_fields = [&Field::new("parameter", &Type::String, &[])];
171        let invalid_param = Error::new("InvalidParameter", &invalid_param_fields, &[]);
172
173        let permission_denied = Error::new("PermissionDenied", &[], &[]);
174        let expected_more = Error::new("ExpectedMore", &[], &[]);
175
176        let methods = &[&get_info, &get_interface_desc];
177        let errors = &[
178            &interface_not_found,
179            &method_not_found,
180            &method_not_impl,
181            &invalid_param,
182            &permission_denied,
183            &expected_more,
184        ];
185
186        let interface = Interface::new("org.varlink.service", methods, &[], errors, &[]);
187
188        assert_eq!(interface.name(), "org.varlink.service");
189        assert_eq!(interface.methods().count(), 2);
190        assert_eq!(interface.errors().count(), 6);
191        assert!(!interface.is_empty());
192
193        // Check method count
194        assert_eq!(interface.methods().count(), 2);
195
196        // Check error count
197        assert_eq!(interface.errors().count(), 6);
198
199        // Test Display output
200        use core::fmt::Write;
201        let mut idl = String::new();
202        write!(idl, "{}", interface).unwrap();
203        assert!(idl.as_str().starts_with("interface org.varlink.service"));
204        assert!(idl.as_str().contains("method GetInfo()"));
205        assert!(idl
206            .as_str()
207            .contains("method GetInterfaceDescription(interface: string)"));
208        assert!(idl
209            .as_str()
210            .contains("error InterfaceNotFound (interface: string)"));
211        assert!(idl.as_str().contains("error PermissionDenied ()"));
212
213        // Test parsing the official org.varlink.service IDL and compare with manually constructed
214        #[cfg(feature = "idl-parse")]
215        {
216            use crate::idl::parse;
217
218            const ORG_VARLINK_SERVICE_IDL: &str = r#"interface org.varlink.service
219
220method GetInfo() -> (
221  vendor: string,
222  product: string,
223  version: string,
224  url: string,
225  interfaces: []string
226)
227
228method GetInterfaceDescription(interface: string) -> (description: string)
229
230error InterfaceNotFound (interface: string)
231
232error MethodNotFound (method: string)
233
234error MethodNotImplemented (method: string)
235
236error InvalidParameter (parameter: string)
237
238error PermissionDenied ()
239
240error ExpectedMore ()
241"#;
242
243            let parsed_interface = parse::parse_interface(ORG_VARLINK_SERVICE_IDL)
244                .expect("Failed to parse org.varlink.service IDL");
245
246            // Compare the parsed interface with our manually constructed one
247            assert_eq!(parsed_interface, interface);
248        }
249    }
250
251    #[test]
252    fn empty_interface() {
253        let interface = Interface::new("com.example.empty", &[], &[], &[], &[]);
254        assert!(interface.is_empty());
255        assert_eq!(interface.methods().count(), 0);
256        assert_eq!(interface.errors().count(), 0);
257        assert_eq!(interface.custom_types().count(), 0);
258    }
259
260    #[cfg(feature = "idl-parse")]
261    #[test]
262    fn systemd_resolved_interface_parsing() {
263        use alloc::vec::Vec;
264
265        use crate::idl::{parse, CustomObject, CustomType, TypeRef};
266
267        // Manually construct the systemd-resolved interface for comparison.
268
269        // Define types used in the interface.
270        let optional_int_type = Type::Optional(TypeRef::new(&Type::Int));
271        let int_array_type = Type::Array(TypeRef::new(&Type::Int));
272        let optional_string_type = Type::Optional(TypeRef::new(&Type::String));
273        let optional_int_array_type = Type::Optional(TypeRef::new(&int_array_type));
274
275        // Build ResolvedAddress custom type.
276        let resolved_address_fields = [
277            &Field::new("ifindex", &optional_int_type, &[]),
278            &Field::new("family", &Type::Int, &[]),
279            &Field::new("address", &int_array_type, &[]),
280        ];
281        let resolved_address = CustomType::from(CustomObject::new(
282            "ResolvedAddress",
283            &resolved_address_fields,
284            &[],
285        ));
286
287        // Build ResolvedName custom type.
288        let resolved_name_fields = [
289            &Field::new("ifindex", &optional_int_type, &[]),
290            &Field::new("name", &Type::String, &[]),
291        ];
292        let resolved_name = CustomType::from(CustomObject::new(
293            "ResolvedName",
294            &resolved_name_fields,
295            &[],
296        ));
297
298        // Build ResourceKey custom type.
299        let resource_key_fields = [
300            &Field::new("class", &Type::Int, &[]),
301            &Field::new("type", &Type::Int, &[]),
302            &Field::new("name", &Type::String, &[]),
303        ];
304        let resource_key =
305            CustomType::from(CustomObject::new("ResourceKey", &resource_key_fields, &[]));
306
307        // Build ResourceRecord custom type (references ResourceKey).
308        let resource_key_type = Type::Custom("ResourceKey");
309        let resource_record_fields = [
310            &Field::new("key", &resource_key_type, &[]),
311            &Field::new("priority", &optional_int_type, &[]),
312            &Field::new("weight", &optional_int_type, &[]),
313            &Field::new("port", &optional_int_type, &[]),
314            &Field::new("name", &optional_string_type, &[]),
315            &Field::new("address", &optional_int_array_type, &[]),
316        ];
317        let resource_record = CustomType::from(CustomObject::new(
318            "ResourceRecord",
319            &resource_record_fields,
320            &[],
321        ));
322
323        // Build methods.
324        let resolved_address_array_type =
325            Type::Array(TypeRef::new(&Type::Custom("ResolvedAddress")));
326        let resolved_name_array_type = Type::Array(TypeRef::new(&Type::Custom("ResolvedName")));
327
328        let resolve_hostname_inputs = [
329            &Parameter::new("ifindex", &optional_int_type, &[]),
330            &Parameter::new("name", &Type::String, &[]),
331            &Parameter::new("family", &optional_int_type, &[]),
332            &Parameter::new("flags", &optional_int_type, &[]),
333        ];
334        let resolve_hostname_outputs = [
335            &Parameter::new("addresses", &resolved_address_array_type, &[]),
336            &Parameter::new("name", &Type::String, &[]),
337            &Parameter::new("flags", &Type::Int, &[]),
338        ];
339        let resolve_hostname = Method::new(
340            "ResolveHostname",
341            &resolve_hostname_inputs,
342            &resolve_hostname_outputs,
343            &[],
344        );
345
346        let resolve_address_inputs = [
347            &Parameter::new("ifindex", &optional_int_type, &[]),
348            &Parameter::new("family", &Type::Int, &[]),
349            &Parameter::new("address", &int_array_type, &[]),
350            &Parameter::new("flags", &optional_int_type, &[]),
351        ];
352        let resolve_address_outputs = [
353            &Parameter::new("names", &resolved_name_array_type, &[]),
354            &Parameter::new("flags", &Type::Int, &[]),
355        ];
356        let resolve_address = Method::new(
357            "ResolveAddress",
358            &resolve_address_inputs,
359            &resolve_address_outputs,
360            &[],
361        );
362
363        // Build errors.
364        let no_name_servers = Error::new("NoNameServers", &[], &[]);
365        let query_timed_out = Error::new("QueryTimedOut", &[], &[]);
366
367        let dnssec_validation_failed_fields = [
368            &Field::new("result", &Type::String, &[]),
369            &Field::new("extendedDNSErrorCode", &optional_int_type, &[]),
370            &Field::new("extendedDNSErrorMessage", &optional_string_type, &[]),
371        ];
372        let dnssec_validation_failed = Error::new(
373            "DNSSECValidationFailed",
374            &dnssec_validation_failed_fields,
375            &[],
376        );
377
378        let dns_error_fields = [
379            &Field::new("rcode", &Type::Int, &[]),
380            &Field::new("extendedDNSErrorCode", &optional_int_type, &[]),
381            &Field::new("extendedDNSErrorMessage", &optional_string_type, &[]),
382        ];
383        let dns_error = Error::new("DNSError", &dns_error_fields, &[]);
384
385        // Build the complete interface.
386        let custom_types = &[
387            &resolved_address,
388            &resolved_name,
389            &resource_key,
390            &resource_record,
391        ];
392        let methods = &[&resolve_hostname, &resolve_address];
393        let errors = &[
394            &no_name_servers,
395            &query_timed_out,
396            &dnssec_validation_failed,
397            &dns_error,
398        ];
399
400        let interface = Interface::new("io.systemd.Resolve", methods, custom_types, errors, &[]);
401
402        // Test parsing the IDL and compare with manually constructed interface.
403        const SYSTEMD_RESOLVED_IDL: &str = r#"interface io.systemd.Resolve
404
405type ResolvedAddress(
406    ifindex: ?int,
407    family: int,
408    address: []int
409)
410
411type ResolvedName(
412    ifindex: ?int,
413    name: string
414)
415
416type ResourceKey(
417    class: int,
418    type: int,
419    name: string
420)
421
422type ResourceRecord(
423    key: ResourceKey,
424    priority: ?int,
425    weight: ?int,
426    port: ?int,
427    name: ?string,
428    address: ?[]int
429)
430
431method ResolveHostname(
432    ifindex: ?int,
433    name: string,
434    family: ?int,
435    flags: ?int
436) -> (
437    addresses: []ResolvedAddress,
438    name: string,
439    flags: int
440)
441
442method ResolveAddress(
443    ifindex: ?int,
444    family: int,
445    address: []int,
446    flags: ?int
447) -> (
448    names: []ResolvedName,
449    flags: int
450)
451
452error NoNameServers()
453
454error QueryTimedOut()
455
456error DNSSECValidationFailed(
457    result: string,
458    extendedDNSErrorCode: ?int,
459    extendedDNSErrorMessage: ?string
460)
461
462error DNSError(
463    rcode: int,
464    extendedDNSErrorCode: ?int,
465    extendedDNSErrorMessage: ?string
466)
467"#;
468
469        let parsed_interface = parse::parse_interface(SYSTEMD_RESOLVED_IDL)
470            .expect("Failed to parse systemd-resolved interface");
471
472        // Verify basic interface properties match.
473        assert_eq!(parsed_interface.name(), interface.name());
474        assert_eq!(
475            parsed_interface.custom_types().count(),
476            interface.custom_types().count()
477        );
478        assert_eq!(
479            parsed_interface.methods().count(),
480            interface.methods().count()
481        );
482        assert_eq!(
483            parsed_interface.errors().count(),
484            interface.errors().count()
485        );
486
487        // Check specific type validation - ResolvedAddress.
488        let parsed_resolved_address = parsed_interface
489            .custom_types()
490            .find(|t| t.name() == "ResolvedAddress")
491            .expect("ResolvedAddress type should exist");
492        let manual_resolved_address = interface
493            .custom_types()
494            .find(|t| t.name() == "ResolvedAddress")
495            .expect("ResolvedAddress type should exist in manual interface");
496
497        // Verify field types in ResolvedAddress.
498        let parsed_fields: Vec<_> = parsed_resolved_address
499            .as_object()
500            .unwrap()
501            .fields()
502            .collect();
503        let manual_fields: Vec<_> = manual_resolved_address
504            .as_object()
505            .unwrap()
506            .fields()
507            .collect();
508        assert_eq!(parsed_fields.len(), manual_fields.len());
509
510        assert_eq!(parsed_fields[0].name(), "ifindex");
511        assert_eq!(
512            *parsed_fields[0].ty(),
513            Type::Optional(TypeRef::new(&Type::Int))
514        );
515        assert_eq!(parsed_fields[1].name(), "family");
516        assert_eq!(*parsed_fields[1].ty(), Type::Int);
517        assert_eq!(parsed_fields[2].name(), "address");
518        assert_eq!(
519            *parsed_fields[2].ty(),
520            Type::Array(TypeRef::new(&Type::Int))
521        );
522
523        // Check method parameter types - ResolveHostname.
524        let parsed_resolve_hostname = parsed_interface
525            .methods()
526            .find(|m| m.name() == "ResolveHostname")
527            .expect("ResolveHostname method should exist");
528        let manual_resolve_hostname = interface
529            .methods()
530            .find(|m| m.name() == "ResolveHostname")
531            .expect("ResolveHostname method should exist in manual interface");
532
533        let parsed_inputs: Vec<_> = parsed_resolve_hostname.inputs().collect();
534        let manual_inputs: Vec<_> = manual_resolve_hostname.inputs().collect();
535        assert_eq!(parsed_inputs.len(), manual_inputs.len());
536
537        // Verify input parameter types.
538        assert_eq!(parsed_inputs[0].name(), "ifindex");
539        assert_eq!(
540            *parsed_inputs[0].ty(),
541            Type::Optional(TypeRef::new(&Type::Int))
542        );
543        assert_eq!(parsed_inputs[1].name(), "name");
544        assert_eq!(*parsed_inputs[1].ty(), Type::String);
545        assert_eq!(parsed_inputs[2].name(), "family");
546        assert_eq!(
547            *parsed_inputs[2].ty(),
548            Type::Optional(TypeRef::new(&Type::Int))
549        );
550
551        // Verify output parameter types.
552        let parsed_outputs: Vec<_> = parsed_resolve_hostname.outputs().collect();
553        assert_eq!(parsed_outputs[0].name(), "addresses");
554        assert_eq!(
555            *parsed_outputs[0].ty(),
556            Type::Array(TypeRef::new(&Type::Custom("ResolvedAddress")))
557        );
558        assert_eq!(parsed_outputs[1].name(), "name");
559        assert_eq!(*parsed_outputs[1].ty(), Type::String);
560        assert_eq!(parsed_outputs[2].name(), "flags");
561        assert_eq!(*parsed_outputs[2].ty(), Type::Int);
562
563        // Check error field types - DNSError.
564        let parsed_dns_error = parsed_interface
565            .errors()
566            .find(|e| e.name() == "DNSError")
567            .expect("DNSError should exist");
568        let dns_error_fields: Vec<_> = parsed_dns_error.fields().collect();
569
570        assert_eq!(dns_error_fields[0].name(), "rcode");
571        assert_eq!(*dns_error_fields[0].ty(), Type::Int);
572        assert_eq!(dns_error_fields[1].name(), "extendedDNSErrorCode");
573        assert_eq!(
574            *dns_error_fields[1].ty(),
575            Type::Optional(TypeRef::new(&Type::Int))
576        );
577        assert_eq!(dns_error_fields[2].name(), "extendedDNSErrorMessage");
578        assert_eq!(
579            *dns_error_fields[2].ty(),
580            Type::Optional(TypeRef::new(&Type::String))
581        );
582
583        // Verify no-field errors work.
584        let parsed_no_name_servers = parsed_interface
585            .errors()
586            .find(|e| e.name() == "NoNameServers")
587            .expect("NoNameServers should exist");
588        assert_eq!(parsed_no_name_servers.fields().count(), 0);
589
590        // Compare the parsed interface with our manually constructed one.
591        assert_eq!(parsed_interface, interface);
592    }
593
594    #[test]
595    fn display_with_comments() {
596        use crate::idl::{Comment, Method};
597        use core::fmt::Write;
598
599        let comment1 = Comment::new("Interface documentation");
600        let comment2 = Comment::new("Version 1.0");
601        let interface_comments = [&comment1, &comment2];
602
603        let method_comment = Comment::new("Test method");
604        let method_comments = [&method_comment];
605        let method = Method::new("Test", &[], &[], &method_comments);
606        let methods = [&method];
607
608        let interface = Interface::new("org.example.test", &methods, &[], &[], &interface_comments);
609
610        let mut output = String::new();
611        write!(&mut output, "{}", interface).unwrap();
612
613        let expected = "# Interface documentation\n# Version 1.0\ninterface org.example.test\n\n# Test method\nmethod Test() -> ()";
614        assert_eq!(output, expected);
615    }
616
617    #[test]
618    fn comprehensive_display_with_nested_comments() {
619        use crate::idl::{
620            Comment, CustomObject, CustomType, Error, Field, Method, Parameter, Type,
621        };
622        use core::fmt::Write;
623
624        // Interface comments
625        let interface_comment = Comment::new("Comprehensive test interface");
626        let interface_comments = [&interface_comment];
627
628        // Custom type with comments
629        let type_comment = Comment::new("User data structure");
630        let type_comments = [&type_comment];
631        let name_field_comment = Comment::new("Full name");
632        let name_field_comments = [&name_field_comment];
633        let name_field = Field::new("name", &Type::String, &name_field_comments);
634        let age_field = Field::new("age", &Type::Int, &[]);
635        let fields = [&name_field, &age_field];
636        let user_object = CustomObject::new("User", &fields, &type_comments);
637        let user_type = CustomType::from(user_object);
638        let custom_types = [&user_type];
639
640        // Method with comments
641        let method_comment = Comment::new("Get user by ID");
642        let method_comments = [&method_comment];
643        let id_param_comment = Comment::new("User ID");
644        let id_param_comments = [&id_param_comment];
645        let id_param = Parameter::new("id", &Type::Int, &id_param_comments);
646        let user_param = Parameter::new("user", &Type::Custom("User"), &[]);
647        let inputs = [&id_param];
648        let outputs = [&user_param];
649        let method = Method::new("GetUser", &inputs, &outputs, &method_comments);
650        let methods = [&method];
651
652        // Error with comments
653        let error_comment = Comment::new("User not found error");
654        let error_comments = [&error_comment];
655        let msg_field = Field::new("message", &Type::String, &[]);
656        let error_fields = [&msg_field];
657        let error = Error::new("UserNotFound", &error_fields, &error_comments);
658        let errors = [&error];
659
660        let interface = Interface::new(
661            "org.example.comprehensive",
662            &methods,
663            &custom_types,
664            &errors,
665            &interface_comments,
666        );
667
668        let mut output = String::new();
669        write!(&mut output, "{}", interface).unwrap();
670
671        let expected = "# Comprehensive test interface\ninterface org.example.comprehensive\n\n# User data structure\ntype User (# Full name\nname: string, age: int)\n\n# Get user by ID\nmethod GetUser(# User ID\nid: int) -> (user: User)\n\n# User not found error\nerror UserNotFound (message: string)";
672        assert_eq!(output, expected);
673    }
674
675    #[test]
676    #[cfg(feature = "idl-parse")]
677    fn parse_and_display_round_trip_with_comments() {
678        use core::fmt::Write;
679
680        let input = r#"# Main interface documentation
681# Version 1.0
682interface org.example.test
683
684# User data structure
685# Contains basic information
686type User (# User's full name
687name: string, age: int)
688
689# Get user by ID
690# Returns user details
691method GetUser(# User identifier
692id: int) -> (user: User)
693
694# User not found error
695error UserNotFound (id: int)"#;
696
697        let parsed = Interface::try_from(input).unwrap();
698        let mut output = String::new();
699        write!(&mut output, "{}", parsed).unwrap();
700
701        // The output should exactly match the input (normalized whitespace)
702        assert_eq!(output.trim(), input.trim());
703    }
704}