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