grid_sdk/protocol/location/
payload.rs

1// Copyright 2020 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 Location transaction payloads
16
17use protobuf::Message;
18use protobuf::RepeatedField;
19
20use std::error::Error as StdError;
21
22use super::errors::BuilderError;
23
24use crate::protocol::schema::state::PropertyValue;
25use crate::protos;
26use crate::protos::{location_payload, location_payload::LocationPayload_Action};
27use crate::protos::{
28    FromBytes, FromNative, FromProto, IntoBytes, IntoNative, IntoProto, ProtoConversionError,
29};
30
31/// Possible Location namespaces
32///
33/// The namespace determines the schema used to define a Location's properties
34#[derive(Copy, Clone, Debug, PartialEq)]
35pub enum LocationNamespace {
36    Gs1,
37}
38
39impl Default for LocationNamespace {
40    fn default() -> Self {
41        LocationNamespace::Gs1
42    }
43}
44
45impl FromProto<protos::location_payload::LocationNamespace> for LocationNamespace {
46    fn from_proto(
47        namespace: protos::location_payload::LocationNamespace,
48    ) -> Result<Self, ProtoConversionError> {
49        match namespace {
50            protos::location_payload::LocationNamespace::GS1 => Ok(LocationNamespace::Gs1),
51            protos::location_payload::LocationNamespace::UNSET_TYPE => {
52                Err(ProtoConversionError::InvalidTypeError(
53                    "Cannot convert Location_LocationType with type UNSET_TYPE".to_string(),
54                ))
55            }
56        }
57    }
58}
59
60impl FromNative<LocationNamespace> for protos::location_payload::LocationNamespace {
61    fn from_native(namespace: LocationNamespace) -> Result<Self, ProtoConversionError> {
62        match namespace {
63            LocationNamespace::Gs1 => Ok(protos::location_payload::LocationNamespace::GS1),
64        }
65    }
66}
67
68impl IntoProto<protos::location_payload::LocationNamespace> for LocationNamespace {}
69impl IntoNative<LocationNamespace> for protos::location_payload::LocationNamespace {}
70
71/// The Location payload's action envelope
72#[derive(Debug, Clone, PartialEq)]
73pub enum Action {
74    LocationCreate(LocationCreateAction),
75    LocationUpdate(LocationUpdateAction),
76    LocationDelete(LocationDeleteAction),
77}
78
79/// Native representation of a Location transaction payload
80#[derive(Debug, Clone, PartialEq)]
81pub struct LocationPayload {
82    action: Action,
83    timestamp: u64,
84}
85
86impl LocationPayload {
87    pub fn action(&self) -> &Action {
88        &self.action
89    }
90
91    pub fn timestamp(&self) -> &u64 {
92        &self.timestamp
93    }
94}
95
96impl FromProto<protos::location_payload::LocationPayload> for LocationPayload {
97    fn from_proto(
98        payload: protos::location_payload::LocationPayload,
99    ) -> Result<Self, ProtoConversionError> {
100        let action = match payload.get_action() {
101            LocationPayload_Action::LOCATION_CREATE => Action::LocationCreate(
102                LocationCreateAction::from_proto(payload.get_location_create().clone())?,
103            ),
104            LocationPayload_Action::LOCATION_UPDATE => Action::LocationUpdate(
105                LocationUpdateAction::from_proto(payload.get_location_update().clone())?,
106            ),
107            LocationPayload_Action::LOCATION_DELETE => Action::LocationDelete(
108                LocationDeleteAction::from_proto(payload.get_location_delete().clone())?,
109            ),
110            LocationPayload_Action::UNSET_ACTION => {
111                return Err(ProtoConversionError::InvalidTypeError(
112                    "Cannot convert LocationPayload_Action with type unset".to_string(),
113                ));
114            }
115        };
116        Ok(LocationPayload {
117            action,
118            timestamp: payload.get_timestamp(),
119        })
120    }
121}
122
123impl FromNative<LocationPayload> for protos::location_payload::LocationPayload {
124    fn from_native(native: LocationPayload) -> Result<Self, ProtoConversionError> {
125        let mut proto = location_payload::LocationPayload::new();
126
127        proto.set_timestamp(*native.timestamp());
128
129        match native.action() {
130            Action::LocationCreate(payload) => {
131                proto.set_action(LocationPayload_Action::LOCATION_CREATE);
132                proto.set_location_create(payload.clone().into_proto()?);
133            }
134            Action::LocationUpdate(payload) => {
135                proto.set_action(LocationPayload_Action::LOCATION_UPDATE);
136                proto.set_location_update(payload.clone().into_proto()?);
137            }
138            Action::LocationDelete(payload) => {
139                proto.set_action(LocationPayload_Action::LOCATION_DELETE);
140                proto.set_location_delete(payload.clone().into_proto()?);
141            }
142        }
143
144        Ok(proto)
145    }
146}
147
148impl FromBytes<LocationPayload> for LocationPayload {
149    fn from_bytes(bytes: &[u8]) -> Result<LocationPayload, ProtoConversionError> {
150        let proto: location_payload::LocationPayload =
151            Message::parse_from_bytes(bytes).map_err(|_| {
152                ProtoConversionError::SerializationError(
153                    "Unable to get LocationPayload from bytes".into(),
154                )
155            })?;
156        proto.into_native()
157    }
158}
159
160impl IntoBytes for LocationPayload {
161    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
162        let proto = self.into_proto()?;
163        let bytes = proto.write_to_bytes().map_err(|_| {
164            ProtoConversionError::SerializationError(
165                "Unable to get LocationPayload from bytes".into(),
166            )
167        })?;
168        Ok(bytes)
169    }
170}
171
172impl IntoProto<protos::location_payload::LocationPayload> for LocationPayload {}
173impl IntoNative<LocationPayload> for protos::location_payload::LocationPayload {}
174
175/// Returned if any required fields in a `LocationPayload` are not present when being
176/// converted from the corresponding builder
177#[derive(Debug)]
178pub enum LocationPayloadBuildError {
179    MissingField(String),
180}
181
182impl StdError for LocationPayloadBuildError {
183    fn description(&self) -> &str {
184        match *self {
185            LocationPayloadBuildError::MissingField(ref msg) => msg,
186        }
187    }
188
189    fn cause(&self) -> Option<&dyn StdError> {
190        match *self {
191            LocationPayloadBuildError::MissingField(_) => None,
192        }
193    }
194}
195
196impl std::fmt::Display for LocationPayloadBuildError {
197    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
198        match *self {
199            LocationPayloadBuildError::MissingField(ref s) => write!(f, "missing field \"{}\"", s),
200        }
201    }
202}
203
204/// Builder used to create a Location transaction payload
205#[derive(Default, Clone)]
206pub struct LocationPayloadBuilder {
207    action: Option<Action>,
208    timestamp: Option<u64>,
209}
210
211impl LocationPayloadBuilder {
212    pub fn new() -> Self {
213        LocationPayloadBuilder::default()
214    }
215    pub fn with_action(mut self, action: Action) -> Self {
216        self.action = Some(action);
217        self
218    }
219    pub fn with_timestamp(mut self, value: u64) -> Self {
220        self.timestamp = Some(value);
221        self
222    }
223    pub fn build(self) -> Result<LocationPayload, BuilderError> {
224        let action = self
225            .action
226            .ok_or_else(|| BuilderError::MissingField("'action' field is required".into()))?;
227        let timestamp = self
228            .timestamp
229            .ok_or_else(|| BuilderError::MissingField("'timestamp' field is required".into()))?;
230        Ok(LocationPayload { action, timestamp })
231    }
232}
233
234/// Native representation of the "create location" action payload
235#[derive(Debug, Default, Clone, PartialEq)]
236pub struct LocationCreateAction {
237    namespace: LocationNamespace,
238    location_id: String,
239    owner: String,
240    properties: Vec<PropertyValue>,
241}
242
243impl LocationCreateAction {
244    pub fn namespace(&self) -> &LocationNamespace {
245        &self.namespace
246    }
247
248    pub fn location_id(&self) -> &str {
249        &self.location_id
250    }
251
252    pub fn owner(&self) -> &str {
253        &self.owner
254    }
255
256    pub fn properties(&self) -> &[PropertyValue] {
257        &self.properties
258    }
259}
260
261impl FromProto<location_payload::LocationCreateAction> for LocationCreateAction {
262    fn from_proto(
263        proto: location_payload::LocationCreateAction,
264    ) -> Result<Self, ProtoConversionError> {
265        Ok(LocationCreateAction {
266            namespace: LocationNamespace::from_proto(proto.get_namespace())?,
267            location_id: proto.get_location_id().to_string(),
268            owner: proto.get_owner().to_string(),
269            properties: proto
270                .get_properties()
271                .iter()
272                .cloned()
273                .map(PropertyValue::from_proto)
274                .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
275        })
276    }
277}
278
279impl FromNative<LocationCreateAction> for location_payload::LocationCreateAction {
280    fn from_native(native: LocationCreateAction) -> Result<Self, ProtoConversionError> {
281        let mut proto = protos::location_payload::LocationCreateAction::new();
282        proto.set_namespace((*native.namespace()).into_proto()?);
283        proto.set_location_id(native.location_id().to_string());
284        proto.set_owner(native.owner().to_string());
285        proto.set_properties(RepeatedField::from_vec(
286            native
287                .properties()
288                .iter()
289                .cloned()
290                .map(PropertyValue::into_proto)
291                .collect::<Result<Vec<protos::schema_state::PropertyValue>, ProtoConversionError>>(
292                )?,
293        ));
294        Ok(proto)
295    }
296}
297
298impl FromBytes<LocationCreateAction> for LocationCreateAction {
299    fn from_bytes(bytes: &[u8]) -> Result<LocationCreateAction, ProtoConversionError> {
300        let proto: protos::location_payload::LocationCreateAction =
301            Message::parse_from_bytes(bytes).map_err(|_| {
302                ProtoConversionError::SerializationError(
303                    "Unable to get LocationCreateAction from bytes".to_string(),
304                )
305            })?;
306        proto.into_native()
307    }
308}
309
310impl IntoBytes for LocationCreateAction {
311    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
312        let proto = self.into_proto()?;
313        let bytes = proto.write_to_bytes().map_err(|_| {
314            ProtoConversionError::SerializationError(
315                "Unable to get bytes from LocationCreateAction".to_string(),
316            )
317        })?;
318        Ok(bytes)
319    }
320}
321
322impl IntoProto<protos::location_payload::LocationCreateAction> for LocationCreateAction {}
323impl IntoNative<LocationCreateAction> for protos::location_payload::LocationCreateAction {}
324
325/// Builder used to create a "create location" action
326#[derive(Default, Debug)]
327pub struct LocationCreateActionBuilder {
328    namespace: Option<LocationNamespace>,
329    location_id: Option<String>,
330    owner: Option<String>,
331    properties: Option<Vec<PropertyValue>>,
332}
333
334impl LocationCreateActionBuilder {
335    pub fn new() -> Self {
336        LocationCreateActionBuilder::default()
337    }
338    pub fn with_namespace(mut self, value: LocationNamespace) -> Self {
339        self.namespace = Some(value);
340        self
341    }
342    pub fn with_location_id(mut self, value: String) -> Self {
343        self.location_id = Some(value);
344        self
345    }
346    pub fn with_owner(mut self, value: String) -> Self {
347        self.owner = Some(value);
348        self
349    }
350    pub fn with_properties(mut self, value: Vec<PropertyValue>) -> Self {
351        self.properties = Some(value);
352        self
353    }
354    pub fn build(self) -> Result<LocationCreateAction, BuilderError> {
355        let namespace = self.namespace.ok_or_else(|| {
356            BuilderError::MissingField("'namespace' field is required".to_string())
357        })?;
358        let location_id = self
359            .location_id
360            .ok_or_else(|| BuilderError::MissingField("'location_id' field is required".into()))?;
361        let owner = self
362            .owner
363            .ok_or_else(|| BuilderError::MissingField("'owner' field is required".into()))?;
364        let properties = self
365            .properties
366            .ok_or_else(|| BuilderError::MissingField("'properties' field is required".into()))?;
367        Ok(LocationCreateAction {
368            namespace,
369            location_id,
370            owner,
371            properties,
372        })
373    }
374}
375
376/// Native representation of an "update location" action
377#[derive(Debug, Default, Clone, PartialEq)]
378pub struct LocationUpdateAction {
379    namespace: LocationNamespace,
380    location_id: String,
381    properties: Vec<PropertyValue>,
382}
383
384impl LocationUpdateAction {
385    pub fn namespace(&self) -> &LocationNamespace {
386        &self.namespace
387    }
388
389    pub fn location_id(&self) -> &str {
390        &self.location_id
391    }
392
393    pub fn properties(&self) -> &[PropertyValue] {
394        &self.properties
395    }
396}
397
398impl FromProto<protos::location_payload::LocationUpdateAction> for LocationUpdateAction {
399    fn from_proto(
400        proto: protos::location_payload::LocationUpdateAction,
401    ) -> Result<Self, ProtoConversionError> {
402        Ok(LocationUpdateAction {
403            namespace: LocationNamespace::from_proto(proto.get_namespace())?,
404            location_id: proto.get_location_id().to_string(),
405            properties: proto
406                .get_properties()
407                .iter()
408                .cloned()
409                .map(PropertyValue::from_proto)
410                .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
411        })
412    }
413}
414
415impl FromNative<LocationUpdateAction> for protos::location_payload::LocationUpdateAction {
416    fn from_native(native: LocationUpdateAction) -> Result<Self, ProtoConversionError> {
417        let mut proto = protos::location_payload::LocationUpdateAction::new();
418        proto.set_namespace((*native.namespace()).into_proto()?);
419        proto.set_location_id(native.location_id().to_string());
420        proto.set_properties(RepeatedField::from_vec(
421            native
422                .properties()
423                .iter()
424                .cloned()
425                .map(PropertyValue::into_proto)
426                .collect::<Result<Vec<protos::schema_state::PropertyValue>, ProtoConversionError>>(
427                )?,
428        ));
429
430        Ok(proto)
431    }
432}
433
434impl FromBytes<LocationUpdateAction> for LocationUpdateAction {
435    fn from_bytes(bytes: &[u8]) -> Result<LocationUpdateAction, ProtoConversionError> {
436        let proto: protos::location_payload::LocationUpdateAction =
437            Message::parse_from_bytes(bytes).map_err(|_| {
438                ProtoConversionError::SerializationError(
439                    "Unable to get LocationUpdateAction from bytes".to_string(),
440                )
441            })?;
442        proto.into_native()
443    }
444}
445
446impl IntoBytes for LocationUpdateAction {
447    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
448        let proto = self.into_proto()?;
449        let bytes = proto.write_to_bytes().map_err(|_| {
450            ProtoConversionError::SerializationError(
451                "Unable to get bytes from LocationUpdateAction".to_string(),
452            )
453        })?;
454        Ok(bytes)
455    }
456}
457
458impl IntoProto<protos::location_payload::LocationUpdateAction> for LocationUpdateAction {}
459impl IntoNative<LocationUpdateAction> for protos::location_payload::LocationUpdateAction {}
460
461/// Builder used to create an "update location" action
462#[derive(Default, Clone)]
463pub struct LocationUpdateActionBuilder {
464    namespace: Option<LocationNamespace>,
465    location_id: Option<String>,
466    properties: Vec<PropertyValue>,
467}
468
469impl LocationUpdateActionBuilder {
470    pub fn new() -> Self {
471        LocationUpdateActionBuilder::default()
472    }
473
474    pub fn with_namespace(mut self, namespace: LocationNamespace) -> Self {
475        self.namespace = Some(namespace);
476        self
477    }
478
479    pub fn with_location_id(mut self, location_id: String) -> Self {
480        self.location_id = Some(location_id);
481        self
482    }
483
484    pub fn with_properties(mut self, properties: Vec<PropertyValue>) -> Self {
485        self.properties = properties;
486        self
487    }
488
489    pub fn build(self) -> Result<LocationUpdateAction, BuilderError> {
490        let namespace = self.namespace.ok_or_else(|| {
491            BuilderError::MissingField("'namespace' field is required".to_string())
492        })?;
493
494        let location_id = self.location_id.ok_or_else(|| {
495            BuilderError::MissingField("'location_id' field is required".to_string())
496        })?;
497
498        let properties = {
499            if !self.properties.is_empty() {
500                self.properties
501            } else {
502                return Err(BuilderError::MissingField(
503                    "'properties' field is required".to_string(),
504                ));
505            }
506        };
507
508        Ok(LocationUpdateAction {
509            namespace,
510            location_id,
511            properties,
512        })
513    }
514}
515
516/// Native representation of a "delete location" action
517#[derive(Debug, Default, Clone, PartialEq)]
518pub struct LocationDeleteAction {
519    namespace: LocationNamespace,
520    location_id: String,
521}
522
523/// Native implementation for LocationDeleteAction
524impl LocationDeleteAction {
525    pub fn namespace(&self) -> &LocationNamespace {
526        &self.namespace
527    }
528
529    pub fn location_id(&self) -> &str {
530        &self.location_id
531    }
532}
533
534impl FromProto<protos::location_payload::LocationDeleteAction> for LocationDeleteAction {
535    fn from_proto(
536        proto: protos::location_payload::LocationDeleteAction,
537    ) -> Result<Self, ProtoConversionError> {
538        Ok(LocationDeleteAction {
539            namespace: LocationNamespace::from_proto(proto.get_namespace())?,
540            location_id: proto.get_location_id().to_string(),
541        })
542    }
543}
544
545impl FromNative<LocationDeleteAction> for protos::location_payload::LocationDeleteAction {
546    fn from_native(native: LocationDeleteAction) -> Result<Self, ProtoConversionError> {
547        let mut proto = protos::location_payload::LocationDeleteAction::new();
548        proto.set_namespace((*native.namespace()).into_proto()?);
549        proto.set_location_id(native.location_id().to_string());
550        Ok(proto)
551    }
552}
553
554impl FromBytes<LocationDeleteAction> for LocationDeleteAction {
555    fn from_bytes(bytes: &[u8]) -> Result<LocationDeleteAction, ProtoConversionError> {
556        let proto: protos::location_payload::LocationDeleteAction =
557            Message::parse_from_bytes(bytes).map_err(|_| {
558                ProtoConversionError::SerializationError(
559                    "Unable to get LocationDeleteAction from bytes".to_string(),
560                )
561            })?;
562        proto.into_native()
563    }
564}
565
566impl IntoBytes for LocationDeleteAction {
567    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
568        let proto = self.into_proto()?;
569        let bytes = proto.write_to_bytes().map_err(|_| {
570            ProtoConversionError::SerializationError(
571                "Unable to get bytes from LocationDeleteAction".to_string(),
572            )
573        })?;
574        Ok(bytes)
575    }
576}
577
578impl IntoProto<protos::location_payload::LocationDeleteAction> for LocationDeleteAction {}
579impl IntoNative<LocationDeleteAction> for protos::location_payload::LocationDeleteAction {}
580
581/// Builder used to create a "delete location" action
582#[derive(Default, Clone)]
583pub struct LocationDeleteActionBuilder {
584    namespace: Option<LocationNamespace>,
585    location_id: Option<String>,
586}
587
588impl LocationDeleteActionBuilder {
589    pub fn new() -> Self {
590        LocationDeleteActionBuilder::default()
591    }
592
593    pub fn with_namespace(mut self, namespace: LocationNamespace) -> Self {
594        self.namespace = Some(namespace);
595        self
596    }
597
598    pub fn with_location_id(mut self, location_id: String) -> Self {
599        self.location_id = Some(location_id);
600        self
601    }
602
603    pub fn build(self) -> Result<LocationDeleteAction, BuilderError> {
604        let namespace = self.namespace.ok_or_else(|| {
605            BuilderError::MissingField("'namespace' field is required".to_string())
606        })?;
607
608        let location_id = self.location_id.ok_or_else(|| {
609            BuilderError::MissingField("'location_id' field is required".to_string())
610        })?;
611
612        Ok(LocationDeleteAction {
613            namespace,
614            location_id,
615        })
616    }
617}