Skip to main content

ridl_rt/
contract.rs

1//! Identity, and the interaction descriptors generated code writes.
2//!
3//! A descriptor is a zero-sized marker type with constants. The descriptors
4//! are the per-member form of the ordinal table of ADR-0013 decision 3,
5//! extended with the interface number and the catalog hash.
6
7use crate::sample::Duration;
8
9/// A member's ordinal: its position in the interface body, counted from 1
10/// (ridl §11). An ordinal is never 0.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
12pub struct Ordinal(pub u32);
13
14/// An interface's number in its catalog: the number the lock file froze, or a
15/// provisional number. An interface number is never 0.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub struct InterfaceNo(pub u32);
18
19/// The catalog hash: SHA-256 over the catalog's interfaces, their numbers, and
20/// the types they reach.
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct CatalogHash(pub [u8; 32]);
23
24/// A catalog: one package's interfaces.
25///
26/// Two `CatalogRef`s are equal only when both the names and the hashes are
27/// equal, because two packages with the same contents can have the same hash.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct CatalogRef {
30    /// The package name.
31    pub name: &'static str,
32    /// The catalog hash.
33    pub hash: CatalogHash,
34}
35
36/// The five interaction kinds of the language.
37#[repr(u8)]
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
39pub enum Kind {
40    /// A `signal`.
41    Signal = 1,
42    /// An `event`.
43    Event = 2,
44    /// A `command`.
45    Command = 3,
46    /// A `query`.
47    Query = 4,
48    /// A `fixed`.
49    Fixed = 5,
50}
51
52/// An interface's descriptor.
53pub trait Interface {
54    /// The catalog the interface belongs to.
55    const CATALOG: &'static CatalogRef;
56    /// The interface number in that catalog.
57    const NUMBER: InterfaceNo;
58    /// `true` when the number is provisional, not yet frozen in the lock file.
59    const PROVISIONAL: bool;
60    /// The interface name.
61    const NAME: &'static str;
62    /// The members, in ordinal order. A reserved ordinal has no row, so the
63    /// index of a row is not its ordinal minus one.
64    const MEMBERS: &'static [Member];
65}
66
67/// An interaction's descriptor: the part every kind has.
68pub trait Interaction {
69    /// The interface that declares the interaction.
70    type Iface: Interface;
71    /// The interaction's row in `Iface::MEMBERS`.
72    const MEMBER: &'static Member;
73}
74
75/// A `signal` (ridl §4).
76pub trait Signal: Interaction {
77    /// The value type.
78    type Payload;
79    /// The channel's init value (ridl §4.4).
80    fn init() -> Self::Payload;
81}
82
83/// An `event` (ridl §5).
84pub trait Event: Interaction {
85    /// The occurrence type.
86    type Payload;
87}
88
89/// A `fixed` (ridl §8).
90pub trait Fixed: Interaction {
91    /// The provisioned value type.
92    type Payload;
93}
94
95/// A `command` (ridl §6).
96pub trait Command: Interaction {
97    /// The argument type.
98    type Args;
99    /// Evaluates the command's `require` clauses. `Ok` when every clause is
100    /// true or the command declares none. `Err` when a clause is false, which
101    /// the provider reports as
102    /// [`Contract::PreconditionFailed`](crate::error::Contract::PreconditionFailed).
103    #[allow(
104        clippy::result_unit_err,
105        reason = "the failing method decides the contract error, so the error carries no value"
106    )]
107    fn require(args: &Self::Args) -> Result<(), ()>;
108}
109
110/// A `query` (ridl §7).
111pub trait Query: Interaction {
112    /// The argument type.
113    type Args;
114    /// The reply type.
115    type Reply;
116    /// Evaluates the query's `require` clauses. `Ok` when every clause is
117    /// true or the query declares none. `Err` when a clause is false, which
118    /// the provider reports as
119    /// [`Contract::PreconditionFailed`](crate::error::Contract::PreconditionFailed).
120    #[allow(
121        clippy::result_unit_err,
122        reason = "the failing method decides the contract error, so the error carries no value"
123    )]
124    fn require(args: &Self::Args) -> Result<(), ()>;
125    /// Evaluates the query's `ensure` clauses. `Ok` when every clause is true
126    /// or the query declares none. `Err` when a clause is false, which the
127    /// provider reports as
128    /// [`Contract::ContractBroken`](crate::error::Contract::ContractBroken).
129    #[allow(
130        clippy::result_unit_err,
131        reason = "the failing method decides the contract error, so the error carries no value"
132    )]
133    fn ensure(args: &Self::Args, reply: &Self::Reply) -> Result<(), ()>;
134}
135
136/// One member of an interface.
137#[derive(Clone, Copy, Debug, PartialEq, Eq)]
138pub struct Member {
139    /// The member's ordinal.
140    pub ordinal: Ordinal,
141    /// The member's kind.
142    pub kind: Kind,
143    /// The member's name.
144    pub name: &'static str,
145    /// The member's timing, as the IR resolved it. `None` when the IR carries
146    /// no timing: a `command` or a `query` with no timing annotation, or a
147    /// `fixed`.
148    pub timing: Option<Timing>,
149    /// One entry per payload: two for a `query` (the request, then the
150    /// reply), one for every other kind.
151    pub payloads: &'static [PayloadInfo],
152}
153
154/// The form of a timing annotation (ridl §9).
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156pub enum TimingMode {
157    /// `@Xms`: a strict period, on a signal only (ridl §9.2).
158    StrictPeriodic,
159    /// `@[min..max]`, where either side may be absent.
160    Range,
161}
162
163/// A member's timing (ridl §9).
164///
165/// `max` is the staleness bound of a signal, the time to live of an event, and
166/// the response bound of a call.
167///
168/// Under `StrictPeriodic`, `min` and `max` both hold the period.
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
170pub struct Timing {
171    /// The form of the annotation.
172    pub mode: TimingMode,
173    /// The lower bound. `None` when the IR leaves it unset.
174    pub min: Option<Duration>,
175    /// The upper bound. `None` when the IR leaves it unset.
176    pub max: Option<Duration>,
177}
178
179/// One payload of a member.
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
181pub struct PayloadInfo {
182    /// The payload type's name.
183    pub type_name: &'static str,
184    /// The payload's largest encoded size in each core encoding.
185    pub max_size: EncodedSizes,
186}
187
188/// A payload's largest encoded size in bytes, one field per core encoding.
189///
190/// A field is `None` when the toolchain cannot size the payload for that
191/// encoding. That covers two cases the reader does not have to tell apart: the
192/// encoding cannot carry the payload at all, and the encoding can carry it but
193/// the size is not derivable yet, because the story that defines the layout has
194/// not landed. The `repr(C)` column is `None` for every payload until E11.12
195/// defines the C-representable layout, and a backend that emits descriptors
196/// before its codec exists writes `None` for that codec's column too.
197///
198/// So a consumer reads `None` as "no size is available here", never as "this
199/// payload cannot be encoded this way", and asks the catalog descriptor rather
200/// than this field when it needs to know which encodings a payload has.
201#[derive(Clone, Copy, Debug, PartialEq, Eq)]
202pub struct EncodedSizes {
203    /// The proto3 size.
204    pub proto3: Option<u32>,
205    /// The FlatBuffers size.
206    pub flatbuffers: Option<u32>,
207    /// The `repr(C)` size.
208    pub repr_c: Option<u32>,
209}
210
211#[cfg(test)]
212mod tests {
213    use super::{CatalogHash, CatalogRef, Kind};
214
215    #[test]
216    fn catalog_refs_are_equal_only_when_name_and_hash_are_equal() {
217        let base = CatalogRef {
218            name: "vehicle",
219            hash: CatalogHash([1; 32]),
220        };
221        let same = CatalogRef {
222            name: "vehicle",
223            hash: CatalogHash([1; 32]),
224        };
225        let other_hash = CatalogRef {
226            name: "vehicle",
227            hash: CatalogHash([2; 32]),
228        };
229        let other_name = CatalogRef {
230            name: "cabin",
231            hash: CatalogHash([1; 32]),
232        };
233        assert_eq!(base, same);
234        assert_ne!(base, other_hash);
235        assert_ne!(base, other_name);
236    }
237
238    #[test]
239    fn kind_values_are_the_language_order_from_one() {
240        assert_eq!(Kind::Signal as u8, 1);
241        assert_eq!(Kind::Event as u8, 2);
242        assert_eq!(Kind::Command as u8, 3);
243        assert_eq!(Kind::Query as u8, 4);
244        assert_eq!(Kind::Fixed as u8, 5);
245    }
246}