subxt_core/utils/
wrapper_opaque.rs1use super::PhantomDataSendSync;
6use codec::{Compact, Decode, DecodeAll, Encode};
7use derive_where::derive_where;
8use scale_decode::{IntoVisitor, TypeResolver, Visitor, ext::scale_type_resolver::visitor};
9use scale_encode::EncodeAsType;
10
11use alloc::format;
12use alloc::vec::Vec;
13
14#[derive(Encode, Decode)]
25#[derive_where(Debug, Clone, PartialEq, Eq, Default, Hash)]
26pub struct WrapperKeepOpaque<T> {
27 data: Vec<u8>,
28 _phantom: PhantomDataSendSync<T>,
29}
30
31impl<T> WrapperKeepOpaque<T> {
32 pub fn try_decode(&self) -> Option<T>
36 where
37 T: Decode,
38 {
39 T::decode_all(&mut &self.data[..]).ok()
40 }
41
42 pub fn encoded_len(&self) -> usize {
44 self.data.len()
45 }
46
47 pub fn encoded(&self) -> &[u8] {
49 &self.data
50 }
51
52 pub fn from_encoded(data: Vec<u8>) -> Self {
54 Self {
55 data,
56 _phantom: PhantomDataSendSync::new(),
57 }
58 }
59
60 pub fn from_value(value: T) -> Self
62 where
63 T: Encode,
64 {
65 Self {
66 data: value.encode(),
67 _phantom: PhantomDataSendSync::new(),
68 }
69 }
70}
71
72impl<T> EncodeAsType for WrapperKeepOpaque<T> {
73 fn encode_as_type_to<R: TypeResolver>(
74 &self,
75 type_id: R::TypeId,
76 types: &R,
77 out: &mut Vec<u8>,
78 ) -> Result<(), scale_encode::Error> {
79 use scale_encode::error::{Error, ErrorKind, Kind};
80
81 let ctx = (type_id.clone(), out);
82 let visitor = visitor::new(ctx, |(type_id, _out), _| {
83 Err(Error::new(ErrorKind::WrongShape {
85 actual: Kind::Struct,
86 expected_id: format!("{type_id:?}"),
87 }))
88 })
89 .visit_composite(|(_type_id, out), _path, _fields| {
90 self.data.encode_to(out);
91 Ok(())
92 });
93
94 types
95 .resolve_type(type_id.clone(), visitor)
96 .map_err(|_| Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))?
97 }
98}
99
100pub struct WrapperKeepOpaqueVisitor<T, R>(core::marker::PhantomData<(T, R)>);
101impl<T, R: TypeResolver> Visitor for WrapperKeepOpaqueVisitor<T, R> {
102 type Value<'scale, 'info> = WrapperKeepOpaque<T>;
103 type Error = scale_decode::Error;
104 type TypeResolver = R;
105
106 fn visit_composite<'scale, 'info>(
107 self,
108 value: &mut scale_decode::visitor::types::Composite<'scale, 'info, R>,
109 _type_id: R::TypeId,
110 ) -> Result<Self::Value<'scale, 'info>, Self::Error> {
111 use scale_decode::error::{Error, ErrorKind};
112 use scale_decode::visitor::DecodeError;
113
114 if value.name() != Some("WrapperKeepOpaque") {
115 return Err(Error::new(ErrorKind::VisitorDecodeError(
116 DecodeError::TypeResolvingError(format!(
117 "Expected a type named 'WrapperKeepOpaque', got: {:?}",
118 value.name()
119 )),
120 )));
121 }
122
123 if value.remaining() != 2 {
124 return Err(Error::new(ErrorKind::WrongLength {
125 actual_len: value.remaining(),
126 expected_len: 2,
127 }));
128 }
129
130 let Compact(len) = value
132 .decode_item(Compact::<u32>::into_visitor())
133 .expect("length checked")?;
134 let field = value.next().expect("length checked")?;
135
136 if field.bytes().len() != len as usize {
138 return Err(Error::custom_str(
139 "WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len",
140 ));
141 }
142
143 Ok(WrapperKeepOpaque {
144 data: field.bytes().to_vec(),
145 _phantom: PhantomDataSendSync::new(),
146 })
147 }
148}
149
150impl<T> IntoVisitor for WrapperKeepOpaque<T> {
151 type AnyVisitor<R: TypeResolver> = WrapperKeepOpaqueVisitor<T, R>;
152 fn into_visitor<R: TypeResolver>() -> WrapperKeepOpaqueVisitor<T, R> {
153 WrapperKeepOpaqueVisitor(core::marker::PhantomData)
154 }
155}
156
157#[cfg(test)]
158mod test {
159 use scale_decode::DecodeAsType;
160
161 use alloc::vec;
162
163 use super::*;
164
165 impl<T: scale_info::TypeInfo + 'static> scale_info::TypeInfo for WrapperKeepOpaque<T> {
169 type Identity = Self;
170 fn type_info() -> scale_info::Type {
171 use scale_info::{Path, Type, TypeParameter, build::Fields, meta_type};
172
173 Type::builder()
174 .path(Path::new("WrapperKeepOpaque", module_path!()))
175 .type_params(vec![TypeParameter::new("T", Some(meta_type::<T>()))])
176 .composite(
177 Fields::unnamed()
178 .field(|f| f.compact::<u32>())
179 .field(|f| f.ty::<T>().type_name("T")),
180 )
181 }
182 }
183
184 fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
186 let m = scale_info::MetaType::new::<T>();
187 let mut types = scale_info::Registry::new();
188 let id = types.register_type(&m);
189 let portable_registry: scale_info::PortableRegistry = types.into();
190 (id.id, portable_registry)
191 }
192
193 fn roundtrips_like_scale_codec<T>(t: T)
194 where
195 T: EncodeAsType
196 + DecodeAsType
197 + Encode
198 + Decode
199 + PartialEq
200 + core::fmt::Debug
201 + scale_info::TypeInfo
202 + 'static,
203 {
204 let (type_id, types) = make_type::<T>();
205
206 let scale_codec_encoded = t.encode();
207 let encode_as_type_encoded = t.encode_as_type(type_id, &types).unwrap();
208
209 assert_eq!(
210 scale_codec_encoded, encode_as_type_encoded,
211 "encoded bytes should match"
212 );
213
214 let decode_as_type_bytes = &mut &*scale_codec_encoded;
215 let decoded_as_type = T::decode_as_type(decode_as_type_bytes, type_id, &types)
216 .expect("decode-as-type decodes");
217
218 let decode_scale_codec_bytes = &mut &*scale_codec_encoded;
219 let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes");
220
221 assert!(
222 decode_as_type_bytes.is_empty(),
223 "no bytes should remain in decode-as-type impl"
224 );
225 assert!(
226 decode_scale_codec_bytes.is_empty(),
227 "no bytes should remain in codec-decode impl"
228 );
229
230 assert_eq!(
231 decoded_as_type, decoded_scale_codec,
232 "decoded values should match"
233 );
234 }
235
236 #[test]
237 fn wrapper_keep_opaque_roundtrips_ok() {
238 roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64));
239 roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true));
240 roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4]));
241 }
242}