grovedb_costs/
context.rs

1// MIT LICENSE
2//
3// Copyright (c) 2021 Dash Core Group
4//
5// Permission is hereby granted, free of charge, to any
6// person obtaining a copy of this software and associated
7// documentation files (the "Software"), to deal in the
8// Software without restriction, including without
9// limitation the rights to use, copy, modify, merge,
10// publish, distribute, sublicense, and/or sell copies of
11// the Software, and to permit persons to whom the Software
12// is furnished to do so, subject to the following
13// conditions:
14//
15// The above copyright notice and this permission notice
16// shall be included in all copies or substantial portions
17// of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27// DEALINGS IN THE SOFTWARE.
28
29use crate::OperationCost;
30
31/// Wrapped operation result with associated cost.
32#[must_use]
33#[derive(Debug, Eq, PartialEq)]
34pub struct CostContext<T> {
35    /// Wrapped operation's return value.
36    pub value: T,
37    /// Cost of the operation.
38    pub cost: OperationCost,
39}
40
41/// General combinators for `CostContext`.
42impl<T> CostContext<T> {
43    /// Take wrapped value out adding its cost to provided accumulator.
44    pub fn unwrap_add_cost(self, acc_cost: &mut OperationCost) -> T {
45        *acc_cost += self.cost;
46        self.value
47    }
48
49    /// Take wrapped value out dropping cost data.
50    pub fn unwrap(self) -> T {
51        self.value
52    }
53
54    /// Borrow costs data.
55    pub fn cost(&self) -> &OperationCost {
56        &self.cost
57    }
58
59    /// Borrow wrapped data.
60    pub fn value(&self) -> &T {
61        &self.value
62    }
63
64    /// Applies function to wrapped value keeping cost the same as before.
65    pub fn map<B>(self, f: impl FnOnce(T) -> B) -> CostContext<B> {
66        let cost = self.cost;
67        let value = f(self.value);
68        CostContext { value, cost }
69    }
70
71    /// Applies function to wrapped value adding costs.
72    pub fn flat_map<B>(self, f: impl FnOnce(T) -> CostContext<B>) -> CostContext<B> {
73        let mut cost = self.cost;
74        let value = f(self.value).unwrap_add_cost(&mut cost);
75        CostContext { value, cost }
76    }
77
78    /// Adds previously accumulated cost
79    pub fn add_cost(mut self, cost: OperationCost) -> Self {
80        self.cost += cost;
81        self
82    }
83}
84
85/// Type alias for `Result` wrapped into `CostContext`.
86pub type CostResult<T, E> = CostContext<Result<T, E>>;
87
88/// Combinators to use with `Result` wrapped in `CostContext`.
89impl<T, E> CostResult<T, E> {
90    /// Applies function to wrapped value in case of `Ok` keeping cost the same
91    /// as before.
92    pub fn map_ok<B>(self, f: impl FnOnce(T) -> B) -> CostResult<B, E> {
93        self.map(|result| result.map(f))
94    }
95
96    /// Applies function to wrapped value in case of `Err` keeping cost the same
97    /// as before.
98    pub fn map_err<B>(self, f: impl FnOnce(E) -> B) -> CostResult<T, B> {
99        self.map(|result| result.map_err(f))
100    }
101
102    /// Applies function to wrapped result in case of `Ok` adding costs.
103    pub fn flat_map_ok<B>(self, f: impl FnOnce(T) -> CostResult<B, E>) -> CostResult<B, E> {
104        let mut cost = self.cost;
105        let result = match self.value {
106            Ok(x) => f(x).unwrap_add_cost(&mut cost),
107            Err(e) => Err(e),
108        };
109        CostContext {
110            value: result,
111            cost,
112        }
113    }
114
115    /// Gets the cost as a result
116    pub fn cost_as_result(self) -> Result<OperationCost, E> {
117        self.value.map(|_| self.cost)
118    }
119
120    /// Call the provided function on success without altering result or cost.
121    pub fn for_ok(self, f: impl FnOnce(&T)) -> CostResult<T, E> {
122        if let Ok(x) = &self.value {
123            f(x)
124        }
125
126        self
127    }
128}
129
130impl<T, E> CostResult<Result<T, E>, E> {
131    /// Flattens nested errors inside `CostContext`
132    pub fn flatten(self) -> CostResult<T, E> {
133        self.map(|value| match value {
134            Err(e) => Err(e),
135            Ok(Err(e)) => Err(e),
136            Ok(Ok(v)) => Ok(v),
137        })
138    }
139}
140
141impl<T> CostContext<CostContext<T>> {
142    /// Flattens nested `CostContext`s adding costs.
143    pub fn flatten(self) -> CostContext<T> {
144        let mut cost = OperationCost::default();
145        let inner = self.unwrap_add_cost(&mut cost);
146        inner.add_cost(cost)
147    }
148}
149
150/// Extension trait to add costs context to values.
151pub trait CostsExt {
152    /// Wraps any value into a `CostContext` object with provided costs.
153    fn wrap_with_cost(self, cost: OperationCost) -> CostContext<Self>
154    where
155        Self: Sized,
156    {
157        CostContext { value: self, cost }
158    }
159
160    /// Wraps any value into `CostContext` object with costs computed using the
161    /// value getting wrapped.
162    fn wrap_fn_cost(self, f: impl FnOnce(&Self) -> OperationCost) -> CostContext<Self>
163    where
164        Self: Sized,
165    {
166        CostContext {
167            cost: f(&self),
168            value: self,
169        }
170    }
171}
172
173impl<T> CostsExt for T {}
174
175/// Macro to achieve kind of what the `?` operator does, but with `CostContext`
176/// on top.
177///
178/// Main properties are:
179/// 1. Early termination on error;
180/// 2. Because of 1, `Result` is removed from the equation;
181/// 3. `CostContext` is removed too because it is added to external cost
182///    accumulator;
183/// 4. Early termination uses external cost accumulator so previous costs won't
184///    be lost.
185#[macro_export]
186macro_rules! cost_return_on_error {
187    ( &mut $cost:ident, $($body:tt)+ ) => {
188        {
189            use $crate::CostsExt;
190            let result_with_cost = { $($body)+ };
191            let result = result_with_cost.unwrap_add_cost(&mut $cost);
192            match result {
193                Ok(x) => x,
194                Err(e) => return Err(e).wrap_with_cost($cost),
195            }
196        }
197    };
198}
199
200/// Macro to achieve kind of what the `?` operator does, but with `CostContext`
201/// on top.
202///
203/// Main properties are:
204/// 1. Early termination on error;
205/// 2. Because of 1, `Result` is removed from the equation;
206/// 3. `CostContext` is removed too because it is added to external cost
207///    accumulator;
208/// 4. Early termination uses external cost accumulator so previous costs won't
209///    be lost.
210#[macro_export]
211macro_rules! cost_return_on_error_into {
212    ( &mut $cost:ident, $($body:tt)+ ) => {
213        {
214            use $crate::CostsExt;
215            let result_with_cost = { $($body)+ };
216            let result = result_with_cost.unwrap_add_cost(&mut $cost);
217            match result {
218                Ok(x) => x,
219                Err(e) => return Err(e.into()).wrap_with_cost($cost),
220            }
221        }
222    };
223}
224
225/// Macro to achieve kind of what the `?` operator does, but with `CostContext`
226/// on top. The difference between this macro and `cost_return_on_error` is that
227/// it is intended to be used on `Result` rather than `CostContext<Result<..>>`,
228/// so no costs will be added except previously accumulated.
229#[macro_export]
230macro_rules! cost_return_on_error_no_add {
231    ( $cost:ident, $($body:tt)+ ) => {
232        {
233            use $crate::CostsExt;
234            let result = { $($body)+ };
235            match result {
236                Ok(x) => x,
237                Err(e) => return Err(e).wrap_with_cost($cost),
238            }
239        }
240    };
241}
242
243/// Macro to achieve kind of what the `?` operator does, but with `CostContext`
244/// on top. The difference between this macro and `cost_return_on_error` is that
245/// it is intended to be used on `Result` rather than `CostContext<Result<..>>`,
246/// so no costs will be added except previously accumulated.
247#[macro_export]
248macro_rules! cost_return_on_error_into_no_add {
249    ( $cost:ident, $($body:tt)+ ) => {
250        {
251            use $crate::CostsExt;
252            let result = { $($body)+ };
253            match result {
254                Ok(x) => x,
255                Err(e) => return Err(e.into()).wrap_with_cost($cost),
256            }
257        }
258    };
259}
260
261/// Macro to achieve kind of what the `?` operator does, but with `CostContext`
262/// on top. The difference between this macro and `cost_return_on_error` is that
263/// it is intended to be used on `Result` rather than `CostContext<Result<..>>`,
264/// so no costs will be added except previously accumulated. The error case
265/// returns a default `OperationCost`.
266#[macro_export]
267macro_rules! cost_return_on_error_default {
268    ( $($body:tt)+ ) => {
269        {
270            use $crate::CostsExt;
271            let result = { $($body)+ };
272            match result {
273                Ok(x) => x,
274                Err(e) => return Err(e).wrap_with_cost(OperationCost::default()),
275            }
276        }
277    };
278}
279
280/// Macro to achieve kind of what the `?` operator does, but with `CostContext`
281/// on top. The difference between this macro and `cost_return_on_error` is that
282/// it is intended to be used on `Result` rather than `CostContext<Result<..>>`,
283/// so no costs will be added except previously accumulated. The error case
284/// returns a default `OperationCost`.
285#[macro_export]
286macro_rules! cost_return_on_error_into_default {
287    ( $($body:tt)+ ) => {
288        {
289            use $crate::CostsExt;
290            let result = { $($body)+ };
291            match result {
292                Ok(x) => x,
293                Err(e) => return Err(e.into()).wrap_with_cost(OperationCost::default()),
294            }
295        }
296    };
297}