scale_value/
at.rs

1// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-value crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16// Indexing into Value's to access things. We can't use the `Index` trait,
17// since it returns references, and we can't necessarily give back a reference
18// (`serde_json::Value` uses a statically initialised value to give back a ref
19// to in these cases, but we have a generic `Ctx` and can't do that ourselves).
20
21use super::{Composite, Value, ValueDef, Variant};
22use crate::prelude::*;
23
24/// This trait allows indexing into [`Value`]s (and options of [`Value`]s)
25/// using the [`At::at()`] function. It's a little like Rust's [`core::ops::Index`]
26/// trait, but adapted so that we can return and work with optionals.
27///
28/// Indexing into a [`Value`] never panics; instead it will return `None` if a
29/// value at the given location cannot be found.
30///
31/// # Example
32///
33/// ```
34/// use scale_value::{ Value, At };
35///
36/// let val = Value::named_composite([
37///     ("hello", Value::unnamed_composite([
38///         Value::u128(1),
39///         Value::bool(true),
40///         Value::named_composite([
41///             ("wibble", Value::bool(false)),
42///             ("foo", Value::named_composite([
43///                 ("bar", Value::u128(123))
44///             ]))
45///         ])
46///     ]))
47/// ]);
48///
49/// // Use `at` to access nested values:
50/// assert_eq!(val.at("hello").at(0), Some(&Value::u128(1)));
51/// assert_eq!(val.at("hello").at(1), Some(&Value::bool(true)));
52/// assert_eq!(val.at("hello").at(2).at("wibble"), Some(&Value::bool(false)));
53/// assert_eq!(val.at("hello").at(2).at("foo").at("bar"), Some(&Value::u128(123)));
54///
55/// // If the value doesn't exist, None will be returned:
56/// assert_eq!(val.at("wibble").at(3), None);
57/// assert_eq!(val.at("wibble").at("wobble").at("nope"), None);
58/// ```
59pub trait At<Ctx>: private::Sealed {
60    /// Index into a value, returning a reference to the value if one
61    /// exists, or [`None`] if not.
62    fn at<L: AsLocation>(&self, loc: L) -> Option<&Value<Ctx>>;
63}
64
65// Prevent users from implementing the At trait.
66mod private {
67    use super::*;
68    pub trait Sealed {}
69    impl<Ctx> Sealed for Value<Ctx> {}
70    impl<Ctx> Sealed for Composite<Ctx> {}
71    impl<Ctx> Sealed for Variant<Ctx> {}
72    impl<T: Sealed> Sealed for Option<&T> {}
73}
74
75impl<Ctx> At<Ctx> for Composite<Ctx> {
76    fn at<L: AsLocation>(&self, loc: L) -> Option<&Value<Ctx>> {
77        match loc.as_location().inner {
78            LocationInner::Str(s) => match self {
79                Composite::Named(vals) => {
80                    vals.iter().find_map(|(n, v)| if s == n { Some(v) } else { None })
81                }
82                _ => None,
83            },
84            LocationInner::Usize(n) => match self {
85                Composite::Named(vals) => {
86                    let val = vals.get(n);
87                    val.map(|v| &v.1)
88                }
89                Composite::Unnamed(vals) => vals.get(n),
90            },
91        }
92    }
93}
94
95impl<Ctx> At<Ctx> for Variant<Ctx> {
96    fn at<L: AsLocation>(&self, loc: L) -> Option<&Value<Ctx>> {
97        self.values.at(loc)
98    }
99}
100
101impl<Ctx> At<Ctx> for Value<Ctx> {
102    fn at<L: AsLocation>(&self, loc: L) -> Option<&Value<Ctx>> {
103        match &self.value {
104            ValueDef::Composite(c) => c.at(loc),
105            ValueDef::Variant(v) => v.at(loc),
106            _ => None,
107        }
108    }
109}
110
111impl<Ctx, T: At<Ctx>> At<Ctx> for Option<&T> {
112    fn at<L: AsLocation>(&self, loc: L) -> Option<&Value<Ctx>> {
113        self.as_ref().and_then(|v| v.at(loc))
114    }
115}
116
117/// Types which can be used as a lookup location with [`At::at`]
118/// implement this trait.
119///
120/// Users cannot implement this as the [`Location`] type internals
121/// are opaque and subject to change.
122pub trait AsLocation {
123    fn as_location(&self) -> Location<'_>;
124}
125
126impl AsLocation for usize {
127    fn as_location(&self) -> Location<'_> {
128        Location { inner: LocationInner::Usize(*self) }
129    }
130}
131
132impl AsLocation for &str {
133    fn as_location(&self) -> Location<'_> {
134        Location { inner: LocationInner::Str(self) }
135    }
136}
137
138impl AsLocation for String {
139    fn as_location(&self) -> Location<'_> {
140        Location { inner: LocationInner::Str(self) }
141    }
142}
143
144impl<T: AsLocation> AsLocation for &T {
145    fn as_location(&self) -> Location<'_> {
146        (*self).as_location()
147    }
148}
149
150/// A struct representing a location to access in a [`Value`].
151#[derive(Copy, Clone)]
152pub struct Location<'a> {
153    inner: LocationInner<'a>,
154}
155
156#[derive(Copy, Clone)]
157enum LocationInner<'a> {
158    Usize(usize),
159    Str(&'a str),
160}
161
162#[cfg(test)]
163mod test {
164    use crate::value;
165
166    use super::*;
167
168    // This is basically the doc example with a little extra.
169    #[test]
170    fn nested_accessing() {
171        let val = value!({hello: (1u32, true, { wibble: false, foo: {bar: 123u32}})});
172        assert_eq!(val.at("hello").at(0), Some(&Value::u128(1)));
173        assert_eq!(val.at("hello").at(1), Some(&Value::bool(true)));
174        assert_eq!(val.at("hello").at(2).at("wibble"), Some(&Value::bool(false)));
175        assert_eq!(val.at("hello").at(2).at("foo").at("bar"), Some(&Value::u128(123)));
176
177        assert_eq!(val.at("wibble").at(3), None);
178        assert_eq!(val.at("wibble").at("wobble").at("nope"), None);
179
180        // Strings can be used:
181        assert_eq!(val.at("hello".to_string()).at(0), Some(&Value::u128(1)));
182        // References to valid locations are fine too:
183        {
184            assert_eq!(val.at("hello").at(0), Some(&Value::u128(1)));
185        }
186    }
187
188    #[test]
189    fn accessing_variants() {
190        let val = value!(TheVariant { foo: 12345u32, bar: 'c' });
191
192        assert_eq!(val.at("foo").unwrap().as_u128().unwrap(), 12345);
193        assert_eq!(val.at("bar").unwrap().as_char().unwrap(), 'c');
194
195        let val = value!(TheVariant(12345u32, 'c'));
196
197        assert_eq!(val.at(0).unwrap().as_u128().unwrap(), 12345);
198        assert_eq!(val.at(1).unwrap().as_char().unwrap(), 'c');
199
200        // We can use `at()` on the variant directly, too:
201
202        let val =
203            Variant::named_fields("TheVariant", [("foo", value!(12345u32)), ("bar", value!('c'))]);
204
205        assert_eq!(val.at("foo").unwrap().as_u128().unwrap(), 12345);
206        assert_eq!(val.at("bar").unwrap().as_char().unwrap(), 'c');
207
208        let val = Variant::unnamed_fields("TheVariant", [value!(12345u32), value!('c')]);
209
210        assert_eq!(val.at(0).unwrap().as_u128().unwrap(), 12345);
211        assert_eq!(val.at(1).unwrap().as_char().unwrap(), 'c');
212    }
213
214    #[test]
215    fn accessing_composites() {
216        // We already test accessing composite Values. This also checks that `at` works on
217        // the Composite type, too..
218
219        let val = Composite::named([("foo", value!(12345u32)), ("bar", value!('c'))]);
220
221        assert_eq!(val.at("foo").unwrap().as_u128().unwrap(), 12345);
222        assert_eq!(val.at("bar").unwrap().as_char().unwrap(), 'c');
223
224        let val = Composite::unnamed([value!(12345u32), value!('c')]);
225
226        assert_eq!(val.at(0).unwrap().as_u128().unwrap(), 12345);
227        assert_eq!(val.at(1).unwrap().as_char().unwrap(), 'c');
228    }
229}