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!(
206            idl.as_str()
207                .contains("method GetInterfaceDescription(interface: string)")
208        );
209        assert!(
210            idl.as_str()
211                .contains("error InterfaceNotFound (interface: string)")
212        );
213        assert!(idl.as_str().contains("error PermissionDenied ()"));
214
215        // Test parsing the official org.varlink.service IDL and compare with manually constructed
216        #[cfg(feature = "idl-parse")]
217        {
218            use crate::idl::parse;
219
220            const ORG_VARLINK_SERVICE_IDL: &str = r#"interface org.varlink.service
221
222method GetInfo() -> (
223  vendor: string,
224  product: string,
225  version: string,
226  url: string,
227  interfaces: []string
228)
229
230method GetInterfaceDescription(interface: string) -> (description: string)
231
232error InterfaceNotFound (interface: string)
233
234error MethodNotFound (method: string)
235
236error MethodNotImplemented (method: string)
237
238error InvalidParameter (parameter: string)
239
240error PermissionDenied ()
241
242error ExpectedMore ()
243"#;
244
245            let parsed_interface = parse::parse_interface(ORG_VARLINK_SERVICE_IDL)
246                .expect("Failed to parse org.varlink.service IDL");
247
248            // Compare the parsed interface with our manually constructed one
249            assert_eq!(parsed_interface, interface);
250        }
251    }
252
253    #[test]
254    fn empty_interface() {
255        let interface = Interface::new("com.example.empty", &[], &[], &[], &[]);
256        assert!(interface.is_empty());
257        assert_eq!(interface.methods().count(), 0);
258        assert_eq!(interface.errors().count(), 0);
259        assert_eq!(interface.custom_types().count(), 0);
260    }
261
262    #[cfg(feature = "idl-parse")]
263    #[test]
264    fn systemd_resolved_interface_parsing() {
265        use alloc::vec::Vec;
266
267        use crate::idl::{CustomObject, CustomType, TypeRef, parse};
268
269        // Manually construct the systemd-resolved interface for comparison.
270
271        // Define types used in the interface.
272        let optional_int_type = Type::Optional(TypeRef::new(&Type::Int));
273        let int_array_type = Type::Array(TypeRef::new(&Type::Int));
274        let optional_string_type = Type::Optional(TypeRef::new(&Type::String));
275        let optional_int_array_type = Type::Optional(TypeRef::new(&int_array_type));
276
277        // Build ResolvedAddress custom type.
278        let resolved_address_fields = [
279            &Field::new("ifindex", &optional_int_type, &[]),
280            &Field::new("family", &Type::Int, &[]),
281            &Field::new("address", &int_array_type, &[]),
282        ];
283        let resolved_address = CustomType::from(CustomObject::new(
284            "ResolvedAddress",
285            &resolved_address_fields,
286            &[],
287        ));
288
289        // Build ResolvedName custom type.
290        let resolved_name_fields = [
291            &Field::new("ifindex", &optional_int_type, &[]),
292            &Field::new("name", &Type::String, &[]),
293        ];
294        let resolved_name = CustomType::from(CustomObject::new(
295            "ResolvedName",
296            &resolved_name_fields,
297            &[],
298        ));
299
300        // Build ResourceKey custom type.
301        let resource_key_fields = [
302            &Field::new("class", &Type::Int, &[]),
303            &Field::new("type", &Type::Int, &[]),
304            &Field::new("name", &Type::String, &[]),
305        ];
306        let resource_key =
307            CustomType::from(CustomObject::new("ResourceKey", &resource_key_fields, &[]));
308
309        // Build ResourceRecord custom type (references ResourceKey).
310        let resource_key_type = Type::Custom("ResourceKey");
311        let resource_record_fields = [
312            &Field::new("key", &resource_key_type, &[]),
313            &Field::new("priority", &optional_int_type, &[]),
314            &Field::new("weight", &optional_int_type, &[]),
315            &Field::new("port", &optional_int_type, &[]),
316            &Field::new("name", &optional_string_type, &[]),
317            &Field::new("address", &optional_int_array_type, &[]),
318        ];
319        let resource_record = CustomType::from(CustomObject::new(
320            "ResourceRecord",
321            &resource_record_fields,
322            &[],
323        ));
324
325        // Build methods.
326        let resolved_address_array_type =
327            Type::Array(TypeRef::new(&Type::Custom("ResolvedAddress")));
328        let resolved_name_array_type = Type::Array(TypeRef::new(&Type::Custom("ResolvedName")));
329
330        let resolve_hostname_inputs = [
331            &Parameter::new("ifindex", &optional_int_type, &[]),
332            &Parameter::new("name", &Type::String, &[]),
333            &Parameter::new("family", &optional_int_type, &[]),
334            &Parameter::new("flags", &optional_int_type, &[]),
335        ];
336        let resolve_hostname_outputs = [
337            &Parameter::new("addresses", &resolved_address_array_type, &[]),
338            &Parameter::new("name", &Type::String, &[]),
339            &Parameter::new("flags", &Type::Int, &[]),
340        ];
341        let resolve_hostname = Method::new(
342            "ResolveHostname",
343            &resolve_hostname_inputs,
344            &resolve_hostname_outputs,
345            &[],
346        );
347
348        let resolve_address_inputs = [
349            &Parameter::new("ifindex", &optional_int_type, &[]),
350            &Parameter::new("family", &Type::Int, &[]),
351            &Parameter::new("address", &int_array_type, &[]),
352            &Parameter::new("flags", &optional_int_type, &[]),
353        ];
354        let resolve_address_outputs = [
355            &Parameter::new("names", &resolved_name_array_type, &[]),
356            &Parameter::new("flags", &Type::Int, &[]),
357        ];
358        let resolve_address = Method::new(
359            "ResolveAddress",
360            &resolve_address_inputs,
361            &resolve_address_outputs,
362            &[],
363        );
364
365        // Build errors.
366        let no_name_servers = Error::new("NoNameServers", &[], &[]);
367        let query_timed_out = Error::new("QueryTimedOut", &[], &[]);
368
369        let dnssec_validation_failed_fields = [
370            &Field::new("result", &Type::String, &[]),
371            &Field::new("extendedDNSErrorCode", &optional_int_type, &[]),
372            &Field::new("extendedDNSErrorMessage", &optional_string_type, &[]),
373        ];
374        let dnssec_validation_failed = Error::new(
375            "DNSSECValidationFailed",
376            &dnssec_validation_failed_fields,
377            &[],
378        );
379
380        let dns_error_fields = [
381            &Field::new("rcode", &Type::Int, &[]),
382            &Field::new("extendedDNSErrorCode", &optional_int_type, &[]),
383            &Field::new("extendedDNSErrorMessage", &optional_string_type, &[]),
384        ];
385        let dns_error = Error::new("DNSError", &dns_error_fields, &[]);
386
387        // Build the complete interface.
388        let custom_types = &[
389            &resolved_address,
390            &resolved_name,
391            &resource_key,
392            &resource_record,
393        ];
394        let methods = &[&resolve_hostname, &resolve_address];
395        let errors = &[
396            &no_name_servers,
397            &query_timed_out,
398            &dnssec_validation_failed,
399            &dns_error,
400        ];
401
402        let interface = Interface::new("io.systemd.Resolve", methods, custom_types, errors, &[]);
403
404        // Test parsing the IDL and compare with manually constructed interface.
405        const SYSTEMD_RESOLVED_IDL: &str = r#"interface io.systemd.Resolve
406
407type ResolvedAddress(
408    ifindex: ?int,
409    family: int,
410    address: []int
411)
412
413type ResolvedName(
414    ifindex: ?int,
415    name: string
416)
417
418type ResourceKey(
419    class: int,
420    type: int,
421    name: string
422)
423
424type ResourceRecord(
425    key: ResourceKey,
426    priority: ?int,
427    weight: ?int,
428    port: ?int,
429    name: ?string,
430    address: ?[]int
431)
432
433method ResolveHostname(
434    ifindex: ?int,
435    name: string,
436    family: ?int,
437    flags: ?int
438) -> (
439    addresses: []ResolvedAddress,
440    name: string,
441    flags: int
442)
443
444method ResolveAddress(
445    ifindex: ?int,
446    family: int,
447    address: []int,
448    flags: ?int
449) -> (
450    names: []ResolvedName,
451    flags: int
452)
453
454error NoNameServers()
455
456error QueryTimedOut()
457
458error DNSSECValidationFailed(
459    result: string,
460    extendedDNSErrorCode: ?int,
461    extendedDNSErrorMessage: ?string
462)
463
464error DNSError(
465    rcode: int,
466    extendedDNSErrorCode: ?int,
467    extendedDNSErrorMessage: ?string
468)
469"#;
470
471        let parsed_interface = parse::parse_interface(SYSTEMD_RESOLVED_IDL)
472            .expect("Failed to parse systemd-resolved interface");
473
474        // Verify basic interface properties match.
475        assert_eq!(parsed_interface.name(), interface.name());
476        assert_eq!(
477            parsed_interface.custom_types().count(),
478            interface.custom_types().count()
479        );
480        assert_eq!(
481            parsed_interface.methods().count(),
482            interface.methods().count()
483        );
484        assert_eq!(
485            parsed_interface.errors().count(),
486            interface.errors().count()
487        );
488
489        // Check specific type validation - ResolvedAddress.
490        let parsed_resolved_address = parsed_interface
491            .custom_types()
492            .find(|t| t.name() == "ResolvedAddress")
493            .expect("ResolvedAddress type should exist");
494        let manual_resolved_address = interface
495            .custom_types()
496            .find(|t| t.name() == "ResolvedAddress")
497            .expect("ResolvedAddress type should exist in manual interface");
498
499        // Verify field types in ResolvedAddress.
500        let parsed_fields: Vec<_> = parsed_resolved_address
501            .as_object()
502            .unwrap()
503            .fields()
504            .collect();
505        let manual_fields: Vec<_> = manual_resolved_address
506            .as_object()
507            .unwrap()
508            .fields()
509            .collect();
510        assert_eq!(parsed_fields.len(), manual_fields.len());
511
512        assert_eq!(parsed_fields[0].name(), "ifindex");
513        assert_eq!(
514            *parsed_fields[0].ty(),
515            Type::Optional(TypeRef::new(&Type::Int))
516        );
517        assert_eq!(parsed_fields[1].name(), "family");
518        assert_eq!(*parsed_fields[1].ty(), Type::Int);
519        assert_eq!(parsed_fields[2].name(), "address");
520        assert_eq!(
521            *parsed_fields[2].ty(),
522            Type::Array(TypeRef::new(&Type::Int))
523        );
524
525        // Check method parameter types - ResolveHostname.
526        let parsed_resolve_hostname = parsed_interface
527            .methods()
528            .find(|m| m.name() == "ResolveHostname")
529            .expect("ResolveHostname method should exist");
530        let manual_resolve_hostname = interface
531            .methods()
532            .find(|m| m.name() == "ResolveHostname")
533            .expect("ResolveHostname method should exist in manual interface");
534
535        let parsed_inputs: Vec<_> = parsed_resolve_hostname.inputs().collect();
536        let manual_inputs: Vec<_> = manual_resolve_hostname.inputs().collect();
537        assert_eq!(parsed_inputs.len(), manual_inputs.len());
538
539        // Verify input parameter types.
540        assert_eq!(parsed_inputs[0].name(), "ifindex");
541        assert_eq!(
542            *parsed_inputs[0].ty(),
543            Type::Optional(TypeRef::new(&Type::Int))
544        );
545        assert_eq!(parsed_inputs[1].name(), "name");
546        assert_eq!(*parsed_inputs[1].ty(), Type::String);
547        assert_eq!(parsed_inputs[2].name(), "family");
548        assert_eq!(
549            *parsed_inputs[2].ty(),
550            Type::Optional(TypeRef::new(&Type::Int))
551        );
552
553        // Verify output parameter types.
554        let parsed_outputs: Vec<_> = parsed_resolve_hostname.outputs().collect();
555        assert_eq!(parsed_outputs[0].name(), "addresses");
556        assert_eq!(
557            *parsed_outputs[0].ty(),
558            Type::Array(TypeRef::new(&Type::Custom("ResolvedAddress")))
559        );
560        assert_eq!(parsed_outputs[1].name(), "name");
561        assert_eq!(*parsed_outputs[1].ty(), Type::String);
562        assert_eq!(parsed_outputs[2].name(), "flags");
563        assert_eq!(*parsed_outputs[2].ty(), Type::Int);
564
565        // Check error field types - DNSError.
566        let parsed_dns_error = parsed_interface
567            .errors()
568            .find(|e| e.name() == "DNSError")
569            .expect("DNSError should exist");
570        let dns_error_fields: Vec<_> = parsed_dns_error.fields().collect();
571
572        assert_eq!(dns_error_fields[0].name(), "rcode");
573        assert_eq!(*dns_error_fields[0].ty(), Type::Int);
574        assert_eq!(dns_error_fields[1].name(), "extendedDNSErrorCode");
575        assert_eq!(
576            *dns_error_fields[1].ty(),
577            Type::Optional(TypeRef::new(&Type::Int))
578        );
579        assert_eq!(dns_error_fields[2].name(), "extendedDNSErrorMessage");
580        assert_eq!(
581            *dns_error_fields[2].ty(),
582            Type::Optional(TypeRef::new(&Type::String))
583        );
584
585        // Verify no-field errors work.
586        let parsed_no_name_servers = parsed_interface
587            .errors()
588            .find(|e| e.name() == "NoNameServers")
589            .expect("NoNameServers should exist");
590        assert_eq!(parsed_no_name_servers.fields().count(), 0);
591
592        // Compare the parsed interface with our manually constructed one.
593        assert_eq!(parsed_interface, interface);
594    }
595
596    #[test]
597    fn display_with_comments() {
598        use crate::idl::{Comment, Method};
599        use core::fmt::Write;
600
601        let comment1 = Comment::new("Interface documentation");
602        let comment2 = Comment::new("Version 1.0");
603        let interface_comments = [&comment1, &comment2];
604
605        let method_comment = Comment::new("Test method");
606        let method_comments = [&method_comment];
607        let method = Method::new("Test", &[], &[], &method_comments);
608        let methods = [&method];
609
610        let interface = Interface::new("org.example.test", &methods, &[], &[], &interface_comments);
611
612        let mut output = String::new();
613        write!(&mut output, "{}", interface).unwrap();
614
615        let expected = "# Interface documentation\n# Version 1.0\ninterface org.example.test\n\n# Test method\nmethod Test() -> ()";
616        assert_eq!(output, expected);
617    }
618
619    #[test]
620    fn comprehensive_display_with_nested_comments() {
621        use crate::idl::{
622            Comment, CustomObject, CustomType, Error, Field, Method, Parameter, Type,
623        };
624        use core::fmt::Write;
625
626        // Interface comments
627        let interface_comment = Comment::new("Comprehensive test interface");
628        let interface_comments = [&interface_comment];
629
630        // Custom type with comments
631        let type_comment = Comment::new("User data structure");
632        let type_comments = [&type_comment];
633        let name_field_comment = Comment::new("Full name");
634        let name_field_comments = [&name_field_comment];
635        let name_field = Field::new("name", &Type::String, &name_field_comments);
636        let age_field = Field::new("age", &Type::Int, &[]);
637        let fields = [&name_field, &age_field];
638        let user_object = CustomObject::new("User", &fields, &type_comments);
639        let user_type = CustomType::from(user_object);
640        let custom_types = [&user_type];
641
642        // Method with comments
643        let method_comment = Comment::new("Get user by ID");
644        let method_comments = [&method_comment];
645        let id_param_comment = Comment::new("User ID");
646        let id_param_comments = [&id_param_comment];
647        let id_param = Parameter::new("id", &Type::Int, &id_param_comments);
648        let user_param = Parameter::new("user", &Type::Custom("User"), &[]);
649        let inputs = [&id_param];
650        let outputs = [&user_param];
651        let method = Method::new("GetUser", &inputs, &outputs, &method_comments);
652        let methods = [&method];
653
654        // Error with comments
655        let error_comment = Comment::new("User not found error");
656        let error_comments = [&error_comment];
657        let msg_field = Field::new("message", &Type::String, &[]);
658        let error_fields = [&msg_field];
659        let error = Error::new("UserNotFound", &error_fields, &error_comments);
660        let errors = [&error];
661
662        let interface = Interface::new(
663            "org.example.comprehensive",
664            &methods,
665            &custom_types,
666            &errors,
667            &interface_comments,
668        );
669
670        let mut output = String::new();
671        write!(&mut output, "{}", interface).unwrap();
672
673        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)";
674        assert_eq!(output, expected);
675    }
676
677    #[test]
678    #[cfg(feature = "idl-parse")]
679    fn parse_and_display_round_trip_with_comments() {
680        use core::fmt::Write;
681
682        let input = r#"# Main interface documentation
683# Version 1.0
684interface org.example.test
685
686# User data structure
687# Contains basic information
688type User (# User's full name
689name: string, age: int)
690
691# Get user by ID
692# Returns user details
693method GetUser(# User identifier
694id: int) -> (user: User)
695
696# User not found error
697error UserNotFound (id: int)"#;
698
699        let parsed = Interface::try_from(input).unwrap();
700        let mut output = String::new();
701        write!(&mut output, "{}", parsed).unwrap();
702
703        // The output should exactly match the input (normalized whitespace)
704        assert_eq!(output.trim(), input.trim());
705    }
706}