grid_sdk/protocol/location/
state.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 state
16
17use protobuf::Message;
18use protobuf::RepeatedField;
19
20use std::error::Error as StdError;
21
22use crate::protos;
23use crate::protos::schema_state;
24use crate::protos::{
25    FromBytes, FromNative, FromProto, IntoBytes, IntoNative, IntoProto, ProtoConversionError,
26};
27
28use crate::protocol::schema::state::PropertyValue;
29
30/// Possible Location namespaces
31///
32/// The namespace determines the schema used to define a Location's properties
33#[derive(Copy, Clone, Debug, PartialEq)]
34pub enum LocationNamespace {
35    Gs1,
36}
37
38impl Default for LocationNamespace {
39    fn default() -> Self {
40        LocationNamespace::Gs1
41    }
42}
43
44impl FromProto<protos::location_state::Location_LocationNamespace> for LocationNamespace {
45    fn from_proto(
46        namespace: protos::location_state::Location_LocationNamespace,
47    ) -> Result<Self, ProtoConversionError> {
48        match namespace {
49            protos::location_state::Location_LocationNamespace::GS1 => Ok(LocationNamespace::Gs1),
50            protos::location_state::Location_LocationNamespace::UNSET_TYPE => {
51                Err(ProtoConversionError::InvalidTypeError(
52                    "Cannot convert Location_LocationType with type UNSET_TYPE".to_string(),
53                ))
54            }
55        }
56    }
57}
58
59impl FromNative<LocationNamespace> for protos::location_state::Location_LocationNamespace {
60    fn from_native(namespace: LocationNamespace) -> Result<Self, ProtoConversionError> {
61        match namespace {
62            LocationNamespace::Gs1 => Ok(protos::location_state::Location_LocationNamespace::GS1),
63        }
64    }
65}
66
67impl IntoProto<protos::location_state::Location_LocationNamespace> for LocationNamespace {}
68impl IntoNative<LocationNamespace> for protos::location_state::Location_LocationNamespace {}
69
70/// Native representation of a `Location`
71///
72/// A `Location` represents an arbitrary list of properties. The properties defined in the
73/// `Location` are determined by the `Location`'s `namespace`.
74#[derive(Debug, Clone, PartialEq)]
75pub struct Location {
76    location_id: String,
77    namespace: LocationNamespace,
78    owner: String,
79    properties: Vec<PropertyValue>,
80}
81
82impl Location {
83    pub fn location_id(&self) -> &str {
84        &self.location_id
85    }
86
87    pub fn namespace(&self) -> &LocationNamespace {
88        &self.namespace
89    }
90
91    pub fn owner(&self) -> &str {
92        &self.owner
93    }
94
95    pub fn properties(&self) -> &[PropertyValue] {
96        &self.properties
97    }
98
99    pub fn into_builder(self) -> LocationBuilder {
100        LocationBuilder::new()
101            .with_location_id(self.location_id)
102            .with_namespace(self.namespace)
103            .with_owner(self.owner)
104            .with_properties(self.properties)
105    }
106}
107
108impl FromProto<protos::location_state::Location> for Location {
109    fn from_proto(
110        location: protos::location_state::Location,
111    ) -> Result<Self, ProtoConversionError> {
112        Ok(Location {
113            location_id: location.get_location_id().to_string(),
114            namespace: LocationNamespace::from_proto(location.get_namespace())?,
115            owner: location.get_owner().to_string(),
116            properties: location
117                .get_properties()
118                .iter()
119                .cloned()
120                .map(PropertyValue::from_proto)
121                .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
122        })
123    }
124}
125
126impl FromNative<Location> for protos::location_state::Location {
127    fn from_native(location: Location) -> Result<Self, ProtoConversionError> {
128        let mut proto = protos::location_state::Location::new();
129        proto.set_location_id(location.location_id().to_string());
130        proto.set_namespace(location.namespace().into_proto()?);
131        proto.set_owner(location.owner().to_string());
132        proto.set_properties(RepeatedField::from_vec(
133            location
134                .properties()
135                .iter()
136                .cloned()
137                .map(PropertyValue::into_proto)
138                .collect::<Result<Vec<schema_state::PropertyValue>, ProtoConversionError>>()?,
139        ));
140        Ok(proto)
141    }
142}
143
144impl IntoBytes for Location {
145    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
146        let proto = self.into_proto()?;
147        let bytes = proto.write_to_bytes().map_err(|_| {
148            ProtoConversionError::SerializationError(
149                "Unable to get bytes from Location".to_string(),
150            )
151        })?;
152
153        Ok(bytes)
154    }
155}
156
157impl IntoProto<protos::location_state::Location> for Location {}
158impl IntoNative<Location> for protos::location_state::Location {}
159
160/// Returned if any required fields in a `Location` are not present when being
161/// converted from the corresponding builder
162#[derive(Debug)]
163pub enum LocationBuildError {
164    MissingField(String),
165    EmptyVec(String),
166}
167
168impl StdError for LocationBuildError {
169    fn description(&self) -> &str {
170        match *self {
171            LocationBuildError::MissingField(ref msg) => msg,
172            LocationBuildError::EmptyVec(ref msg) => msg,
173        }
174    }
175
176    fn source(&self) -> Option<&(dyn StdError + 'static)> {
177        match *self {
178            LocationBuildError::MissingField(_) => None,
179            LocationBuildError::EmptyVec(_) => None,
180        }
181    }
182}
183
184impl std::fmt::Display for LocationBuildError {
185    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
186        match *self {
187            LocationBuildError::MissingField(ref s) => write!(f, "missing field \"{}\"", s),
188            LocationBuildError::EmptyVec(ref s) => write!(f, "\"{}\" must not be empty", s),
189        }
190    }
191}
192
193/// Builder used to create a `Location`
194#[derive(Default, Clone, PartialEq)]
195pub struct LocationBuilder {
196    pub location_id: Option<String>,
197    pub namespace: Option<LocationNamespace>,
198    pub owner: Option<String>,
199    pub properties: Option<Vec<PropertyValue>>,
200}
201
202impl LocationBuilder {
203    pub fn new() -> Self {
204        LocationBuilder::default()
205    }
206
207    pub fn with_location_id(mut self, location_id: String) -> Self {
208        self.location_id = Some(location_id);
209        self
210    }
211
212    pub fn with_namespace(mut self, namespace: LocationNamespace) -> Self {
213        self.namespace = Some(namespace);
214        self
215    }
216
217    pub fn with_owner(mut self, owner: String) -> Self {
218        self.owner = Some(owner);
219        self
220    }
221
222    pub fn with_properties(mut self, properties: Vec<PropertyValue>) -> Self {
223        self.properties = Some(properties);
224        self
225    }
226
227    pub fn build(self) -> Result<Location, LocationBuildError> {
228        let location_id = self.location_id.ok_or_else(|| {
229            LocationBuildError::MissingField("'location_id' field is required".to_string())
230        })?;
231
232        let namespace = self.namespace.ok_or_else(|| {
233            LocationBuildError::MissingField("'namespace' field is required".to_string())
234        })?;
235
236        let owner = self.owner.ok_or_else(|| {
237            LocationBuildError::MissingField("'owner' field is required".to_string())
238        })?;
239
240        let properties = self.properties.ok_or_else(|| {
241            LocationBuildError::MissingField("'properties' field is required".to_string())
242        })?;
243
244        Ok(Location {
245            location_id,
246            namespace,
247            owner,
248            properties,
249        })
250    }
251}
252
253/// Native representation of a list of `Location`s
254#[derive(Debug, Clone, PartialEq)]
255pub struct LocationList {
256    locations: Vec<Location>,
257}
258
259impl LocationList {
260    pub fn locations(&self) -> &[Location] {
261        &self.locations
262    }
263
264    pub fn into_builder(self) -> LocationListBuilder {
265        LocationListBuilder::new().with_locations(self.locations)
266    }
267}
268
269impl FromProto<protos::location_state::LocationList> for LocationList {
270    fn from_proto(
271        location_list: protos::location_state::LocationList,
272    ) -> Result<Self, ProtoConversionError> {
273        Ok(LocationList {
274            locations: location_list
275                .get_entries()
276                .iter()
277                .cloned()
278                .map(Location::from_proto)
279                .collect::<Result<Vec<Location>, ProtoConversionError>>()?,
280        })
281    }
282}
283
284impl FromNative<LocationList> for protos::location_state::LocationList {
285    fn from_native(location_list: LocationList) -> Result<Self, ProtoConversionError> {
286        let mut location_list_proto = protos::location_state::LocationList::new();
287
288        location_list_proto.set_entries(RepeatedField::from_vec(
289            location_list
290                .locations()
291                .iter()
292                .cloned()
293                .map(Location::into_proto)
294                .collect::<Result<Vec<protos::location_state::Location>, ProtoConversionError>>()?,
295        ));
296
297        Ok(location_list_proto)
298    }
299}
300
301impl FromBytes<LocationList> for LocationList {
302    fn from_bytes(bytes: &[u8]) -> Result<LocationList, ProtoConversionError> {
303        let proto: protos::location_state::LocationList = Message::parse_from_bytes(bytes)
304            .map_err(|_| {
305                ProtoConversionError::SerializationError(
306                    "Unable to get LocationList from bytes".to_string(),
307                )
308            })?;
309
310        proto.into_native()
311    }
312}
313
314impl IntoBytes for LocationList {
315    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
316        let proto = self.into_proto()?;
317        let bytes = proto.write_to_bytes().map_err(|_| {
318            ProtoConversionError::SerializationError(
319                "Unable to get bytes from LocationList".to_string(),
320            )
321        })?;
322
323        Ok(bytes)
324    }
325}
326
327impl IntoProto<protos::location_state::LocationList> for LocationList {}
328impl IntoNative<LocationList> for protos::location_state::LocationList {}
329
330/// Builder used to create a `LocationList`
331#[derive(Default, Clone)]
332pub struct LocationListBuilder {
333    pub locations: Option<Vec<Location>>,
334}
335
336impl LocationListBuilder {
337    pub fn new() -> Self {
338        LocationListBuilder::default()
339    }
340
341    pub fn with_locations(mut self, locations: Vec<Location>) -> LocationListBuilder {
342        self.locations = Some(locations);
343        self
344    }
345
346    pub fn build(self) -> Result<LocationList, LocationBuildError> {
347        // Product values are not required
348        let locations = self
349            .locations
350            .ok_or_else(|| LocationBuildError::MissingField("locations".to_string()))?;
351
352        let locations = {
353            if locations.is_empty() {
354                return Err(LocationBuildError::EmptyVec("locations".to_string()));
355            } else {
356                locations
357            }
358        };
359
360        Ok(LocationList { locations })
361    }
362}