diff --git a/score/mw/com/example/com-api-example/basic-consumer-producer.rs b/score/mw/com/example/com-api-example/basic-consumer-producer.rs index 33a06e562..2c53fe85e 100644 --- a/score/mw/com/example/com-api-example/basic-consumer-producer.rs +++ b/score/mw/com/example/com-api-example/basic-consumer-producer.rs @@ -12,12 +12,19 @@ ********************************************************************************/ use com_api::{ - Builder, Error, FindServiceSpecifier, InstanceSpecifier, LolaRuntimeBuilderImpl, + Builder, Error, FindServiceSpecifier, InstanceSpecifier, Interface, LolaRuntimeBuilderImpl, OfferedProducer, Producer, Publisher, Result, Runtime, RuntimeBuilder, SampleContainer, SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription, }; -use com_api_gen::{Tire, VehicleConsumer, VehicleInterface, VehicleOfferedProducer}; +use com_api_gen::{Tire, VehicleInterface }; + +// Type aliases for generated consumer and offered producer types for the Vehicle interface +// VehicleConsumer is the consumer type generated for the Vehicle interface, parameterized by the runtime R +type VehicleConsumer = ::Consumer; +// VehicleOfferedProducer is the offered producer type generated for the Vehicle interface, parameterized by the runtime R +type VehicleOfferedProducer = + <::Producer as Producer>::OfferedProducer; // Example struct demonstrating composition with VehicleConsumer pub struct VehicleMonitor { diff --git a/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs b/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs index 586a8976c..c888eb75d 100644 --- a/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs +++ b/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs @@ -11,10 +11,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -use com_api::{ - CommData, Consumer, Interface, OfferedProducer, Producer, ProviderInfo, Publisher, Reloc, - Runtime, Subscriber, -}; +use com_api::{interface, CommData, ProviderInfo, Publisher, Reloc, Subscriber}; #[derive(Debug, Reloc)] #[repr(C)] @@ -34,81 +31,23 @@ impl CommData for Exhaust { const ID: &'static str = "Exhaust"; } -pub struct VehicleInterface {} - -/// Generic -impl Interface for VehicleInterface { - const INTERFACE_ID: &'static str = "VehicleInterface"; - type Consumer = VehicleConsumer; - type Producer = VehicleProducer; -} - -pub struct VehicleConsumer { - pub left_tire: R::Subscriber, - pub exhaust: R::Subscriber, -} - -impl Consumer for VehicleConsumer { - fn new(instance_info: R::ConsumerInfo) -> Self { - VehicleConsumer { - left_tire: R::Subscriber::new("left_tire", instance_info.clone()) - .expect("Failed to create subscriber"), - exhaust: R::Subscriber::new("exhaust", instance_info.clone()) - .expect("Failed to create subscriber"), - } - } -} - -pub struct AnotherInterface {} - -pub struct VehicleProducer { - _runtime: core::marker::PhantomData, - instance_info: R::ProviderInfo, -} - -impl Producer for VehicleProducer { - type Interface = VehicleInterface; - type OfferedProducer = VehicleOfferedProducer; - - fn offer(self) -> com_api::Result { - let vehicle_offered_producer = VehicleOfferedProducer { - left_tire: R::Publisher::new("left_tire", self.instance_info.clone()) - .expect("Failed to create publisher"), - exhaust: R::Publisher::new("exhaust", self.instance_info.clone()) - .expect("Failed to create publisher"), - instance_info: self.instance_info.clone(), - }; - // Offer the service instance to make it discoverable - // this is called after skeleton created using producer_builder API - self.instance_info.offer_service()?; - Ok(vehicle_offered_producer) - } - - fn new(instance_info: R::ProviderInfo) -> com_api::Result { - Ok(VehicleProducer { - _runtime: core::marker::PhantomData, - instance_info, - }) - } -} - -pub struct VehicleOfferedProducer { - pub left_tire: R::Publisher, - pub exhaust: R::Publisher, - instance_info: R::ProviderInfo, -} - -impl OfferedProducer for VehicleOfferedProducer { - type Interface = VehicleInterface; - type Producer = VehicleProducer; - - fn unoffer(self) -> com_api::Result { - let vehicle_producer = VehicleProducer { - _runtime: std::marker::PhantomData, - instance_info: self.instance_info.clone(), - }; - // Stop offering the service instance to withdraw it from system availability - self.instance_info.stop_offer_service()?; - Ok(vehicle_producer) - } -} +// Example interface definition using the interface macro with a custom UID for the interface. +// This will generate the following types and trait implementations: +// - VehicleInterface struct with INTERFACE_ID = "VehicleInterface" +// - VehicleConsumer, VehicleProducer, VehicleOfferedProducer with appropriate trait +// implementations for the Vehicle interface. +// The macro invocation defines an interface named "Vehicle" with two events: "left_tire" and "exhaust". +// The generated code will include: +// - VehicleInterface struct with INTERFACE_ID = "VehicleInterface" +// - VehicleConsumer struct that implements Consumer trait for subscribing to "left_tire" +// and "exhaust" events. +// - VehicleProducer struct that implements Producer trait for producing "left_tire" and +// "exhaust" events. +// - VehicleOfferedProducer struct that implements OfferedProducer trait for offering +// "left_tire" and "exhaust" events. +interface!( + interface Vehicle, "VehicleInterface", { + left_tire: Event, + exhaust: Event, + } +); diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/BUILD b/score/mw/com/impl/rust/com-api/com-api-concept/BUILD index 501975788..382b489b9 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api-concept/BUILD @@ -10,17 +10,19 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test","rust_doc_test") rust_library( name = "com-api-concept", srcs = [ + "lib.rs", "com_api_concept.rs", "reloc.rs", + "interface_macros.rs", ], - crate_root = "com_api_concept.rs", proc_macro_deps = [ "//score/mw/com/impl/rust/com-api/com-api-concept-macros", + "@crate_index//:paste", ], visibility = [ "//visibility:public", # platform_only @@ -36,3 +38,15 @@ rust_test( tags = ["manual"], deps = [":com-api-concept"], ) + +rust_doc_test( + name = "com-api-concept-macros-tests", + crate = ":com-api-concept", + deps = ["//score/mw/com/impl/rust/com-api/com-api",], +) + +rust_test( + name = "com-api-concept-macros-unit-tests", + srcs = ["interface_macros.rs"], + deps = ["//score/mw/com/impl/rust/com-api/com-api",], +) diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs b/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs index d391c9071..bc8fe407b 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs @@ -49,10 +49,9 @@ use core::fmt::Debug; use core::future::Future; use core::ops::{Deref, DerefMut}; -pub mod reloc; use containers::fixed_capacity::FixedCapacityQueue; -pub use reloc::Reloc; use std::path::Path; +use crate::Reloc; /// Error enumeration for different failure cases in the Consumer/Producer/Runtime APIs. #[derive(Debug)] diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/interface_macros.rs b/score/mw/com/impl/rust/com-api/com-api-concept/interface_macros.rs new file mode 100644 index 000000000..0e5a973ca --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-concept/interface_macros.rs @@ -0,0 +1,896 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +/// Main interface macro that generates Consumer, Producer, and OfferedProducer types +/// along with all necessary trait implementations. +/// +/// Automatically generates unique type names from the identifier of macro invocation. +/// For an interface with identifier `{id}`, it generates: +/// - `{id}Interface` - Struct representing the interface with INTERFACE_ID constant +/// - `{id}Consumer` - Consumer implementation with event subscribers +/// - `{id}Producer` - Producer implementation +/// - `{id}OfferedProducer` - Offered producer implementation with event publishers +/// - Implements the `Interface`, `Consumer`, `Producer`, and `OfferedProducer` traits +/// for the respective types. +/// - `Interface_ID` is generated by default as the module path + interface name, +/// but can be overridden by providing a custom UID as a second parameter to the macro. +/// +/// Parameters: +/// - Keywords: `interface` followed by the interface identifier and a block of event definitions. +/// - `$id`: Simple identifier used for type name generation (e.g., Vehicle, Engine) +/// - `$event_name`: Event field name +/// - `$event_type`: Event data type +/// Example usage: +/// With default UID generation (module path + interface name): +/// ```ignore +/// mod abc { +/// use com_api_concept::interface; +/// interface!( +/// interface Vehicle { +/// left_tire: Event, +/// exhaust: Event, +/// } +/// ); +/// } +/// ``` +/// The generated code will include: +/// - `VehicleInterface` struct with `INTERFACE_ID = "abc::Vehicle"` +/// - `VehicleConsumer` struct that implements `Consumer` trait for subscribing to "left_tire" and "exhaust" events. +/// - `VehicleProducer` struct that implements `Producer` trait for producing "left_tire" and "exhaust" events. +/// - `VehicleOfferedProducer` struct that implements `OfferedProducer` trait for offering "left_tire" and "exhaust" events. +/// +/// With custom UID: +/// ```ignore +/// mod abc { +/// use com_api_concept::interface; +/// interface!( +/// interface Vehicle, "AbcInterface", { +/// left_tire: Event, +/// exhaust: Event, +/// } +/// ); +/// } +/// ``` +/// The generated code will include: +/// - `VehicleInterface` struct with `INTERFACE_ID = "AbcInterface"` +/// - `VehicleConsumer` struct that implements `Consumer` trait for subscribing to "left_tire" and "exhaust" events. +/// - `VehicleProducer` struct that implements `Producer` trait for producing "left_tire" and "exhaust" events. +/// - `VehicleOfferedProducer` struct that implements `OfferedProducer` trait for offering "left_tire" and "exhaust" events. + +#[macro_export] +macro_rules! interface { + // Default unique ID based on the module path and interface name + (interface $id:ident { $($event_name:ident : Event<$event_type:ty>),+ $(,)? }) => { + $crate::interface_common!($id); + $crate::interface_consumer!($id, $($event_name, Event<$event_type>),+); + $crate::interface_producer!($id, $($event_name, Event<$event_type>),+); + }; + + // Custom unique ID provided by the user + (interface $id:ident, $uid:expr, { $($event_name:ident : Event<$event_type:ty>),+ $(,)? }) => { + $crate::interface_common!($id, $uid); + $crate::interface_consumer!($id, $($event_name, Event<$event_type>),+); + $crate::interface_producer!($id, $($event_name, Event<$event_type>),+); + }; + + (interface $id:ident { $($event_name:ident : Method<$event_type:ty>),+$(,)? }) => { + compile_error!("Method definitions are not supported in this macro version. Please use Event syntax for defining events."); + }; + + (interface $id:ident { $($event_name:ident : Field<$event_type:ty>),+$(,)? }) => { + compile_error!("Field definitions are not supported in this macro version. Please use Event syntax for defining events."); + }; +} + +/// Macro to create a unique interface struct and implement the Interface trait for it. +/// +/// Generates the INTERFACE_ID constant and associated Consumer/Producer types. +/// INTERFACE_ID is generated by default as the module path + interface name, +/// but can be overridden by providing a custom UID as a second parameter to the macro. +#[macro_export] +macro_rules! interface_common { + // Default: auto ID = module path + type name + ($id:ident) => { + com_api::paste::paste! { + pub struct [<$id Interface>] {} + impl com_api::Interface for [<$id Interface>] { + const INTERFACE_ID: &'static str = + concat!(module_path!(), "::", stringify!($id)); + type Consumer = [<$id Consumer>]; + type Producer = [<$id Producer>]; + } + } + }; + // Explicit ID override + ($id:ident, $uid:expr) => { + com_api::paste::paste! { + pub struct [<$id Interface>] {} + impl com_api::Interface for [<$id Interface>] { + const INTERFACE_ID: &'static str = $uid; + type Consumer = [<$id Consumer>]; + type Producer = [<$id Producer>]; + } + } + }; +} + +/// Macro to implement the Consumer trait for a given interface ID and its events. +/// +/// Generates the Consumer struct with subscribers for each event. +#[macro_export] +macro_rules! interface_consumer { + ($id:ident, $($event_name:ident, Event<$event_type:ty>),+$(,)?) => { + com_api::paste::paste! { + pub struct [<$id Consumer>] { + $( + pub $event_name: R::Subscriber<$event_type>, + )+ + } + + impl com_api::Consumer for [<$id Consumer>] { + fn new(instance_info: R::ConsumerInfo) -> Self { + [<$id Consumer>] { + $( + $event_name: R::Subscriber::new( + stringify!($event_name), + instance_info.clone() + ).expect(&format!("Failed to create subscriber for {}", stringify!($event_name))), + )+ + } + } + } + } + }; +} + +/// Macro to implement the Producer and OfferedProducer traits for a given interface ID and its events. +/// +/// Generates Producer and OfferedProducer structs with publishers for each event. +#[macro_export] +macro_rules! interface_producer { + ($id:ident, $($event_name:ident, Event<$event_type:ty>),+$(,)?) => { + com_api::paste::paste! { + pub struct [<$id Producer>] { + _runtime: core::marker::PhantomData, + instance_info: R::ProviderInfo, + } + + pub struct [<$id OfferedProducer>] { + $( + pub $event_name: R::Publisher<$event_type>, + )+ + instance_info: R::ProviderInfo, + } + + impl com_api::Producer for [<$id Producer>] { + type Interface = [<$id Interface>]; + type OfferedProducer = [<$id OfferedProducer>]; + fn offer(self) -> com_api::Result { + let offered = [<$id OfferedProducer>] { + $( + $event_name: R::Publisher::new( + stringify!($event_name), + self.instance_info.clone() + ).expect(&format!("Failed to create publisher for {}", stringify!($event_name))), + )+ + instance_info: self.instance_info.clone(), + }; + // Offer the service instance to make it discoverable + self.instance_info.offer_service()?; + Ok(offered) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok([<$id Producer>] { + _runtime: core::marker::PhantomData, + instance_info, + }) + } + } + + impl com_api::OfferedProducer for [<$id OfferedProducer>] { + type Interface = [<$id Interface>]; + type Producer = [<$id Producer>]; + fn unoffer(self) -> com_api::Result { + let producer = [<$id Producer>] { + _runtime: core::marker::PhantomData, + instance_info: self.instance_info.clone(), + }; + // Stop offering the service instance to withdraw it from system availability + self.instance_info.stop_offer_service()?; + Ok(producer) + } + } + } + }; +} + +mod tests { + /// ``` + /// mod my_module { + /// use com_api::{interface,CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// + /// interface!( + /// interface Vehicle { + /// left_tire: Event, + /// exhaust: Event, + /// } + /// ); + /// } + /// ``` + /// This will generate the following types and trait implementations: + /// - `VehicleInterface` struct with `INTERFACE_ID = "my_module::Vehicle"` + /// - `VehicleConsumer`, `VehicleProducer`, `VehicleOfferedProducer` with appropriate trait implementations for the Vehicle interface. + #[cfg(doctest)] + fn interface_macro_with_auto_id() {} + + /// ``` + /// mod my_module { + /// use com_api::{interface, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// + /// interface!( + /// interface Vehicle, "VehicleInterface", { + /// left_tire: Event, + /// exhaust: Event, + /// } + /// ); + /// } + /// ``` + /// This will generate the following types and trait implementations: + /// - `VehicleInterface` struct with `INTERFACE_ID = "VehicleInterface"` + /// - `VehicleConsumer`, `VehicleProducer`, `VehicleOfferedProducer` with appropriate trait implementations for the Vehicle interface. + #[cfg(doctest)] + fn interface_macro_with_custom_id() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// + /// interface!( + /// interface Vehicle, "VehicleInterface", { + /// left_tire: Method, + /// exhaust: Method, + /// } + /// ); + /// } + /// ``` + /// This will fail to compile because the macro does not support Method definitions and will produce a compile-time error indicating that Method definitions are not supported. + #[cfg(doctest)] + fn interface_macro_with_Method() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// + /// interface!( + /// interface Vehicle { + /// left_tire: Field, + /// exhaust: Field, + /// } + /// ); + /// } + /// ``` + /// This will fail to compile because the macro does not support Field definitions and will produce a compile-time error indicating that Field definitions are not supported. + #[cfg(doctest)] + fn interface_macro_with_Field() {} + + /// ``` + /// mod my_module { + /// use com_api::{interface_common, interface_consumer, interface_producer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_common!(Vehicle); + /// interface_consumer!(Vehicle, left_tire, Event, exhaust, Event); + /// interface_producer!(Vehicle, left_tire, Event, exhaust, Event); + /// } + /// ``` + /// This will generate a `VehicleInterface` struct with an `INTERFACE_ID` constant that is automatically generated as the module path plus the interface name (e.g., "my_module::Vehicle"). + /// It will also define associated `Consumer` and `Producer` types for the `VehicleInterface`. + /// The `VehicleConsumer` struct will implement the `Consumer` trait for the `VehicleInterface`, with subscribers for the `left_tire` and `exhaust` events. + /// The `VehicleProducer` struct will implement the `Producer` trait for the `VehicleInterface`, with publishers for the `left_tire` and `exhaust` events. + #[cfg(doctest)] + fn individual_common_macro_with_auto_id() {} + + /// ``` + /// mod my_module { + /// use com_api::{interface_common, interface_consumer, interface_producer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_common!(Vehicle, "CustomVehicleInterface"); + /// interface_consumer!(Vehicle, left_tire, Event, exhaust, Event); + /// interface_producer!(Vehicle, left_tire, Event, exhaust, Event); + /// } + /// ``` + /// This will generate a `VehicleInterface` struct with an `INTERFACE_ID` constant set to "CustomVehicleInterface". + /// It will also define associated `Consumer` and `Producer` types for the `VehicleInterface`. + /// The `VehicleConsumer` struct will implement the `Consumer` trait for the `VehicleInterface`, with subscribers for the `left_tire` and `exhaust` events. + /// The `VehicleProducer` struct will implement the `Producer` trait for the `VehicleInterface`, with publishers for the `left_tire` and `exhaust` events. + #[cfg(doctest)] + fn individual_macro_with_custom_id() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_common, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_common!(Vehicle, "CustomVehicleInterface", { + /// left_tire: Event, + /// exhaust: Event, + /// }); + /// } + /// ``` + /// This will fail to compile because the `interface_common!` macro does not accept event definitions and will produce a compile-time error indicating that the macro does not support event definitions. + #[cfg(doctest)] + fn interface_common_macro_with_events() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_common, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_common!(Vehicle, "CustomVehicleInterface", { + /// left_tire: Method, + /// exhaust: Method, + /// }); + /// } + /// ``` + /// This will fail to compile because the `interface_common!` macro does not accept method definitions and will produce a compile-time error indicating that the macro does not support method definitions. + #[cfg(doctest)] + fn interface_common_macro_with_methods() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_common, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_common!(Vehicle, "CustomVehicleInterface", { + /// left_tire: Field, + /// exhaust: Field, + /// }); + /// } + /// ``` + /// This will fail to compile because the `interface_common!` macro does not accept field definitions and will produce a compile-time error indicating that the macro does not support field definitions. + #[cfg(doctest)] + fn interface_common_macro_with_fields() {} + + /// ``` + /// mod my_module { + /// use com_api::{interface_consumer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_consumer!(Vehicle, left_tire, Event, exhaust, Event); + /// } + /// ``` + /// This will generate a `VehicleConsumer` struct that implements the `Consumer` trait for the `VehicleInterface`, with subscribers for the `left_tire` and `exhaust` events. + /// The generated `VehicleConsumer` struct will have fields for each event subscriber, and the `new` method will initialize these subscribers using the runtime's `Subscriber::new` method. + /// The macro will also include error handling to ensure that subscriber creation failures are properly reported. + #[cfg(doctest)] + fn interface_consumer_macro() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_consumer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_consumer!(Vehicle, left_tire, Method, exhaust, Method); + /// } + /// ``` + /// This will fail to compile because the `interface_consumer!` macro does not support Method definitions and will produce a compile-time error indicating that Method definitions are not supported. + #[cfg(doctest)] + fn interface_consumer_macro_with_Method() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_consumer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_consumer!(Vehicle, left_tire, Field, exhaust, Field); + /// } + /// ``` + /// This will fail to compile because the `interface_consumer!` macro does not support Field definitions and will produce a compile-time error indicating that Field definitions are not supported. + #[cfg(doctest)] + fn interface_consumer_macro_with_Field() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_producer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_producer!(Vehicle, left_tire, Event, exhaust, Event); + /// } + /// ``` + /// This will generate a `VehicleProducer` struct that implements the `Producer` trait for the + /// `VehicleInterface`, with publishers for the `left_tire` and `exhaust` events. + /// So it requires interface_common macro to be called before to generate the VehicleInterface struct + /// and implement the Interface trait for it. + #[cfg(doctest)] + fn interface_producer_macro() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_producer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_producer!(Vehicle, left_tire, Method, exhaust, Method); + /// } + /// ``` + /// This will fail to compile because the `interface_producer!` macro does not support Method definitions and will produce a compile-time error indicating that Method definitions are not supported. + #[cfg(doctest)] + fn interface_producer_macro_with_Method() {} + + /// ```compile_fail + /// mod my_module { + /// use com_api::{interface_producer, CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Tire { pub pressure: f32 } + /// impl CommData for Tire { + /// const ID: &'static str = "Tire"; + /// } + /// + /// #[derive(Debug, Reloc)] + /// #[repr(C)] + /// pub struct Exhaust {} + /// impl CommData for Exhaust { + /// const ID: &'static str = "Exhaust"; + /// } + /// interface_producer!(Vehicle, left_tire, Field, exhaust, Field); + /// } + /// ``` + /// This will fail to compile because the `interface_producer!` macro does not support Field definitions and will produce a compile-time error indicating that Field definitions are not supported. + #[cfg(doctest)] + fn interface_producer_macro_with_Field() {} + +} + +#[cfg(test)] +#[allow(dead_code)] +#[allow(unused_imports)] +mod validation_tests { + #[test] + fn test_interface_id_auto_generated() { + mod test_module { + use com_api::{CommData, Reloc, ProviderInfo, Subscriber, Publisher}; + + #[derive(Debug, Reloc)] + #[repr(C)] + pub struct Tire { pub pressure: f32 } + impl CommData for Tire { + const ID: &'static str = "Tire"; + } + + crate::interface!( + interface Vehicle { + left_tire: Event, + } + ); + + pub fn validate() { + // Referencing VehicleInterface by name is itself proof the type was generated; + // the compiler enforces the name at compile time. + let interface_id = ::INTERFACE_ID; + let expected_id = concat!(module_path!(), "::", "Vehicle"); + assert_eq!(interface_id, expected_id, "Interface ID mismatch for VehicleInterface"); + } + } + test_module::validate(); + } + + #[test] + fn test_consumer_type_generated() { + mod test_module { + use com_api::{CommData, Reloc, Consumer, ProviderInfo, Subscriber, Publisher, LolaRuntimeImpl as LolaRuntime}; + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Tire { pub pressure: f32 } + impl CommData for Tire { + const ID: &'static str = "Tire"; + } + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Exhaust { pub temp: f32 } + impl CommData for Exhaust { + const ID: &'static str = "Exhaust"; + } + + crate::interface!( + interface Vehicle { + left_tire: Event, + exhaust: Event, + } + ); + + pub fn validate() { + // Referencing VehicleConsumer by name is proof the type was generated. + // The size check verifies that subscriber fields were generated. + assert!(std::mem::size_of::>() > 0, + "VehicleConsumer should have subscriber fields"); + } + } + test_module::validate(); + } + + #[test] + fn test_producer_type_generated() { + mod test_module { + use com_api::{CommData, Reloc, Producer, ProviderInfo, Subscriber, Publisher, LolaRuntimeImpl as LolaRuntime}; + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Tire { pub pressure: f32 } + impl CommData for Tire { + const ID: &'static str = "Tire"; + } + + crate::interface!( + interface Engine { + rpm: Event, + } + ); + + pub fn validate() { + // Referencing EngineProducer by name is proof the type was generated. + let _ = core::marker::PhantomData::>; + } + } + test_module::validate(); + } + + #[test] + fn test_offered_producer_type_generated() { + mod test_module { + use com_api::{CommData, Reloc, Producer, ProviderInfo, Subscriber, Publisher, LolaRuntimeImpl as LolaRuntime}; + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Tire { pub pressure: f32 } + impl CommData for Tire { + const ID: &'static str = "Tire"; + } + + crate::interface!( + interface Transmission { + gear: Event, + } + ); + + pub fn validate() { + // Referencing TransmissionOfferedProducer by name is proof the type was generated. + // The size check verifies that publisher fields were generated. + assert!(std::mem::size_of::>() > 0, + "TransmissionOfferedProducer should have publisher fields"); + } + } + test_module::validate(); + } + + #[test] + fn test_interface_with_custom_id_validation() { + mod test_module { + use com_api::{CommData, Reloc, ProviderInfo, Subscriber, Publisher, Interface}; + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Tire { pub pressure: f32 } + impl CommData for Tire { + const ID: &'static str = "Tire"; + } + + crate::interface!( + interface Battery, "com.example.Battery", { + voltage: Event, + } + ); + + pub fn validate() { + // Referencing BatteryInterface by name is proof the type was generated with the + // correct naming convention; the custom ID is a meaningful runtime assertion. + let interface_id = ::INTERFACE_ID; + assert_eq!(interface_id, "com.example.Battery", + "Custom interface ID should match provided UID"); + } + } + test_module::validate(); + } + + #[test] + fn test_interface_with_multiple_events_validation() { + mod test_module { + use com_api::{CommData, Reloc, ProviderInfo, Subscriber, Publisher, LolaRuntimeImpl as LolaRuntime, Interface}; + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Event1Data { pub value: i32 } + impl CommData for Event1Data { + const ID: &'static str = "Event1Data"; + } + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Event2Data { pub value: f64 } + impl CommData for Event2Data { + const ID: &'static str = "Event2Data"; + } + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Event3Data { pub value: bool } + impl CommData for Event3Data { + const ID: &'static str = "Event3Data"; + } + + crate::interface!( + interface MultiEvent { + event_one: Event, + event_two: Event, + event_three: Event, + } + ); + + pub fn validate() { + // ID assertion is meaningful at runtime. + let interface_id = ::INTERFACE_ID; + assert_eq!(interface_id, concat!(module_path!(), "::", "MultiEvent"), + "Interface ID should be auto-generated from module path and interface name"); + + // Referencing Consumer, Producer, and OfferedProducer by name proves all four + // types were generated; the compiler enforces the names at compile time. + let _ = core::marker::PhantomData::>; + let _ = core::marker::PhantomData::>; + let _ = core::marker::PhantomData::>; + } + } + test_module::validate(); + } + + #[test] + fn test_interface_type_consistency_across_traits() { + mod test_module { + use com_api::{CommData, Reloc, ProviderInfo, Subscriber, Publisher, LolaRuntimeImpl as LolaRuntime, Interface}; + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Tire { pub pressure: f32 } + impl CommData for Tire { + const ID: &'static str = "Tire"; + } + + crate::interface!( + interface Suspension { + travel: Event, + } + ); + + pub fn validate() { + // Referencing all four generated types by their expected names is proof of + // consistent naming; the compiler enforces the names at compile time. + let _ = core::marker::PhantomData::; + let _ = core::marker::PhantomData::>; + let _ = core::marker::PhantomData::>; + let _ = core::marker::PhantomData::>; + + let interface_id = ::INTERFACE_ID; + assert_eq!(interface_id, concat!(module_path!(), "::", "Suspension"), + "Interface ID should be auto-generated from module path and interface name"); + } + } + test_module::validate(); + } + + #[test] + fn test_interface_naming_convention_validation() { + mod test_module { + use com_api::{CommData, Reloc, ProviderInfo, Subscriber, Publisher, LolaRuntimeImpl as LolaRuntime}; + + #[derive(Debug, Reloc, Clone)] + #[repr(C)] + pub struct Data { pub value: u32 } + impl CommData for Data { + const ID: &'static str = "Data"; + } + + crate::interface!( + interface ABS { + status: Event, + } + ); + + pub fn validate() { + // Referencing each generated type by its expected name is itself the proof of + // correct naming conventions; the compiler enforces the names at compile time. + // Pattern: {Name}Interface, {Name}Consumer, {Name}Producer, {Name}OfferedProducer + let _ = core::marker::PhantomData::; + let _ = core::marker::PhantomData::>; + let _ = core::marker::PhantomData::>; + let _ = core::marker::PhantomData::>; + } + } + test_module::validate(); + } +} diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/lib.rs b/score/mw/com/impl/rust/com-api/com-api-concept/lib.rs new file mode 100644 index 000000000..d29d84dae --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-concept/lib.rs @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +///This is main library file for the com-api-concept crate, which defines the core traits, types, and macros for the communication API concept. +/// It re-exports the main components of the crate, including the interface macro and the Reloc type, which is used for safe data relocation in the communication API. +/// The interface macro generates the necessary types and trait implementations for defining communication interfaces, +/// while the Reloc type provides a safe abstraction for moving data across thread or process boundaries without violating Rust's ownership rules. + +mod com_api_concept; +mod interface_macros; +mod reloc; +#[doc(hidden)] +pub use paste; +pub use com_api_concept::*; +pub use reloc::Reloc; diff --git a/score/mw/com/impl/rust/com-api/com-api/com_api.rs b/score/mw/com/impl/rust/com-api/com-api/com_api.rs index 39ef55c68..78fa43e9f 100644 --- a/score/mw/com/impl/rust/com-api/com-api/com_api.rs +++ b/score/mw/com/impl/rust/com-api/com-api/com_api.rs @@ -21,4 +21,8 @@ pub use com_api_concept::{ InstanceSpecifier, Interface, OfferedProducer, PlacementDefault, Producer, ProducerBuilder, ProviderInfo, Publisher, Reloc, Result, Runtime, RuntimeBuilder, SampleContainer, SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription, + interface, interface_common, interface_consumer, interface_producer, }; + +#[doc(hidden)] +pub use com_api_concept::paste;