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!(
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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let interface_comment = Comment::new("Comprehensive test interface");
628 let interface_comments = [&interface_comment];
629
630 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 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 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 assert_eq!(output.trim(), input.trim());
705 }
706}