From 55d5d0cea13bb1aaaa7db4ac888c17da29c4354c Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Tue, 17 Mar 2026 09:53:26 +0530 Subject: [PATCH 1/7] Rust::com COM-API Error reporting optimization * Error id based on the use cases * Optimized the error handling on runtime impl * Example app handle the error --- .../basic-consumer-producer.rs | 71 +++++++--- .../impl/rust/com-api/com-api-concept/BUILD | 1 + .../com-api-concept/com_api_concept.rs | 28 ++-- .../rust/com-api/com-api-concept/error.rs | 129 ++++++++++++++++++ .../impl/rust/com-api/com-api-concept/lib.rs | 2 + .../com-api/com-api-runtime-lola/consumer.rs | 124 ++++++++++++++--- .../com-api/com-api-runtime-lola/producer.rs | 51 +++++-- .../com-api/com-api-runtime-lola/runtime.rs | 5 +- 8 files changed, 344 insertions(+), 67 deletions(-) create mode 100644 score/mw/com/impl/rust/com-api/com-api-concept/error.rs 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 1c2d60b39..668bfb15e 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 @@ -11,11 +11,18 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +// This demo app writing and reading tire pressure data using producer and consumer respectively. +// It is demonstrating the composition of consumer and producer in one struct, +// but they can be used separately as well. +// The example is using Lola runtime, but it can be used with any runtime by changing the runtime initialization part. +// Note: The example is using unwrap and panic in some places for simplicity, +// but it is recommended to handle errors properly in production code. + use clap::Parser; use std::path::PathBuf; use com_api::{ - Builder, Error, FindServiceSpecifier, InstanceSpecifier, Interface, LolaRuntimeBuilderImpl, + Builder, FindServiceSpecifier, InstanceSpecifier, Interface, LolaRuntimeBuilderImpl, OfferedProducer, Producer, Publisher, Result, Runtime, RuntimeBuilder, SampleContainer, SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription, }; @@ -88,7 +95,7 @@ impl VehicleMonitor { let mut sample_buf = SampleContainer::new(3); match self.tire_subscriber.try_receive(&mut sample_buf, 1) { - Ok(0) => Err(Error::Fail), + Ok(0) => Ok("No tire data received".to_string()), Ok(x) => { let sample = sample_buf.pop_front().unwrap(); Ok(format!("{} samples received: sample[0] = {:?}", x, *sample)) @@ -119,7 +126,9 @@ impl VehicleMonitor { fn create_consumer(runtime: &R, service_id: InstanceSpecifier) -> VehicleConsumer { let consumer_discovery = runtime.find_service::(FindServiceSpecifier::Specific(service_id)); - let available_service_instances = consumer_discovery.get_available_instances().unwrap(); + let available_service_instances = consumer_discovery + .get_available_instances() + .unwrap_or_else(|e| panic!("{:?}", e)); // Select service instance at specific handle_index let handle_index = 0; // or any index you need from vector of instances @@ -128,7 +137,9 @@ fn create_consumer(runtime: &R, service_id: InstanceSpecifier) -> Ve .nth(handle_index) .unwrap(); - consumer_builder.build().unwrap() + consumer_builder + .build() + .unwrap_or_else(|e| panic!("{:?}", e)) } #[allow(dead_code)] @@ -160,28 +171,42 @@ fn create_producer( service_id: InstanceSpecifier, ) -> VehicleOfferedProducer { let producer_builder = runtime.producer_builder::(service_id); - let producer = producer_builder.build().unwrap(); - producer.offer().unwrap() + let producer = producer_builder + .build() + .unwrap_or_else(|e| panic!("{:?}", e)); + producer.offer().unwrap_or_else(|e| panic!("{:?}", e)) } // Run the example with the specified runtime fn run_with_runtime(name: &str, runtime: &R) { println!("\n=== Running with {name} runtime ==="); - let service_id = InstanceSpecifier::new("/Vehicle/Service1/Instance") - .expect("Failed to create InstanceSpecifier"); + let service_id = match InstanceSpecifier::new("/Vehicle/Service1/Instance") { + Ok(specifier) => specifier, + Err(e) => panic!("{:?}", e), + }; let producer = create_producer(runtime, service_id.clone()); let consumer = create_consumer(runtime, service_id); let monitor = VehicleMonitor::new(consumer, producer).unwrap(); let tire_pressure = 5.0; println!("Setting tire pressure to {tire_pressure}"); for i in 0..5 { - monitor - .write_tire_data(Tire { - pressure: tire_pressure + i as f32, - }) - .unwrap(); - let tire_data = monitor.read_tire_data().unwrap(); + match monitor.write_tire_data(Tire { + pressure: tire_pressure + i as f32, + }) { + Ok(_) => (), + Err(e) => { + eprintln!("Failed to write tire data: {:?}", e); + continue; + } + } + let tire_data = match monitor.read_tire_data() { + Ok(data) => data, + Err(e) => { + eprintln!("Failed to read tire data: {:?}", e); + continue; + } + }; println!("{tire_data}"); } //unoffer returns producer back, so if needed it can be used further @@ -338,15 +363,21 @@ mod test { offered_producer: VehicleOfferedProducer, ) -> VehicleOfferedProducer { for i in 0..10 { - let uninit_sample = offered_producer.left_tire.allocate().unwrap(); + let uninit_sample = match offered_producer.left_tire.allocate() { + Ok(sample) => sample, + Err(e) => { + eprintln!("Failed to allocate sample: {:?}", e); + continue; + } + }; let sample = uninit_sample.write(Tire { pressure: 1.0 + i as f32, }); - sample.send().unwrap(); - println!( - "[SENDER] Sent sample with pressure: {:.2} psi", - 1.0 + i as f32 - ); + match sample.send() { + Ok(_) => (), + Err(e) => eprintln!("Failed to send sample: {:?}", e), + } + println!("Sent sample with pressure: {}", 1.0 + i as f32); tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; } offered_producer 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 dab726eb3..9fb465cb3 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 @@ -17,6 +17,7 @@ rust_library( name = "com-api-concept", srcs = [ "com_api_concept.rs", + "error.rs", "interface_macros.rs", "lib.rs", "reloc.rs", 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 e7a05ecc2..a803ed20d 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 @@ -48,6 +48,7 @@ //! - Structures //! - Tuples +use crate::error::*; use crate::Reloc; pub use com_api_concept_macros::CommData; use containers::fixed_capacity::FixedCapacityQueue; @@ -56,16 +57,6 @@ use core::future::Future; use core::ops::{Deref, DerefMut}; use std::path::Path; -/// Error enumeration for different failure cases in the Consumer/Producer/Runtime APIs. -#[derive(Debug)] -pub enum Error { - /// TODO: To be replaced, dummy value for "something went wrong" - Fail, - Timeout, - AllocateFailed, - SubscribeFailed, -} - /// Result type alias with `std::result::Result` using `com_api::Error` as error type pub type Result = core::result::Result; @@ -270,7 +261,7 @@ impl InstanceSpecifier { /// an `Error` on failure. /// /// # Errors - /// Returns `Error::Fail` if the provided string does not conform to the required format. + /// Returns `Error::InstanceSpecifierInvalid` if the provided string does not conform to the required format. pub fn new(service_name: impl AsRef) -> Result { let service_name = service_name.as_ref(); if Self::check_str(service_name) { @@ -278,7 +269,10 @@ impl InstanceSpecifier { specifier: service_name.to_string(), }) } else { - Err(Error::Fail) + Err(Error::InstanceSpecifierInvalid(format!( + "Invalid instance specifier format: '{}'", + service_name + ))) } } } @@ -712,11 +706,13 @@ impl SampleContainer { /// A `Result` indicating success or failure. /// /// # Errors - /// Returns 'Error' if container is already full. + /// Returns 'Error::SampleContainerAllocateFailed' if container is already full. pub fn push_back(&mut self, new: S) -> Result<()> { - self.inner - .push_back(new) - .map_err(|_| Error::AllocateFailed)?; + self.inner.push_back(new).map_err(|_| { + Error::SampleContainerAllocateFailed(AllocationFailureReason::OutOfMemory( + "Sample container is full".to_string(), + )) + })?; Ok(()) } diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs new file mode 100644 index 000000000..79147b3d7 --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs @@ -0,0 +1,129 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +/// Detailed information about Service Discovery failure reasons, +/// including specific issues with service interfaces, instance specifiers, timeouts, and handle retrieval. +#[derive(Debug)] +pub enum ServiceDiscoveryFailedReason { + /// Invalid instance specifier format or content + InstanceSpecifierInvalid(String), + /// Service handle not found + ServiceNotFound(String), + /// Internal error with details + InternalError(String), +} + +/// Reason for producer creation failure +#[derive(Debug)] +pub enum ProducerCreationReason { + /// Invalid instance specifier format or content + InstanceSpecifierInvalid(String), + /// Skeleton creation failed with details + SkeletonCreationFailed(String), + /// Internal error with details + InternalError(String), +} + +/// Reason for consumer creation failure +#[derive(Debug)] +pub enum ConsumerCreationReason { + /// Invalid instance specifier format or content + InstanceSpecifierInvalid(String), + /// Service handle not found from discovery + ServiceHandleNotFound(String), + /// Proxy creation failed with details + ProxyCreationFailed(String), + /// Internal error with details + InternalError(String), +} + +/// Reason for subscribe operation failure +#[derive(Debug)] +pub enum SubscribeFailureReason { + /// Event not available + EventNotAvailable(String), + /// Internal subscription error + InternalError(String), +} +/// Memory allocation error details +#[derive(Debug)] +pub enum AllocationFailureReason { + /// Requested size exceeds available memory + OutOfMemory(String), + /// Invalid allocation request + InvalidRequest(String), + /// Internal allocation error + InternalError(String), +} + +/// Comprehensive error reasons asynchronous receive failure +#[derive(Debug)] +pub enum AsyncReceiveFailedReason { + /// initialization failed with details + InitializationFailed(String), + /// receive operation failed with details + ReceiveError(String), + /// sample count out of bounds + SampleCountOutOfBounds(String), +} + +/// Comprehensive error reasons for receive failure +#[derive(Debug)] +pub enum ReceiveFailedReason { + /// initialization failed with details + InitializationFailed(String), + /// receive operation failed with details + ReceiveError(String), + /// sample count out of bounds + SampleCountOutOfBounds(String), +} + +/// Comprehensive error reasons for event-related failures +#[derive(Debug)] +pub enum EventFailedReason { + /// Event creation failed with details + CreationFailed(String), + /// Event publication failed with details + PublishFailed(String), +} + +/// Error enumeration for different failure cases in the Consumer/Producer/Runtime APIs. +#[derive(Debug)] +pub enum Error { + /// Generic failure with optional message + Fail(Option), + /// Service discovery failed with detailed reason + ServiceDiscoveryFailed(ServiceDiscoveryFailedReason), + /// Failed to create producer instance + ProducerCreationFailed(ProducerCreationReason), + /// Failed to create consumer instance + ConsumerCreationFailed(ConsumerCreationReason), + /// Failed to offer service instance for discovery + OfferServiceFailed(String), + /// Failed to send sample + SendingDataFailed(String), + /// Event Error with details + EventError(EventFailedReason), + /// Sample container allocation failed + SampleContainerAllocateFailed(AllocationFailureReason), + /// Memory allocation failed + MemoryAllocationFailed(AllocationFailureReason), + /// Asynchronous receive operation failed + AsyncReceiveFailed(AsyncReceiveFailedReason), + /// Receive operation failed + ReceiveFailed(ReceiveFailedReason), + /// Invalid instance specifier provided + InstanceSpecifierInvalid(String), + /// Event subscription failed with details + SubscribeFailed(SubscribeFailureReason), +} 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 index 3b54b8644..a944aa17f 100644 --- 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 @@ -20,9 +20,11 @@ /// 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 error; mod interface_macros; mod reloc; pub use com_api_concept::*; +pub use error::*; #[doc(hidden)] pub use paste; pub use reloc::Reloc; diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs index 04aadb41c..c68d10731 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs @@ -41,8 +41,10 @@ use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::Arc; use com_api_concept::{ - Builder, CommData, Consumer, ConsumerBuilder, ConsumerDescriptor, Error, InstanceSpecifier, - Interface, Result, SampleContainer, ServiceDiscovery, Subscriber, Subscription, + AsyncReceiveFailedReason, Builder, CommData, Consumer, ConsumerBuilder, ConsumerCreationReason, + ConsumerDescriptor, Error, InstanceSpecifier, Interface, ReceiveFailedReason, Result, + SampleContainer, ServiceDiscovery, ServiceDiscoveryFailedReason, SubscribeFailureReason, + Subscriber, Subscription, }; use bridge_ffi_rs::*; @@ -286,8 +288,22 @@ pub struct SubscribableImpl { impl Subscriber for SubscribableImpl { type Subscription = SubscriberImpl; fn new(identifier: &'static str, instance_info: LolaConsumerInfo) -> Result { - let handle = instance_info.get_handle().ok_or(Error::Fail)?; + let handle = instance_info + .get_handle() + .ok_or(Error::ConsumerCreationFailed( + ConsumerCreationReason::ServiceHandleNotFound(format!( + "Service handle not found for instance: {}", + identifier + )), + ))?; let native_proxy = NativeProxyBase::new(instance_info.interface_id, handle); + if native_proxy.proxy.is_null() { + return Err(Error::ConsumerCreationFailed( + ConsumerCreationReason::ProxyCreationFailed( + "Failed to create proxy instance".to_string(), + ), + )); + } let proxy_instance = ProxyInstanceManager(Arc::new(native_proxy)); Ok(Self { identifier, @@ -303,6 +319,14 @@ impl Subscriber for SubscribableImpl self.instance_info.interface_id, self.identifier, ); + if event_instance.proxy_event_ptr.is_null() { + return Err(Error::SubscribeFailed( + SubscribeFailureReason::EventNotAvailable(format!( + "Event not available for identifier: {}", + self.identifier + )), + )); + } //SAFETY: It is safe to subscribe to event because event_instance is valid // which was obtained from valid proxy instance @@ -313,7 +337,12 @@ impl Subscriber for SubscribableImpl ) }; if !status { - return Err(Error::Fail); + return Err(Error::SubscribeFailed( + SubscribeFailureReason::InternalError(format!( + "Failed to subscribe to event: {}", + self.identifier + )), + )); } // Store in SubscriberImpl with event, max_num_samples Ok(SubscriberImpl { @@ -511,7 +540,14 @@ where ) -> impl Future>>> + 'a { async move { if max_samples > self.max_num_samples || new_samples > self.max_num_samples { - return Err(Error::Fail); + return Err(Error::AsyncReceiveFailed( + AsyncReceiveFailedReason::SampleCountOutOfBounds( + format!( + "Requested sample count exceeds subscription limit: requested new_samples {}, requested max_samples {}, subscription max_num_samples {}", + new_samples, max_samples, self.max_num_samples + ) + ), + )); } // Get the event guard to ensure no concurrent receive calls // on the same subscriber instance. @@ -522,7 +558,11 @@ where .load(std::sync::atomic::Ordering::Relaxed) { if let Err(_e) = self.init_async_receive(&mut event_guard) { - return Err(Error::Fail); + return Err(Error::AsyncReceiveFailed( + AsyncReceiveFailedReason::InitializationFailed( + "Failed to initialize async receive".to_string(), + ), + )); } self.async_init_status .store(true, std::sync::atomic::Ordering::Relaxed); @@ -582,10 +622,18 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { self.scratch = Some(scratch); result } else { - Err(Error::Fail) + Err(Error::AsyncReceiveFailed( + AsyncReceiveFailedReason::ReceiveError( + "Failed to receive sample data".to_string(), + ), + )) } } else { - Err(Error::Fail) + Err(Error::AsyncReceiveFailed( + AsyncReceiveFailedReason::ReceiveError( + "Internal buffer not available".to_string(), + ), + )) } }; match samples_received { @@ -597,7 +645,11 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { //event_guard will be dropped here, allowing new receive calls to access the // proxy event self.event_guard = None; - return Poll::Ready(self.scratch.take().ok_or(Error::Fail)); + return Poll::Ready(self.scratch.take().ok_or(Error::AsyncReceiveFailed( + AsyncReceiveFailedReason::ReceiveError( + "Internal buffer not available".to_string(), + ), + ))); } // Have some samples but not enough yet, wait for more via waker Poll::Pending @@ -658,12 +710,24 @@ where fn get_available_instances(&self) -> Result { //If ANY Support is added in Lola, then we need to return all available instances - let instance_specifier_lola = - mw_com::InstanceSpecifier::try_from(self.instance_specifier.as_ref()) - .map_err(|_| Error::Fail)?; + let instance_specifier_lola = mw_com::InstanceSpecifier::try_from( + self.instance_specifier.as_ref(), + ) + .map_err(|_| { + Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::InstanceSpecifierInvalid( + "Failed to parse instance specifier".to_string(), + )) + })?; let service_handle = - mw_com::proxy::find_service(instance_specifier_lola).map_err(|_| Error::Fail)?; + mw_com::proxy::find_service(instance_specifier_lola).map_err(|_| { + Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::ServiceNotFound( + format!( + "No service found matching specifier: {}", + self.instance_specifier.as_ref() + ), + )) + })?; let service_handle_arc = Arc::new(service_handle); let available_instances = (0..service_handle_arc.len()) @@ -697,8 +761,13 @@ where // Convert to Lola InstanceSpecifier early let instance_specifier_lola = - mw_com::InstanceSpecifier::try_from(instance_specifier.as_ref()) - .map_err(|_| Error::Fail); + mw_com::InstanceSpecifier::try_from(instance_specifier.as_ref()).map_err(|_| { + Error::ServiceDiscoveryFailed( + ServiceDiscoveryFailedReason::InstanceSpecifierInvalid( + "Failed to parse instance specifier".to_string(), + ), + ) + }); let waker_storage = Arc::new(futures::task::AtomicWaker::new()); @@ -740,7 +809,11 @@ where // stop_find_service is always called in Drop. let raw_handle = unsafe { bridge_ffi_rs::start_find_service(&fat_ptr, spec) }; if raw_handle.is_null() { - Err(Error::Fail) + Err(Error::ServiceDiscoveryFailed( + ServiceDiscoveryFailedReason::InternalError( + "Failed to start service discovery".to_string(), + ), + )) } else { // Single authoritative source of find_handle — return value only. // Callback's find_handle argument is ignored to prevent double-write. @@ -856,7 +929,10 @@ impl ConsumerDescriptor for SampleConsumerBuilder fn get_instance_identifier(&self) -> &InstanceSpecifier { //if InstanceSpecifier::ANY support enable by lola //then this API should get InstanceSpecifier from FFI Call - todo!() + panic!( + "InstanceSpecifier::ANY is not supported in LolaRuntimeImpl, + Please use FindServiceSpecifier::Specific with a valid instance specifier." + ); } } @@ -877,7 +953,12 @@ fn try_receive_samples( max_samples: usize, ) -> Result { if max_samples > max_num_samples { - return Err(Error::Fail); + return Err(Error::ReceiveFailed( + ReceiveFailedReason::SampleCountOutOfBounds(format!( + "Requested sample count exceeds subscription limit: requested {}, max {}", + max_samples, max_num_samples + )), + )); } // Create a callback that will be called by the C++ side for each new sample arrival let mut callback = create_sample_callback(scratch, max_samples); @@ -897,7 +978,12 @@ fn try_receive_samples( ) }; if count > max_num_samples as u32 { - return Err(Error::Fail); + return Err(Error::ReceiveFailed( + ReceiveFailedReason::SampleCountOutOfBounds(format!( + "Returned more samples than subscription limit: returned {}, max {}", + count, max_num_samples + )), + )); } Ok(count as usize) } diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs index 784e3cf60..aef688fe6 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs @@ -36,8 +36,8 @@ use core::ops::{Deref, DerefMut}; use std::sync::Arc; use com_api_concept::{ - Builder, CommData, Error, InstanceSpecifier, Interface, Producer, ProducerBuilder, - ProviderInfo, Result, + AllocationFailureReason, Builder, CommData, Error, EventFailedReason, InstanceSpecifier, + Interface, Producer, ProducerBuilder, ProducerCreationReason, ProviderInfo, Result, }; use bridge_ffi_rs::*; @@ -58,7 +58,10 @@ impl ProviderInfo for LolaProviderInfo { let status = unsafe { bridge_ffi_rs::skeleton_offer_service(self.skeleton_handle.0.handle) }; if !status { - return Err(Error::Fail); + return Err(Error::OfferServiceFailed(format!( + "Failed to offer service: {:?}", + self.instance_specifier + ))); } Ok(()) } @@ -187,7 +190,10 @@ where ) }; if !status { - return Err(Error::Fail); + return Err(Error::SendingDataFailed(format!( + "Failed to send sample for event, underlying status is: {}", + status + ))); } Ok(()) } @@ -389,7 +395,11 @@ where T::ID, ); if !status { - return Err(Error::Fail); + return Err(Error::MemoryAllocationFailed( + AllocationFailureReason::InternalError( + "Failed to allocate sample via skeleton event".to_string(), + ), + )); } sample.assume_init() }; @@ -405,7 +415,11 @@ where fn new(identifier: &str, instance_info: LolaProviderInfo) -> Result { let skeleton_event = NativeSkeletonEventBase::new(&instance_info, identifier); - + if skeleton_event.skeleton_event_ptr.is_null() { + return Err(Error::EventError(EventFailedReason::CreationFailed( + "Failed to get skeleton event for publisher".to_string(), + ))); + } Ok(Self { identifier: identifier.to_string(), skeleton_event, @@ -433,20 +447,35 @@ impl ProducerBuilder for SampleProducerBuilder impl Builder> for SampleProducerBuilder { fn build(self) -> Result> { - let instance_specifier_runtime = - mw_com::InstanceSpecifier::try_from(self.instance_specifier.as_ref()) - .map_err(|_| Error::Fail)?; + let instance_specifier_runtime = mw_com::InstanceSpecifier::try_from( + self.instance_specifier.as_ref(), + ) + .map_err(|_| { + Error::ProducerCreationFailed(ProducerCreationReason::InstanceSpecifierInvalid( + "Failed to parse instance specifier".to_string(), + )) + })?; let skeleton_handle = NativeSkeletonHandle::new(I::INTERFACE_ID, &instance_specifier_runtime); - + if skeleton_handle.handle.is_null() { + return Err(Error::ProducerCreationFailed( + ProducerCreationReason::SkeletonCreationFailed( + "Failed to create skeleton for producer".to_string(), + ), + )); + } let instance_info = LolaProviderInfo { instance_specifier: self.instance_specifier, interface_id: I::INTERFACE_ID, skeleton_handle: SkeletonInstanceManager(Arc::new(skeleton_handle)), }; - I::Producer::new(instance_info).map_err(|_| Error::Fail) + I::Producer::new(instance_info).map_err(|_| { + Error::ProducerCreationFailed(ProducerCreationReason::InternalError( + "Failed to construct producer from instance info".to_string(), + )) + }) } } diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs index 529def246..927051feb 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs @@ -39,7 +39,10 @@ impl Runtime for LolaRuntimeImpl { ) -> Self::ServiceDiscovery { SampleConsumerDiscovery { instance_specifier: match instance_specifier { - FindServiceSpecifier::Any => todo!(), // TODO:[eclipse-score/communication/issues/133]Add error msg or panic like "ANY not supported by Lola" + FindServiceSpecifier::Any => panic!( + "FindServiceSpecifier::Any is not supported in LolaRuntimeImpl, + Please use FindServiceSpecifier::Specific with a valid instance specifier." + ), FindServiceSpecifier::Specific(spec) => spec, }, _interface: PhantomData, From afc254ee5a3d1567152bcb8f982540a33a3a318c Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Wed, 18 Mar 2026 10:57:24 +0530 Subject: [PATCH 2/7] Rust::com COM-API Error optimization * Updated Error enums and removed string from variant --- .../com-api-concept/com_api_concept.rs | 9 +- .../rust/com-api/com-api-concept/error.rs | 108 +++++++++++------- .../com-api/com-api-runtime-lola/consumer.rs | 106 ++++++----------- .../com-api/com-api-runtime-lola/producer.rs | 66 +++++------ 4 files changed, 134 insertions(+), 155 deletions(-) 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 a803ed20d..65cb5dd36 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 @@ -269,10 +269,7 @@ impl InstanceSpecifier { specifier: service_name.to_string(), }) } else { - Err(Error::InstanceSpecifierInvalid(format!( - "Invalid instance specifier format: '{}'", - service_name - ))) + Err(Error::InstanceSpecifierInvalid) } } } @@ -709,9 +706,7 @@ impl SampleContainer { /// Returns 'Error::SampleContainerAllocateFailed' if container is already full. pub fn push_back(&mut self, new: S) -> Result<()> { self.inner.push_back(new).map_err(|_| { - Error::SampleContainerAllocateFailed(AllocationFailureReason::OutOfMemory( - "Sample container is full".to_string(), - )) + Error::SampleContainerAllocateFailed(AllocationFailureReason::OutOfMemory) })?; Ok(()) } diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs index 79147b3d7..cdcd7f444 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs @@ -12,89 +12,93 @@ ********************************************************************************/ /// Detailed information about Service Discovery failure reasons, -/// including specific issues with service interfaces, instance specifiers, timeouts, and handle retrieval. +/// including specific issues with service interfaces, instance specifiers, and handle retrieval. #[derive(Debug)] pub enum ServiceDiscoveryFailedReason { /// Invalid instance specifier format or content - InstanceSpecifierInvalid(String), + InstanceSpecifierInvalid, /// Service handle not found - ServiceNotFound(String), - /// Internal error with details - InternalError(String), + ServiceNotFound, + /// Internal error + InternalError(&'static str), } /// Reason for producer creation failure #[derive(Debug)] pub enum ProducerCreationReason { /// Invalid instance specifier format or content - InstanceSpecifierInvalid(String), - /// Skeleton creation failed with details - SkeletonCreationFailed(String), - /// Internal error with details - InternalError(String), + InstanceSpecifierInvalid, + /// Skeleton creation failed + SkeletonCreationFailed, + /// Builder creation failed + BuilderCreationFailed, + /// Internal error + InternalError(&'static str), } /// Reason for consumer creation failure #[derive(Debug)] pub enum ConsumerCreationReason { /// Invalid instance specifier format or content - InstanceSpecifierInvalid(String), + InstanceSpecifierInvalid, /// Service handle not found from discovery - ServiceHandleNotFound(String), - /// Proxy creation failed with details - ProxyCreationFailed(String), - /// Internal error with details - InternalError(String), + ServiceHandleNotFound, + /// Proxy creation failed + ProxyCreationFailed, + /// Internal error + InternalError(&'static str), } /// Reason for subscribe operation failure #[derive(Debug)] pub enum SubscribeFailureReason { /// Event not available - EventNotAvailable(String), + EventNotAvailable, /// Internal subscription error - InternalError(String), + InternalError(&'static str), } /// Memory allocation error details #[derive(Debug)] pub enum AllocationFailureReason { /// Requested size exceeds available memory - OutOfMemory(String), + OutOfMemory, /// Invalid allocation request - InvalidRequest(String), + InvalidRequest, /// Internal allocation error - InternalError(String), + InternalError(&'static str), } /// Comprehensive error reasons asynchronous receive failure #[derive(Debug)] pub enum AsyncReceiveFailedReason { - /// initialization failed with details - InitializationFailed(String), - /// receive operation failed with details - ReceiveError(String), + /// initialization failed + InitializationFailed, + /// receive operation failed + ReceiveError, + /// Bufffer not available for receive operation + BufferUnavailable, /// sample count out of bounds - SampleCountOutOfBounds(String), + SampleCountOutOfBounds(&'static str, usize, usize), } /// Comprehensive error reasons for receive failure #[derive(Debug)] pub enum ReceiveFailedReason { - /// initialization failed with details - InitializationFailed(String), - /// receive operation failed with details - ReceiveError(String), + /// initialization failed + InitializationFailed, + /// receive operation failed + ReceiveError, /// sample count out of bounds - SampleCountOutOfBounds(String), + SampleCountOutOfBounds(&'static str, usize, usize), } /// Comprehensive error reasons for event-related failures #[derive(Debug)] pub enum EventFailedReason { - /// Event creation failed with details - CreationFailed(String), - /// Event publication failed with details - PublishFailed(String), + /// Event creation failed + EventCreationFailed, + /// Event publication failed + EventPublishFailed, } /// Error enumeration for different failure cases in the Consumer/Producer/Runtime APIs. @@ -109,10 +113,10 @@ pub enum Error { /// Failed to create consumer instance ConsumerCreationFailed(ConsumerCreationReason), /// Failed to offer service instance for discovery - OfferServiceFailed(String), + OfferServiceFailed, /// Failed to send sample - SendingDataFailed(String), - /// Event Error with details + SendingDataFailed, + /// Event Error EventError(EventFailedReason), /// Sample container allocation failed SampleContainerAllocateFailed(AllocationFailureReason), @@ -123,7 +127,31 @@ pub enum Error { /// Receive operation failed ReceiveFailed(ReceiveFailedReason), /// Invalid instance specifier provided - InstanceSpecifierInvalid(String), - /// Event subscription failed with details + InstanceSpecifierInvalid, + /// Event subscription failed SubscribeFailed(SubscribeFailureReason), } + +impl std::fmt::Display for ServiceDiscoveryFailedReason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InstanceSpecifierInvalid => { + write!(f, "invalid instance specifier format or content") + } + Self::ServiceNotFound => write!(f, "service handle not found"), + Self::InternalError(msg) => write!(f, "internal error: {}", msg), + } + } +} + +impl core::fmt::Display for ReceiveFailedReason { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InitializationFailed => write!(f, "initialization failed"), + Self::ReceiveError => write!(f, "receive operation failed"), + Self::SampleCountOutOfBounds(msg, requested, max) => { + write!(f, "{}: requested {}, max {}", msg, requested, max) + } + } + } +} diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs index c68d10731..5abfac5d6 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs @@ -42,9 +42,9 @@ use std::sync::Arc; use com_api_concept::{ AsyncReceiveFailedReason, Builder, CommData, Consumer, ConsumerBuilder, ConsumerCreationReason, - ConsumerDescriptor, Error, InstanceSpecifier, Interface, ReceiveFailedReason, Result, - SampleContainer, ServiceDiscovery, ServiceDiscoveryFailedReason, SubscribeFailureReason, - Subscriber, Subscription, + ConsumerDescriptor, Error, EventFailedReason, InstanceSpecifier, Interface, + ReceiveFailedReason, Result, SampleContainer, ServiceDiscovery, ServiceDiscoveryFailedReason, + SubscribeFailureReason, Subscriber, Subscription, }; use bridge_ffi_rs::*; @@ -225,11 +225,16 @@ impl std::fmt::Debug for NativeProxyBase { } impl NativeProxyBase { - pub fn new(interface_id: &str, handle: &HandleType) -> Self { + pub fn new(interface_id: &str, handle: &HandleType) -> Result { //SAFETY: It is safe to create the proxy because interface_id and handle are valid //Handle received at the time of get_avaible_instances called with correct interface_id let proxy = unsafe { bridge_ffi_rs::create_proxy(interface_id, handle) }; - Self { proxy } + if proxy.is_null() { + return Err(Error::ConsumerCreationFailed( + ConsumerCreationReason::ProxyCreationFailed, + )); + } + Ok(Self { proxy }) } } @@ -251,12 +256,15 @@ pub struct NativeProxyEventBase { unsafe impl Send for NativeProxyEventBase {} impl NativeProxyEventBase { - pub fn new(proxy: *mut ProxyBase, interface_id: &str, identifier: &str) -> Self { + pub fn new(proxy: *mut ProxyBase, interface_id: &str, identifier: &str) -> Result { //SAFETY: It is safe as we are passing valid proxy pointer and interface id to get event // proxy pointer is created during consumer creation let proxy_event_ptr = unsafe { bridge_ffi_rs::get_event_from_proxy(proxy, interface_id, identifier) }; - Self { proxy_event_ptr } + if proxy_event_ptr.is_null() { + return Err(Error::EventError(EventFailedReason::EventCreationFailed)); + } + Ok(Self { proxy_event_ptr }) } /// Provides access to the underlying proxy event base. @@ -291,19 +299,9 @@ impl Subscriber for SubscribableImpl let handle = instance_info .get_handle() .ok_or(Error::ConsumerCreationFailed( - ConsumerCreationReason::ServiceHandleNotFound(format!( - "Service handle not found for instance: {}", - identifier - )), + ConsumerCreationReason::ServiceHandleNotFound, ))?; - let native_proxy = NativeProxyBase::new(instance_info.interface_id, handle); - if native_proxy.proxy.is_null() { - return Err(Error::ConsumerCreationFailed( - ConsumerCreationReason::ProxyCreationFailed( - "Failed to create proxy instance".to_string(), - ), - )); - } + let native_proxy = NativeProxyBase::new(instance_info.interface_id, handle)?; let proxy_instance = ProxyInstanceManager(Arc::new(native_proxy)); Ok(Self { identifier, @@ -318,16 +316,7 @@ impl Subscriber for SubscribableImpl self.proxy_instance.0.proxy, self.instance_info.interface_id, self.identifier, - ); - if event_instance.proxy_event_ptr.is_null() { - return Err(Error::SubscribeFailed( - SubscribeFailureReason::EventNotAvailable(format!( - "Event not available for identifier: {}", - self.identifier - )), - )); - } - + )?; //SAFETY: It is safe to subscribe to event because event_instance is valid // which was obtained from valid proxy instance let status = unsafe { @@ -338,10 +327,7 @@ impl Subscriber for SubscribableImpl }; if !status { return Err(Error::SubscribeFailed( - SubscribeFailureReason::InternalError(format!( - "Failed to subscribe to event: {}", - self.identifier - )), + SubscribeFailureReason::InternalError("Failed to subscribe to event"), )); } // Store in SubscriberImpl with event, max_num_samples @@ -542,12 +528,11 @@ where if max_samples > self.max_num_samples || new_samples > self.max_num_samples { return Err(Error::AsyncReceiveFailed( AsyncReceiveFailedReason::SampleCountOutOfBounds( - format!( - "Requested sample count exceeds subscription limit: requested new_samples {}, requested max_samples {}, subscription max_num_samples {}", - new_samples, max_samples, self.max_num_samples + "Requested sample count exceeds subscription limit: requested max_samples {}, subscription max_num_samples {}", + max_samples, self.max_num_samples ) ), - )); + ); } // Get the event guard to ensure no concurrent receive calls // on the same subscriber instance. @@ -559,9 +544,7 @@ where { if let Err(_e) = self.init_async_receive(&mut event_guard) { return Err(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::InitializationFailed( - "Failed to initialize async receive".to_string(), - ), + AsyncReceiveFailedReason::InitializationFailed, )); } self.async_init_status @@ -623,16 +606,12 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { result } else { Err(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::ReceiveError( - "Failed to receive sample data".to_string(), - ), + AsyncReceiveFailedReason::ReceiveError, )) } } else { Err(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::ReceiveError( - "Internal buffer not available".to_string(), - ), + AsyncReceiveFailedReason::BufferUnavailable, )) } }; @@ -646,9 +625,7 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { // proxy event self.event_guard = None; return Poll::Ready(self.scratch.take().ok_or(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::ReceiveError( - "Internal buffer not available".to_string(), - ), + AsyncReceiveFailedReason::BufferUnavailable, ))); } // Have some samples but not enough yet, wait for more via waker @@ -714,19 +691,12 @@ where self.instance_specifier.as_ref(), ) .map_err(|_| { - Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::InstanceSpecifierInvalid( - "Failed to parse instance specifier".to_string(), - )) + Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::InstanceSpecifierInvalid) })?; let service_handle = mw_com::proxy::find_service(instance_specifier_lola).map_err(|_| { - Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::ServiceNotFound( - format!( - "No service found matching specifier: {}", - self.instance_specifier.as_ref() - ), - )) + Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::ServiceNotFound) })?; let service_handle_arc = Arc::new(service_handle); @@ -763,9 +733,7 @@ where let instance_specifier_lola = mw_com::InstanceSpecifier::try_from(instance_specifier.as_ref()).map_err(|_| { Error::ServiceDiscoveryFailed( - ServiceDiscoveryFailedReason::InstanceSpecifierInvalid( - "Failed to parse instance specifier".to_string(), - ), + ServiceDiscoveryFailedReason::InstanceSpecifierInvalid, ) }); @@ -811,7 +779,7 @@ where if raw_handle.is_null() { Err(Error::ServiceDiscoveryFailed( ServiceDiscoveryFailedReason::InternalError( - "Failed to start service discovery".to_string(), + "Failed to start service discovery", ), )) } else { @@ -954,10 +922,11 @@ fn try_receive_samples( ) -> Result { if max_samples > max_num_samples { return Err(Error::ReceiveFailed( - ReceiveFailedReason::SampleCountOutOfBounds(format!( + ReceiveFailedReason::SampleCountOutOfBounds( "Requested sample count exceeds subscription limit: requested {}, max {}", - max_samples, max_num_samples - )), + max_samples, + max_num_samples, + ), )); } // Create a callback that will be called by the C++ side for each new sample arrival @@ -979,10 +948,11 @@ fn try_receive_samples( }; if count > max_num_samples as u32 { return Err(Error::ReceiveFailed( - ReceiveFailedReason::SampleCountOutOfBounds(format!( + ReceiveFailedReason::SampleCountOutOfBounds( "Returned more samples than subscription limit: returned {}, max {}", - count, max_num_samples - )), + count as usize, + max_num_samples, + ), )); } Ok(count as usize) diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs index aef688fe6..72dc4c80f 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs @@ -36,8 +36,8 @@ use core::ops::{Deref, DerefMut}; use std::sync::Arc; use com_api_concept::{ - AllocationFailureReason, Builder, CommData, Error, EventFailedReason, InstanceSpecifier, - Interface, Producer, ProducerBuilder, ProducerCreationReason, ProviderInfo, Result, + AllocationFailureReason, Builder, CommData, Error, InstanceSpecifier, Interface, Producer, + ProducerBuilder, ProducerCreationReason, ProviderInfo, Result, }; use bridge_ffi_rs::*; @@ -58,10 +58,7 @@ impl ProviderInfo for LolaProviderInfo { let status = unsafe { bridge_ffi_rs::skeleton_offer_service(self.skeleton_handle.0.handle) }; if !status { - return Err(Error::OfferServiceFailed(format!( - "Failed to offer service: {:?}", - self.instance_specifier - ))); + return Err(Error::OfferServiceFailed); } Ok(()) } @@ -190,10 +187,7 @@ where ) }; if !status { - return Err(Error::SendingDataFailed(format!( - "Failed to send sample for event, underlying status is: {}", - status - ))); + return Err(Error::SendingDataFailed); } Ok(()) } @@ -303,11 +297,16 @@ unsafe impl Sync for NativeSkeletonHandle {} unsafe impl Send for NativeSkeletonHandle {} impl NativeSkeletonHandle { - pub fn new(interface_id: &str, instance_specifier: &mw_com::InstanceSpecifier) -> Self { + pub fn new(interface_id: &str, instance_specifier: &mw_com::InstanceSpecifier) -> Result { //SAFETY: It is safe as we are passing valid type id and instance specifier to create skeleton let handle = unsafe { bridge_ffi_rs::create_skeleton(interface_id, instance_specifier.as_native()) }; - Self { handle } + if handle.is_null() { + return Err(Error::ProducerCreationFailed( + ProducerCreationReason::SkeletonCreationFailed, + )); + } + Ok(Self { handle }) } } @@ -335,7 +334,7 @@ pub struct NativeSkeletonEventBase { unsafe impl Send for NativeSkeletonEventBase {} impl NativeSkeletonEventBase { - pub fn new(instance_info: &LolaProviderInfo, identifier: &str) -> Self { + pub fn new(instance_info: &LolaProviderInfo, identifier: &str) -> Result { //SAFETY: It is safe as we are passing valid skeleton handle and interface id to get event // skeleton handle is created during producer offer call let skeleton_event_ptr = unsafe { @@ -345,7 +344,12 @@ impl NativeSkeletonEventBase { identifier, ) }; - Self { skeleton_event_ptr } + if skeleton_event_ptr.is_null() { + return Err(Error::ProducerCreationFailed( + ProducerCreationReason::SkeletonCreationFailed, + )); + } + Ok(Self { skeleton_event_ptr }) } } @@ -397,7 +401,7 @@ where if !status { return Err(Error::MemoryAllocationFailed( AllocationFailureReason::InternalError( - "Failed to allocate sample via skeleton event".to_string(), + "Failed to allocate sample via skeleton event", ), )); } @@ -414,12 +418,7 @@ where } fn new(identifier: &str, instance_info: LolaProviderInfo) -> Result { - let skeleton_event = NativeSkeletonEventBase::new(&instance_info, identifier); - if skeleton_event.skeleton_event_ptr.is_null() { - return Err(Error::EventError(EventFailedReason::CreationFailed( - "Failed to get skeleton event for publisher".to_string(), - ))); - } + let skeleton_event = NativeSkeletonEventBase::new(&instance_info, identifier)?; Ok(Self { identifier: identifier.to_string(), skeleton_event, @@ -447,24 +446,13 @@ impl ProducerBuilder for SampleProducerBuilder impl Builder> for SampleProducerBuilder { fn build(self) -> Result> { - let instance_specifier_runtime = mw_com::InstanceSpecifier::try_from( - self.instance_specifier.as_ref(), - ) - .map_err(|_| { - Error::ProducerCreationFailed(ProducerCreationReason::InstanceSpecifierInvalid( - "Failed to parse instance specifier".to_string(), - )) - })?; + let instance_specifier_runtime = + mw_com::InstanceSpecifier::try_from(self.instance_specifier.as_ref()).map_err( + |_| Error::ProducerCreationFailed(ProducerCreationReason::InstanceSpecifierInvalid), + )?; let skeleton_handle = - NativeSkeletonHandle::new(I::INTERFACE_ID, &instance_specifier_runtime); - if skeleton_handle.handle.is_null() { - return Err(Error::ProducerCreationFailed( - ProducerCreationReason::SkeletonCreationFailed( - "Failed to create skeleton for producer".to_string(), - ), - )); - } + NativeSkeletonHandle::new(I::INTERFACE_ID, &instance_specifier_runtime)?; let instance_info = LolaProviderInfo { instance_specifier: self.instance_specifier, interface_id: I::INTERFACE_ID, @@ -472,9 +460,7 @@ impl Builder> for SampleProducerBuild }; I::Producer::new(instance_info).map_err(|_| { - Error::ProducerCreationFailed(ProducerCreationReason::InternalError( - "Failed to construct producer from instance info".to_string(), - )) + Error::ProducerCreationFailed(ProducerCreationReason::BuilderCreationFailed) }) } } From 2efd3c33be450106d970270c1165ba1be3cc1b6f Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Wed, 18 Mar 2026 16:56:09 +0530 Subject: [PATCH 3/7] Rust::com Created macro for display and debug trait impl for Error enum * Created ErrorDisplay macro for display and debug trait impl for enum * Optimized error module --- .../com-api/com-api-concept-macros/lib.rs | 118 +++++++++++++++++ .../rust/com-api/com-api-concept/error.rs | 125 +++++++----------- .../com-api/com-api-runtime-lola/consumer.rs | 19 +-- 3 files changed, 172 insertions(+), 90 deletions(-) diff --git a/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs index f19b5c94c..5a7da92c6 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs @@ -335,6 +335,124 @@ fn collect_field_types(data: &Data) -> Result, ()> { Ok(out) } +/// Automatically generates Display and Debug implementations from #[error] attributes +/// +/// Generates both Display and Debug traits using `#[error("...")]` attributes (thiserror-style): +/// - Display: Shows human-readable error message from #[error] attributes +/// - Debug: Delegates to Display for consistent, user-friendly output +/// +/// # Usage +/// ```ignore +/// #[derive(ErrorDisplay)] +/// pub enum MyError { +/// #[error("error message here")] +/// SomeVariant, +/// +/// #[error("nested error")] +/// OtherVariant(SomeError), +/// } +/// ``` +#[proc_macro_derive(ErrorDisplay, attributes(error))] +pub fn error_display(item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DeriveInput); + let name = &input.ident; + + let display_arms = match &input.data { + Data::Enum(data) => { + data.variants + .iter() + .map(|variant| { + let variant_name = &variant.ident; + + // Extract error message from #[error("...")] attribute + let error_msg = variant + .attrs + .iter() + .find_map(|attr| { + if attr.path().is_ident("error") { + if let syn::Meta::NameValue(nv) = &attr.meta { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit_str), + .. + }) = &nv.value + { + return Some(lit_str.value()); + } + } + } + None + }) + .unwrap_or_else(|| format!("{}", variant_name)); + + // Match pattern based on field types + match &variant.fields { + Fields::Unit => { + quote! { + #name::#variant_name => write!(f, "{}", #error_msg) + } + } + Fields::Unnamed(fields) => { + let field_count = fields.unnamed.len(); + if field_count == 1 { + quote! {#name::#variant_name(err) => write!(f, "{}: {}", #error_msg, err)} + } else { + let has_placeholders = error_msg.contains("{}"); + if has_placeholders { + let field_names: Vec<_> = (0..field_count) + .map(|i| { + syn::Ident::new( + &format!("f{}", i), + proc_macro::Span::call_site().into(), + ) + }) + .collect(); + let field_refs = field_names.iter(); + quote! { + #name::#variant_name(#(#field_names),*) => { + write!(f, "{}", format!(#error_msg, #(#field_refs),*)) + } + } + } else { + let wildcards = (0..field_count).map(|_| quote! { _ }); + quote! {#name::#variant_name(#(#wildcards),*) => write!(f, "{}", #error_msg)} + } + } + } + Fields::Named(_) => { + quote! { + #name::#variant_name { .. } => write!(f, "{}", #error_msg) + } + } + } + }) + .collect::>() + } + _ => { + return syn::Error::new_spanned(&input, "ErrorDisplay only works on enums") + .to_compile_error() + .into() + } + }; + + let expanded = quote! { + impl core::fmt::Display for #name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + #(#display_arms),* + } + } + } + + impl core::fmt::Debug for #name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self) + } + } + }; + + TokenStream::from(expanded) +} + // Use doctest to test failed compilations and successful ones /// ``` diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs index cdcd7f444..f58bf942e 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs @@ -11,147 +11,120 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +pub use com_api_concept_macros::ErrorDisplay; + /// Detailed information about Service Discovery failure reasons, /// including specific issues with service interfaces, instance specifiers, and handle retrieval. -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum ServiceDiscoveryFailedReason { - /// Invalid instance specifier format or content + #[error = "Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content"] InstanceSpecifierInvalid, - /// Service handle not found + #[error = "Service not found, which may not be available currently or accessible"] ServiceNotFound, - /// Internal error + #[error = "Internal error during service discovery"] InternalError(&'static str), } /// Reason for producer creation failure -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum ProducerCreationReason { - /// Invalid instance specifier format or content + #[error = "Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content"] InstanceSpecifierInvalid, - /// Skeleton creation failed + #[error = "Skeleton creation failed"] SkeletonCreationFailed, - /// Builder creation failed + #[error = "Builder creation failed for producer instance"] BuilderCreationFailed, - /// Internal error - InternalError(&'static str), } /// Reason for consumer creation failure -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum ConsumerCreationReason { - /// Invalid instance specifier format or content + #[error = "Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content"] InstanceSpecifierInvalid, - /// Service handle not found from discovery + #[error = "Service not found from service discovery handle"] ServiceHandleNotFound, - /// Proxy creation failed + #[error = "Proxy creation failed"] ProxyCreationFailed, - /// Internal error - InternalError(&'static str), } /// Reason for subscribe operation failure -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum SubscribeFailureReason { - /// Event not available + #[error = "Event not available for subscription, possibly due to missing event type or incompatible service"] EventNotAvailable, - /// Internal subscription error + #[error = "Internal subscription error"] InternalError(&'static str), } + /// Memory allocation error details -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum AllocationFailureReason { - /// Requested size exceeds available memory + #[error = "Requested size exceeds available memory which is configured during subscription setup"] OutOfMemory, - /// Invalid allocation request + #[error = "Invalid allocation request"] InvalidRequest, - /// Internal allocation error + #[error = "Internal allocation error related to allocation operations of shared memory or sample container"] InternalError(&'static str), } /// Comprehensive error reasons asynchronous receive failure -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum AsyncReceiveFailedReason { - /// initialization failed + #[error = "Error at the time of initialzing async receive operation"] InitializationFailed, - /// receive operation failed + #[error = "Receive operation failed because of internal error"] ReceiveError, - /// Bufffer not available for receive operation + #[error = "Internal receive buffer not available for receive operation"] BufferUnavailable, - /// sample count out of bounds - SampleCountOutOfBounds(&'static str, usize, usize), + #[error = "Sample size out of bounds, expected at most {}, but got {}"] + SampleCountOutOfBounds(usize, usize), } /// Comprehensive error reasons for receive failure -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum ReceiveFailedReason { - /// initialization failed + #[error = "Error at the time of initializing receive operation"] InitializationFailed, - /// receive operation failed + #[error = "Receive operation failed because of internal error"] ReceiveError, - /// sample count out of bounds - SampleCountOutOfBounds(&'static str, usize, usize), + #[error = "Sample size out of bounds, expected at most {}, but got {}"] + SampleCountOutOfBounds(usize, usize), } /// Comprehensive error reasons for event-related failures -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum EventFailedReason { - /// Event creation failed + #[error = "Event creation error, possibly due to invalid event type or internal error"] EventCreationFailed, - /// Event publication failed + #[error = "Event publication failed, possibly due to internal error"] EventPublishFailed, } /// Error enumeration for different failure cases in the Consumer/Producer/Runtime APIs. -#[derive(Debug)] +#[derive(ErrorDisplay)] pub enum Error { - /// Generic failure with optional message - Fail(Option), - /// Service discovery failed with detailed reason + #[error = "Service discovery operation failed due to"] ServiceDiscoveryFailed(ServiceDiscoveryFailedReason), - /// Failed to create producer instance + #[error = "Producer creation failed due to"] ProducerCreationFailed(ProducerCreationReason), - /// Failed to create consumer instance + #[error = "Consumer creation failed due to"] ConsumerCreationFailed(ConsumerCreationReason), - /// Failed to offer service instance for discovery + #[error = "Failed to offer service instance for discovery"] OfferServiceFailed, - /// Failed to send sample + #[error = "Failed to send sample data to the service instance, possibly due to internal error"] SendingDataFailed, - /// Event Error + #[error = "Event operation failed due to"] EventError(EventFailedReason), - /// Sample container allocation failed + #[error = "Sample container allocation failed due to"] SampleContainerAllocateFailed(AllocationFailureReason), - /// Memory allocation failed + #[error = "Memory allocation failed due to"] MemoryAllocationFailed(AllocationFailureReason), - /// Asynchronous receive operation failed + #[error = "Asynchronous receive operation failed due to"] AsyncReceiveFailed(AsyncReceiveFailedReason), - /// Receive operation failed + #[error = "Receive operation failed due to"] ReceiveFailed(ReceiveFailedReason), - /// Invalid instance specifier provided + #[error = "Invalid instance specifier provided, which may be not according to the expected format or contain invalid content"] InstanceSpecifierInvalid, - /// Event subscription failed + #[error = "Event subscription failed due to"] SubscribeFailed(SubscribeFailureReason), } - -impl std::fmt::Display for ServiceDiscoveryFailedReason { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InstanceSpecifierInvalid => { - write!(f, "invalid instance specifier format or content") - } - Self::ServiceNotFound => write!(f, "service handle not found"), - Self::InternalError(msg) => write!(f, "internal error: {}", msg), - } - } -} - -impl core::fmt::Display for ReceiveFailedReason { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::InitializationFailed => write!(f, "initialization failed"), - Self::ReceiveError => write!(f, "receive operation failed"), - Self::SampleCountOutOfBounds(msg, requested, max) => { - write!(f, "{}: requested {}, max {}", msg, requested, max) - } - } - } -} diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs index 5abfac5d6..c59c1f0a7 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs @@ -528,11 +528,10 @@ where if max_samples > self.max_num_samples || new_samples > self.max_num_samples { return Err(Error::AsyncReceiveFailed( AsyncReceiveFailedReason::SampleCountOutOfBounds( - "Requested sample count exceeds subscription limit: requested max_samples {}, subscription max_num_samples {}", - max_samples, self.max_num_samples - ) + self.max_num_samples, + max_samples.max(new_samples), ), - ); + )); } // Get the event guard to ensure no concurrent receive calls // on the same subscriber instance. @@ -922,11 +921,7 @@ fn try_receive_samples( ) -> Result { if max_samples > max_num_samples { return Err(Error::ReceiveFailed( - ReceiveFailedReason::SampleCountOutOfBounds( - "Requested sample count exceeds subscription limit: requested {}, max {}", - max_samples, - max_num_samples, - ), + ReceiveFailedReason::SampleCountOutOfBounds(max_num_samples, max_samples), )); } // Create a callback that will be called by the C++ side for each new sample arrival @@ -948,11 +943,7 @@ fn try_receive_samples( }; if count > max_num_samples as u32 { return Err(Error::ReceiveFailed( - ReceiveFailedReason::SampleCountOutOfBounds( - "Returned more samples than subscription limit: returned {}, max {}", - count as usize, - max_num_samples, - ), + ReceiveFailedReason::SampleCountOutOfBounds(max_num_samples, count as usize), )); } Ok(count as usize) From c7402f3616b60673eb9d07c197cba3c106c6d639 Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Fri, 20 Mar 2026 21:25:36 +0530 Subject: [PATCH 4/7] Rust::com Added thiserror crate for Error enum * Added thiserror crate for display of Error msg --- .../com-api/com-api-concept-macros/lib.rs | 118 ---------------- .../impl/rust/com-api/com-api-concept/BUILD | 1 + .../com-api-concept/com_api_concept.rs | 6 +- .../rust/com-api/com-api-concept/error.rs | 132 +++++++----------- .../com-api/com-api-runtime-lola/consumer.rs | 95 ++++++------- .../com-api/com-api-runtime-lola/producer.rs | 36 +++-- 6 files changed, 117 insertions(+), 271 deletions(-) diff --git a/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs index 5a7da92c6..f19b5c94c 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs @@ -335,124 +335,6 @@ fn collect_field_types(data: &Data) -> Result, ()> { Ok(out) } -/// Automatically generates Display and Debug implementations from #[error] attributes -/// -/// Generates both Display and Debug traits using `#[error("...")]` attributes (thiserror-style): -/// - Display: Shows human-readable error message from #[error] attributes -/// - Debug: Delegates to Display for consistent, user-friendly output -/// -/// # Usage -/// ```ignore -/// #[derive(ErrorDisplay)] -/// pub enum MyError { -/// #[error("error message here")] -/// SomeVariant, -/// -/// #[error("nested error")] -/// OtherVariant(SomeError), -/// } -/// ``` -#[proc_macro_derive(ErrorDisplay, attributes(error))] -pub fn error_display(item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as DeriveInput); - let name = &input.ident; - - let display_arms = match &input.data { - Data::Enum(data) => { - data.variants - .iter() - .map(|variant| { - let variant_name = &variant.ident; - - // Extract error message from #[error("...")] attribute - let error_msg = variant - .attrs - .iter() - .find_map(|attr| { - if attr.path().is_ident("error") { - if let syn::Meta::NameValue(nv) = &attr.meta { - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit_str), - .. - }) = &nv.value - { - return Some(lit_str.value()); - } - } - } - None - }) - .unwrap_or_else(|| format!("{}", variant_name)); - - // Match pattern based on field types - match &variant.fields { - Fields::Unit => { - quote! { - #name::#variant_name => write!(f, "{}", #error_msg) - } - } - Fields::Unnamed(fields) => { - let field_count = fields.unnamed.len(); - if field_count == 1 { - quote! {#name::#variant_name(err) => write!(f, "{}: {}", #error_msg, err)} - } else { - let has_placeholders = error_msg.contains("{}"); - if has_placeholders { - let field_names: Vec<_> = (0..field_count) - .map(|i| { - syn::Ident::new( - &format!("f{}", i), - proc_macro::Span::call_site().into(), - ) - }) - .collect(); - let field_refs = field_names.iter(); - quote! { - #name::#variant_name(#(#field_names),*) => { - write!(f, "{}", format!(#error_msg, #(#field_refs),*)) - } - } - } else { - let wildcards = (0..field_count).map(|_| quote! { _ }); - quote! {#name::#variant_name(#(#wildcards),*) => write!(f, "{}", #error_msg)} - } - } - } - Fields::Named(_) => { - quote! { - #name::#variant_name { .. } => write!(f, "{}", #error_msg) - } - } - } - }) - .collect::>() - } - _ => { - return syn::Error::new_spanned(&input, "ErrorDisplay only works on enums") - .to_compile_error() - .into() - } - }; - - let expanded = quote! { - impl core::fmt::Display for #name { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - #(#display_arms),* - } - } - } - - impl core::fmt::Debug for #name { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self) - } - } - }; - - TokenStream::from(expanded) -} - // Use doctest to test failed compilations and successful ones /// ``` 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 9fb465cb3..76f9b0d18 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 @@ -31,6 +31,7 @@ rust_library( ], deps = [ "@score_baselibs_rust//src/containers", + "@score_communication_crate_index//:thiserror", ], ) 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 65cb5dd36..08db9a69b 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 @@ -269,7 +269,7 @@ impl InstanceSpecifier { specifier: service_name.to_string(), }) } else { - Err(Error::InstanceSpecifierInvalid) + Err(Error::ServiceError(ServiceFailedReason::InstanceSpecifierInvalid)) } } } @@ -703,10 +703,10 @@ impl SampleContainer { /// A `Result` indicating success or failure. /// /// # Errors - /// Returns 'Error::SampleContainerAllocateFailed' if container is already full. + /// Returns 'Error::AllocateError' if container is already full. pub fn push_back(&mut self, new: S) -> Result<()> { self.inner.push_back(new).map_err(|_| { - Error::SampleContainerAllocateFailed(AllocationFailureReason::OutOfMemory) + Error::AllocateError(AllocationFailureReason::OutOfMemory) })?; Ok(()) } diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs index f58bf942e..ef7c2fd93 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs @@ -11,120 +11,96 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -pub use com_api_concept_macros::ErrorDisplay; +use thiserror::Error; /// Detailed information about Service Discovery failure reasons, /// including specific issues with service interfaces, instance specifiers, and handle retrieval. -#[derive(ErrorDisplay)] -pub enum ServiceDiscoveryFailedReason { - #[error = "Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content"] +#[derive(Debug, Error)] +pub enum ServiceFailedReason { + #[error("Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content")] InstanceSpecifierInvalid, - #[error = "Service not found, which may not be available currently or accessible"] + #[error("Service not found, which may not be available currently or accessible")] ServiceNotFound, - #[error = "Internal error during service discovery"] - InternalError(&'static str), + #[error("Failed to offer service instance for discovery")] + OfferServiceFailed, + #[error("Failed to start asynchronous discovery")] + FailedToStartDiscovery, } -/// Reason for producer creation failure -#[derive(ErrorDisplay)] -pub enum ProducerCreationReason { - #[error = "Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content"] +/// Reason for producer failure +#[derive(Debug, Error)] +pub enum ProducerFailedReason { + #[error("Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content")] InstanceSpecifierInvalid, - #[error = "Skeleton creation failed"] + #[error("Skeleton creation failed")] SkeletonCreationFailed, - #[error = "Builder creation failed for producer instance"] + #[error("Builder creation failed for producer instance")] BuilderCreationFailed, } -/// Reason for consumer creation failure -#[derive(ErrorDisplay)] -pub enum ConsumerCreationReason { - #[error = "Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content"] +/// Reason for consumer failure +#[derive(Debug, Error)] +pub enum ConsumerFailedReason { + #[error("Invalid instance specifier format or content, which may not be according to the expected format or contain invalid content")] InstanceSpecifierInvalid, - #[error = "Service not found from service discovery handle"] + #[error("Service not found from service discovery handle")] ServiceHandleNotFound, - #[error = "Proxy creation failed"] + #[error("Proxy creation failed")] ProxyCreationFailed, } -/// Reason for subscribe operation failure -#[derive(ErrorDisplay)] -pub enum SubscribeFailureReason { - #[error = "Event not available for subscription, possibly due to missing event type or incompatible service"] - EventNotAvailable, - #[error = "Internal subscription error"] - InternalError(&'static str), -} - /// Memory allocation error details -#[derive(ErrorDisplay)] +#[derive(Debug, Error)] pub enum AllocationFailureReason { - #[error = "Requested size exceeds available memory which is configured during subscription setup"] + #[error( + "Requested size exceeds available memory which is configured during subscription setup" + )] OutOfMemory, - #[error = "Invalid allocation request"] + #[error("Invalid allocation request")] InvalidRequest, - #[error = "Internal allocation error related to allocation operations of shared memory or sample container"] - InternalError(&'static str), -} - -/// Comprehensive error reasons asynchronous receive failure -#[derive(ErrorDisplay)] -pub enum AsyncReceiveFailedReason { - #[error = "Error at the time of initialzing async receive operation"] - InitializationFailed, - #[error = "Receive operation failed because of internal error"] - ReceiveError, - #[error = "Internal receive buffer not available for receive operation"] - BufferUnavailable, - #[error = "Sample size out of bounds, expected at most {}, but got {}"] - SampleCountOutOfBounds(usize, usize), + #[error("Failed to allocate sample in shared memory for skeleton event")] + AllocationToSharedMemoryFailed, } /// Comprehensive error reasons for receive failure -#[derive(ErrorDisplay)] +#[derive(Debug, Error)] pub enum ReceiveFailedReason { - #[error = "Error at the time of initializing receive operation"] + #[error("Error at the time of initializing receive operation")] InitializationFailed, - #[error = "Receive operation failed because of internal error"] + #[error("Receive operation failed because of internal error")] ReceiveError, - #[error = "Sample size out of bounds, expected at most {}, but got {}"] - SampleCountOutOfBounds(usize, usize), + #[error("Internal receive buffer not available for receive operation")] + BufferUnavailable, + #[error("Sample size out of bounds, expected at most {max}, but got {requested}")] + SampleCountOutOfBounds { max: usize, requested: usize }, } /// Comprehensive error reasons for event-related failures -#[derive(ErrorDisplay)] +#[derive(Debug, Error)] pub enum EventFailedReason { - #[error = "Event creation error, possibly due to invalid event type or internal error"] + #[error("Event creation error, possibly due to invalid event type or internal error")] EventCreationFailed, - #[error = "Event publication failed, possibly due to internal error"] + #[error("Event publication failed, possibly due to internal error")] EventPublishFailed, + #[error("Failed to send sample data to the service instance, possibly due to internal error")] + SendingDataFailed, + #[error("Event not available for subscription, possibly due to missing event type or incompatible service")] + EventNotAvailable, } /// Error enumeration for different failure cases in the Consumer/Producer/Runtime APIs. -#[derive(ErrorDisplay)] +#[derive(Debug, Error)] pub enum Error { - #[error = "Service discovery operation failed due to"] - ServiceDiscoveryFailed(ServiceDiscoveryFailedReason), - #[error = "Producer creation failed due to"] - ProducerCreationFailed(ProducerCreationReason), - #[error = "Consumer creation failed due to"] - ConsumerCreationFailed(ConsumerCreationReason), - #[error = "Failed to offer service instance for discovery"] - OfferServiceFailed, - #[error = "Failed to send sample data to the service instance, possibly due to internal error"] - SendingDataFailed, - #[error = "Event operation failed due to"] + #[error("Service error due to: {0}")] + ServiceError(ServiceFailedReason), + #[error("Producer error due to: {0}")] + ProducerError(ProducerFailedReason), + #[error("Consumer error due to: {0}")] + ConsumerError(ConsumerFailedReason), + #[error("Event operation failed due to: {0}")] EventError(EventFailedReason), - #[error = "Sample container allocation failed due to"] - SampleContainerAllocateFailed(AllocationFailureReason), - #[error = "Memory allocation failed due to"] - MemoryAllocationFailed(AllocationFailureReason), - #[error = "Asynchronous receive operation failed due to"] - AsyncReceiveFailed(AsyncReceiveFailedReason), - #[error = "Receive operation failed due to"] - ReceiveFailed(ReceiveFailedReason), - #[error = "Invalid instance specifier provided, which may be not according to the expected format or contain invalid content"] - InstanceSpecifierInvalid, - #[error = "Event subscription failed due to"] - SubscribeFailed(SubscribeFailureReason), + #[error("Sample container allocation failed due to: {0}")] + AllocateError(AllocationFailureReason), + #[error("Receive operation failed due to: {0}")] + ReceiveError(ReceiveFailedReason), } diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs index c59c1f0a7..528edb8ce 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs @@ -41,10 +41,9 @@ use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::Arc; use com_api_concept::{ - AsyncReceiveFailedReason, Builder, CommData, Consumer, ConsumerBuilder, ConsumerCreationReason, - ConsumerDescriptor, Error, EventFailedReason, InstanceSpecifier, Interface, - ReceiveFailedReason, Result, SampleContainer, ServiceDiscovery, ServiceDiscoveryFailedReason, - SubscribeFailureReason, Subscriber, Subscription, + Builder, CommData, Consumer, ConsumerBuilder, ConsumerDescriptor, ConsumerFailedReason, Error, + EventFailedReason, InstanceSpecifier, Interface, ReceiveFailedReason, Result, SampleContainer, + ServiceDiscovery, ServiceFailedReason, Subscriber, Subscription, }; use bridge_ffi_rs::*; @@ -230,8 +229,8 @@ impl NativeProxyBase { //Handle received at the time of get_avaible_instances called with correct interface_id let proxy = unsafe { bridge_ffi_rs::create_proxy(interface_id, handle) }; if proxy.is_null() { - return Err(Error::ConsumerCreationFailed( - ConsumerCreationReason::ProxyCreationFailed, + return Err(Error::ConsumerError( + ConsumerFailedReason::ProxyCreationFailed, )); } Ok(Self { proxy }) @@ -296,11 +295,9 @@ pub struct SubscribableImpl { impl Subscriber for SubscribableImpl { type Subscription = SubscriberImpl; fn new(identifier: &'static str, instance_info: LolaConsumerInfo) -> Result { - let handle = instance_info - .get_handle() - .ok_or(Error::ConsumerCreationFailed( - ConsumerCreationReason::ServiceHandleNotFound, - ))?; + let handle = instance_info.get_handle().ok_or(Error::ConsumerError( + ConsumerFailedReason::ServiceHandleNotFound, + ))?; let native_proxy = NativeProxyBase::new(instance_info.interface_id, handle)?; let proxy_instance = ProxyInstanceManager(Arc::new(native_proxy)); Ok(Self { @@ -326,9 +323,7 @@ impl Subscriber for SubscribableImpl ) }; if !status { - return Err(Error::SubscribeFailed( - SubscribeFailureReason::InternalError("Failed to subscribe to event"), - )); + return Err(Error::EventError(EventFailedReason::EventNotAvailable)); } // Store in SubscriberImpl with event, max_num_samples Ok(SubscriberImpl { @@ -526,11 +521,11 @@ where ) -> impl Future>>> + 'a { async move { if max_samples > self.max_num_samples || new_samples > self.max_num_samples { - return Err(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::SampleCountOutOfBounds( - self.max_num_samples, - max_samples.max(new_samples), - ), + return Err(Error::ReceiveError( + ReceiveFailedReason::SampleCountOutOfBounds { + max: self.max_num_samples, + requested: max_samples.max(new_samples), + }, )); } // Get the event guard to ensure no concurrent receive calls @@ -542,8 +537,8 @@ where .load(std::sync::atomic::Ordering::Relaxed) { if let Err(_e) = self.init_async_receive(&mut event_guard) { - return Err(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::InitializationFailed, + return Err(Error::ReceiveError( + ReceiveFailedReason::InitializationFailed, )); } self.async_init_status @@ -604,14 +599,10 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { self.scratch = Some(scratch); result } else { - Err(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::ReceiveError, - )) + Err(Error::ReceiveError(ReceiveFailedReason::ReceiveError)) } } else { - Err(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::BufferUnavailable, - )) + Err(Error::ReceiveError(ReceiveFailedReason::BufferUnavailable)) } }; match samples_received { @@ -623,9 +614,11 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { //event_guard will be dropped here, allowing new receive calls to access the // proxy event self.event_guard = None; - return Poll::Ready(self.scratch.take().ok_or(Error::AsyncReceiveFailed( - AsyncReceiveFailedReason::BufferUnavailable, - ))); + return Poll::Ready( + self.scratch + .take() + .ok_or(Error::ReceiveError(ReceiveFailedReason::BufferUnavailable)), + ); } // Have some samples but not enough yet, wait for more via waker Poll::Pending @@ -686,17 +679,12 @@ where fn get_available_instances(&self) -> Result { //If ANY Support is added in Lola, then we need to return all available instances - let instance_specifier_lola = mw_com::InstanceSpecifier::try_from( - self.instance_specifier.as_ref(), - ) - .map_err(|_| { - Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::InstanceSpecifierInvalid) - })?; + let instance_specifier_lola = + mw_com::InstanceSpecifier::try_from(self.instance_specifier.as_ref()) + .map_err(|_| Error::ServiceError(ServiceFailedReason::InstanceSpecifierInvalid))?; - let service_handle = - mw_com::proxy::find_service(instance_specifier_lola).map_err(|_| { - Error::ServiceDiscoveryFailed(ServiceDiscoveryFailedReason::ServiceNotFound) - })?; + let service_handle = mw_com::proxy::find_service(instance_specifier_lola) + .map_err(|_| Error::ServiceError(ServiceFailedReason::ServiceNotFound))?; let service_handle_arc = Arc::new(service_handle); let available_instances = (0..service_handle_arc.len()) @@ -730,11 +718,8 @@ where // Convert to Lola InstanceSpecifier early let instance_specifier_lola = - mw_com::InstanceSpecifier::try_from(instance_specifier.as_ref()).map_err(|_| { - Error::ServiceDiscoveryFailed( - ServiceDiscoveryFailedReason::InstanceSpecifierInvalid, - ) - }); + mw_com::InstanceSpecifier::try_from(instance_specifier.as_ref()) + .map_err(|_| Error::ServiceError(ServiceFailedReason::InstanceSpecifierInvalid)); let waker_storage = Arc::new(futures::task::AtomicWaker::new()); @@ -776,10 +761,8 @@ where // stop_find_service is always called in Drop. let raw_handle = unsafe { bridge_ffi_rs::start_find_service(&fat_ptr, spec) }; if raw_handle.is_null() { - Err(Error::ServiceDiscoveryFailed( - ServiceDiscoveryFailedReason::InternalError( - "Failed to start service discovery", - ), + Err(Error::ServiceError( + ServiceFailedReason::FailedToStartDiscovery, )) } else { // Single authoritative source of find_handle — return value only. @@ -920,8 +903,11 @@ fn try_receive_samples( max_samples: usize, ) -> Result { if max_samples > max_num_samples { - return Err(Error::ReceiveFailed( - ReceiveFailedReason::SampleCountOutOfBounds(max_num_samples, max_samples), + return Err(Error::ReceiveError( + ReceiveFailedReason::SampleCountOutOfBounds { + max: max_num_samples, + requested: max_samples, + }, )); } // Create a callback that will be called by the C++ side for each new sample arrival @@ -942,8 +928,11 @@ fn try_receive_samples( ) }; if count > max_num_samples as u32 { - return Err(Error::ReceiveFailed( - ReceiveFailedReason::SampleCountOutOfBounds(max_num_samples, count as usize), + return Err(Error::ReceiveError( + ReceiveFailedReason::SampleCountOutOfBounds { + max: max_num_samples, + requested: count as usize, + }, )); } Ok(count as usize) diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs index 72dc4c80f..3340dee28 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs @@ -36,8 +36,9 @@ use core::ops::{Deref, DerefMut}; use std::sync::Arc; use com_api_concept::{ - AllocationFailureReason, Builder, CommData, Error, InstanceSpecifier, Interface, Producer, - ProducerBuilder, ProducerCreationReason, ProviderInfo, Result, + AllocationFailureReason, Builder, CommData, Error, EventFailedReason, InstanceSpecifier, + Interface, Producer, ProducerBuilder, ProducerFailedReason, ProviderInfo, Result, + ServiceFailedReason, }; use bridge_ffi_rs::*; @@ -58,7 +59,7 @@ impl ProviderInfo for LolaProviderInfo { let status = unsafe { bridge_ffi_rs::skeleton_offer_service(self.skeleton_handle.0.handle) }; if !status { - return Err(Error::OfferServiceFailed); + return Err(Error::ServiceError(ServiceFailedReason::OfferServiceFailed)); } Ok(()) } @@ -187,7 +188,7 @@ where ) }; if !status { - return Err(Error::SendingDataFailed); + return Err(Error::EventError(EventFailedReason::SendingDataFailed)); } Ok(()) } @@ -302,8 +303,8 @@ impl NativeSkeletonHandle { let handle = unsafe { bridge_ffi_rs::create_skeleton(interface_id, instance_specifier.as_native()) }; if handle.is_null() { - return Err(Error::ProducerCreationFailed( - ProducerCreationReason::SkeletonCreationFailed, + return Err(Error::ProducerError( + ProducerFailedReason::SkeletonCreationFailed, )); } Ok(Self { handle }) @@ -345,8 +346,8 @@ impl NativeSkeletonEventBase { ) }; if skeleton_event_ptr.is_null() { - return Err(Error::ProducerCreationFailed( - ProducerCreationReason::SkeletonCreationFailed, + return Err(Error::ProducerError( + ProducerFailedReason::SkeletonCreationFailed, )); } Ok(Self { skeleton_event_ptr }) @@ -399,10 +400,8 @@ where T::ID, ); if !status { - return Err(Error::MemoryAllocationFailed( - AllocationFailureReason::InternalError( - "Failed to allocate sample via skeleton event", - ), + return Err(Error::AllocateError( + AllocationFailureReason::AllocationToSharedMemoryFailed, )); } sample.assume_init() @@ -446,10 +445,10 @@ impl ProducerBuilder for SampleProducerBuilder impl Builder> for SampleProducerBuilder { fn build(self) -> Result> { - let instance_specifier_runtime = - mw_com::InstanceSpecifier::try_from(self.instance_specifier.as_ref()).map_err( - |_| Error::ProducerCreationFailed(ProducerCreationReason::InstanceSpecifierInvalid), - )?; + let instance_specifier_runtime = mw_com::InstanceSpecifier::try_from( + self.instance_specifier.as_ref(), + ) + .map_err(|_| Error::ProducerError(ProducerFailedReason::InstanceSpecifierInvalid))?; let skeleton_handle = NativeSkeletonHandle::new(I::INTERFACE_ID, &instance_specifier_runtime)?; @@ -459,9 +458,8 @@ impl Builder> for SampleProducerBuild skeleton_handle: SkeletonInstanceManager(Arc::new(skeleton_handle)), }; - I::Producer::new(instance_info).map_err(|_| { - Error::ProducerCreationFailed(ProducerCreationReason::BuilderCreationFailed) - }) + I::Producer::new(instance_info) + .map_err(|_| Error::ProducerError(ProducerFailedReason::BuilderCreationFailed)) } } From d33f8439479498e7e4567868c6b1e3c1f21863ac Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Tue, 24 Mar 2026 13:28:20 +0530 Subject: [PATCH 5/7] Rust::com Added score_crate updated commit on MODULE.bazel * Overrite the commit id for score_crate --- MODULE.bazel | 5 +++++ .../com/example/com-api-example/basic-consumer-producer.rs | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index abd7b8f53..59c564f38 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -181,6 +181,11 @@ bazel_dep(name = "rules_rust", version = "0.68.1-score") bazel_dep(name = "buildifier_prebuilt", version = "8.2.1.2", dev_dependency = True) bazel_dep(name = "score_crates", version = "0.0.7", repo_name = "score_communication_crate_index") +git_override( + module_name = "score_crates", + commit = "a5f4f5765acc83f9c23b0b12860e130b9ebef3a7", + remote = "https://github.com/eclipse-score/score-crates.git", +) bazel_dep(name = "boost.program_options", version = "1.87.0") bazel_dep(name = "boost.interprocess", version = "1.87.0") 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 668bfb15e..6be03a196 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 @@ -18,9 +18,6 @@ // Note: The example is using unwrap and panic in some places for simplicity, // but it is recommended to handle errors properly in production code. -use clap::Parser; -use std::path::PathBuf; - use com_api::{ Builder, FindServiceSpecifier, InstanceSpecifier, Interface, LolaRuntimeBuilderImpl, OfferedProducer, Producer, Publisher, Result, Runtime, RuntimeBuilder, SampleContainer, From 11a449924632a78a094d3b1856d650b785d1f7af Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Wed, 25 Mar 2026 14:02:45 +0530 Subject: [PATCH 6/7] Rust::com Error Enums Optimization * Updated Example app * Producer Builder error returning update --- .../basic-consumer-producer.rs | 25 ++++++++++--------- .../com-api/com-api-runtime-lola/consumer.rs | 12 +++++---- .../com-api/com-api-runtime-lola/producer.rs | 2 +- 3 files changed, 21 insertions(+), 18 deletions(-) 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 6be03a196..cbed79435 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 @@ -18,6 +18,9 @@ // Note: The example is using unwrap and panic in some places for simplicity, // but it is recommended to handle errors properly in production code. +use clap::Parser; +use std::path::PathBuf; + use com_api::{ Builder, FindServiceSpecifier, InstanceSpecifier, Interface, LolaRuntimeBuilderImpl, OfferedProducer, Producer, Publisher, Result, Runtime, RuntimeBuilder, SampleContainer, @@ -125,18 +128,18 @@ fn create_consumer(runtime: &R, service_id: InstanceSpecifier) -> Ve runtime.find_service::(FindServiceSpecifier::Specific(service_id)); let available_service_instances = consumer_discovery .get_available_instances() - .unwrap_or_else(|e| panic!("{:?}", e)); + .expect("Failed to get available service instances"); // Select service instance at specific handle_index let handle_index = 0; // or any index you need from vector of instances let consumer_builder = available_service_instances .into_iter() .nth(handle_index) - .unwrap(); + .expect("Failed to get consumer builder at specified handle index"); consumer_builder .build() - .unwrap_or_else(|e| panic!("{:?}", e)) + .expect("Failed to build consumer instance") } #[allow(dead_code)] @@ -150,16 +153,16 @@ async fn create_consumer_async( let available_service_instances = consumer_discovery .get_available_instances_async() .await - .unwrap(); + .expect("Failed to get available service instances asynchronously"); // Select service instance at specific handle_index let handle_index = 0; // or any index you need from vector of instances let consumer_builder = available_service_instances .into_iter() .nth(handle_index) - .unwrap(); + .expect("Failed to get consumer builder at specified handle index"); - consumer_builder.build().unwrap() + consumer_builder.build().expect("Failed to build consumer instance") } // Create a producer for the specified service identifier @@ -170,18 +173,16 @@ fn create_producer( let producer_builder = runtime.producer_builder::(service_id); let producer = producer_builder .build() - .unwrap_or_else(|e| panic!("{:?}", e)); - producer.offer().unwrap_or_else(|e| panic!("{:?}", e)) + .expect("Failed to build producer instance"); + producer.offer().expect("Failed to offer producer instance") } // Run the example with the specified runtime fn run_with_runtime(name: &str, runtime: &R) { println!("\n=== Running with {name} runtime ==="); - let service_id = match InstanceSpecifier::new("/Vehicle/Service1/Instance") { - Ok(specifier) => specifier, - Err(e) => panic!("{:?}", e), - }; + let service_id = InstanceSpecifier::new("/Vehicle/Service1/Instance") + .expect("Failed to create InstanceSpecifier"); let producer = create_producer(runtime, service_id.clone()); let consumer = create_consumer(runtime, service_id); let monitor = VehicleMonitor::new(consumer, producer).unwrap(); diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs index 528edb8ce..6bf807ecf 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs @@ -34,6 +34,7 @@ use core::future::Future; use core::marker::PhantomData; use core::mem::ManuallyDrop; use core::ops::{Deref, DerefMut}; +use core::panic; use futures::task::{AtomicWaker, Context, Poll}; use std::cmp::Ordering; use std::pin::Pin; @@ -614,11 +615,10 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { //event_guard will be dropped here, allowing new receive calls to access the // proxy event self.event_guard = None; - return Poll::Ready( - self.scratch - .take() - .ok_or(Error::ReceiveError(ReceiveFailedReason::BufferUnavailable)), - ); + return Poll::Ready(Ok(self + .scratch + .take() + .expect("SampleContainer is not available when returning Future result"))); } // Have some samples but not enough yet, wait for more via waker Poll::Pending @@ -679,6 +679,7 @@ where fn get_available_instances(&self) -> Result { //If ANY Support is added in Lola, then we need to return all available instances + //Once FFI layer error handling is in place (SWP-253124), we should convert this error to a proper FFI error instead of using map_err here let instance_specifier_lola = mw_com::InstanceSpecifier::try_from(self.instance_specifier.as_ref()) .map_err(|_| Error::ServiceError(ServiceFailedReason::InstanceSpecifierInvalid))?; @@ -717,6 +718,7 @@ where let instance_specifier = self.instance_specifier.clone(); // Convert to Lola InstanceSpecifier early + //Once FFI layer error handling is in place (SWP-253124), we should convert this error to a proper FFI error instead of using map_err here let instance_specifier_lola = mw_com::InstanceSpecifier::try_from(instance_specifier.as_ref()) .map_err(|_| Error::ServiceError(ServiceFailedReason::InstanceSpecifierInvalid)); diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs index 3340dee28..13c93f5cb 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs @@ -445,6 +445,7 @@ impl ProducerBuilder for SampleProducerBuilder impl Builder> for SampleProducerBuilder { fn build(self) -> Result> { + //Once FFI layer error handling is in place (SWP-253124), we should convert this error to a proper FFI error instead of using map_err here let instance_specifier_runtime = mw_com::InstanceSpecifier::try_from( self.instance_specifier.as_ref(), ) @@ -459,7 +460,6 @@ impl Builder> for SampleProducerBuild }; I::Producer::new(instance_info) - .map_err(|_| Error::ProducerError(ProducerFailedReason::BuilderCreationFailed)) } } From 95e09d49d4f7432c964c2779709037d6f605dcfe Mon Sep 17 00:00:00 2001 From: bharatgoswami Date: Wed, 25 Mar 2026 18:34:18 +0530 Subject: [PATCH 7/7] Rust::com Updated score crate version to 0.0.9 * score crate version update --- MODULE.bazel | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 59c564f38..9d3ab8a88 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -180,12 +180,7 @@ bazel_dep(name = "rules_rust", version = "0.68.1-score") bazel_dep(name = "buildifier_prebuilt", version = "8.2.1.2", dev_dependency = True) -bazel_dep(name = "score_crates", version = "0.0.7", repo_name = "score_communication_crate_index") -git_override( - module_name = "score_crates", - commit = "a5f4f5765acc83f9c23b0b12860e130b9ebef3a7", - remote = "https://github.com/eclipse-score/score-crates.git", -) +bazel_dep(name = "score_crates", version = "0.0.9", repo_name = "score_communication_crate_index") bazel_dep(name = "boost.program_options", version = "1.87.0") bazel_dep(name = "boost.interprocess", version = "1.87.0")