drogue_client/
translator.rs

1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3
4/// A translator for the data sections of a resource.
5///
6/// The translator trait, in combination with the [`Dialect`] trait allows easy access to
7/// spec and status section in a strongly typed fashion:
8///
9/// ```rust
10/// use drogue_client::{dialect, Section, Translator};
11/// use drogue_client::registry::v1::Application;
12/// use serde::Deserialize;
13///
14/// #[derive(Deserialize)]
15/// pub struct FooSpec {
16/// }
17///
18/// dialect!(FooSpec[Section::Spec => "foo"]);
19///
20/// fn work_with(app: &Application) {
21///   match app.section::<FooSpec>() {
22///     Some(Ok(foo)) => {
23///         // foo section existed and could be parsed.
24///     },
25///     Some(Err(err)) => {
26///         // foo section existed, but could not be parsed.
27///     },
28///     None => {
29///         // foo section did not exist.
30///     },
31///   }
32/// }
33pub trait Translator {
34    fn spec(&self) -> &Map<String, Value>;
35    fn status(&self) -> &Map<String, Value>;
36
37    fn spec_mut(&mut self) -> &mut Map<String, Value>;
38    fn status_mut(&mut self) -> &mut Map<String, Value>;
39
40    fn section<D>(&self) -> Option<Result<D, serde_json::Error>>
41    where
42        D: for<'de> Deserialize<'de> + Dialect,
43    {
44        match D::section() {
45            Section::Spec => self.spec_for(D::key()),
46            Section::Status => self.status_for(D::key()),
47        }
48    }
49
50    fn set_section<D>(&mut self, d: D) -> Result<(), serde_json::Error>
51    where
52        D: Serialize + Dialect,
53    {
54        let v = serde_json::to_value(d)?;
55
56        match D::section() {
57            Section::Spec => self.spec_mut().insert(D::key().to_string(), v),
58            Section::Status => self.status_mut().insert(D::key().to_string(), v),
59        };
60
61        Ok(())
62    }
63
64    fn update_section<D, F>(&mut self, f: F) -> Result<(), serde_json::Error>
65    where
66        D: Serialize + for<'de> Deserialize<'de> + Dialect + Default,
67        F: FnOnce(D) -> D,
68    {
69        let s = match self.section::<D>() {
70            Some(Ok(s)) => f(s),
71            None => {
72                let s = D::default();
73                f(s)
74            }
75            Some(Err(err)) => return Err(err),
76        };
77
78        self.set_section(s)
79    }
80
81    fn clear_section<D>(&mut self)
82    where
83        D: Serialize + Dialect,
84    {
85        match D::section() {
86            Section::Spec => self.spec_mut().remove(D::key()),
87            Section::Status => self.status_mut().remove(D::key()),
88        };
89    }
90
91    fn spec_for<T, S>(&self, key: S) -> Option<Result<T, serde_json::Error>>
92    where
93        T: for<'de> Deserialize<'de>,
94        S: AsRef<str>,
95    {
96        let result = self
97            .spec()
98            .get(key.as_ref())
99            .map(|value| serde_json::from_value(value.clone()));
100
101        result
102    }
103
104    fn status_for<T, S>(&self, key: S) -> Option<Result<T, serde_json::Error>>
105    where
106        T: for<'de> Deserialize<'de>,
107        S: AsRef<str>,
108    {
109        let result = self
110            .status()
111            .get(key.as_ref())
112            .map(|value| serde_json::from_value(value.clone()));
113
114        result
115    }
116
117    fn attribute<A>(&self) -> A::Output
118    where
119        A: Attribute,
120    {
121        A::extract(self.section::<A::Dialect>())
122    }
123}
124
125/// An enum of main data sections.
126#[derive(Eq, PartialEq, Clone, Copy, Debug)]
127pub enum Section {
128    /// The `spec` section.
129    Spec,
130    // The `status` section.
131    Status,
132}
133
134/// A "dialect", of strongly typed main section.
135pub trait Dialect {
136    /// The name of the field inside the section.
137    fn key() -> &'static str;
138    /// The section.
139    fn section() -> Section;
140}
141
142/// Implements the [`Dialect`] trait for a structure.
143///
144/// ```rust
145/// use drogue_client::{dialect, Section};
146///
147/// pub struct FooSpec {
148/// }
149///
150/// dialect!(FooSpec[Section::Spec => "foo"]);
151/// ```
152#[macro_export]
153macro_rules! dialect {
154    ($dialect:ty [ $section:expr => $key:literal ]) => {
155        impl $crate::Dialect for $dialect {
156            fn key() -> &'static str {
157                $key
158            }
159
160            fn section() -> $crate::Section {
161                $section
162            }
163        }
164    };
165}
166
167/// A specific attribute of a dialected section.
168pub trait Attribute {
169    type Dialect: for<'de> Deserialize<'de> + Dialect;
170    type Output;
171
172    fn extract(dialect: Option<Result<Self::Dialect, serde_json::Error>>) -> Self::Output;
173}
174
175/// Implements the [`Attribute`] trait for an attribute/field of a dialect.
176#[macro_export]
177macro_rules! attribute {
178    ($v:vis $dialect:ty [$name:ident : $output:ty] => | $value:ident | $($code:tt)* ) => {
179        $v struct $name;
180
181        impl $crate::Attribute for $name {
182            type Dialect = $dialect;
183            type Output = $output;
184
185            fn extract(dialect: Option<Result<Self::Dialect, serde_json::Error>>) -> Self::Output {
186                let $value = dialect;
187                $($code)*
188            }
189        }
190    };
191}
192
193/// Implements the [`Translator`] trait for a structure.
194///
195/// This macro requires that the struct has a `spec` and `status` field of type `Map<String, Value>`.
196#[macro_export]
197macro_rules! translator {
198    ($name:ty) => {
199        impl Translator for $name {
200            fn spec(&self) -> &Map<String, Value> {
201                &self.spec
202            }
203
204            fn status(&self) -> &Map<String, Value> {
205                &self.status
206            }
207
208            fn spec_mut(&mut self) -> &mut Map<String, Value> {
209                &mut self.spec
210            }
211
212            fn status_mut(&mut self) -> &mut Map<String, Value> {
213                &mut self.status
214            }
215        }
216    };
217}
218
219#[cfg(test)]
220mod test {
221
222    use super::*;
223    use serde_json::Error;
224
225    #[derive(Deserialize, Debug, Clone, Default)]
226    pub struct Foo {
227        pub spec: Map<String, Value>,
228        pub status: Map<String, Value>,
229    }
230
231    translator!(Foo);
232
233    #[derive(Deserialize, Debug, Clone, Default)]
234    pub struct Bar {
235        pub name: String,
236    }
237
238    impl Dialect for Bar {
239        fn key() -> &'static str {
240            "bar"
241        }
242
243        fn section() -> Section {
244            Section::Spec
245        }
246    }
247
248    pub struct Name {}
249
250    impl Attribute for Name {
251        type Dialect = Bar;
252        type Output = String;
253
254        fn extract(dialect: Option<Result<Self::Dialect, Error>>) -> Self::Output {
255            dialect
256                .and_then(|d| d.map(|d| d.name).ok())
257                .unwrap_or_default()
258        }
259    }
260
261    #[test]
262    fn test1() {
263        let i = Foo::default();
264        let _: Option<Result<Bar, _>> = i.section::<Bar>();
265    }
266
267    #[test]
268    fn test_attr() {
269        let i = Foo::default();
270        let name: String = i.attribute::<Name>();
271        assert_eq!(name, "");
272    }
273}