Skip to main content

scratch_test_model/aux/
json_ctx.rs

1/// Combines an error with a json value that serves as context.
2///
3/// Typically, this json context is the input the function, that produced the error, received.
4#[derive(Debug, PartialEq, Clone, Hash, derive_more::Display)]
5#[display("{inner_error}: {json_ctx}")]
6pub struct JsonCtx<'a, E> {
7    pub(crate) inner_error: E,
8    pub(crate) json_ctx: &'a serde_json::Value,
9}
10
11impl<'a, E> JsonCtx<'a, E> {
12    /// The wrapped error
13    pub fn inner_error(&self) -> &E {
14        &self.inner_error
15    }
16    /// The json value that is related to the error.
17    /// Normally, this will be the input to the function that produced the error.
18    pub fn json_ctx(&self) -> &serde_json::Value {
19        self.json_ctx
20    }
21    /// This consumes the [`JsonCtx`] and returns its parts as tuple
22    /// so that they can be used further.
23    pub fn into_inner(self) -> (E, &'a serde_json::Value) {
24        (self.inner_error, self.json_ctx)
25    }
26    /// Wraps the error in a closure. It doesn't change the context.
27    pub fn map_err<F>(self, m: impl FnOnce(E) -> F) -> JsonCtx<'a, F> {
28        JsonCtx {
29            inner_error: m(self.inner_error),
30            json_ctx: self.json_ctx,
31        }
32    }
33}
34
35#[allow(unused)]
36/// Lifts a function that returns a `Result` given an `&serde_json::Value`
37/// into a function that includes the json value into the error details
38pub fn json_ctx_lift<T, E>(
39    func: impl for<'a> FnOnce(&'a serde_json::Value) -> Result<T, E>,
40) -> impl for<'a> FnOnce(&'a serde_json::Value) -> Result<T, JsonCtx<'a, E>> {
41    |value| {
42        let res = func(value);
43        res.map_err(|err| JsonCtx {
44            inner_error: err,
45            json_ctx: value,
46        })
47    }
48}
49
50/// This is an awesome trick that allows augmenting different computations with
51/// a contextual json value.
52///
53/// This trait is implemented for [`Result`] where it wraps the [`Result::Err`] value with context.
54///
55/// The more interesting implementation is the one for functions (and closures)
56/// that take a reference to [`serde_json::Value`] and produce a result.
57/// It allows _calling_ the method [`WithJsonContextExt::with_ctx`] on an unevaluated
58/// function/method.
59/// This enabled code like `SomeStruct::from_json.with_ctx(value)` where `from_json` is
60/// a function transforming a json value reference into a `Result`.
61pub trait WithJsonContextExt<'a> {
62    type T;
63    type E;
64    fn with_ctx(self, value: &'a serde_json::Value) -> Result<Self::T, JsonCtx<'a, Self::E>>;
65    // fn without_ctx(self, value: &'a serde_json::Value) -> Result<Self::T, Self::E>;
66}
67impl<'a, F, T, E> WithJsonContextExt<'a> for F
68where
69    F: FnOnce(&'a serde_json::Value) -> Result<T, E>,
70{
71    type T = T;
72    type E = E;
73    fn with_ctx(self, value: &'a serde_json::Value) -> Result<Self::T, JsonCtx<'a, Self::E>> {
74        let res = self(value);
75        res.map_err(|err| JsonCtx {
76            inner_error: err,
77            json_ctx: value,
78        })
79    }
80    /*fn without_ctx(self, value: &'a serde_json::Value) -> Result<Self::T, Self::E> {
81        self(value)
82    }*/
83}
84
85impl<'a, T, E> WithJsonContextExt<'a> for Result<T, E> {
86    type T = T;
87    type E = E;
88    fn with_ctx(self, json: &'a serde_json::Value) -> Result<T, JsonCtx<'a, E>> {
89        self.map_err(|err| JsonCtx {
90            inner_error: err,
91            json_ctx: json,
92        })
93    }
94    /*fn without_ctx(self, _value: &'a serde_json::Value) -> Result<Self::T, Self::E> {
95        self
96    }*/
97}