Skip to main content

elicitation/verification/types/
tuples.rs

1//! Tuple contract types demonstrating compositional verification.
2//!
3//! Tuples compose contract types - if all elements are valid contract types,
4//! the tuple is guaranteed valid by construction.
5
6use crate::{ElicitClient, ElicitResult, Elicitation, Prompt};
7
8// Tuple2 - 2-element tuple where both satisfy contracts
9/// A 2-element tuple where both elements are contract types.
10///
11/// **Compositional verification:** If C1 and C2 are valid contracts,
12/// Tuple2<C1, C2> is automatically valid by construction.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct Tuple2<C1, C2>(pub C1, pub C2);
15
16impl<C1, C2> Tuple2<C1, C2> {
17    /// Create a new Tuple2. Both elements are already validated contract types.
18    pub fn new(first: C1, second: C2) -> Self {
19        Self(first, second)
20    }
21
22    /// Get the first element.
23    pub fn first(&self) -> &C1 {
24        &self.0
25    }
26
27    /// Get the second element.
28    pub fn second(&self) -> &C2 {
29        &self.1
30    }
31
32    /// Unwrap into components.
33    pub fn into_inner(self) -> (C1, C2) {
34        (self.0, self.1)
35    }
36}
37
38impl<C1, C2> Prompt for Tuple2<C1, C2>
39where
40    C1: Elicitation + Send,
41    C2: Elicitation + Send,
42{
43    fn prompt() -> Option<&'static str> {
44        Some("Eliciting tuple with 2 elements:")
45    }
46}
47
48impl<C1, C2> Elicitation for Tuple2<C1, C2>
49where
50    C1: Elicitation + Send,
51    C2: Elicitation + Send,
52{
53    type Style = <(C1, C2) as Elicitation>::Style;
54
55    #[tracing::instrument(skip(client))]
56    async fn elicit(client: &ElicitClient) -> ElicitResult<Self> {
57        tracing::debug!("Eliciting Tuple2");
58        let first = C1::elicit(client).await?; // Guaranteed valid by contract!
59        let second = C2::elicit(client).await?; // Guaranteed valid by contract!
60        Ok(Self::new(first, second)) // Composition = proven valid
61    }
62}
63
64// Tuple3 - 3-element tuple
65/// A 3-element tuple where all elements are contract types.
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
67pub struct Tuple3<C1, C2, C3>(pub C1, pub C2, pub C3);
68
69impl<C1, C2, C3> Tuple3<C1, C2, C3> {
70    /// Create a new Tuple3.
71    pub fn new(first: C1, second: C2, third: C3) -> Self {
72        Self(first, second, third)
73    }
74
75    /// Unwrap into components.
76    pub fn into_inner(self) -> (C1, C2, C3) {
77        (self.0, self.1, self.2)
78    }
79}
80
81impl<C1, C2, C3> Prompt for Tuple3<C1, C2, C3>
82where
83    C1: Elicitation + Send,
84    C2: Elicitation + Send,
85    C3: Elicitation + Send,
86{
87    fn prompt() -> Option<&'static str> {
88        Some("Eliciting tuple with 3 elements:")
89    }
90}
91
92impl<C1, C2, C3> Elicitation for Tuple3<C1, C2, C3>
93where
94    C1: Elicitation + Send,
95    C2: Elicitation + Send,
96    C3: Elicitation + Send,
97{
98    type Style = <(C1, C2, C3) as Elicitation>::Style;
99
100    #[tracing::instrument(skip(client))]
101    async fn elicit(client: &ElicitClient) -> ElicitResult<Self> {
102        tracing::debug!("Eliciting Tuple3");
103        let first = C1::elicit(client).await?;
104        let second = C2::elicit(client).await?;
105        let third = C3::elicit(client).await?;
106        Ok(Self::new(first, second, third))
107    }
108}
109
110// Tuple4 - 4-element tuple
111/// A 4-element tuple where all elements are contract types.
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113pub struct Tuple4<C1, C2, C3, C4>(pub C1, pub C2, pub C3, pub C4);
114
115impl<C1, C2, C3, C4> Tuple4<C1, C2, C3, C4> {
116    /// Create a new Tuple4.
117    pub fn new(first: C1, second: C2, third: C3, fourth: C4) -> Self {
118        Self(first, second, third, fourth)
119    }
120
121    /// Unwrap into components.
122    pub fn into_inner(self) -> (C1, C2, C3, C4) {
123        (self.0, self.1, self.2, self.3)
124    }
125}
126
127impl<C1, C2, C3, C4> Prompt for Tuple4<C1, C2, C3, C4>
128where
129    C1: Elicitation + Send,
130    C2: Elicitation + Send,
131    C3: Elicitation + Send,
132    C4: Elicitation + Send,
133{
134    fn prompt() -> Option<&'static str> {
135        Some("Eliciting tuple with 4 elements:")
136    }
137}
138
139impl<C1, C2, C3, C4> Elicitation for Tuple4<C1, C2, C3, C4>
140where
141    C1: Elicitation + Send,
142    C2: Elicitation + Send,
143    C3: Elicitation + Send,
144    C4: Elicitation + Send,
145{
146    type Style = <(C1, C2, C3, C4) as Elicitation>::Style;
147
148    #[tracing::instrument(skip(client))]
149    async fn elicit(client: &ElicitClient) -> ElicitResult<Self> {
150        tracing::debug!("Eliciting Tuple4");
151        let first = C1::elicit(client).await?;
152        let second = C2::elicit(client).await?;
153        let third = C3::elicit(client).await?;
154        let fourth = C4::elicit(client).await?;
155        Ok(Self::new(first, second, third, fourth))
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use crate::verification::types::{BoolTrue, I8Positive, StringNonEmpty};
163
164    #[test]
165    fn test_tuple2_new() {
166        let s: StringNonEmpty = StringNonEmpty::new("test".to_string()).unwrap();
167        let t = Tuple2::new(I8Positive::new(5).unwrap(), s);
168        assert_eq!(t.first().get(), 5);
169        assert_eq!(t.second().get(), "test");
170    }
171
172    #[test]
173    fn test_tuple2_into_inner() {
174        let s: StringNonEmpty = StringNonEmpty::new("test".to_string()).unwrap();
175        let t = Tuple2::new(I8Positive::new(5).unwrap(), s);
176        let (first, second) = t.into_inner();
177        assert_eq!(first.get(), 5);
178        assert_eq!(second.get(), "test");
179    }
180
181    #[test]
182    fn test_tuple3_new() {
183        let s: StringNonEmpty = StringNonEmpty::new("test".to_string()).unwrap();
184        let t = Tuple3::new(I8Positive::new(5).unwrap(), s, BoolTrue::new(true).unwrap());
185        let (first, second, third) = t.into_inner();
186        assert_eq!(first.get(), 5);
187        assert_eq!(second.get(), "test");
188        assert!(third.get());
189    }
190
191    #[test]
192    fn test_tuple4_new() {
193        let t = Tuple4::new(
194            I8Positive::new(1).unwrap(),
195            I8Positive::new(2).unwrap(),
196            I8Positive::new(3).unwrap(),
197            I8Positive::new(4).unwrap(),
198        );
199        let (a, b, c, d) = t.into_inner();
200        assert_eq!(a.get(), 1);
201        assert_eq!(b.get(), 2);
202        assert_eq!(c.get(), 3);
203        assert_eq!(d.get(), 4);
204    }
205}