strict_types/value/
path.rs

1// Strict encoding schema library, implementing validation and parsing of strict encoded data
2// against a schema.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
8//
9// Copyright (C) 2022-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2022-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
15// in compliance with the License. You may obtain a copy of the License at
16//
17//        http://www.apache.org/licenses/LICENSE-2.0
18//
19// Unless required by applicable law or agreed to in writing, software distributed under the License
20// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21// or implied. See the License for the specific language governing permissions and limitations under
22// the License.
23
24//! Path accessors into strict values.
25
26use std::fmt::{self, Display, Formatter};
27
28use amplify::confinement::{SmallVec, TinyBlob, TinyString};
29use encoding::{FieldName, STRICT_TYPES_LIB};
30
31use crate::value::{EnumTag, StrictNum};
32use crate::StrictVal;
33
34// TODO: Convert into `StrictKey` and use in `StrictVal::Map` for key value repr.
35#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
36#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
37#[strict_type(lib = STRICT_TYPES_LIB, tags = order, dumb = Self::Number(strict_dumb!()))]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
39#[non_exhaustive]
40pub enum KeyStep {
41    #[from]
42    Number(u128),
43
44    #[from]
45    TinyBlob(TinyBlob),
46
47    #[from]
48    TinyString(TinyString),
49}
50
51impl KeyStep {
52    pub fn has_match(&self, val: &StrictVal) -> bool {
53        match (self, val) {
54            (KeyStep::Number(no), StrictVal::Enum(EnumTag::Ord(tag))) if *tag as u128 == *no => {
55                true
56            }
57            (KeyStep::Number(num1), StrictVal::Number(StrictNum::Uint(num2)))
58                if *num1 == *num2 as u128 =>
59            {
60                true
61            }
62            (KeyStep::TinyBlob(blob1), StrictVal::Bytes(blob2))
63                if blob1.as_slice() == blob2.as_slice() =>
64            {
65                true
66            }
67            (KeyStep::TinyString(s1), StrictVal::String(s2)) if s1.as_str() == s2.as_str() => true,
68            _ => false,
69        }
70    }
71}
72
73impl Display for KeyStep {
74    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
75        match self {
76            KeyStep::Number(num) => Display::fmt(num, f),
77            KeyStep::TinyBlob(data) => {
78                f.write_str("0h")?;
79                for byte in data {
80                    write!(f, "{byte:02X}")?;
81                }
82                Ok(())
83            }
84            KeyStep::TinyString(s) => {
85                let s = s.replace('"', "\\\"");
86                f.write_str(&s)
87            }
88        }
89    }
90}
91
92#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
93#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
94#[strict_type(lib = STRICT_TYPES_LIB, tags = order, dumb = Self::UnnamedField(strict_dumb!()))]
95#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
96pub enum Step {
97    #[display(".{0}")]
98    #[from]
99    NamedField(FieldName),
100
101    #[display(".{0}")]
102    #[from]
103    UnnamedField(u8),
104
105    #[display("[{0}]")]
106    Index(u32),
107
108    #[display("{{{0}}}")]
109    #[from]
110    Key(KeyStep),
111}
112
113#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From)]
114#[wrapper(Deref)]
115#[wrapper_mut(DerefMut)]
116#[derive(StrictType, StrictEncode, StrictDecode)]
117#[strict_type(lib = STRICT_TYPES_LIB)]
118#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
119pub struct Path(SmallVec<Step>);
120
121impl Path {
122    pub fn new() -> Path { Path::default() }
123
124    pub fn with(step: Step) -> Path { Path(small_vec!(step)) }
125
126    pub fn iter(&self) -> std::slice::Iter<Step> { self.0.iter() }
127}
128
129impl<'path> IntoIterator for &'path Path {
130    type Item = &'path Step;
131    type IntoIter = std::slice::Iter<'path, Step>;
132
133    fn into_iter(self) -> Self::IntoIter { self.0.iter() }
134}
135
136impl Display for Path {
137    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
138        for step in self {
139            Display::fmt(step, f)?;
140        }
141        Ok(())
142    }
143}
144
145// TODO: Implement FromStr for value Path expressions
146
147#[derive(Clone, PartialEq, Eq, Debug, Display, Error)]
148#[display(doc_comments)]
149pub enum PathError {
150    /// collection has less items than requested in the path ({0} vs {1}).
151    CollectionIndexOutOfBounds(u32, usize),
152    /// tuple has less fields than requested in the path ({0} vs {1}).
153    FieldNoOutOfBounds(u8, usize),
154    /// struct doesn't have field named `{0}`.
155    UnknownFieldName(FieldName),
156    /// map doesn't have key named `{0}`.
157    UnknownKey(KeyStep),
158    /// path doesn't match value at step {0}.
159    TypeMismatch(Step, StrictVal),
160}
161
162impl StrictVal {
163    pub fn at_path<'p>(
164        &self,
165        path: impl IntoIterator<Item = &'p Step>,
166    ) -> Result<&StrictVal, PathError> {
167        let mut iter = path.into_iter();
168        match (self, iter.next()) {
169            (val, None) => Ok(val),
170            (StrictVal::Tuple(fields), Some(Step::UnnamedField(no)))
171                if *no as usize >= fields.len() =>
172            {
173                Err(PathError::FieldNoOutOfBounds(*no, fields.len()))
174            }
175            (StrictVal::Tuple(fields), Some(Step::UnnamedField(no))) => Ok(&fields[*no as usize]),
176            (StrictVal::Struct(fields), Some(Step::NamedField(name))) => {
177                fields.get(name).ok_or(PathError::UnknownFieldName(name.clone()))
178            }
179            (StrictVal::List(items) | StrictVal::Set(items), Some(Step::Index(idx)))
180                if *idx as usize >= items.len() =>
181            {
182                Err(PathError::CollectionIndexOutOfBounds(*idx, items.len()))
183            }
184            (StrictVal::List(items) | StrictVal::Set(items), Some(Step::Index(idx))) => {
185                Ok(&items[*idx as usize])
186            }
187            (StrictVal::Map(items), Some(Step::Key(idx))) => items
188                .iter()
189                .find(|(key, _)| idx.has_match(key))
190                .map(|(_, val)| val)
191                .ok_or(PathError::UnknownKey(idx.clone())),
192
193            (_, Some(step)) => Err(PathError::TypeMismatch(step.clone(), self.clone())),
194        }
195    }
196}