1//! Laws selected by capability.
2//!
3//! An adapter's harness says what it can make by implementing the fixture trait of each capability it has
4//! (`MemoryFixture`, `JournalFixture`, ...); [`Suite`] then offers exactly the law sets of those capabilities.
5//! A law set for a capability the harness has no fixture for is not skipped, it cannot be selected: the method
6//! that would select it does not apply to the harness, and the compiler says which fixture is missing.
7//!
8//! The fixture traits sit beside the `Has…` capability traits of `whiskers-ports` and mirror them: a capability
9//! the service can be given (`HasJournal`) has a fixture the suite can be given (`JournalFixture`). A
10//! harness for a whole adapter implements [`Fixtures`], which [`Suite::complete`] runs in full.
11//!
12//! Capabilities with no fixture here have no law yet: the embedder and the thinker are held to nothing a
13//! conformance run can show without calling out (what a real model server says back is the adapter's own
14//! test), and `Secrets` is not served by a route; its laws are `suite::secrets`.
15
16use whiskers_ports::{Allowance, ChatHub, Clock, HouseholdHub, Journal, Judge, MemoryHub, PictureShelf, Voice};
17
18use super::hubs::WriteSwitch;
19use super::{allowance, hubs, journal, outbound, pictures};
20
21macro_rules! stateful_fixture {
22    ($(#[$doc:meta])* $name:ident, $port:ident) => {
23        $(#[$doc])*
24        pub trait $name {
25            type Port: $port;
26            /// A new, empty port of this capability.
27            fn fresh(&self) -> Self::Port;
28            /// The same port after its process restarted: it must find what it kept.
29            fn restart(&self, port: Self::Port) -> Self::Port;
30        }
31    };
32}
33
34stateful_fixture!(
35    /// How to make, and restart, the memory hub (capability `HasMemory`).
36    MemoryFixture, MemoryHub
37);
38stateful_fixture!(
39    /// How to make, and restart, the household hub (capability `HasHousehold`).
40    HouseholdFixture, HouseholdHub
41);
42stateful_fixture!(
43    /// How to make, and restart, the chat hub (capability `HasChat`).
44    ChatFixture, ChatHub
45);
46stateful_fixture!(
47    /// How to make, and restart, the journal (capability `HasJournal`).
48    JournalFixture, Journal
49);
50stateful_fixture!(
51    /// How to make, and restart, the picture shelf (capability `HasPictures`).
52    PicturesFixture, PictureShelf
53);
54stateful_fixture!(
55    /// How to make, and restart, the allowance (capability `HasAllowance`).
56    AllowanceFixture, Allowance
57);
58
59/// How to make a chat hub whose storage can be told to refuse writes, as a full disk would. Not every harness
60/// can (a black box over HTTP cannot), so the law that needs it is selected apart from the chat hub's others.
61pub trait ChatFaultFixture {
62    type Port: ChatHub;
63    /// A new chat hub and the switch that makes its storage refuse writes (`true`) or take them again.
64    fn fresh_refusing(&self) -> (Self::Port, WriteSwitch);
65    /// The same hub after a restart, its storage working again.
66    fn restart_refusing(&self, port: Self::Port) -> Self::Port;
67}
68
69/// A judge with nothing to call with (capability `HasJudge`).
70pub trait JudgeFixture {
71    type Port: Judge;
72    fn without_credentials(&self) -> Self::Port;
73}
74
75/// A voice with nothing to call with (capability `HasVoice`). The law that has it speak, which a paid voice
76/// runs only when its operator agrees to the spend, is `suite::timing::speaks_timed`, called on purpose.
77pub trait VoiceFixture {
78    type Port: Voice;
79    fn without_credentials(&self) -> Self::Port;
80}
81
82/// A clock (capability `HasClock`).
83pub trait ClockFixture {
84    type Port: Clock;
85    fn clock(&self) -> Self::Port;
86}
87
88/// A harness for a whole adapter: a fixture for every capability there is a law for.
89pub trait Fixtures:
90    MemoryFixture + HouseholdFixture + ChatFixture + ChatFaultFixture + JournalFixture + PicturesFixture + AllowanceFixture + JudgeFixture + VoiceFixture + ClockFixture
91{
92}
93
94impl<F> Fixtures for F where
95    F: MemoryFixture
96        + HouseholdFixture
97        + ChatFixture
98        + ChatFaultFixture
99        + JournalFixture
100        + PicturesFixture
101        + AllowanceFixture
102        + JudgeFixture
103        + VoiceFixture
104        + ClockFixture
105{
106}
107
108/// The capabilities whose laws a [`Suite`] has run, by name, in the order run.
109pub type Ran = Vec<&'static str>;
110
111/// A harness, and the laws chosen for it so far. Every method runs the laws of one capability, and applies
112/// only to a harness with that capability's fixture. A harness with the memory hub and nothing else can run
113/// the memory hub's laws:
114///
115/// ```
116/// use whiskers_conformance::memory::MemMemoryHub;
117/// use whiskers_conformance::suite::fixture::{MemoryFixture, Suite};
118///
119/// struct OnlyMemory;
120/// impl MemoryFixture for OnlyMemory {
121///     type Port = MemMemoryHub;
122///     fn fresh(&self) -> MemMemoryHub { MemMemoryHub::default() }
123///     fn restart(&self, port: MemMemoryHub) -> MemMemoryHub { port }
124/// }
125///
126/// let suite = Suite::new(OnlyMemory).memory();
127/// assert_eq!(suite.ran(), ["memory"]);
128/// ```
129///
130/// It cannot select the journal's laws. They are not skipped, there is no way to ask for them:
131///
132/// ```compile_fail
133/// use whiskers_conformance::memory::MemMemoryHub;
134/// use whiskers_conformance::suite::fixture::{MemoryFixture, Suite};
135///
136/// struct OnlyMemory;
137/// impl MemoryFixture for OnlyMemory {
138///     type Port = MemMemoryHub;
139///     fn fresh(&self) -> MemMemoryHub { MemMemoryHub::default() }
140///     fn restart(&self, port: MemMemoryHub) -> MemMemoryHub { port }
141/// }
142///
143/// // error[E0277]: the trait bound `OnlyMemory: JournalFixture` is not satisfied
144/// let _ = Suite::new(OnlyMemory).memory().journal();
145/// ```
146pub struct Suite<F> {
147    fixture: F,
148    ran: Ran,
149}
150
151impl<F> Suite<F> {
152    /// A suite that has run nothing.
153    pub fn new(fixture: F) -> Self {
154        Self { fixture, ran: Vec::new() }
155    }
156
157    /// Which capabilities' laws have run.
158    pub fn ran(&self) -> &[&'static str] {
159        &self.ran
160    }
161
162    /// The memory hub's laws.
163    pub fn memory(mut self) -> Self
164    where
165        F: MemoryFixture,
166    {
167        hubs::memory_hub(&|| MemoryFixture::fresh(&self.fixture), &|p| MemoryFixture::restart(&self.fixture, p));
168        self.ran.push("memory");
169        self
170    }
171
172    /// The household hub's laws.
173    pub fn household(mut self) -> Self
174    where
175        F: HouseholdFixture,
176    {
177        hubs::household_hub(&|| HouseholdFixture::fresh(&self.fixture), &|p| HouseholdFixture::restart(&self.fixture, p));
178        self.ran.push("household");
179        self
180    }
181
182    /// The chat hub's laws, all but the one that needs its storage to fail ([`Suite::chat_write_faults`]).
183    pub fn chat(mut self) -> Self
184    where
185        F: ChatFixture,
186    {
187        hubs::chat_hub_whose_storage_works(&|| ChatFixture::fresh(&self.fixture), &|p| ChatFixture::restart(&self.fixture, p));
188        self.ran.push("chat");
189        self
190    }
191
192    /// The chat-hub law that a copy that cannot be kept is an error and changes nothing.
193    pub fn chat_write_faults(mut self) -> Self
194    where
195        F: ChatFaultFixture,
196    {
197        hubs::chat_a_copy_that_cannot_be_kept_is_an_error_and_changes_nothing(
198            &|| ChatFaultFixture::fresh_refusing(&self.fixture),
199            &|p| ChatFaultFixture::restart_refusing(&self.fixture, p),
200        );
201        self.ran.push("chat write faults");
202        self
203    }
204
205    /// The journal's laws.
206    pub fn journal(mut self) -> Self
207    where
208        F: JournalFixture,
209    {
210        journal::journal(&|| JournalFixture::fresh(&self.fixture), &|p| JournalFixture::restart(&self.fixture, p));
211        self.ran.push("journal");
212        self
213    }
214
215    /// The picture shelf's laws.
216    pub fn pictures(mut self) -> Self
217    where
218        F: PicturesFixture,
219    {
220        pictures::pictures(&|| PicturesFixture::fresh(&self.fixture), &|p| PicturesFixture::restart(&self.fixture, p));
221        self.ran.push("pictures");
222        self
223    }
224
225    /// The allowance's laws.
226    pub fn allowance(mut self) -> Self
227    where
228        F: AllowanceFixture,
229    {
230        allowance::allowance(&|| AllowanceFixture::fresh(&self.fixture), &|p| AllowanceFixture::restart(&self.fixture, p));
231        self.ran.push("allowance");
232        self
233    }
234
235    /// What a judge with no credentials does.
236    pub fn judge(mut self) -> Self
237    where
238        F: JudgeFixture,
239    {
240        outbound::judge_without_credentials(&JudgeFixture::without_credentials(&self.fixture));
241        self.ran.push("judge");
242        self
243    }
244
245    /// What a voice with no credentials does.
246    pub fn voice(mut self) -> Self
247    where
248        F: VoiceFixture,
249    {
250        outbound::voice_without_credentials(&VoiceFixture::without_credentials(&self.fixture));
251        self.ran.push("voice");
252        self
253    }
254
255    /// The clock's laws.
256    pub fn clock(mut self) -> Self
257    where
258        F: ClockFixture,
259    {
260        outbound::clock(&ClockFixture::clock(&self.fixture));
261        self.ran.push("clock");
262        self
263    }
264
265    /// Every law there is for every capability, for a harness of a whole adapter.
266    pub fn complete(fixture: F) -> Self
267    where
268        F: Fixtures,
269    {
270        Self::new(fixture).memory().household().chat().chat_write_faults().journal().pictures().allowance().judge().voice().clock()
271    }
272}