1use dapi_grpc::mock::Mockable;
4
5use crate::{
6 mock::{Key, MockResult},
7 transport::TransportRequest,
8 DapiClient,
9};
10use std::{any::type_name, path::PathBuf};
11
12#[derive(Clone)]
14pub struct DumpData<T: TransportRequest> {
15 pub serialized_request: Vec<u8>,
17 pub serialized_response: Vec<u8>,
19
20 phantom: std::marker::PhantomData<T>,
21}
22impl<T: TransportRequest> DumpData<T> {
23 pub fn deserialize(&self) -> (T, MockResult<T>) {
25 let req = T::mock_deserialize(&self.serialized_request).unwrap_or_else(|| {
26 panic!(
27 "unable to deserialize mock data of type {}",
28 type_name::<T>()
29 )
30 });
31 let Some(resp) = <MockResult<T>>::mock_deserialize(&self.serialized_response) else {
32 panic!(
33 "unable to deserialize mock data of type {}",
34 type_name::<T::Response>()
35 )
36 };
37
38 (req, resp)
39 }
40}
41
42impl<T: TransportRequest> dapi_grpc::mock::Mockable for DumpData<T>
43where
44 T: Mockable,
45 T::Response: Mockable,
46{
47 fn mock_serialize(&self) -> Option<Vec<u8>> {
49 if self.serialized_request.contains(&0) {
51 panic!("null byte in serialized request");
52 }
53 if self.serialized_response.contains(&0) {
54 panic!("null byte in serialized response");
55 }
56
57 let data = [
58 &self.serialized_request,
59 "\n\0\n".as_bytes(),
60 &self.serialized_response,
61 ]
62 .concat();
63
64 Some(data)
65 }
66
67 fn mock_deserialize(buf: &[u8]) -> Option<Self> {
68 let buf = buf.split(|&b| b == 0).collect::<Vec<_>>();
72 if buf.len() != 2 {
73 panic!("invalid mock data format, expected exactly two items separated by null byte");
74 }
75
76 let request = buf.first().expect("missing request in mock data");
77 let response = buf.last().expect("missing response in mock data");
78
79 Some(Self {
80 serialized_request: request.to_vec(),
81 serialized_response: response.to_vec(),
82 phantom: std::marker::PhantomData,
83 })
84 }
85}
86
87impl<T: TransportRequest> DumpData<T> {
88 pub fn new(request: &T, response: &MockResult<T>) -> Self {
90 let request = request
91 .mock_serialize()
92 .expect("unable to serialize request");
93 let response = response
94 .mock_serialize()
95 .expect("unable to serialize response");
96
97 Self {
98 serialized_request: request,
99 serialized_response: response,
100 phantom: std::marker::PhantomData,
101 }
102 }
103
104 fn request_type() -> String {
106 let req_type = std::any::type_name::<T>();
107 req_type.rsplit(':').next().unwrap_or(req_type).to_string()
108 }
109 pub fn filename(&self) -> Result<String, std::io::Error> {
117 let key = Key::try_new(&self.serialized_request)?;
118 let request_type = Self::request_type().replace('_', "-");
120
121 let file = format!(
122 "{}_{}_{}.json",
123 DapiClient::DUMP_FILE_PREFIX,
124 request_type,
125 key
126 );
127
128 Ok(file)
129 }
130
131 pub fn load<P: AsRef<std::path::Path>>(file: P) -> Result<Self, std::io::Error>
133 where
134 T: Mockable,
135 T::Response: Mockable,
136 {
137 let data = std::fs::read(file)?;
138
139 Self::mock_deserialize(&data).ok_or(std::io::Error::new(
140 std::io::ErrorKind::InvalidData,
141 format!(
142 "unable to deserialize mock data of type {}",
143 type_name::<T>()
144 ),
145 ))
146 }
147
148 pub fn save(&self, file: &std::path::Path) -> Result<(), std::io::Error>
150 where
151 T: Mockable,
152 T::Response: Mockable,
153 {
154 let encoded = self.mock_serialize().ok_or(std::io::Error::new(
155 std::io::ErrorKind::InvalidData,
156 format!("unable to serialize mock data of type {}", type_name::<T>()),
157 ))?;
158
159 std::fs::write(file, encoded)
160 }
161}
162
163impl DapiClient {
164 pub const DUMP_FILE_PREFIX: &'static str = "msg";
166
167 pub fn dump_dir(mut self, dump_dir: Option<PathBuf>) -> Self {
178 self.dump_dir = dump_dir;
179
180 self
181 }
182
183 pub(crate) fn dump_request_response<R: TransportRequest>(
187 request: &R,
188 response: &MockResult<R>,
189 dump_dir: Option<PathBuf>,
190 ) where
191 <R as TransportRequest>::Response: Mockable,
192 {
193 let path = match dump_dir {
194 Some(p) => p,
195 None => return,
196 };
197
198 let data = DumpData::new(request, response);
199
200 let filename = match data.filename() {
202 Ok(f) => f,
203 Err(e) => return tracing::warn!("unable to create dump file name: {}", e),
204 };
205
206 let file = path.join(filename);
207
208 if let Err(e) = data.save(&file) {
209 tracing::warn!("unable to write dump file {:?}: {}", path, e);
210 }
211 }
212}