strict_encoding/
types.rs

1// Strict encoding library for deterministic binary serialization.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7//
8// Copyright (C) 2019-2022 LNP/BP Standards Association.
9// Copyright (C) 2022-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2019-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
24use std::any;
25use std::collections::BTreeSet;
26use std::fmt::{Debug, Display};
27use std::marker::PhantomData;
28
29use crate::{LibName, TypeName, VariantName};
30
31pub fn type_name<T>() -> String {
32    fn get_ident(path: &str) -> &str {
33        path.rsplit_once("::").map(|(_, n)| n.trim()).unwrap_or(path)
34    }
35
36    let name = any::type_name::<T>().replace('&', "");
37    let mut ident = vec![];
38    for mut arg in name.split([',', '<', '>', '(', ')']) {
39        arg = arg.trim();
40        if arg.is_empty() {
41            continue;
42        }
43        ident.push(get_ident(arg));
44    }
45    ident.join("")
46}
47
48#[derive(Clone, Eq, PartialEq, Debug, Display, Error)]
49#[display("unexpected variant {1} for enum or union {0:?}")]
50pub struct VariantError<V: Debug + Display>(pub Option<String>, pub V);
51
52impl<V: Debug + Display> VariantError<V> {
53    pub fn with<T>(val: V) -> Self { VariantError(Some(type_name::<T>()), val) }
54    pub fn typed(name: impl Into<String>, val: V) -> Self { VariantError(Some(name.into()), val) }
55    pub fn untyped(val: V) -> Self { VariantError(None, val) }
56}
57
58pub trait StrictDumb: Sized {
59    fn strict_dumb() -> Self;
60}
61
62impl<T> StrictDumb for T
63where T: StrictType + Default
64{
65    fn strict_dumb() -> T { T::default() }
66}
67
68pub trait StrictType: Sized {
69    const STRICT_LIB_NAME: &'static str;
70    fn strict_name() -> Option<TypeName> { Some(tn!(type_name::<Self>())) }
71}
72
73impl<T: StrictType> StrictType for &T {
74    const STRICT_LIB_NAME: &'static str = T::STRICT_LIB_NAME;
75}
76
77impl<T> StrictType for PhantomData<T> {
78    const STRICT_LIB_NAME: &'static str = "";
79}
80
81pub trait StrictProduct: StrictType + StrictDumb {}
82
83pub trait StrictTuple: StrictProduct {
84    const FIELD_COUNT: u8;
85    fn strict_check_fields() {
86        let name = Self::strict_name().unwrap_or_else(|| tn!("__unnamed"));
87        assert_ne!(
88            Self::FIELD_COUNT,
89            0,
90            "tuple type {} does not contain a single field defined",
91            name
92        );
93    }
94
95    fn strict_type_info() -> TypeInfo<Self> {
96        Self::strict_check_fields();
97        TypeInfo {
98            lib: libname!(Self::STRICT_LIB_NAME),
99            name: Self::strict_name().map(|name| tn!(name)),
100            cls: TypeClass::Tuple(Self::FIELD_COUNT),
101            dumb: Self::strict_dumb(),
102        }
103    }
104}
105
106pub trait StrictStruct: StrictProduct {
107    const ALL_FIELDS: &'static [&'static str];
108
109    fn strict_check_fields() {
110        let name = Self::strict_name().unwrap_or_else(|| tn!("__unnamed"));
111        assert!(
112            !Self::ALL_FIELDS.is_empty(),
113            "struct type {} does not contain a single field defined",
114            name
115        );
116        let names: BTreeSet<_> = Self::ALL_FIELDS.iter().copied().collect();
117        assert_eq!(
118            names.len(),
119            Self::ALL_FIELDS.len(),
120            "struct type {} contains repeated field names",
121            name
122        );
123    }
124
125    fn strict_type_info() -> TypeInfo<Self> {
126        Self::strict_check_fields();
127        TypeInfo {
128            lib: libname!(Self::STRICT_LIB_NAME),
129            name: Self::strict_name().map(|name| tn!(name)),
130            cls: TypeClass::Struct(Self::ALL_FIELDS),
131            dumb: Self::strict_dumb(),
132        }
133    }
134}
135
136pub trait StrictSum: StrictType {
137    const ALL_VARIANTS: &'static [(u8, &'static str)];
138
139    fn strict_check_variants() {
140        let name = Self::strict_name().unwrap_or_else(|| tn!("__unnamed"));
141        assert!(
142            !Self::ALL_VARIANTS.is_empty(),
143            "type {} does not contain a single variant defined",
144            name
145        );
146        let (ords, names): (BTreeSet<_>, BTreeSet<_>) = Self::ALL_VARIANTS.iter().copied().unzip();
147        assert_eq!(
148            ords.len(),
149            Self::ALL_VARIANTS.len(),
150            "type {} contains repeated variant ids",
151            name
152        );
153        assert_eq!(
154            names.len(),
155            Self::ALL_VARIANTS.len(),
156            "type {} contains repeated variant names",
157            name
158        );
159    }
160
161    fn variant_name_by_tag(tag: u8) -> Option<VariantName> {
162        Self::ALL_VARIANTS
163            .iter()
164            .find(|(n, _)| *n == tag)
165            .map(|(_, variant_name)| vname!(*variant_name))
166    }
167
168    fn variant_ord(&self) -> u8 {
169        let variant = self.variant_name();
170        for (tag, name) in Self::ALL_VARIANTS {
171            if *name == variant {
172                return *tag;
173            }
174        }
175        unreachable!(
176            "not all variants are enumerated for {} enum in StrictUnion::all_variants \
177             implementation",
178            type_name::<Self>()
179        )
180    }
181    fn variant_name(&self) -> &'static str;
182}
183
184pub trait StrictUnion: StrictSum + StrictDumb {
185    fn strict_type_info() -> TypeInfo<Self> {
186        Self::strict_check_variants();
187        TypeInfo {
188            lib: libname!(Self::STRICT_LIB_NAME),
189            name: Self::strict_name().map(|name| tn!(name)),
190            cls: TypeClass::Union(Self::ALL_VARIANTS),
191            dumb: Self::strict_dumb(),
192        }
193    }
194}
195
196pub trait StrictEnum
197where
198    Self: StrictSum + Copy + TryFrom<u8, Error = VariantError<u8>>,
199    u8: From<Self>,
200{
201    fn from_variant_name(name: &VariantName) -> Result<Self, VariantError<&VariantName>> {
202        for (tag, n) in Self::ALL_VARIANTS {
203            if *n == name.as_str() {
204                return Self::try_from(*tag).map_err(|_| VariantError::with::<Self>(name));
205            }
206        }
207        Err(VariantError::with::<Self>(name))
208    }
209
210    fn strict_type_info() -> TypeInfo<Self> {
211        Self::strict_check_variants();
212        TypeInfo {
213            lib: libname!(Self::STRICT_LIB_NAME),
214            name: Self::strict_name().map(|name| tn!(name)),
215            cls: TypeClass::Enum(Self::ALL_VARIANTS),
216            dumb: Self::try_from(Self::ALL_VARIANTS[0].0)
217                .expect("first variant contains invalid value"),
218        }
219    }
220}
221
222pub enum TypeClass {
223    Embedded,
224    Enum(&'static [(u8, &'static str)]),
225    Union(&'static [(u8, &'static str)]),
226    Tuple(u8),
227    Struct(&'static [&'static str]),
228}
229
230pub struct TypeInfo<T: StrictType> {
231    pub lib: LibName,
232    pub name: Option<TypeName>,
233    pub cls: TypeClass,
234    pub dumb: T,
235}
236
237#[cfg(test)]
238mod test {
239    use amplify::confinement::TinyVec;
240
241    use super::*;
242
243    #[test]
244    fn name_derivation() { assert_eq!(Option::<TinyVec<u8>>::strict_name(), None) }
245}