Skip to main content

zlink_idl/
interface.rs

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