use crate::descriptors::{query_reply_type, single_param_type};
use crate::{GenerateError, ident, type_path};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
use ridl_ir::name::camel_case;
use ridl_ir::v2;
pub(crate) fn interface_items(package: &v2::Package) -> Result<Vec<TokenStream>, GenerateError> {
let mut items = Vec::new();
for shape in package.shapes() {
if shape.service.is_some() {
continue;
}
if let Some(module) = one_interface(shape.name, shape.interface)? {
items.push(module);
}
}
Ok(items)
}
struct Member<'a> {
ordinal: TokenStream,
method: Ident,
descriptor: TokenStream,
declared: &'a str,
}
struct Call<'a> {
member: Member<'a>,
arg: Ident,
arg_type: &'a str,
reply_type: Option<&'a str>,
}
pub(crate) fn one_interface(
iface_name: &str,
interface: &v2::Interface,
) -> Result<Option<TokenStream>, GenerateError> {
let iface = ident(iface_name);
let module = ident(&snake_case(iface_name));
let mut signals: Vec<(Member, &str)> = Vec::new();
let mut events: Vec<(Member, &str)> = Vec::new();
let mut commands: Vec<Call> = Vec::new();
let mut queries: Vec<Call> = Vec::new();
for decl in &interface.interactions {
let member = Member {
ordinal: {
let value = Literal::u32_suffixed(decl.ordinal);
quote! { ::ridl_rt::contract::Ordinal(#value) }
},
method: ident(&snake_case(&decl.name)),
descriptor: {
let name = ident(&format!("{iface}{}", camel_case(&decl.name)));
quote! { super::#name }
},
declared: decl.name.as_str(),
};
match decl.kind.as_ref() {
Some(v2::decl::Kind::SignalDef(signal)) => {
signals.push((member, signal.payload.as_str()));
}
Some(v2::decl::Kind::EventDef(event)) => {
events.push((member, event.payload.as_str()));
}
Some(v2::decl::Kind::CommandDef(command)) => {
commands.push(Call {
arg_type: single_param_type(&command.params, &decl.name)?,
arg: single_param_name(&command.params),
member,
reply_type: None,
});
}
Some(v2::decl::Kind::QueryDef(query)) => {
queries.push(Call {
arg_type: single_param_type(&query.params, &decl.name)?,
arg: single_param_name(&query.params),
member,
reply_type: Some(query_reply_type(query, &decl.name)?),
});
}
_ => {}
}
}
let mut body: Vec<TokenStream> = Vec::new();
body.extend(correlations(&commands, &queries));
if !signals.is_empty() || !events.is_empty() || !commands.is_empty() || !queries.is_empty() {
body.push(client(
&iface, iface_name, &signals, &events, &commands, &queries,
));
}
if !events.is_empty() {
body.push(event_enum(iface_name, &events));
}
if !signals.is_empty() || !events.is_empty() {
body.push(publisher(&iface, iface_name, &signals, &events));
}
if !commands.is_empty() || !queries.is_empty() {
body.push(provider(iface_name, &commands, &queries));
body.push(dispatch(&iface, iface_name, &commands, &queries));
}
if body.is_empty() {
return Ok(None);
}
let module_doc = format!("The generated interaction face of interface `{iface_name}`.");
Ok(Some(quote! {
#[doc = #module_doc]
pub mod #module {
#(#body)*
}
}))
}
fn correlation_type(call: &Call) -> Ident {
ident(&format!("{}Correlation", camel_case(call.member.declared)))
}
fn correlations(commands: &[Call], queries: &[Call]) -> Vec<TokenStream> {
let mut items = Vec::new();
for (call, kind) in commands
.iter()
.map(|c| (c, "command"))
.chain(queries.iter().map(|q| (q, "query")))
{
let name = correlation_type(call);
let doc = format!(
"Identifies one sent {kind} `{}` to its caller. It is returned by \
the send method and accepted by that call's own outcome method, \
and by no other.",
call.member.declared
);
items.push(quote! {
#[doc = #doc]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct #name(pub ::ridl_rt::port::Correlation);
});
}
items
}
fn client(
iface: &Ident,
iface_name: &str,
signals: &[(Member, &str)],
events: &[(Member, &str)],
commands: &[Call],
queries: &[Call],
) -> TokenStream {
let number = interface_number(iface);
let mut bounds: Vec<TokenStream> = Vec::new();
if !signals.is_empty() {
bounds.push(quote! { ::ridl_rt::port::SignalReader });
}
if !events.is_empty() {
bounds.push(quote! { ::ridl_rt::port::EventSource });
}
if !commands.is_empty() || !queries.is_empty() {
bounds.push(quote! { ::ridl_rt::port::Caller });
}
let mut methods: Vec<TokenStream> = Vec::new();
for (member, payload) in signals {
let method = &member.method;
let ordinal = &member.ordinal;
let descriptor = &member.descriptor;
let path = ty(payload);
let buffer = payload_buffer(payload);
let doc = format!(
"Reads signal `{}` and returns its value with the provenance, the \
freshness and the envelope the runtime resolved. A payload that \
fails its check is reported as `Provenance::Invalid` with the \
detection, and the value is the channel's init value.",
member.declared
);
methods.push(quote! {
#[doc = #doc]
pub fn #method(
&self,
) -> ::core::result::Result<
::ridl_rt::sample::Sample<#path>,
::ridl_rt::port::ReadError,
> {
let mut buf = #buffer;
let raw = self.port.read(#number, #ordinal, &mut buf)?;
match ::ridl_rt::payload::Ref::<#path, super::Wire>::verify(
&buf[..raw.len],
) {
Ok(checked) => Ok(::ridl_rt::sample::Sample {
value: checked.decode(),
provenance: raw.provenance,
freshness: raw.freshness,
envelope: raw.envelope,
}),
Err(error) => Ok(::ridl_rt::sample::Sample {
value: <#descriptor as ::ridl_rt::contract::Signal>::init(),
provenance: ::ridl_rt::sample::Provenance::Invalid(
::ridl_rt::sample::Cause::Detected(match error {
::ridl_rt::payload::VerifyError::Contract(violation) => {
::ridl_rt::sample::Detection::InvalidValue(violation)
}
_ => ::ridl_rt::sample::Detection::Corrupt,
}),
),
freshness: raw.freshness,
envelope: raw.envelope,
}),
}
}
});
}
for (member, _) in events {
let method = ident(&format!("subscribe_{}", member.method));
let ordinal = &member.ordinal;
let doc = format!("Starts delivery of event `{}`.", member.declared);
methods.push(quote! {
#[doc = #doc]
pub fn #method(
&mut self,
) -> ::core::result::Result<(), ::ridl_rt::port::SubscribeError> {
self.port.subscribe(#number, &[#ordinal])
}
});
}
if !events.is_empty() {
let buffer = quote! { [0u8; super::#iface::EVENT_SOURCE_BUFFER_SIZE] };
let arms = events.iter().map(|(member, payload)| {
let ordinal = &member.ordinal;
let variant = ident(&camel_case(member.declared));
let path = ty(payload);
quote! {
#ordinal => Ok(Some(Event::#variant(::ridl_rt::sample::Occurrence {
payload: match ::ridl_rt::payload::Ref::<
#path,
super::Wire,
>::verify(&buf[..occurrence.len]) {
Ok(checked) => Ok(checked.decode()),
Err(::ridl_rt::payload::VerifyError::Contract(violation)) => {
Err(::ridl_rt::sample::Detection::InvalidValue(violation))
}
Err(_) => Err(::ridl_rt::sample::Detection::Corrupt),
},
envelope: occurrence.envelope,
})))
}
});
let doc = format!(
"Takes the next occurrence of any subscribed event of interface \
`{iface_name}`, routed to its variant by ordinal. `Ok(None)` when \
none is waiting. One method serves every event, because the \
payload type is not known until the occurrence's ordinal is \
read.\n\nThe interface number is checked before the ordinal, \
for the reason `dispatch` checks it: a port is attached to a \
whole catalog, ordinals restart at 1 in each interface, and an \
occurrence of a sibling interface at the same ordinal would \
otherwise be decoded as this interface's payload. Such an \
occurrence is reported as `Contract::UnknownInteraction`; \
`EventSource::next` has already consumed it, so this face cannot \
hand it back to the interface it belongs to. Subscribe on a port \
this interface owns."
);
methods.push(quote! {
#[doc = #doc]
pub fn next_event(
&mut self,
) -> ::core::result::Result<
::core::option::Option<Event>,
::ridl_rt::port::ReadError,
> {
let mut buf = #buffer;
let Some(occurrence) = self.port.next(&mut buf)? else {
return Ok(None);
};
if occurrence.iface
!= <super::#iface as ::ridl_rt::contract::Interface>::NUMBER
{
return Err(::ridl_rt::port::ReadError::Contract(
::ridl_rt::error::Contract::UnknownInteraction,
));
}
match occurrence.ord {
#(#arms,)*
_ => Err(::ridl_rt::port::ReadError::Contract(
::ridl_rt::error::Contract::UnknownInteraction,
)),
}
}
});
}
for call in commands {
methods.push(send(
&number,
call,
quote! { ::ridl_rt::contract::Command },
quote! { command },
"command",
));
}
for call in queries {
methods.push(send(
&number,
call,
quote! { ::ridl_rt::contract::Query },
quote! { query },
"query",
));
let member = &call.member;
let reply = call.reply_type.unwrap_or(call.arg_type);
let correlation = correlation_type(call);
let method = ident(&format!("{}_reply", member.method));
let path = ty(reply);
let buffer = payload_buffer(reply);
let doc = format!(
"Takes query `{}`'s reply once it is known, or `Ok(None)` while it \
is not. It does not wait.",
member.declared
);
methods.push(quote! {
#[doc = #doc]
pub fn #method(
&mut self,
correlation: #correlation,
) -> ::core::result::Result<
::core::option::Option<
::core::result::Result<#path, ::ridl_rt::error::CallError>,
>,
::ridl_rt::port::ReadError,
> {
let mut buf = #buffer;
match self.port.reply(correlation.0, &mut buf)? {
None => Ok(None),
Some(Err(error)) => Ok(Some(Err(error))),
Some(Ok(len)) => Ok(Some(
match ::ridl_rt::payload::Ref::<
#path,
super::Wire,
>::verify(&buf[..len]) {
Ok(checked) => Ok(checked.decode()),
Err(::ridl_rt::payload::VerifyError::Contract(violation)) => {
Err(::ridl_rt::error::CallError::Contract(
::ridl_rt::error::Contract::InvalidValue(violation),
))
}
Err(_) => Err(::ridl_rt::error::CallError::Transport(
::ridl_rt::error::Transport::Corrupt,
)),
},
)),
}
}
});
}
for call in commands {
let member = &call.member;
let correlation = correlation_type(call);
let method = ident(&format!("{}_ack", member.method));
let doc = format!(
"Takes command `{}`'s delivery acknowledgment once it is known, \
or `None` while it is not. It does not wait.",
member.declared
);
methods.push(quote! {
#[doc = #doc]
pub fn #method(
&mut self,
correlation: #correlation,
) -> ::core::option::Option<
::core::result::Result<(), ::ridl_rt::error::CallError>,
> {
self.port.ack(correlation.0)
}
});
}
let doc = format!(
"The consumer face of interface `{iface_name}`, generic over exactly \
the ports the interface's interactions need."
);
quote! {
#[doc = #doc]
pub struct Client<P: #(#bounds)+*> {
port: P,
}
impl<P: #(#bounds)+*> Client<P> {
pub fn new(port: P) -> Self {
Client { port }
}
#(#methods)*
}
}
}
fn send(
number: &TokenStream,
call: &Call,
contract_trait: TokenStream,
port_method: TokenStream,
kind: &str,
) -> TokenStream {
let member = &call.member;
let method = &member.method;
let ordinal = &member.ordinal;
let descriptor = &member.descriptor;
let correlation = correlation_type(call);
let arg = &call.arg;
let path = ty(call.arg_type);
let buffer = payload_buffer(call.arg_type);
let encode = encode_into(
call.arg_type,
quote! { &#arg },
quote! { &mut buf },
"the argument buffer",
);
let doc = format!(
"Sends {kind} `{}` and returns the correlation of its outcome. A \
`require` clause that fails is reported as \
`SendError::Contract(Contract::PreconditionFailed)` and nothing is \
sent.",
member.declared
);
quote! {
#[doc = #doc]
pub fn #method(
&mut self,
#arg: #path,
) -> ::core::result::Result<#correlation, ::ridl_rt::port::SendError> {
<#descriptor as #contract_trait>::require(&#arg).map_err(|()| {
::ridl_rt::port::SendError::Contract(
::ridl_rt::error::Contract::PreconditionFailed,
)
})?;
let mut buf = #buffer;
let bytes = #encode;
self.port.#port_method(#number, #ordinal, bytes).map(#correlation)
}
}
}
fn event_enum(iface_name: &str, events: &[(Member, &str)]) -> TokenStream {
let variants = events.iter().map(|(member, payload)| {
let variant = ident(&camel_case(member.declared));
let path = ty(payload);
let doc = format!("An occurrence of event `{}`.", member.declared);
quote! {
#[doc = #doc]
#variant(::ridl_rt::sample::Occurrence<#path>)
}
});
let doc = format!("One occurrence of an event of interface `{iface_name}`.");
quote! {
#[doc = #doc]
pub enum Event {
#(#variants),*
}
}
}
fn publisher(
iface: &Ident,
iface_name: &str,
signals: &[(Member, &str)],
events: &[(Member, &str)],
) -> TokenStream {
let number = interface_number(iface);
let mut bounds: Vec<TokenStream> = Vec::new();
if !signals.is_empty() {
bounds.push(quote! { ::ridl_rt::port::SignalWriter });
}
if !events.is_empty() {
bounds.push(quote! { ::ridl_rt::port::EventSink });
}
let mut methods: Vec<TokenStream> = Vec::new();
for (member, payload) in signals {
let method = &member.method;
let ordinal = &member.ordinal;
let path = ty(payload);
let buffer = payload_buffer(payload);
let encode = encode_into(
payload,
quote! { &value },
quote! { &mut buf },
"the payload buffer",
);
let set_doc = format!(
"Stages a new value for signal `{}`. It is published by `commit`.",
member.declared
);
let invalidate_doc = format!(
"Stages the invalid state for signal `{}`, with \
`Cause::Declared`. It is published by `commit`.",
member.declared
);
let invalidate = ident(&format!("invalidate_{}", member.method));
methods.push(quote! {
#[doc = #set_doc]
pub fn #method(
&mut self,
value: #path,
) -> ::core::result::Result<(), ::ridl_rt::port::WriteError> {
let mut buf = #buffer;
let bytes = #encode;
self.port.set(#number, #ordinal, bytes)
}
#[doc = #invalidate_doc]
pub fn #invalidate(
&mut self,
) -> ::core::result::Result<(), ::ridl_rt::port::WriteError> {
self.port.invalidate(#number, #ordinal)
}
});
}
for (member, payload) in events {
let method = &member.method;
let ordinal = &member.ordinal;
let path = ty(payload);
let buffer = payload_buffer(payload);
let encode = encode_into(
payload,
quote! { &value },
quote! { &mut buf },
"the payload buffer",
);
let doc = format!("Raises one occurrence of event `{}`.", member.declared);
methods.push(quote! {
#[doc = #doc]
pub fn #method(
&mut self,
value: #path,
) -> ::core::result::Result<(), ::ridl_rt::port::RaiseError> {
let mut buf = #buffer;
let bytes = #encode;
self.port.raise(#number, #ordinal, bytes)
}
});
}
if !signals.is_empty() {
methods.push(quote! {
pub fn commit(&mut self) {
self.port.commit()
}
});
}
let doc = format!("The provider face of interface `{iface_name}`'s signals and events.");
quote! {
#[doc = #doc]
pub struct Publisher<W: #(#bounds)+*> {
port: W,
}
impl<W: #(#bounds)+*> Publisher<W> {
pub fn new(port: W) -> Self {
Publisher { port }
}
#(#methods)*
}
}
}
fn provider(iface_name: &str, commands: &[Call], queries: &[Call]) -> TokenStream {
let command_methods = commands.iter().map(|call| {
let member = &call.member;
let method = &member.method;
let arg = &call.arg;
let path = ty(call.arg_type);
let doc = format!(
"Serves command `{}`. It returns nothing: a command has no failure \
the application reports (ridl §6.1). Arguments that break their \
typl constraints or the `require` clauses never reach it.",
member.declared
);
quote! {
#[doc = #doc]
fn #method(&mut self, #arg: &#path);
}
});
let query_methods = queries.iter().map(|call| {
let member = &call.member;
let method = &member.method;
let arg = &call.arg;
let path = ty(call.arg_type);
let reply_path = ty(call.reply_type.unwrap_or(call.arg_type));
let doc = format!(
"Serves query `{}`. A reply that breaks an `ensure` clause is \
discarded by `dispatch`, which settles `ContractBroken` instead.",
member.declared
);
quote! {
#[doc = #doc]
fn #method(&mut self, #arg: &#path) -> #reply_path;
}
});
let doc = format!(
"What an application implements to serve interface `{iface_name}`'s \
calls.\n\nAn argument is taken by reference because `dispatch` reads \
it again when it evaluates a query's `ensure` clauses, and the \
generated payload types implement neither `Copy` nor `Clone`."
);
quote! {
#[doc = #doc]
pub trait Provider {
#(#command_methods)*
#(#query_methods)*
}
}
}
fn dispatch(iface: &Ident, iface_name: &str, commands: &[Call], queries: &[Call]) -> TokenStream {
let number = interface_number(iface);
let command_arms = commands.iter().map(|call| {
let member = &call.member;
let ordinal = &member.ordinal;
let method = &member.method;
let descriptor = &member.descriptor;
let decode = decode_args(call.arg_type);
let arg = &call.arg;
quote! {
#ordinal => {
let decoded = #decode;
match decoded {
Err(error) => h.settle(claim.id, Err(error)),
Ok(#arg) => {
match <#descriptor as ::ridl_rt::contract::Command>::require(&#arg) {
Err(()) => h.settle(
claim.id,
Err(::ridl_rt::error::CallError::Contract(
::ridl_rt::error::Contract::PreconditionFailed,
)),
),
Ok(()) => {
let accepted = h.settle(claim.id, Ok(&[]));
p.#method(&#arg);
accepted
}
}
}
}
}
}
});
let query_arms = queries.iter().map(|call| {
let member = &call.member;
let ordinal = &member.ordinal;
let method = &member.method;
let descriptor = &member.descriptor;
let decode = decode_args(call.arg_type);
let encode = encode_into(
call.reply_type.unwrap_or(call.arg_type),
quote! { &reply },
quote! { buf },
"the dispatch buffer",
);
let arg = &call.arg;
quote! {
#ordinal => {
let decoded = #decode;
match decoded {
Err(error) => h.settle(claim.id, Err(error)),
Ok(#arg) => {
match <#descriptor as ::ridl_rt::contract::Query>::require(&#arg) {
Err(()) => h.settle(
claim.id,
Err(::ridl_rt::error::CallError::Contract(
::ridl_rt::error::Contract::PreconditionFailed,
)),
),
Ok(()) => {
let reply = p.#method(&#arg);
match <#descriptor as ::ridl_rt::contract::Query>::ensure(
&#arg,
&reply,
) {
Err(()) => h.settle(
claim.id,
Err(::ridl_rt::error::CallError::Contract(
::ridl_rt::error::Contract::ContractBroken,
)),
),
Ok(()) => {
let bytes = #encode;
h.settle(claim.id, Ok(bytes))
}
}
}
}
}
}
}
}
});
let doc = format!(
"Settles every claim of interface `{iface_name}` that is waiting, and \
returns how many were settled.\n\nIt does not wait: it makes one pass \
over the claims the handler already has and returns. The loop that \
calls it belongs to the application or to the runtime.\n\n`buf` is \
caller-owned and must be at least `{iface_name}::MAX_BUFFER_SIZE` \
bytes, because a reply is encoded into the same buffer as the \
arguments. A shorter buffer returns `0` without consuming a claim, so \
the caller can retry with a correctly sized one.\n\nEvery claim that \
is taken is settled, including one whose interface number or ordinal \
this interface does not recognise, which settles \
`Contract::UnknownInteraction`. A claim is counted only once \
`Handler::settle` has accepted it; a `SettleError` is left to the \
handler, which already owns that claim's settlement, and the pass \
continues with the next claim.\n\nA command is settled `Ok(&[])` \
once its arguments and its `require` clauses pass and **before** the \
application's method runs, because a command's acknowledgment is a \
delivery acknowledgment and not a completion one (ridl §6.1, and \
`Handler`'s own contract). A query is settled after the application \
returns, because its settlement carries the reply."
);
quote! {
#[doc = #doc]
pub fn dispatch<H, P>(h: &mut H, p: &mut P, buf: &mut [u8]) -> usize
where
H: ::ridl_rt::port::Handler,
P: Provider,
{
if buf.len() < super::#iface::MAX_BUFFER_SIZE {
return 0;
}
let mut settled = 0usize;
loop {
let Ok(Some(claim)) = h.next_claim(buf) else {
return settled;
};
let settlement = if claim.iface != #number {
h.settle(
claim.id,
Err(::ridl_rt::error::CallError::Contract(
::ridl_rt::error::Contract::UnknownInteraction,
)),
)
} else {
match claim.ord {
#(#command_arms)*
#(#query_arms)*
_ => h.settle(
claim.id,
Err(::ridl_rt::error::CallError::Contract(
::ridl_rt::error::Contract::UnknownInteraction,
)),
),
}
};
if settlement.is_ok() {
settled += 1;
}
}
}
}
}
fn interface_number(iface: &Ident) -> TokenStream {
quote! { <super::#iface as ::ridl_rt::contract::Interface>::NUMBER }
}
fn ty(reference: &str) -> TokenStream {
if reference.contains('.') {
type_path(reference)
} else {
let id = ident(reference);
quote! { super::#id }
}
}
fn payload_buffer(type_name: &str) -> TokenStream {
let path = ty(type_name);
quote! {
[0u8; <#path as ::ridl_rt::payload::Payload<super::Wire>>::MAX_SIZE]
}
}
fn encode_into(
type_name: &str,
value: TokenStream,
target: TokenStream,
target_name: &str,
) -> TokenStream {
let path = ty(type_name);
let capacity = format!(
"encoding `{type_name}` needs {{}} bytes and {target_name} has {{}}; a legal value \
cannot exceed `<{type_name} as Payload<Wire>>::MAX_SIZE`, so the value is outside \
its own type's range or its `Payload` implementation does not honor `MAX_SIZE`"
);
let other = format!("encoding `{type_name}` failed");
quote! {
match ::ridl_rt::payload::Ref::<#path, super::Wire>::encode(
#value,
#target,
) {
Ok(encoded) => encoded.bytes(),
Err(::ridl_rt::payload::EncodeError::Capacity { needed, available }) => {
unreachable!(#capacity, needed, available)
}
Err(_) => unreachable!(#other),
}
}
}
fn decode_args(type_name: &str) -> TokenStream {
let path = ty(type_name);
quote! {
match ::ridl_rt::payload::Ref::<#path, super::Wire>::verify(
&buf[..claim.len],
) {
Ok(checked) => Ok(checked.decode()),
Err(::ridl_rt::payload::VerifyError::Structure(_)) => {
Err(::ridl_rt::error::CallError::Transport(
::ridl_rt::error::Transport::Corrupt,
))
}
Err(::ridl_rt::payload::VerifyError::Contract(violation)) => {
Err(::ridl_rt::error::CallError::Contract(
::ridl_rt::error::Contract::InvalidValue(violation),
))
}
Err(_) => Err(::ridl_rt::error::CallError::Transport(
::ridl_rt::error::Transport::Corrupt,
)),
}
}
}
fn single_param_name(params: &[v2::Param]) -> Ident {
params
.first()
.map_or_else(|| ident("value"), |param| ident(&snake_case(¶m.name)))
}
fn snake_case(name: &str) -> String {
let mut out = String::new();
let mut previous_was_lower = false;
for ch in name.chars() {
if ch == '_' {
out.push('_');
previous_was_lower = false;
continue;
}
if ch.is_uppercase() {
if previous_was_lower {
out.push('_');
}
out.extend(ch.to_lowercase());
previous_was_lower = false;
} else {
out.push(ch);
previous_was_lower = ch.is_lowercase() || ch.is_numeric();
}
}
out
}