1use core::fmt;
4
5use alloc::vec::Vec;
6
7use super::List;
8
9#[derive(Debug, Clone, Eq)]
11pub struct Interface<'a> {
12 name: &'a str,
14 methods: List<'a, super::Method<'a>>,
16 custom_types: List<'a, super::CustomType<'a>>,
18 errors: List<'a, super::Error<'a>>,
20 comments: List<'a, super::Comment<'a>>,
22}
23
24impl<'a> Interface<'a> {
25 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 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 pub fn name(&self) -> &'a str {
61 self.name
62 }
63
64 pub fn methods(&self) -> impl Iterator<Item = &super::Method<'a>> {
66 self.methods.iter()
67 }
68
69 pub fn custom_types(&self) -> impl Iterator<Item = &super::CustomType<'a>> {
71 self.custom_types.iter()
72 }
73
74 pub fn errors(&self) -> impl Iterator<Item = &super::Error<'a>> {
76 self.errors.iter()
77 }
78
79 pub fn comments(&self) -> impl Iterator<Item = &super::Comment<'a>> {
81 self.comments.iter()
82 }
83
84 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 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 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 assert_eq!(interface.methods().count(), 2);
192
193 assert_eq!(interface.errors().count(), 6);
195
196 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let interface_comment = Comment::new("Comprehensive test interface");
623 let interface_comments = [&interface_comment];
624
625 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 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 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 assert_eq!(output.trim(), input.trim());
700 }
701}