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. The difference between this macro and `cost_return_on_error` is that
202/// it is intended to be used on `Result` rather than `CostContext<Result<..>>`,
203/// so no costs will be added except previously accumulated.
204#[macro_export]
205macro_rules! cost_return_on_error_no_add {
206 ( $cost:ident, $($body:tt)+ ) => {
207 {
208 use $crate::CostsExt;
209 let result = { $($body)+ };
210 match result {
211 Ok(x) => x,
212 Err(e) => return Err(e).wrap_with_cost($cost),
213 }
214 }
215 };
216}
217
218/// Macro to achieve kind of what the `?` operator does, but with `CostContext`
219/// on top. The difference between this macro and `cost_return_on_error` is that
220/// it is intended to be used on `Result` rather than `CostContext<Result<..>>`,
221/// so no costs will be added except previously accumulated. The error case
222/// returns a default `OperationCost`.
223#[macro_export]
224macro_rules! cost_return_on_error_default {
225 ( $($body:tt)+ ) => {
226 {
227 use $crate::CostsExt;
228 let result = { $($body)+ };
229 match result {
230 Ok(x) => x,
231 Err(e) => return Err(e).wrap_with_cost(OperationCost::default()),
232 }
233 }
234 };
235}