Skip to main content

grid_sdk/protocol/track_and_trace/
payload.rs

1// Copyright 2019 Cargill Incorporated
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Protocol structs for Track and Trace transaction payloads
16
17use protobuf::Message;
18use protobuf::RepeatedField;
19
20use std::default::Default;
21
22use super::errors::BuilderError;
23use crate::protocol::{schema::state::PropertyValue, track_and_trace::state::Role};
24use crate::protos;
25use crate::protos::{
26    track_and_trace_payload, track_and_trace_payload::TrackAndTracePayload_Action,
27};
28use crate::protos::{
29    FromBytes, FromNative, FromProto, IntoBytes, IntoNative, IntoProto, ProtoConversionError,
30};
31
32/// Native representation of a "create record" action
33#[derive(Debug, Clone, PartialEq)]
34pub struct CreateRecordAction {
35    record_id: String,
36    schema: String,
37    properties: Vec<PropertyValue>,
38}
39
40impl CreateRecordAction {
41    pub fn record_id(&self) -> &str {
42        &self.record_id
43    }
44    pub fn schema(&self) -> &str {
45        &self.schema
46    }
47    pub fn properties(&self) -> &[PropertyValue] {
48        &self.properties
49    }
50}
51
52/// Builder used to create a "create record" action
53#[derive(Default, Debug)]
54pub struct CreateRecordActionBuilder {
55    record_id: Option<String>,
56    schema: Option<String>,
57    properties: Option<Vec<PropertyValue>>,
58}
59
60impl CreateRecordActionBuilder {
61    pub fn new() -> Self {
62        CreateRecordActionBuilder::default()
63    }
64    pub fn with_record_id(mut self, value: String) -> Self {
65        self.record_id = Some(value);
66        self
67    }
68    pub fn with_schema(mut self, value: String) -> Self {
69        self.schema = Some(value);
70        self
71    }
72    pub fn with_properties(mut self, value: Vec<PropertyValue>) -> Self {
73        self.properties = Some(value);
74        self
75    }
76    pub fn build(self) -> Result<CreateRecordAction, BuilderError> {
77        let record_id = self
78            .record_id
79            .ok_or_else(|| BuilderError::MissingField("record_id".into()))?;
80        let schema = self
81            .schema
82            .ok_or_else(|| BuilderError::MissingField("schema".into()))?;
83        let properties = self
84            .properties
85            .ok_or_else(|| BuilderError::MissingField("properties".into()))?;
86        Ok(CreateRecordAction {
87            record_id,
88            schema,
89            properties,
90        })
91    }
92}
93
94impl FromProto<track_and_trace_payload::CreateRecordAction> for CreateRecordAction {
95    fn from_proto(
96        proto: track_and_trace_payload::CreateRecordAction,
97    ) -> Result<Self, ProtoConversionError> {
98        Ok(CreateRecordAction {
99            record_id: proto.get_record_id().to_string(),
100            schema: proto.get_schema().to_string(),
101            properties: proto
102                .get_properties()
103                .iter()
104                .cloned()
105                .map(PropertyValue::from_proto)
106                .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
107        })
108    }
109}
110
111impl FromNative<CreateRecordAction> for track_and_trace_payload::CreateRecordAction {
112    fn from_native(create_record_action: CreateRecordAction) -> Result<Self, ProtoConversionError> {
113        let mut proto = track_and_trace_payload::CreateRecordAction::new();
114        proto.set_record_id(create_record_action.record_id().to_string());
115        proto.set_schema(create_record_action.schema().to_string());
116        proto.set_properties(RepeatedField::from_vec(
117            create_record_action
118                .properties()
119                .iter()
120                .cloned()
121                .map(PropertyValue::into_proto)
122                .collect::<Result<Vec<protos::schema_state::PropertyValue>, ProtoConversionError>>(
123                )?,
124        ));
125
126        Ok(proto)
127    }
128}
129
130impl FromBytes<CreateRecordAction> for CreateRecordAction {
131    fn from_bytes(bytes: &[u8]) -> Result<CreateRecordAction, ProtoConversionError> {
132        let proto: track_and_trace_payload::CreateRecordAction = Message::parse_from_bytes(bytes)
133            .map_err(|_| {
134            ProtoConversionError::SerializationError(
135                "Unable to get CreateRecordAction from bytes".into(),
136            )
137        })?;
138        proto.into_native()
139    }
140}
141impl IntoBytes for CreateRecordAction {
142    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
143        let proto = self.into_proto()?;
144        let bytes = proto.write_to_bytes().map_err(|_| {
145            ProtoConversionError::SerializationError(
146                "Unable to get CreateRecordAction from bytes".into(),
147            )
148        })?;
149        Ok(bytes)
150    }
151}
152impl IntoProto<track_and_trace_payload::CreateRecordAction> for CreateRecordAction {}
153impl IntoNative<CreateRecordAction> for track_and_trace_payload::CreateRecordAction {}
154
155/// Native representation of a "finalize record" action
156#[derive(Debug, Clone, PartialEq)]
157pub struct FinalizeRecordAction {
158    record_id: String,
159}
160
161impl FinalizeRecordAction {
162    pub fn record_id(&self) -> &str {
163        &self.record_id
164    }
165}
166
167/// Builder used to create a "finalize record" action
168#[derive(Default, Debug)]
169pub struct FinalizeRecordActionBuilder {
170    record_id: Option<String>,
171}
172
173impl FinalizeRecordActionBuilder {
174    pub fn new() -> Self {
175        FinalizeRecordActionBuilder::default()
176    }
177    pub fn with_record_id(mut self, value: String) -> Self {
178        self.record_id = Some(value);
179        self
180    }
181    pub fn build(self) -> Result<FinalizeRecordAction, BuilderError> {
182        let record_id = self
183            .record_id
184            .ok_or_else(|| BuilderError::MissingField("record_id".into()))?;
185        Ok(FinalizeRecordAction { record_id })
186    }
187}
188
189impl FromProto<track_and_trace_payload::FinalizeRecordAction> for FinalizeRecordAction {
190    fn from_proto(
191        proto: track_and_trace_payload::FinalizeRecordAction,
192    ) -> Result<Self, ProtoConversionError> {
193        Ok(FinalizeRecordAction {
194            record_id: proto.get_record_id().to_string(),
195        })
196    }
197}
198
199impl FromNative<FinalizeRecordAction> for track_and_trace_payload::FinalizeRecordAction {
200    fn from_native(native: FinalizeRecordAction) -> Result<Self, ProtoConversionError> {
201        let mut proto = track_and_trace_payload::FinalizeRecordAction::new();
202        proto.set_record_id(native.record_id().to_string());
203        Ok(proto)
204    }
205}
206
207impl FromBytes<FinalizeRecordAction> for FinalizeRecordAction {
208    fn from_bytes(bytes: &[u8]) -> Result<FinalizeRecordAction, ProtoConversionError> {
209        let proto: track_and_trace_payload::FinalizeRecordAction = Message::parse_from_bytes(bytes)
210            .map_err(|_| {
211                ProtoConversionError::SerializationError(
212                    "Unable to get CreateFinalizeAction from bytes".into(),
213                )
214            })?;
215        proto.into_native()
216    }
217}
218impl IntoBytes for FinalizeRecordAction {
219    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
220        let proto = self.into_proto()?;
221        let bytes = proto.write_to_bytes().map_err(|_| {
222            ProtoConversionError::SerializationError(
223                "Unable to get CreateFinalizeAction from bytes".into(),
224            )
225        })?;
226        Ok(bytes)
227    }
228}
229impl IntoProto<track_and_trace_payload::FinalizeRecordAction> for FinalizeRecordAction {}
230impl IntoNative<FinalizeRecordAction> for track_and_trace_payload::FinalizeRecordAction {}
231
232/// Native representation of an "update properties" action
233#[derive(Debug, Clone, PartialEq)]
234pub struct UpdatePropertiesAction {
235    record_id: String,
236    properties: Vec<PropertyValue>,
237}
238
239impl UpdatePropertiesAction {
240    pub fn record_id(&self) -> &str {
241        &self.record_id
242    }
243    pub fn properties(&self) -> &[PropertyValue] {
244        &self.properties
245    }
246}
247
248/// Builder used to create an "update properties" action
249#[derive(Default, Debug)]
250pub struct UpdatePropertiesActionBuilder {
251    record_id: Option<String>,
252    properties: Option<Vec<PropertyValue>>,
253}
254
255impl UpdatePropertiesActionBuilder {
256    pub fn new() -> Self {
257        UpdatePropertiesActionBuilder::default()
258    }
259    pub fn with_record_id(mut self, value: String) -> Self {
260        self.record_id = Some(value);
261        self
262    }
263    pub fn with_properties(mut self, value: Vec<PropertyValue>) -> Self {
264        self.properties = Some(value);
265        self
266    }
267    pub fn build(self) -> Result<UpdatePropertiesAction, BuilderError> {
268        let record_id = self
269            .record_id
270            .ok_or_else(|| BuilderError::MissingField("record_id".into()))?;
271        let properties = self
272            .properties
273            .ok_or_else(|| BuilderError::MissingField("properties".into()))?;
274        Ok(UpdatePropertiesAction {
275            record_id,
276            properties,
277        })
278    }
279}
280
281impl FromProto<track_and_trace_payload::UpdatePropertiesAction> for UpdatePropertiesAction {
282    fn from_proto(
283        proto: track_and_trace_payload::UpdatePropertiesAction,
284    ) -> Result<Self, ProtoConversionError> {
285        Ok(UpdatePropertiesAction {
286            record_id: proto.get_record_id().to_string(),
287            properties: proto
288                .get_properties()
289                .iter()
290                .cloned()
291                .map(PropertyValue::from_proto)
292                .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
293        })
294    }
295}
296
297impl FromNative<UpdatePropertiesAction> for track_and_trace_payload::UpdatePropertiesAction {
298    fn from_native(native: UpdatePropertiesAction) -> Result<Self, ProtoConversionError> {
299        let mut proto = track_and_trace_payload::UpdatePropertiesAction::new();
300        proto.set_record_id(native.record_id().to_string());
301        proto.set_properties(RepeatedField::from_vec(
302            native
303                .properties()
304                .iter()
305                .cloned()
306                .map(PropertyValue::into_proto)
307                .collect::<Result<Vec<protos::schema_state::PropertyValue>, ProtoConversionError>>(
308                )?,
309        ));
310        Ok(proto)
311    }
312}
313
314impl FromBytes<UpdatePropertiesAction> for UpdatePropertiesAction {
315    fn from_bytes(bytes: &[u8]) -> Result<UpdatePropertiesAction, ProtoConversionError> {
316        let proto: track_and_trace_payload::UpdatePropertiesAction =
317            Message::parse_from_bytes(bytes).map_err(|_| {
318                ProtoConversionError::SerializationError(
319                    "Unable to get UpdatePropertiesAction from bytes".into(),
320                )
321            })?;
322        proto.into_native()
323    }
324}
325impl IntoBytes for UpdatePropertiesAction {
326    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
327        let proto = self.into_proto()?;
328        let bytes = proto.write_to_bytes().map_err(|_| {
329            ProtoConversionError::SerializationError(
330                "Unable to get UpdatePropertiesAction from bytes".into(),
331            )
332        })?;
333        Ok(bytes)
334    }
335}
336impl IntoProto<track_and_trace_payload::UpdatePropertiesAction> for UpdatePropertiesAction {}
337impl IntoNative<UpdatePropertiesAction> for track_and_trace_payload::UpdatePropertiesAction {}
338
339/// Native representation of the "create proposal" action
340#[derive(Debug, Clone, PartialEq)]
341pub struct CreateProposalAction {
342    record_id: String,
343    receiving_agent: String,
344    role: Role,
345    properties: Vec<String>,
346    terms: String,
347}
348
349impl CreateProposalAction {
350    pub fn record_id(&self) -> &str {
351        &self.record_id
352    }
353    pub fn receiving_agent(&self) -> &str {
354        &self.receiving_agent
355    }
356    pub fn role(&self) -> &Role {
357        &self.role
358    }
359    pub fn properties(&self) -> &[String] {
360        &self.properties
361    }
362    pub fn terms(&self) -> &str {
363        &self.terms
364    }
365}
366
367/// Builder used to create a "create proposal" action
368#[derive(Default, Debug)]
369pub struct CreateProposalActionBuilder {
370    record_id: Option<String>,
371    receiving_agent: Option<String>,
372    role: Option<Role>,
373    properties: Option<Vec<String>>,
374    terms: Option<String>,
375}
376
377impl CreateProposalActionBuilder {
378    pub fn new() -> Self {
379        CreateProposalActionBuilder::default()
380    }
381    pub fn with_record_id(mut self, value: String) -> Self {
382        self.record_id = Some(value);
383        self
384    }
385    pub fn with_receiving_agent(mut self, value: String) -> Self {
386        self.receiving_agent = Some(value);
387        self
388    }
389    pub fn with_role(mut self, value: Role) -> Self {
390        self.role = Some(value);
391        self
392    }
393    pub fn with_properties(mut self, value: Vec<String>) -> Self {
394        self.properties = Some(value);
395        self
396    }
397    pub fn with_terms(mut self, value: String) -> Self {
398        self.terms = Some(value);
399        self
400    }
401    pub fn build(self) -> Result<CreateProposalAction, BuilderError> {
402        let record_id = self
403            .record_id
404            .ok_or_else(|| BuilderError::MissingField("record_id".into()))?;
405        let receiving_agent = self
406            .receiving_agent
407            .ok_or_else(|| BuilderError::MissingField("receiving_agent".into()))?;
408        let role = self
409            .role
410            .ok_or_else(|| BuilderError::MissingField("role".into()))?;
411        let properties = self
412            .properties
413            .ok_or_else(|| BuilderError::MissingField("properties".into()))?;
414        let terms = self
415            .terms
416            .ok_or_else(|| BuilderError::MissingField("terms".into()))?;
417        Ok(CreateProposalAction {
418            record_id,
419            receiving_agent,
420            role,
421            properties,
422            terms,
423        })
424    }
425}
426
427impl FromProto<track_and_trace_payload::CreateProposalAction> for CreateProposalAction {
428    fn from_proto(
429        proto: track_and_trace_payload::CreateProposalAction,
430    ) -> Result<Self, ProtoConversionError> {
431        Ok(CreateProposalAction {
432            record_id: proto.get_record_id().to_string(),
433            receiving_agent: proto.get_receiving_agent().to_string(),
434            role: Role::from_proto(proto.get_role())?,
435            properties: proto
436                .get_properties()
437                .iter()
438                .cloned()
439                .map(String::from)
440                .collect(),
441            terms: proto.get_terms().to_string(),
442        })
443    }
444}
445
446impl FromNative<CreateProposalAction> for track_and_trace_payload::CreateProposalAction {
447    fn from_native(native: CreateProposalAction) -> Result<Self, ProtoConversionError> {
448        let mut proto = track_and_trace_payload::CreateProposalAction::new();
449        proto.set_record_id(native.record_id().to_string());
450        proto.set_receiving_agent(native.receiving_agent().to_string());
451        proto.set_role(native.role().clone().into_proto()?);
452        proto.set_properties(RepeatedField::from_vec(native.properties().to_vec()));
453        proto.set_terms(native.terms().to_string());
454        Ok(proto)
455    }
456}
457
458impl FromBytes<CreateProposalAction> for CreateProposalAction {
459    fn from_bytes(bytes: &[u8]) -> Result<CreateProposalAction, ProtoConversionError> {
460        let proto: track_and_trace_payload::CreateProposalAction = Message::parse_from_bytes(bytes)
461            .map_err(|_| {
462                ProtoConversionError::SerializationError(
463                    "Unable to get CreateProposalAction from bytes".into(),
464                )
465            })?;
466        proto.into_native()
467    }
468}
469impl IntoBytes for CreateProposalAction {
470    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
471        let proto = self.into_proto()?;
472        let bytes = proto.write_to_bytes().map_err(|_| {
473            ProtoConversionError::SerializationError(
474                "Unable to get CreateProposalAction from bytes".into(),
475            )
476        })?;
477        Ok(bytes)
478    }
479}
480impl IntoProto<track_and_trace_payload::CreateProposalAction> for CreateProposalAction {}
481impl IntoNative<CreateProposalAction> for track_and_trace_payload::CreateProposalAction {}
482
483/// Native representation of a `Response`
484///
485/// Returned by an agent in response to a proposal for some `Record`. This reponse is then recorded
486/// in the `Proposal`.
487#[derive(Debug, Clone, PartialEq)]
488pub enum Response {
489    Accept,
490    Reject,
491    Cancel,
492}
493
494impl Default for Response {
495    fn default() -> Response {
496        Response::Accept
497    }
498}
499
500impl FromProto<track_and_trace_payload::AnswerProposalAction_Response> for Response {
501    fn from_proto(
502        responses: track_and_trace_payload::AnswerProposalAction_Response,
503    ) -> Result<Self, ProtoConversionError> {
504        match responses {
505            track_and_trace_payload::AnswerProposalAction_Response::ACCEPT => Ok(Response::Accept),
506            track_and_trace_payload::AnswerProposalAction_Response::REJECT => Ok(Response::Reject),
507            track_and_trace_payload::AnswerProposalAction_Response::CANCEL => Ok(Response::Cancel),
508        }
509    }
510}
511
512impl FromNative<Response> for track_and_trace_payload::AnswerProposalAction_Response {
513    fn from_native(responses: Response) -> Result<Self, ProtoConversionError> {
514        match responses {
515            Response::Accept => Ok(track_and_trace_payload::AnswerProposalAction_Response::ACCEPT),
516            Response::Reject => Ok(track_and_trace_payload::AnswerProposalAction_Response::REJECT),
517            Response::Cancel => Ok(track_and_trace_payload::AnswerProposalAction_Response::CANCEL),
518        }
519    }
520}
521
522impl IntoProto<track_and_trace_payload::AnswerProposalAction_Response> for Response {}
523impl IntoNative<Response> for track_and_trace_payload::AnswerProposalAction_Response {}
524
525/// Native representation of an "answer proposal" action
526#[derive(Debug, Clone, PartialEq)]
527pub struct AnswerProposalAction {
528    record_id: String,
529    receiving_agent: String,
530    role: Role,
531    response: Response,
532}
533
534impl AnswerProposalAction {
535    pub fn record_id(&self) -> &str {
536        &self.record_id
537    }
538    pub fn receiving_agent(&self) -> &str {
539        &self.receiving_agent
540    }
541    pub fn role(&self) -> &Role {
542        &self.role
543    }
544    pub fn response(&self) -> &Response {
545        &self.response
546    }
547}
548
549/// Builder used to create an "answer proposal" action
550#[derive(Default, Debug)]
551pub struct AnswerProposalActionBuilder {
552    record_id: Option<String>,
553    receiving_agent: Option<String>,
554    role: Option<Role>,
555    response: Option<Response>,
556}
557
558impl AnswerProposalActionBuilder {
559    pub fn new() -> Self {
560        AnswerProposalActionBuilder::default()
561    }
562    pub fn with_record_id(mut self, value: String) -> Self {
563        self.record_id = Some(value);
564        self
565    }
566    pub fn with_receiving_agent(mut self, value: String) -> Self {
567        self.receiving_agent = Some(value);
568        self
569    }
570    pub fn with_role(mut self, value: Role) -> Self {
571        self.role = Some(value);
572        self
573    }
574    pub fn with_response(mut self, value: Response) -> Self {
575        self.response = Some(value);
576        self
577    }
578    pub fn build(self) -> Result<AnswerProposalAction, BuilderError> {
579        let record_id = self
580            .record_id
581            .ok_or_else(|| BuilderError::MissingField("record_id".into()))?;
582        let receiving_agent = self
583            .receiving_agent
584            .ok_or_else(|| BuilderError::MissingField("receiving_agent".into()))?;
585        let role = self
586            .role
587            .ok_or_else(|| BuilderError::MissingField("role".into()))?;
588        let response = self
589            .response
590            .ok_or_else(|| BuilderError::MissingField("response".into()))?;
591        Ok(AnswerProposalAction {
592            record_id,
593            receiving_agent,
594            role,
595            response,
596        })
597    }
598}
599
600impl FromProto<track_and_trace_payload::AnswerProposalAction> for AnswerProposalAction {
601    fn from_proto(
602        proto: track_and_trace_payload::AnswerProposalAction,
603    ) -> Result<Self, ProtoConversionError> {
604        Ok(AnswerProposalAction {
605            record_id: proto.get_record_id().to_string(),
606            receiving_agent: proto.get_receiving_agent().to_string(),
607            role: Role::from_proto(proto.get_role())?,
608            response: Response::from_proto(proto.get_response())?,
609        })
610    }
611}
612
613impl FromNative<AnswerProposalAction> for track_and_trace_payload::AnswerProposalAction {
614    fn from_native(native: AnswerProposalAction) -> Result<Self, ProtoConversionError> {
615        let mut proto = track_and_trace_payload::AnswerProposalAction::new();
616        proto.set_record_id(native.record_id().to_string());
617        proto.set_receiving_agent(native.receiving_agent().to_string());
618        proto.set_role(native.role().clone().into_proto()?);
619        proto.set_response(native.response().clone().into_proto()?);
620
621        Ok(proto)
622    }
623}
624
625impl FromBytes<AnswerProposalAction> for AnswerProposalAction {
626    fn from_bytes(bytes: &[u8]) -> Result<AnswerProposalAction, ProtoConversionError> {
627        let proto: track_and_trace_payload::AnswerProposalAction = Message::parse_from_bytes(bytes)
628            .map_err(|_| {
629                ProtoConversionError::SerializationError(
630                    "Unable to get AnswerProposalAction from bytes".into(),
631                )
632            })?;
633        proto.into_native()
634    }
635}
636impl IntoBytes for AnswerProposalAction {
637    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
638        let proto = self.into_proto()?;
639        let bytes = proto.write_to_bytes().map_err(|_| {
640            ProtoConversionError::SerializationError(
641                "Unable to get AnswerProposalAction from bytes".into(),
642            )
643        })?;
644        Ok(bytes)
645    }
646}
647impl IntoProto<track_and_trace_payload::AnswerProposalAction> for AnswerProposalAction {}
648impl IntoNative<AnswerProposalAction> for track_and_trace_payload::AnswerProposalAction {}
649
650/// Native representation of a "revoke reporter" action
651#[derive(Debug, Clone, PartialEq)]
652pub struct RevokeReporterAction {
653    record_id: String,
654    reporter_id: String,
655    properties: Vec<String>,
656}
657
658impl RevokeReporterAction {
659    pub fn record_id(&self) -> &str {
660        &self.record_id
661    }
662    pub fn reporter_id(&self) -> &str {
663        &self.reporter_id
664    }
665    pub fn properties(&self) -> &[String] {
666        &self.properties
667    }
668}
669
670/// Builder used to create a "revoke reporter" action
671#[derive(Default, Debug)]
672pub struct RevokeReporterActionBuilder {
673    record_id: Option<String>,
674    reporter_id: Option<String>,
675    properties: Option<Vec<String>>,
676}
677
678impl RevokeReporterActionBuilder {
679    pub fn new() -> Self {
680        RevokeReporterActionBuilder::default()
681    }
682    pub fn with_record_id(mut self, value: String) -> Self {
683        self.record_id = Some(value);
684        self
685    }
686    pub fn with_reporter_id(mut self, value: String) -> Self {
687        self.reporter_id = Some(value);
688        self
689    }
690    pub fn with_properties(mut self, value: Vec<String>) -> Self {
691        self.properties = Some(value);
692        self
693    }
694    pub fn build(self) -> Result<RevokeReporterAction, BuilderError> {
695        let record_id = self
696            .record_id
697            .ok_or_else(|| BuilderError::MissingField("record_id".into()))?;
698        let reporter_id = self
699            .reporter_id
700            .ok_or_else(|| BuilderError::MissingField("reporter_id".into()))?;
701        let properties = self
702            .properties
703            .ok_or_else(|| BuilderError::MissingField("properties".into()))?;
704        Ok(RevokeReporterAction {
705            record_id,
706            reporter_id,
707            properties,
708        })
709    }
710}
711
712impl FromProto<track_and_trace_payload::RevokeReporterAction> for RevokeReporterAction {
713    fn from_proto(
714        proto: track_and_trace_payload::RevokeReporterAction,
715    ) -> Result<Self, ProtoConversionError> {
716        Ok(RevokeReporterAction {
717            record_id: proto.get_record_id().to_string(),
718            reporter_id: proto.get_reporter_id().to_string(),
719            properties: proto
720                .get_properties()
721                .iter()
722                .cloned()
723                .map(String::from)
724                .collect(),
725        })
726    }
727}
728
729impl FromNative<RevokeReporterAction> for track_and_trace_payload::RevokeReporterAction {
730    fn from_native(native: RevokeReporterAction) -> Result<Self, ProtoConversionError> {
731        let mut proto = track_and_trace_payload::RevokeReporterAction::new();
732        proto.set_record_id(native.record_id().to_string());
733        proto.set_reporter_id(native.reporter_id().to_string());
734        proto.set_properties(RepeatedField::from_vec(native.properties().to_vec()));
735
736        Ok(proto)
737    }
738}
739
740impl FromBytes<RevokeReporterAction> for RevokeReporterAction {
741    fn from_bytes(bytes: &[u8]) -> Result<RevokeReporterAction, ProtoConversionError> {
742        let proto: track_and_trace_payload::RevokeReporterAction = Message::parse_from_bytes(bytes)
743            .map_err(|_| {
744                ProtoConversionError::SerializationError(
745                    "Unable to get RevokeReporterAction from bytes".into(),
746                )
747            })?;
748        proto.into_native()
749    }
750}
751impl IntoBytes for RevokeReporterAction {
752    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
753        let proto = self.into_proto()?;
754        let bytes = proto.write_to_bytes().map_err(|_| {
755            ProtoConversionError::SerializationError(
756                "Unable to get RevokeReporterAction from bytes".into(),
757            )
758        })?;
759        Ok(bytes)
760    }
761}
762impl IntoProto<track_and_trace_payload::RevokeReporterAction> for RevokeReporterAction {}
763impl IntoNative<RevokeReporterAction> for track_and_trace_payload::RevokeReporterAction {}
764
765/// The Track and Trace payload action envelope
766#[derive(Debug, Clone, PartialEq)]
767pub enum Action {
768    CreateRecord(CreateRecordAction),
769    FinalizeRecord(FinalizeRecordAction),
770    UpdateProperties(UpdatePropertiesAction),
771    CreateProposal(CreateProposalAction),
772    AnswerProposal(AnswerProposalAction),
773    RevokeReporter(RevokeReporterAction),
774}
775
776/// Native representation of a Track and Trace payload
777#[derive(Debug, Clone, PartialEq)]
778pub struct TrackAndTracePayload {
779    action: Action,
780    timestamp: u64,
781}
782
783impl TrackAndTracePayload {
784    pub fn action(&self) -> &Action {
785        &self.action
786    }
787    pub fn timestamp(&self) -> &u64 {
788        &self.timestamp
789    }
790}
791
792/// Builder used to create a Track and Trace payload
793#[derive(Default, Debug)]
794pub struct TrackAndTracePayloadBuilder {
795    action: Option<Action>,
796    timestamp: Option<u64>,
797}
798
799impl TrackAndTracePayloadBuilder {
800    pub fn new() -> Self {
801        TrackAndTracePayloadBuilder::default()
802    }
803    pub fn with_action(mut self, value: Action) -> Self {
804        self.action = Some(value);
805        self
806    }
807    pub fn with_timestamp(mut self, value: u64) -> Self {
808        self.timestamp = Some(value);
809        self
810    }
811    pub fn build(self) -> Result<TrackAndTracePayload, BuilderError> {
812        let action = self
813            .action
814            .ok_or_else(|| BuilderError::MissingField("action".into()))?;
815        let timestamp = self
816            .timestamp
817            .ok_or_else(|| BuilderError::MissingField("timestamp".into()))?;
818        Ok(TrackAndTracePayload { action, timestamp })
819    }
820}
821
822impl FromProto<track_and_trace_payload::TrackAndTracePayload> for TrackAndTracePayload {
823    fn from_proto(
824        proto: track_and_trace_payload::TrackAndTracePayload,
825    ) -> Result<Self, ProtoConversionError> {
826        let action = match proto.get_action() {
827            TrackAndTracePayload_Action::CREATE_RECORD => Action::CreateRecord(
828                CreateRecordAction::from_proto(proto.get_create_record().clone())?,
829            ),
830            TrackAndTracePayload_Action::FINALIZE_RECORD => Action::FinalizeRecord(
831                FinalizeRecordAction::from_proto(proto.get_finalize_record().clone())?,
832            ),
833            TrackAndTracePayload_Action::UPDATE_PROPERTIES => Action::UpdateProperties(
834                UpdatePropertiesAction::from_proto(proto.get_update_properties().clone())?,
835            ),
836            TrackAndTracePayload_Action::CREATE_PROPOSAL => Action::CreateProposal(
837                CreateProposalAction::from_proto(proto.get_create_proposal().clone())?,
838            ),
839            TrackAndTracePayload_Action::ANSWER_PROPOSAL => Action::AnswerProposal(
840                AnswerProposalAction::from_proto(proto.get_answer_proposal().clone())?,
841            ),
842            TrackAndTracePayload_Action::REVOKE_REPORTER => Action::RevokeReporter(
843                RevokeReporterAction::from_proto(proto.get_revoke_reporter().clone())?,
844            ),
845            TrackAndTracePayload_Action::UNSET_ACTION => {
846                return Err(ProtoConversionError::InvalidTypeError(
847                    "Cannot convert TrackAndTracePayload_Action with type unset.".to_string(),
848                ));
849            }
850        };
851
852        Ok(TrackAndTracePayload {
853            action,
854            timestamp: proto.get_timestamp(),
855        })
856    }
857}
858
859impl FromNative<TrackAndTracePayload> for track_and_trace_payload::TrackAndTracePayload {
860    fn from_native(native: TrackAndTracePayload) -> Result<Self, ProtoConversionError> {
861        let mut proto = track_and_trace_payload::TrackAndTracePayload::new();
862
863        proto.set_timestamp(*native.timestamp());
864
865        match native.action() {
866            Action::CreateRecord(payload) => {
867                proto.set_action(TrackAndTracePayload_Action::CREATE_RECORD);
868                proto.set_create_record(payload.clone().into_proto()?);
869            }
870            Action::FinalizeRecord(payload) => {
871                proto.set_action(TrackAndTracePayload_Action::FINALIZE_RECORD);
872                proto.set_finalize_record(payload.clone().into_proto()?);
873            }
874            Action::UpdateProperties(payload) => {
875                proto.set_action(TrackAndTracePayload_Action::UPDATE_PROPERTIES);
876                proto.set_update_properties(payload.clone().into_proto()?);
877            }
878            Action::CreateProposal(payload) => {
879                proto.set_action(TrackAndTracePayload_Action::CREATE_PROPOSAL);
880                proto.set_create_proposal(payload.clone().into_proto()?);
881            }
882            Action::AnswerProposal(payload) => {
883                proto.set_action(TrackAndTracePayload_Action::ANSWER_PROPOSAL);
884                proto.set_answer_proposal(payload.clone().into_proto()?);
885            }
886            Action::RevokeReporter(payload) => {
887                proto.set_action(TrackAndTracePayload_Action::REVOKE_REPORTER);
888                proto.set_revoke_reporter(payload.clone().into_proto()?);
889            }
890        }
891
892        Ok(proto)
893    }
894}
895
896impl FromBytes<TrackAndTracePayload> for TrackAndTracePayload {
897    fn from_bytes(bytes: &[u8]) -> Result<TrackAndTracePayload, ProtoConversionError> {
898        let proto: track_and_trace_payload::TrackAndTracePayload = Message::parse_from_bytes(bytes)
899            .map_err(|_| {
900                ProtoConversionError::SerializationError(
901                    "Unable to get TrackAndTracePaylaod from bytes".into(),
902                )
903            })?;
904        proto.into_native()
905    }
906}
907impl IntoBytes for TrackAndTracePayload {
908    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
909        let proto = self.into_proto()?;
910        let bytes = proto.write_to_bytes().map_err(|_| {
911            ProtoConversionError::SerializationError(
912                "Unable to get TrackAndTracePaylaod from bytes".into(),
913            )
914        })?;
915        Ok(bytes)
916    }
917}
918impl IntoProto<track_and_trace_payload::TrackAndTracePayload> for TrackAndTracePayload {}
919impl IntoNative<TrackAndTracePayload> for track_and_trace_payload::TrackAndTracePayload {}
920
921#[cfg(test)]
922mod tests {
923    use super::*;
924    use crate::protocol::schema::state::{DataType, PropertyValueBuilder};
925    use std::fmt::Debug;
926
927    fn test_from_bytes<T: FromBytes<T> + Clone + PartialEq + IntoBytes + Debug, F>(
928        under_test: T,
929        from_bytes: F,
930    ) where
931        F: Fn(&[u8]) -> Result<T, ProtoConversionError>,
932    {
933        let bytes = under_test.clone().into_bytes().unwrap();
934        let created_from_bytes = from_bytes(&bytes).unwrap();
935        assert_eq!(under_test, created_from_bytes);
936    }
937
938    #[test]
939    /// Validate a "create record" action is built correctly
940    fn test_create_record_builder() {
941        let property_value = PropertyValueBuilder::new()
942            .with_name("egg".into())
943            .with_data_type(DataType::Number)
944            .with_number_value(42)
945            .build()
946            .unwrap();
947
948        let action = CreateRecordActionBuilder::new()
949            .with_record_id("32".into())
950            .with_schema("schema".into())
951            .with_properties(vec![property_value.clone()])
952            .build()
953            .unwrap();
954
955        assert_eq!(action.record_id(), "32");
956        assert_eq!(action.schema(), "schema");
957        assert!(action.properties().iter().any(|x| *x == property_value));
958    }
959
960    #[test]
961    /// Validate a "create record" action may be converted into bytes and back to its native
962    /// representation successfully
963    fn test_create_record_bytes() {
964        let property_value = PropertyValueBuilder::new()
965            .with_name("egg".into())
966            .with_data_type(DataType::Number)
967            .with_number_value(42)
968            .build()
969            .unwrap();
970
971        let action = CreateRecordActionBuilder::new()
972            .with_record_id("32".into())
973            .with_schema("schema".into())
974            .with_properties(vec![property_value.clone()])
975            .build()
976            .unwrap();
977
978        test_from_bytes(action, CreateRecordAction::from_bytes);
979    }
980
981    #[test]
982    /// Validate a "finalize record" action is built correctly
983    fn test_finalize_record_action_builder() {
984        let action = FinalizeRecordActionBuilder::new()
985            .with_record_id("32".into())
986            .build()
987            .unwrap();
988
989        assert_eq!(action.record_id(), "32");
990    }
991
992    #[test]
993    /// Validate a "finalize record" action may be converted into bytes and back to its native
994    /// representation successfully
995    fn test_finalize_record_action_bytes() {
996        let action = FinalizeRecordActionBuilder::new()
997            .with_record_id("32".into())
998            .build()
999            .unwrap();
1000
1001        test_from_bytes(action, FinalizeRecordAction::from_bytes);
1002    }
1003
1004    #[test]
1005    /// Validate an "update properties" action is built correctly
1006    fn test_update_properties_action() {
1007        let property_value = PropertyValueBuilder::new()
1008            .with_name("egg".into())
1009            .with_data_type(DataType::Number)
1010            .with_number_value(42)
1011            .build()
1012            .unwrap();
1013
1014        let action = UpdatePropertiesActionBuilder::new()
1015            .with_record_id("32".into())
1016            .with_properties(vec![property_value.clone()])
1017            .build()
1018            .unwrap();
1019
1020        assert_eq!(action.record_id(), "32");
1021        assert!(action.properties().iter().any(|x| *x == property_value));
1022    }
1023
1024    #[test]
1025    /// Validate an "update properties" action may be converted into bytes and back to its native
1026    /// representation successfully
1027    fn test_update_properties_action_bytes() {
1028        let property_value = PropertyValueBuilder::new()
1029            .with_name("egg".into())
1030            .with_data_type(DataType::Number)
1031            .with_number_value(42)
1032            .build()
1033            .unwrap();
1034
1035        let action = UpdatePropertiesActionBuilder::new()
1036            .with_record_id("32".into())
1037            .with_properties(vec![property_value.clone()])
1038            .build()
1039            .unwrap();
1040
1041        test_from_bytes(action, UpdatePropertiesAction::from_bytes);
1042    }
1043
1044    #[test]
1045    /// Validate a "create proposal" action is built correctly
1046    fn test_create_proposal_action_builder() {
1047        let action = CreateProposalActionBuilder::new()
1048            .with_record_id("32".into())
1049            .with_receiving_agent("jim".into())
1050            .with_role(Role::Custodian)
1051            .with_properties(vec!["egg".into()])
1052            .with_terms("term".to_string())
1053            .build()
1054            .unwrap();
1055
1056        assert_eq!(action.record_id(), "32");
1057        assert_eq!(action.receiving_agent(), "jim");
1058        assert_eq!(action.terms(), "term");
1059        assert_eq!(*action.role(), Role::Custodian);
1060        assert!(action.properties().iter().any(|x| x == "egg"));
1061    }
1062
1063    #[test]
1064    /// Validate a "create proposal" action may be converted into bytes and back to its native
1065    /// representation successfully
1066    fn test_create_proposal_action_bytes() {
1067        let action = CreateProposalActionBuilder::new()
1068            .with_record_id("32".into())
1069            .with_receiving_agent("jim".into())
1070            .with_role(Role::Custodian)
1071            .with_properties(vec!["egg".into()])
1072            .with_terms("term".to_string())
1073            .build()
1074            .unwrap();
1075
1076        test_from_bytes(action, CreateProposalAction::from_bytes);
1077    }
1078
1079    #[test]
1080    /// Validate an "answer proposal" action is built correctly
1081    fn test_answer_proposal_action_builder() {
1082        let action = AnswerProposalActionBuilder::new()
1083            .with_record_id("32".into())
1084            .with_receiving_agent("jim".into())
1085            .with_role(Role::Custodian)
1086            .with_response(Response::Accept)
1087            .build()
1088            .unwrap();
1089
1090        assert_eq!(action.record_id(), "32");
1091        assert_eq!(action.receiving_agent(), "jim");
1092        assert_eq!(*action.role(), Role::Custodian);
1093        assert_eq!(*action.response(), Response::Accept);
1094    }
1095
1096    #[test]
1097    /// Validate an "answer proposal" action may be converted into bytes and back to its native
1098    /// representation successfully
1099    fn test_answer_proposal_action_bytes() {
1100        let action = AnswerProposalActionBuilder::new()
1101            .with_record_id("32".into())
1102            .with_receiving_agent("jim".into())
1103            .with_role(Role::Custodian)
1104            .with_response(Response::Accept)
1105            .build()
1106            .unwrap();
1107
1108        test_from_bytes(action, AnswerProposalAction::from_bytes);
1109    }
1110
1111    #[test]
1112    /// Validate a "revoke reporter" action is built correctly
1113    fn test_revoke_reporter_action_builder() {
1114        let action = RevokeReporterActionBuilder::new()
1115            .with_record_id("32".into())
1116            .with_reporter_id("jim".into())
1117            .with_properties(vec!["egg".into()])
1118            .build()
1119            .unwrap();
1120
1121        assert_eq!(action.record_id(), "32");
1122        assert_eq!(action.reporter_id(), "jim");
1123        assert!(action.properties().iter().any(|x| x == "egg"));
1124    }
1125
1126    #[test]
1127    /// Validate that a "revoke reporter" action may be converted into bytes and back to its native
1128    /// representation successfully
1129    fn test_revoke_reporter_action_bytes() {
1130        let action = RevokeReporterActionBuilder::new()
1131            .with_record_id("32".into())
1132            .with_reporter_id("jim".into())
1133            .with_properties(vec!["egg".into()])
1134            .build()
1135            .unwrap();
1136
1137        test_from_bytes(action, RevokeReporterAction::from_bytes);
1138    }
1139
1140    #[test]
1141    /// Validate that a Track and Trace payload is built correctly
1142    fn test_payload_builder() {
1143        let action = RevokeReporterActionBuilder::new()
1144            .with_record_id("32".into())
1145            .with_reporter_id("jim".into())
1146            .with_properties(vec!["egg".into()])
1147            .build()
1148            .unwrap();
1149
1150        let payload = TrackAndTracePayloadBuilder::new()
1151            .with_action(Action::RevokeReporter(action.clone()))
1152            .with_timestamp(0)
1153            .build()
1154            .unwrap();
1155
1156        assert_eq!(*payload.action(), Action::RevokeReporter(action));
1157        assert_eq!(*payload.timestamp(), 0);
1158    }
1159
1160    #[test]
1161    /// Validate that a Track and Trace payload may be converted into bytes and back to its native
1162    /// representation successfully
1163    fn test_payload_bytes() {
1164        let action = RevokeReporterActionBuilder::new()
1165            .with_record_id("32".into())
1166            .with_reporter_id("jim".into())
1167            .with_properties(vec!["egg".into()])
1168            .build()
1169            .unwrap();
1170
1171        let payload = TrackAndTracePayloadBuilder::new()
1172            .with_action(Action::RevokeReporter(action.clone()))
1173            .with_timestamp(0)
1174            .build()
1175            .unwrap();
1176
1177        test_from_bytes(payload, TrackAndTracePayload::from_bytes);
1178    }
1179}