tansu_model/
wv.rs

1// Copyright ⓒ 2024-2025 Peter Morgan <peter.james.morgan@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{Error, Kind, MessageKind, Result, VersionRange};
16use serde_json::Value;
17use std::str::FromStr;
18
19// A Wrapped JSON type
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct Wv<'a>(&'a Value);
22
23impl<'a, 'b: 'a> From<&'b Value> for Wv<'a> {
24    fn from(value: &'b Value) -> Self {
25        Self(value)
26    }
27}
28
29pub trait As<'a, T>
30where
31    T: 'a,
32{
33    fn as_a(&'a self, name: &str) -> Result<T>;
34}
35
36impl<'a> As<'a, &'a str> for Wv<'a> {
37    fn as_a(&'a self, name: &str) -> Result<&'a str> {
38        self.as_option(name)
39            .and_then(|maybe| maybe.ok_or(Error::Message(name.into())))
40    }
41}
42
43impl<'a> As<'a, String> for Wv<'a> {
44    fn as_a(&'a self, name: &str) -> Result<String> {
45        self.as_option(name)
46            .and_then(|maybe| maybe.ok_or(Error::Message(name.into())))
47    }
48}
49
50impl<'a> As<'a, &'a [Value]> for Wv<'a> {
51    fn as_a(&'a self, name: &str) -> Result<&'a [Value]> {
52        self.0[name]
53            .as_array()
54            .map(|v| &v[..])
55            .ok_or(Error::Message(String::from(name)))
56    }
57}
58
59impl<'a> As<'a, u64> for Wv<'a> {
60    fn as_a(&'a self, name: &str) -> Result<u64> {
61        self.0[name]
62            .as_u64()
63            .ok_or(Error::Message(String::from(name)))
64    }
65}
66
67impl<'a> As<'a, u32> for Wv<'a> {
68    fn as_a(&'a self, name: &str) -> Result<u32> {
69        As::<'a, u64>::as_a(self, name).and_then(|u| u32::try_from(u).map_err(Into::into))
70    }
71}
72
73impl<'a> As<'a, u16> for Wv<'a> {
74    fn as_a(&'a self, name: &str) -> Result<u16> {
75        As::<'a, u64>::as_a(self, name).and_then(|u| u16::try_from(u).map_err(Into::into))
76    }
77}
78
79impl<'a> As<'a, i64> for Wv<'a> {
80    fn as_a(&'a self, name: &str) -> Result<i64> {
81        self.0[name]
82            .as_i64()
83            .ok_or(Error::Message(String::from(name)))
84    }
85}
86
87impl<'a> As<'a, i16> for Wv<'a> {
88    fn as_a(&'a self, name: &str) -> Result<i16> {
89        As::<'a, i64>::as_a(self, name).and_then(|u| i16::try_from(u).map_err(Into::into))
90    }
91}
92
93impl<'a> As<'a, MessageKind> for Wv<'a> {
94    fn as_a(&'a self, name: &str) -> Result<MessageKind> {
95        self.as_a(name).and_then(MessageKind::from_str)
96    }
97}
98
99impl<'a> As<'a, VersionRange> for Wv<'a> {
100    fn as_a(&'a self, name: &str) -> Result<VersionRange> {
101        self.as_a(name).and_then(VersionRange::from_str)
102    }
103}
104
105impl<'a> As<'a, Kind> for Wv<'a> {
106    fn as_a(&'a self, name: &str) -> Result<Kind> {
107        self.as_a(name).and_then(Kind::from_str)
108    }
109}
110
111pub trait AsOption<'a, T>
112where
113    T: 'a,
114{
115    fn as_option(&'a self, name: &str) -> Result<Option<T>>;
116}
117
118impl<'a> AsOption<'a, bool> for Wv<'a> {
119    fn as_option(&'a self, name: &str) -> Result<Option<bool>> {
120        Ok(self.0[name].as_bool())
121    }
122}
123
124impl<'a> AsOption<'a, u64> for Wv<'a> {
125    fn as_option(&'a self, name: &str) -> Result<Option<u64>> {
126        Ok(self.0[name].as_u64())
127    }
128}
129
130impl<'a> AsOption<'a, u32> for Wv<'a> {
131    fn as_option(&'a self, name: &str) -> Result<Option<u32>> {
132        self.0[name]
133            .as_u64()
134            .map_or(Ok(None), |v| v.try_into().map_err(Into::into).map(Some))
135    }
136}
137
138impl<'a> AsOption<'a, u16> for Wv<'a> {
139    fn as_option(&'a self, name: &str) -> Result<Option<u16>> {
140        self.0[name]
141            .as_u64()
142            .map_or(Ok(None), |v| v.try_into().map_err(Into::into).map(Some))
143    }
144}
145
146impl<'a> AsOption<'a, u8> for Wv<'a> {
147    fn as_option(&'a self, name: &str) -> Result<Option<u8>> {
148        self.0[name]
149            .as_u64()
150            .map_or(Ok(None), |v| v.try_into().map_err(Into::into).map(Some))
151    }
152}
153
154impl<'a> AsOption<'a, i64> for Wv<'a> {
155    fn as_option(&'a self, name: &str) -> Result<Option<i64>> {
156        Ok(self.0[name].as_i64())
157    }
158}
159
160impl<'a> AsOption<'a, i32> for Wv<'a> {
161    fn as_option(&'a self, name: &str) -> Result<Option<i32>> {
162        self.0[name]
163            .as_i64()
164            .map_or(Ok(None), |v| v.try_into().map_err(Into::into).map(Some))
165    }
166}
167
168impl<'a> AsOption<'a, i16> for Wv<'a> {
169    fn as_option(&'a self, name: &str) -> Result<Option<i16>> {
170        self.0[name]
171            .as_i64()
172            .map_or(Ok(None), |v| v.try_into().map_err(Into::into).map(Some))
173    }
174}
175
176impl<'a> AsOption<'a, i8> for Wv<'a> {
177    fn as_option(&'a self, name: &str) -> Result<Option<i8>> {
178        self.0[name]
179            .as_i64()
180            .map_or(Ok(None), |v| v.try_into().map_err(Into::into).map(Some))
181    }
182}
183
184impl<'a> AsOption<'a, &'a str> for Wv<'a> {
185    fn as_option(&'a self, name: &str) -> Result<Option<&'a str>> {
186        Ok(self.0[name].as_str())
187    }
188}
189
190impl<'a> AsOption<'a, String> for Wv<'a> {
191    fn as_option(&'a self, name: &str) -> Result<Option<String>> {
192        Ok(self.0[name].as_str().map(String::from))
193    }
194}
195
196impl<'a> AsOption<'a, &'a [Value]> for Wv<'a> {
197    fn as_option(&'a self, name: &str) -> Result<Option<&'a [Value]>> {
198        Ok(self.0[name].as_array().map(|v| &v[..]))
199    }
200}
201
202impl<'a> AsOption<'a, VersionRange> for Wv<'a> {
203    fn as_option(&'a self, name: &str) -> Result<Option<VersionRange>> {
204        self.0[name]
205            .as_str()
206            .map_or(Ok(None), |s| VersionRange::from_str(s).map(Some))
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use serde_json::json;
213
214    use super::*;
215
216    #[test]
217    fn as_a_str() -> Result<()> {
218        let v = serde_json::from_str::<Value>(
219            r#"
220            {
221                "hello": "world"
222            }
223            "#,
224        )?;
225
226        let wv = Wv::from(&v);
227        let s: &str = wv.as_a("hello")?;
228        assert_eq!("world", s);
229
230        Ok(())
231    }
232
233    #[test]
234    fn as_a_string() -> Result<()> {
235        let v = serde_json::from_str::<Value>(
236            r#"
237            {
238                "hello": "world"
239            }
240            "#,
241        )?;
242
243        let wv = Wv::from(&v);
244        let s: String = wv.as_a("hello")?;
245        assert_eq!("world", s);
246
247        Ok(())
248    }
249
250    #[test]
251    fn as_a_kind() -> Result<()> {
252        let v = serde_json::from_str::<Value>(
253            r#"
254            {
255                "type": "request"
256            }
257            "#,
258        )?;
259
260        let wv = Wv::from(&v);
261        let s: MessageKind = wv.as_a("type")?;
262        assert_eq!(MessageKind::Request, s);
263
264        Ok(())
265    }
266
267    #[test]
268    fn as_option() -> Result<()> {
269        fn as_option<'a, 'b, T>(
270            instance: &'a Wv<'b>,
271            operation: impl Fn(&'a Wv<'b>, &'static str) -> Result<Option<T>>,
272            tests: &[(&'static str, Option<T>)],
273        ) -> Result<()>
274        where
275            T: 'a + PartialEq + std::fmt::Debug,
276        {
277            for (name, expected) in tests {
278                assert_eq!(*expected, operation(instance, name)?, "name: {name}");
279            }
280            Ok(())
281        }
282
283        let v = serde_json::from_str::<Value>(
284            r#"
285            {
286                "u64": 18446744073709551615,
287                "u32": 4294967295,
288                "u16": 65535,
289                "u8": 255,
290
291                "i64_max": 9223372036854775807,
292                "i64_min": -9223372036854775808,
293                "i32_max": 2147483647,
294                "i32_min": -2147483648,
295                "i16_max": 32767,
296                "i16_min": -32768,
297                "i8_max": 127,
298                "i8_min": -128
299            }
300            "#,
301        )?;
302
303        let wv = Wv::from(&v);
304
305        as_option(
306            &wv,
307            <Wv<'_> as AsOption<u64>>::as_option,
308            &[
309                ("u64", Some(u64::MAX)),
310                ("u32", Some(u32::MAX.into())),
311                ("u16", Some(u16::MAX.into())),
312                ("u8", Some(u8::MAX.into())),
313                ("i64_max", Some(i64::MAX.try_into()?)),
314                ("i64_min", None),
315                ("i32_max", Some(i32::MAX.try_into()?)),
316                ("i32_min", None),
317                ("i16_max", Some(i16::MAX.try_into()?)),
318                ("i16_min", None),
319                ("i8_max", Some(i8::MAX.try_into()?)),
320                ("i8_min", None),
321            ],
322        )?;
323
324        assert!(matches!(
325            <Wv<'_> as AsOption<u32>>::as_option(&wv, "u64"),
326            Err(Error::TryFromInt(_)),
327        ));
328        assert!(matches!(
329            <Wv<'_> as AsOption<u32>>::as_option(&wv, "i64_max"),
330            Err(Error::TryFromInt(_)),
331        ));
332
333        as_option(
334            &wv,
335            <Wv<'_> as AsOption<u32>>::as_option,
336            &[
337                // ("u64", None),
338                ("u32", Some(u32::MAX)),
339                ("u16", Some(u16::MAX.into())),
340                ("u8", Some(u8::MAX.into())),
341                // ("i64_max", Some(i64::MAX.try_into()?)),
342                ("i64_min", None),
343                ("i32_max", Some(i32::MAX.try_into()?)),
344                ("i32_min", None),
345                ("i16_max", Some(i16::MAX.try_into()?)),
346                ("i16_min", None),
347                ("i8_max", Some(i8::MAX.try_into()?)),
348                ("i8_min", None),
349            ],
350        )?;
351
352        Ok(())
353    }
354
355    #[test]
356    fn as_a_u64() -> Result<()> {
357        let v = serde_json::from_str::<Value>(
358            r#"
359            {
360                "value": 32123
361            }
362            "#,
363        )?;
364
365        let wv = Wv::from(&v);
366        let s: u64 = wv.as_a("value")?;
367        assert_eq!(32123, s);
368
369        Ok(())
370    }
371
372    #[test]
373    fn as_a_version_range() -> Result<()> {
374        let v = serde_json::from_str::<Value>(
375            r#"
376            {
377                "flexibleVersions": "2+"
378            }
379            "#,
380        )?;
381
382        let wv = Wv::from(&v);
383        let flexible: VersionRange = wv.as_a("flexibleVersions")?;
384
385        assert_eq!(
386            VersionRange {
387                start: 2,
388                end: i16::MAX
389            },
390            flexible
391        );
392
393        Ok(())
394    }
395
396    #[test]
397    fn as_array_value() -> Result<()> {
398        let v = serde_json::from_str::<Value>(
399            r#"
400            {
401                "x": [1,2,3]
402            }
403            "#,
404        )?;
405
406        let wv = Wv::from(&v);
407        let array: &[Value] = wv.as_a("x")?;
408        assert_eq!([json!(1), json!(2), json!(3)], array);
409
410        Ok(())
411    }
412}