photon_protocol/codec/protobuf/
convert.rs1use std::time::{Duration, SystemTime, UNIX_EPOCH};
2
3use bytes::Bytes;
4
5use photon_core::types::ack::{AckResult, AckStatus};
6use photon_core::types::batch::AssembledBatch;
7use photon_core::types::id::RunId;
8use photon_core::types::metric::Metric;
9use photon_core::types::query::{
10 DataPoint, MetricQuery, MetricSeries, QueryRequest, QueryResponse, RangePoint, SeriesData,
11};
12use photon_core::types::sequence::SequenceNumber;
13
14use crate::codec::protobuf::types::{
15 MetricBatchAck, MetricBatchRequest, ProtoAckStatus, ProtoAggregatedData, ProtoDataPoint,
16 ProtoMetricQuery, ProtoMetricSeries, ProtoQueryRequest, ProtoQueryResponse, ProtoRangePoint,
17 ProtoRawData, ProtoSeriesData, WatermarkRequest, WatermarkResponse,
18};
19
20#[derive(Debug, thiserror::Error)]
21pub enum ProtoConversionError {
22 #[error("invalid run_id: {0}")]
23 InvalidRunId(String),
24
25 #[error("invalid metric key: {0}")]
26 InvalidMetricKey(String),
27
28 #[error("unrecognised ack status: {0}")]
29 UnknownAckStatus(i32),
30
31 #[error("missing required field: {0}")]
32 MissingField(&'static str),
33}
34
35impl From<&AssembledBatch> for MetricBatchRequest {
36 fn from(batch: &AssembledBatch) -> Self {
37 Self {
38 run_id: batch.run_id.to_string(),
39 sequence_number: u64::from(batch.sequence_number),
40 compressed_payload: batch.compressed_payload.to_vec(),
41 crc32: batch.crc32,
42 compressor_name: String::new(),
43 created_at_epoch_ms: system_time_to_epoch_ms(batch.created_at),
44 point_count: batch.point_count as u32,
45 uncompressed_size: batch.uncompressed_size as u32,
46 }
47 }
48}
49
50impl TryFrom<MetricBatchRequest> for AssembledBatch {
51 type Error = ProtoConversionError;
52
53 fn try_from(proto: MetricBatchRequest) -> Result<Self, Self::Error> {
54 let run_id: uuid::Uuid = proto
55 .run_id
56 .parse()
57 .map_err(|_| ProtoConversionError::InvalidRunId(proto.run_id.clone()))?;
58
59 Ok(Self {
60 run_id: RunId::from(run_id),
61 sequence_number: SequenceNumber::from(proto.sequence_number),
62 compressed_payload: Bytes::from(proto.compressed_payload),
63 crc32: proto.crc32,
64 created_at: epoch_ms_to_system_time(proto.created_at_epoch_ms),
65 point_count: proto.point_count as usize,
66 uncompressed_size: proto.uncompressed_size as usize,
67 })
68 }
69}
70
71impl From<&AckResult> for MetricBatchAck {
72 fn from(ack: &AckResult) -> Self {
73 let status = match ack.status {
74 AckStatus::Ok => ProtoAckStatus::Ok,
75 AckStatus::Duplicate => ProtoAckStatus::Duplicate,
76 AckStatus::Rejected => ProtoAckStatus::Rejected,
77 };
78
79 Self {
80 sequence_number: u64::from(ack.sequence_number),
81 status: status.into(),
82 message: String::new(),
83 }
84 }
85}
86
87impl TryFrom<MetricBatchAck> for AckResult {
88 type Error = ProtoConversionError;
89
90 fn try_from(proto: MetricBatchAck) -> Result<Self, Self::Error> {
91 let status = match ProtoAckStatus::try_from(proto.status) {
92 Ok(ProtoAckStatus::Ok) => AckStatus::Ok,
93 Ok(ProtoAckStatus::Duplicate) => AckStatus::Duplicate,
94 Ok(ProtoAckStatus::Rejected) => AckStatus::Rejected,
95 Ok(ProtoAckStatus::Unspecified) => {
96 return Err(ProtoConversionError::UnknownAckStatus(proto.status));
97 }
98 Err(_) => {
99 return Err(ProtoConversionError::UnknownAckStatus(proto.status));
100 }
101 };
102
103 Ok(Self {
104 sequence_number: SequenceNumber::from(proto.sequence_number),
105 status,
106 })
107 }
108}
109
110impl From<AckStatus> for ProtoAckStatus {
111 fn from(status: AckStatus) -> Self {
112 match status {
113 AckStatus::Ok => ProtoAckStatus::Ok,
114 AckStatus::Duplicate => ProtoAckStatus::Duplicate,
115 AckStatus::Rejected => ProtoAckStatus::Rejected,
116 }
117 }
118}
119
120impl From<&RunId> for WatermarkRequest {
121 fn from(run_id: &RunId) -> Self {
122 Self {
123 run_id: run_id.to_string(),
124 }
125 }
126}
127
128impl TryFrom<&WatermarkRequest> for RunId {
129 type Error = ProtoConversionError;
130
131 fn try_from(proto: &WatermarkRequest) -> Result<Self, Self::Error> {
132 let uuid: uuid::Uuid = proto
133 .run_id
134 .parse()
135 .map_err(|_| ProtoConversionError::InvalidRunId(proto.run_id.clone()))?;
136 Ok(RunId::from(uuid))
137 }
138}
139
140impl From<SequenceNumber> for WatermarkResponse {
141 fn from(seq: SequenceNumber) -> Self {
142 Self {
143 sequence_number: u64::from(seq),
144 }
145 }
146}
147
148impl From<WatermarkResponse> for SequenceNumber {
149 fn from(proto: WatermarkResponse) -> Self {
150 SequenceNumber::from(proto.sequence_number)
151 }
152}
153
154impl From<&QueryRequest> for ProtoQueryRequest {
155 fn from(request: &QueryRequest) -> Self {
156 Self {
157 queries: request.queries.iter().map(ProtoMetricQuery::from).collect(),
158 }
159 }
160}
161
162impl TryFrom<ProtoQueryRequest> for QueryRequest {
163 type Error = ProtoConversionError;
164
165 fn try_from(proto: ProtoQueryRequest) -> Result<Self, Self::Error> {
166 let queries = proto
167 .queries
168 .into_iter()
169 .map(MetricQuery::try_from)
170 .collect::<Result<Vec<_>, _>>()?;
171
172 Ok(Self { queries })
173 }
174}
175
176impl From<&MetricQuery> for ProtoMetricQuery {
177 fn from(query: &MetricQuery) -> Self {
178 Self {
179 run_id: query.run_id.to_string(),
180 key: query.key.as_str().to_owned(),
181 step_start: query.step_range.start,
182 step_end: query.step_range.end,
183 target_points: query.target_points as u32,
184 }
185 }
186}
187
188impl TryFrom<ProtoMetricQuery> for MetricQuery {
189 type Error = ProtoConversionError;
190
191 fn try_from(proto: ProtoMetricQuery) -> Result<Self, Self::Error> {
192 let run_id: uuid::Uuid = proto
193 .run_id
194 .parse()
195 .map_err(|_| ProtoConversionError::InvalidRunId(proto.run_id.clone()))?;
196 let key = Metric::new(&proto.key)
197 .map_err(|_| ProtoConversionError::InvalidMetricKey(proto.key.clone()))?;
198
199 Ok(Self {
200 run_id: RunId::from(run_id),
201 key,
202 step_range: proto.step_start..proto.step_end,
203 target_points: proto.target_points as usize,
204 })
205 }
206}
207
208impl From<&QueryResponse> for ProtoQueryResponse {
209 fn from(response: &QueryResponse) -> Self {
210 Self {
211 series: response
212 .series
213 .iter()
214 .map(ProtoMetricSeries::from)
215 .collect(),
216 }
217 }
218}
219
220impl TryFrom<ProtoQueryResponse> for QueryResponse {
221 type Error = ProtoConversionError;
222
223 fn try_from(proto: ProtoQueryResponse) -> Result<Self, Self::Error> {
224 let series = proto
225 .series
226 .into_iter()
227 .map(MetricSeries::try_from)
228 .collect::<Result<Vec<_>, _>>()?;
229
230 Ok(Self { series })
231 }
232}
233
234impl From<&MetricSeries> for ProtoMetricSeries {
235 fn from(series: &MetricSeries) -> Self {
236 let data = match &series.data {
237 SeriesData::Raw { points } => ProtoSeriesData::Raw(ProtoRawData {
238 points: points.iter().map(ProtoDataPoint::from).collect(),
239 }),
240 SeriesData::Aggregated { points, envelope } => {
241 ProtoSeriesData::Aggregated(ProtoAggregatedData {
242 points: points.iter().map(ProtoDataPoint::from).collect(),
243 envelope: envelope.iter().map(ProtoRangePoint::from).collect(),
244 })
245 }
246 };
247
248 Self {
249 run_id: series.run_id.to_string(),
250 key: series.key.as_str().to_owned(),
251 data: Some(data),
252 }
253 }
254}
255
256impl TryFrom<ProtoMetricSeries> for MetricSeries {
257 type Error = ProtoConversionError;
258
259 fn try_from(proto: ProtoMetricSeries) -> Result<Self, Self::Error> {
260 let run_id: uuid::Uuid = proto
261 .run_id
262 .parse()
263 .map_err(|_| ProtoConversionError::InvalidRunId(proto.run_id.clone()))?;
264 let key = Metric::new(&proto.key)
265 .map_err(|_| ProtoConversionError::InvalidMetricKey(proto.key.clone()))?;
266
267 let data = match proto
268 .data
269 .ok_or(ProtoConversionError::MissingField("data"))?
270 {
271 ProtoSeriesData::Raw(raw) => SeriesData::Raw {
272 points: raw.points.into_iter().map(DataPoint::from).collect(),
273 },
274 ProtoSeriesData::Aggregated(agg) => SeriesData::Aggregated {
275 points: agg.points.into_iter().map(DataPoint::from).collect(),
276 envelope: agg.envelope.into_iter().map(RangePoint::from).collect(),
277 },
278 };
279
280 Ok(Self {
281 run_id: RunId::from(run_id),
282 key,
283 data,
284 })
285 }
286}
287
288impl From<&DataPoint> for ProtoDataPoint {
289 fn from(p: &DataPoint) -> Self {
290 Self {
291 step: p.step,
292 value: p.value,
293 }
294 }
295}
296
297impl From<ProtoDataPoint> for DataPoint {
298 fn from(proto: ProtoDataPoint) -> Self {
299 Self {
300 step: proto.step,
301 value: proto.value,
302 }
303 }
304}
305
306impl From<&RangePoint> for ProtoRangePoint {
307 fn from(p: &RangePoint) -> Self {
308 Self {
309 step_start: p.step_start,
310 step_end: p.step_end,
311 min: p.min,
312 max: p.max,
313 }
314 }
315}
316
317impl From<ProtoRangePoint> for RangePoint {
318 fn from(proto: ProtoRangePoint) -> Self {
319 Self {
320 step_start: proto.step_start,
321 step_end: proto.step_end,
322 min: proto.min,
323 max: proto.max,
324 }
325 }
326}
327
328fn system_time_to_epoch_ms(time: SystemTime) -> u64 {
329 time.duration_since(UNIX_EPOCH)
330 .unwrap_or(Duration::ZERO)
331 .as_millis() as u64
332}
333
334fn epoch_ms_to_system_time(ms: u64) -> SystemTime {
335 UNIX_EPOCH + Duration::from_millis(ms)
336}