1use core::fmt;
4
5use alloc::vec::Vec;
6
7#[cfg(feature = "idl-parse")]
8use crate::Error;
9
10use super::List;
11
12#[derive(Debug, Clone, Eq)]
14pub struct Interface<'a> {
15 name: &'a str,
17 methods: List<'a, super::Method<'a>>,
19 custom_types: List<'a, super::CustomType<'a>>,
21 errors: List<'a, super::Error<'a>>,
23 comments: List<'a, super::Comment<'a>>,
25}
26
27impl<'a> Interface<'a> {
28 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 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 pub fn name(&self) -> &'a str {
64 self.name
65 }
66
67 pub fn methods(&self) -> impl Iterator<Item = &super::Method<'a>> {
69 self.methods.iter()
70 }
71
72 pub fn custom_types(&self) -> impl Iterator<Item = &super::CustomType<'a>> {
74 self.custom_types.iter()
75 }
76
77 pub fn errors(&self) -> impl Iterator<Item = &super::Error<'a>> {
79 self.errors.iter()
80 }
81
82 pub fn comments(&self) -> impl Iterator<Item = &super::Comment<'a>> {
84 self.comments.iter()
85 }
86
87 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 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 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 assert_eq!(interface.methods().count(), 2);
195
196 assert_eq!(interface.errors().count(), 6);
198
199 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let interface_comment = Comment::new("Comprehensive test interface");
626 let interface_comments = [&interface_comment];
627
628 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 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 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 assert_eq!(output.trim(), input.trim());
703 }
704}