Skip to main content

wavs_types/id/
hash.rs

1use serde::{Deserialize, Deserializer, Serialize};
2use sha2::{Digest as Sha2Digest, Sha256};
3use std::str::FromStr;
4use utoipa::ToSchema;
5
6#[cfg(feature = "ts-bindings")]
7use ts_rs::TS;
8
9macro_rules! new_hash_id_type {
10    ($type_name:ident, true) => {
11        new_hash_id_type!(@base $type_name);
12        new_hash_id_type!(@intoany $type_name);
13    };
14
15    ($type_name:ident, false) => {
16        new_hash_id_type!(@base $type_name);
17    };
18
19    // use "@base" as a way of marking the base implementation
20    (@base $type_name:ident) => {
21        /// It is a string, but hex-encoded 32-byte hash
22        #[cfg_attr(feature = "ts-bindings", derive(TS))]
23        #[cfg_attr(feature = "ts-bindings", ts(export, type = "string"))]
24        #[derive(
25            Clone,
26            PartialEq,
27            Eq,
28            PartialOrd,
29            Ord,
30            Hash,
31            ToSchema,
32            bincode::Decode,
33            bincode::Encode,
34        )]
35        pub struct $type_name([u8; 32]);
36
37        impl $type_name {
38            pub fn hash(bytes: impl AsRef<[u8]>) -> Self {
39                let mut digest = [0u8; 32];
40                let mut hasher = Sha256::new();
41                hasher.update(bytes);
42                hasher.finalize_into((&mut digest).into());
43                $type_name(digest)
44            }
45
46            pub fn inner(&self) -> [u8;32] {
47                self.0
48            }
49
50        }
51
52        impl From<[u8; 32]> for $type_name {
53            fn from(value: [u8; 32]) -> Self {
54                $type_name(value)
55            }
56        }
57
58        impl AsRef<[u8]> for $type_name {
59            fn as_ref(&self) -> &[u8] {
60                &self.0
61            }
62        }
63
64        impl std::fmt::Display for $type_name {
65            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66                write!(f, "{}",const_hex::encode(self.0.as_slice()))
67            }
68        }
69
70        impl std::fmt::Debug for $type_name {
71            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72                write!(f, "{}", self)
73            }
74        }
75
76        impl FromStr for $type_name {
77            type Err = const_hex::FromHexError;
78
79            fn from_str(s: &str) -> Result<Self, Self::Err> {
80                let mut bytes = [0u8; 32];
81                const_hex::decode_to_slice(s, &mut bytes)?;
82                Ok($type_name(bytes))
83            }
84        }
85
86        impl Serialize for $type_name {
87            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88            where
89                S: serde::Serializer,
90            {
91                serializer.serialize_str(&self.to_string())
92            }
93        }
94
95        impl<'de> Deserialize<'de> for $type_name {
96            fn deserialize<D>(deserializer: D) -> Result<$type_name, D::Error>
97            where
98                D: Deserializer<'de>,
99            {
100                struct StrVisitor;
101
102                impl<'de> serde::de::Visitor<'de> for StrVisitor {
103                    type Value = $type_name;
104
105                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
106                        formatter.write_str("expected hex-encoded string")
107                    }
108
109                    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
110                    where
111                        E: serde::de::Error,
112                    {
113                        $type_name::from_str(value).map_err(serde::de::Error::custom)
114                    }
115                }
116
117                deserializer.deserialize_str(StrVisitor)
118            }
119        }
120    };
121
122    // use "@intoany" as a way of marking the Into<AnyDigest> implementation
123    // we intentionally do NOT implement From<AnyDigest> for $type_name
124    // because we want to avoid accidental conversions
125    // in other words - it's fine to erase the type, it's not fine to assume something specific from the erased type
126    (@intoany $type_name:ident) => {
127        impl From<$type_name> for AnyDigest {
128            fn from(digest: $type_name) -> Self {
129                AnyDigest(digest.inner())
130            }
131        }
132    };
133}
134
135// This is just used as a general purpose digest type
136new_hash_id_type!(AnyDigest, false);
137// Digest of the whole Service definition
138new_hash_id_type!(ServiceDigest, true);
139// Digest of the component source (e.g. wasm bytecode)
140new_hash_id_type!(ComponentDigest, true);
141
142// ServiceId is a unique identifier for a service
143// it's a hash of the ServiceManager definition (chain and address)
144new_hash_id_type!(ServiceId, true);