Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,25 @@ interface!(
exhaust: Event<Exhaust>,
}
);

// Example interface definition using the interface macro with a custom UID for the interface.
// First tuple is the input argument type, and the second tuple is the return type.
// This will generate the following types and trait implementations:
// - VehicleMethodsInterface struct with INTERFACE_ID = "VehicleMethodsInterface"
// - VehicleMethodsConsumer<R>, VehicleMethodsProducer<R>, VehicleMethodsOfferedProducer<R>
// with appropriate trait implementations for the VehicleMethods interface.
// As passed methods to macro it will generate the following methods:
// - update_tire_pressure(Tire) -> ()
// - update_front_tires_pressure(Tire, Tire) -> ()
// - get_tire_pressure() -> Tire
// and this method can be accessed through the consumer instance of VehicleMethodsConsumer<R>.
// Methods use fn-like syntax: method_name(ArgType0, ArgType1, ...) -> ReturnType
// For void return, -> () is required so the macro can identify the member as a method.
interface!(
interface VehicleMethods {
Id = "VehicleMethodsInterface",
update_tire_pressure(Tire) -> (),
update_front_tires_pressure(Tire, Tire) -> (),
get_tire_pressure() -> Tire,
}
);
2 changes: 2 additions & 0 deletions score/mw/com/example/com-api-example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
********************************************************************************/

pub mod consumer;
pub mod method_consumer;
pub mod method_producer;
pub mod producer;
pub use consumer::VehicleMonitorConsumer;
pub use producer::VehicleMonitorProducer;
Expand Down
118 changes: 118 additions & 0 deletions score/mw/com/example/com-api-example/src/method_consumer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/********************************************************************************
* 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
********************************************************************************/

// 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.

#![allow(unused)]

use com_api::{
Builder, FindServiceSpecifier, InstanceSpecifier, Interface, MethodCaller,
MethodInArgMaybeUninit, Runtime, ServiceDiscovery,
};

use com_api_gen::{Tire, VehicleMethodsInterface};

type VehicleMethodConsumer<R> = <VehicleMethodsInterface as Interface>::Consumer<R>;

// These functions are just to demonstrate the method APIs, and they can not be used in main of example app,
// as runtime implementation is not available for method APIs.
fn create_consumer_method<R: Runtime>(
runtime: &R,
service_id: InstanceSpecifier,
) -> VehicleMethodConsumer<R> {
let consumer_discovery =
runtime.find_service::<VehicleMethodsInterface>(FindServiceSpecifier::Specific(service_id));
let available_service_instances = consumer_discovery
.get_available_instances()
.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)
.expect("Failed to get consumer builder at specified handle index");

consumer_builder
.build()
.expect("Failed to build consumer instance")
}

// Method calls return `impl Future<Output = com_api::Result<T>>`, so they must be `.await`ed.
// We are having tuple of arguments, so we can have any number of arguments (currently up to 2) without any extra boilerplate.
// But in Method signature, we need to have tuple of arguments, so for zero argument method, we need to pass empty tuple.
// Which need to be improved using macro generated wrapper around method call, so that we can call zero argument method without empty tuple.
// even with argument tuple, method call can be improve using macro generated wrapper, so that we can call method with any number of arguments without tuple.

async fn consumer_method_processing<R: Runtime>(consumer: VehicleMethodConsumer<R>) {
// Copy path: single positional argument — no tuple needed.
let tire = Tire { pressure: 30.0 };
match consumer.update_tire_pressure(tire).await {
Ok(_) => println!("Successfully called update_tire_pressure method"),
Err(e) => eprintln!("Failed to call update_tire_pressure method: {:?}", e),
}

let (uninit1,) = consumer
.update_tire_pressure
.allocate()
.expect("Failed to allocate method arguments");
let tire1ptr = uninit1.write(Tire { pressure: 35.0 });

// Copy path: zero-argument method — empty parens, no empty-tuple needed.
match consumer.get_tire_pressure().await {
Ok(tire) => println!("Current tire pressure: {:?}", tire),
Err(e) => eprintln!("Failed to call get_tire_pressure method: {:?}", e),
}

// Zero-copy path: allocate, write, then call the same wrapper.
match consumer.update_tire_pressure(tire1ptr).await {
Ok(_) => println!("Successfully called update_tire_pressure method with allocated args"),
Err(e) => eprintln!(
"Failed to call update_tire_pressure method with allocated args: {:?}",
e
),
}

// Copy path: two arguments method.
let tire1 = Tire { pressure: 31.0 };
let tire2 = Tire { pressure: 32.0 };
match consumer.update_front_tires_pressure(tire1, tire2).await {
Ok(_) => println!("Successfully called update_front_tires_pressure method"),
Err(e) => eprintln!("Failed to call update_front_tires_pressure method: {:?}", e),
}

let (uninit1, uninit2) = consumer
.update_front_tires_pressure
.allocate()
.expect("Failed to allocate method arguments");
let tire1ptr = uninit1.write(Tire { pressure: 36.0 });
let tire2ptr = uninit2.write(Tire { pressure: 37.0 });
// Zero-copy path: allocate, write both args, then call the same method with allocated args.
match consumer
.update_front_tires_pressure(tire1ptr, tire2ptr)
.await
{
Ok(_) => {
println!("Successfully called update_front_tires_pressure method with allocated args")
}
Err(e) => eprintln!(
"Failed to call update_front_tires_pressure method with allocated args: {:?}",
e
),
}
}
58 changes: 58 additions & 0 deletions score/mw/com/example/com-api-example/src/method_producer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/********************************************************************************
* 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
********************************************************************************/

// 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.

#![allow(unused)]

use com_api::{Builder, InstanceSpecifier, Interface, Producer, Runtime};

use com_api_gen::{Tire, VehicleMethodsInterface};

type VehicleMethodOfferedProducer<R> =
<<VehicleMethodsInterface as Interface>::Producer<R> as Producer<R>>::OfferedProducer;

fn create_producer_method<R: Runtime>(
runtime: &R,
service_id: InstanceSpecifier,
) -> VehicleMethodOfferedProducer<R> {
let producer_builder = runtime.producer_builder::<VehicleMethodsInterface>(service_id);
let producer = producer_builder
.build()
.expect("Failed to build producer instance");
producer
.init()
.register_update_tire_pressure_handler(|tire: Tire| {
println!("Received update_tire_pressure call with tire: {:?}", tire);
()
})
.register_get_tire_pressure_handler(|| {
println!("Received get_tire_pressure call");
// Return a sample tire pressure value, just dummy value returned for demonstration
Tire { pressure: 32.0 }
})
.register_update_front_tires_pressure_handler(|tire1: Tire, tire2: Tire| {
println!(
"Received update_front_tires_pressure call with tire1: {:?}, tire2: {:?}",
tire1, tire2
);
()
})
.offer()
.expect("Failed to offer producer instance")
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ load("@rules_rust//rust:defs.bzl", "rust_doc_test", "rust_proc_macro")

rust_proc_macro(
name = "com-api-concept-macros",
srcs = ["lib.rs"],
srcs = glob(["**/*.rs"]),
crate_name = "com_api_concept_macros",
visibility = ["//visibility:public"],
deps = [
Expand Down
46 changes: 46 additions & 0 deletions score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Generics, Meta, Type};

mod type_state_validator;

/// Derive macro for the `CommData` trait.
///
/// Implements `CommData` for a struct or C-like enum, providing a stable string identity
Expand Down Expand Up @@ -335,6 +337,50 @@ fn collect_field_types(data: &Data) -> Result<Vec<&Type>, ()> {
Ok(out)
}

/// Unified derive macro for compile-time type-state validation of Field and Method producers.
///
/// Detects member types by the last segment of each field's type path:
/// - `FieldPublisher<T>` → generates `update_{name}()` and `register_set_handler_{name}()`
/// - `MethodHandler<Args, Return>` → generates `register_{name}_handler()`
/// - `instance_info` field is always skipped.
///
/// # Generated validator struct
///
/// `{Name}Validator<R, S0..Sn, H0..Hn, M0..Mm>` where:
/// - `Si` = field update state (`Uninit` / `Init`)
/// - `Hi` = field set-handler state (`HandlerNotSet` / `HandlerSet`)
/// - `Mj` = method handler state (`HandlerNotSet` / `HandlerSet`)
///
/// `offer()` is only available when ALL `Si = Init`, ALL `Hi = HandlerSet`, ALL `Mj = HandlerSet`.
///
/// Entry point on the producer: `init()` — begins the type-state chain.
///
/// Degenerates correctly:
/// - Field-only struct → no `Mj` params
/// - Method-only struct → no `Si`/`Hi` params
/// - Mixed struct → all param groups combined
///
/// # Usage
///
/// ```ignore
/// #[derive(TypeStateValidator)]
/// struct VehicleProducer<R: Runtime + ?Sized> {
/// left_tire: R::FieldPublisher<Tire>,
/// process: R::MethodHandler<(Tire,), Tire>,
/// instance_info: R::ProviderInfo,
/// }
/// // Generated: producer.init()
/// // .update_left_tire(&v)?
/// // .register_set_handler_left_tire(|v| {})
/// // .register_process_handler(|req| { ... })
/// // .offer()?
/// ```
// TODO: Document tests need to be added for this macro, including successful and failed compilation cases.
#[proc_macro_derive(TypeStateValidator)]
pub fn derive_typestate_validator(input: TokenStream) -> TokenStream {
type_state_validator::derive_typestate_validator_impl(input)
}

// Use doctest to test failed compilations and successful ones

/// ```
Expand Down
Loading
Loading