1use crate::feature_traits::FeatureTraits;
4use crate::plutus_data::{
5 parse_constr, parse_constr_with_tag, parse_fixed_len_constr_fields, IsPlutusData, PlutusData,
6 PlutusDataError,
7};
8#[cfg(feature = "lbf")]
9use lbr_prelude::json::Json;
10use num_bigint::BigInt;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use std::cmp;
14
15#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub enum Interval<T> {
23 Finite(T, T),
24 StartAt(T),
25 StartAfter(T),
26 EndAt(T),
27 EndBefore(T),
28 Always,
29 Never,
30}
31
32impl<T> From<Interval<T>> for PlutusInterval<T>
33where
34 T: FeatureTraits,
35{
36 fn from(interval: Interval<T>) -> Self {
37 match interval {
38 Interval::Finite(start, end) => PlutusInterval {
39 from: LowerBound {
40 bound: Extended::Finite(start),
41 closed: true,
42 },
43 to: UpperBound {
44 bound: Extended::Finite(end),
45 closed: true,
46 },
47 },
48 Interval::StartAt(start) => PlutusInterval {
49 from: LowerBound {
50 bound: Extended::Finite(start),
51 closed: true,
52 },
53 to: UpperBound {
54 bound: Extended::PosInf,
55 closed: true,
56 },
57 },
58 Interval::StartAfter(start) => PlutusInterval {
59 from: LowerBound {
60 bound: Extended::Finite(start),
61 closed: false,
62 },
63 to: UpperBound {
64 bound: Extended::PosInf,
65 closed: true,
66 },
67 },
68 Interval::EndAt(end) => PlutusInterval {
69 from: LowerBound {
70 bound: Extended::NegInf,
71 closed: true,
72 },
73 to: UpperBound {
74 bound: Extended::Finite(end),
75 closed: true,
76 },
77 },
78 Interval::EndBefore(end) => PlutusInterval {
79 from: LowerBound {
80 bound: Extended::NegInf,
81 closed: true,
82 },
83 to: UpperBound {
84 bound: Extended::Finite(end),
85 closed: false,
86 },
87 },
88 Interval::Always => PlutusInterval {
89 from: LowerBound {
90 bound: Extended::NegInf,
91 closed: true,
92 },
93 to: UpperBound {
94 bound: Extended::PosInf,
95 closed: true,
96 },
97 },
98 Interval::Never => PlutusInterval {
99 from: LowerBound {
100 bound: Extended::PosInf,
101 closed: true,
102 },
103 to: UpperBound {
104 bound: Extended::NegInf,
105 closed: true,
106 },
107 },
108 }
109 }
110}
111
112#[derive(thiserror::Error, Debug)]
113pub enum TryFromPlutusIntervalError {
114 #[error("Interval is invalid.")]
115 InvalidInterval,
116 #[error("Interval with open bound could not be converted.")]
117 UnexpectedOpenBound,
118}
119
120impl<T> TryFrom<PlutusInterval<T>> for Interval<T>
121where
122 T: FeatureTraits + PartialOrd,
123{
124 type Error = TryFromPlutusIntervalError;
125
126 fn try_from(interval: PlutusInterval<T>) -> Result<Self, Self::Error> {
127 Ok(match interval {
128 PlutusInterval {
129 from:
130 LowerBound {
131 bound: Extended::Finite(start),
132 closed: lc,
133 },
134 to:
135 UpperBound {
136 bound: Extended::Finite(end),
137 closed: uc,
138 },
139 } => {
140 if lc && uc {
141 if start > end {
142 Err(TryFromPlutusIntervalError::InvalidInterval)?
143 } else {
144 Interval::Finite(start, end)
145 }
146 } else {
147 Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
148 }
149 }
150 PlutusInterval {
151 from:
152 LowerBound {
153 bound: Extended::Finite(start),
154 closed: lc,
155 },
156 to:
157 UpperBound {
158 bound: Extended::PosInf,
159 closed: uc,
160 },
161 } => {
162 if lc && uc {
163 Interval::StartAt(start)
164 } else if !lc && uc {
165 Interval::StartAfter(start)
166 } else {
167 Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
168 }
169 }
170 PlutusInterval {
171 from:
172 LowerBound {
173 bound: Extended::NegInf,
174 closed: lc,
175 },
176 to:
177 UpperBound {
178 bound: Extended::Finite(end),
179 closed: uc,
180 },
181 } => {
182 if uc && lc {
183 Interval::EndAt(end)
184 } else if !uc && lc {
185 Interval::EndBefore(end)
186 } else {
187 Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
188 }
189 }
190 PlutusInterval {
191 from:
192 LowerBound {
193 bound: Extended::NegInf,
194 closed: lc,
195 },
196 to:
197 UpperBound {
198 bound: Extended::PosInf,
199 closed: uc,
200 },
201 } => {
202 if lc && uc {
203 Interval::Always
204 } else {
205 Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
206 }
207 }
208 PlutusInterval {
209 from:
210 LowerBound {
211 bound: Extended::PosInf,
212 closed: lc,
213 },
214 to:
215 UpperBound {
216 bound: Extended::NegInf,
217 closed: uc,
218 },
219 } => {
220 if lc && uc {
221 Interval::Never
222 } else {
223 Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
224 }
225 }
226
227 _ => Err(TryFromPlutusIntervalError::InvalidInterval)?,
228 })
229 }
230}
231
232#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
251#[cfg_attr(feature = "lbf", derive(Json))]
252#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
253pub struct PlutusInterval<T>
254where
255 T: FeatureTraits,
256{
257 pub from: LowerBound<T>,
258 pub to: UpperBound<T>,
259}
260
261impl<T> IsPlutusData for PlutusInterval<T>
262where
263 T: FeatureTraits + IsPlutusData,
264{
265 fn to_plutus_data(&self) -> PlutusData {
266 PlutusData::Constr(
267 BigInt::from(0),
268 vec![self.from.to_plutus_data(), self.to.to_plutus_data()],
269 )
270 }
271
272 fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
273 let fields = parse_constr_with_tag(data, 0)?;
274 let [field_0, field_1] = parse_fixed_len_constr_fields::<2>(fields)?;
275 Ok(Self {
276 from: IsPlutusData::from_plutus_data(field_0)?,
277 to: IsPlutusData::from_plutus_data(field_1)?,
278 })
279 }
280}
281
282#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
287#[cfg_attr(feature = "lbf", derive(Json))]
288#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
289pub struct UpperBound<T>
290where
291 T: FeatureTraits,
292{
293 pub bound: Extended<T>,
294 pub closed: bool,
295}
296
297impl<T> IsPlutusData for UpperBound<T>
298where
299 T: FeatureTraits + IsPlutusData,
300{
301 fn to_plutus_data(&self) -> PlutusData {
302 PlutusData::Constr(
303 BigInt::from(0),
304 vec![self.bound.to_plutus_data(), self.closed.to_plutus_data()],
305 )
306 }
307
308 fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
309 let fields = parse_constr_with_tag(data, 0)?;
310 let [field_0, field_1] = parse_fixed_len_constr_fields::<2>(fields)?;
311 Ok(Self {
312 bound: IsPlutusData::from_plutus_data(field_0)?,
313 closed: IsPlutusData::from_plutus_data(field_1)?,
314 })
315 }
316}
317
318#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
323#[cfg_attr(feature = "lbf", derive(Json))]
324#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
325pub struct LowerBound<T>
326where
327 T: FeatureTraits,
328{
329 pub bound: Extended<T>,
330 pub closed: bool,
331}
332
333impl<T> IsPlutusData for LowerBound<T>
334where
335 T: FeatureTraits + IsPlutusData,
336{
337 fn to_plutus_data(&self) -> PlutusData {
338 PlutusData::Constr(
339 BigInt::from(0),
340 vec![self.bound.to_plutus_data(), self.closed.to_plutus_data()],
341 )
342 }
343
344 fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
345 let fields = parse_constr_with_tag(data, 0)?;
346 let [field_0, field_1] = parse_fixed_len_constr_fields::<2>(fields)?;
347 Ok(Self {
348 bound: IsPlutusData::from_plutus_data(field_0)?,
349 closed: IsPlutusData::from_plutus_data(field_1)?,
350 })
351 }
352}
353
354#[derive(Clone, Debug, PartialEq, Eq, Hash)]
360#[cfg_attr(feature = "lbf", derive(Json))]
361#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
362pub enum Extended<T>
363where
364 T: FeatureTraits,
365{
366 NegInf,
367 Finite(T),
368 PosInf,
369}
370
371impl<T> Ord for Extended<T>
372where
373 T: FeatureTraits + Ord,
374{
375 fn cmp(&self, other: &Self) -> cmp::Ordering {
376 match (self, other) {
377 (Extended::PosInf, Extended::PosInf) => cmp::Ordering::Equal,
378 (Extended::PosInf, _) => cmp::Ordering::Greater,
379
380 (Extended::NegInf, Extended::NegInf) => cmp::Ordering::Equal,
381 (Extended::NegInf, _) => cmp::Ordering::Less,
382
383 (Extended::Finite(_), Extended::NegInf) => cmp::Ordering::Greater,
384 (Extended::Finite(self_val), Extended::Finite(other_val)) => self_val.cmp(other_val),
385 (Extended::Finite(_), Extended::PosInf) => cmp::Ordering::Less,
386 }
387 }
388}
389
390impl<T> PartialOrd for Extended<T>
391where
392 T: FeatureTraits + PartialOrd,
393{
394 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
395 match self {
396 Extended::PosInf => match other {
397 Extended::PosInf => Some(cmp::Ordering::Equal),
398 _ => Some(cmp::Ordering::Greater),
399 },
400 Extended::NegInf => match other {
401 Extended::NegInf => Some(cmp::Ordering::Equal),
402 _ => Some(cmp::Ordering::Less),
403 },
404 Extended::Finite(self_val) => match other {
405 Extended::NegInf => Some(cmp::Ordering::Greater),
406 Extended::Finite(other_val) => self_val.partial_cmp(other_val),
407 Extended::PosInf => Some(cmp::Ordering::Less),
408 },
409 }
410 }
411}
412
413impl<T> IsPlutusData for Extended<T>
414where
415 T: FeatureTraits + IsPlutusData,
416{
417 fn to_plutus_data(&self) -> PlutusData {
418 match self {
419 Extended::NegInf => PlutusData::Constr(BigInt::from(0), Vec::with_capacity(0)),
420 Extended::Finite(value) => {
421 PlutusData::Constr(BigInt::from(1), vec![value.to_plutus_data()])
422 }
423 Extended::PosInf => PlutusData::Constr(BigInt::from(2), Vec::with_capacity(0)),
424 }
425 }
426
427 fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
428 let (tag, fields) = parse_constr(data)?;
429 match tag {
430 0 => {
431 let [] = parse_fixed_len_constr_fields::<0>(fields)?;
432 Ok(Extended::NegInf)
433 }
434 1 => {
435 let [field] = parse_fixed_len_constr_fields::<1>(fields)?;
436 Ok(Extended::Finite(IsPlutusData::from_plutus_data(field)?))
437 }
438 2 => {
439 let [] = parse_fixed_len_constr_fields::<0>(fields)?;
440 Ok(Extended::PosInf)
441 }
442 _ => Err(PlutusDataError::UnexpectedPlutusInvariant {
443 wanted: "Constr with tag 0, 1 or 2".to_owned(),
444 got: tag.to_string(),
445 }),
446 }
447 }
448}
449
450#[cfg(test)]
451mod test {
452 use super::*;
453 use crate::{generators::correct::v1::arb_interval_posix_time, v1::transaction::POSIXTime};
454 use proptest::prelude::*;
455
456 proptest! {
457 #[test]
458 fn interval_to_from_plutus_interval(interval in arb_interval_posix_time()) {
459 let plutus_interval: PlutusInterval<POSIXTime> = interval.clone().into();
460 let interval2: Interval<POSIXTime> = plutus_interval.clone().try_into().unwrap();
461
462 assert_eq!(interval, interval2);
463 }
464 }
465}