grid_sdk/protocol/product/
state.rs1use 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#[derive(Debug, Clone, PartialEq)]
34pub enum ProductNamespace {
35 Gs1,
36}
37
38impl Default for ProductNamespace {
39 fn default() -> Self {
40 ProductNamespace::Gs1
41 }
42}
43
44impl FromProto<protos::product_state::Product_ProductNamespace> for ProductNamespace {
45 fn from_proto(
46 product_namespace: protos::product_state::Product_ProductNamespace,
47 ) -> Result<Self, ProtoConversionError> {
48 match product_namespace {
49 protos::product_state::Product_ProductNamespace::GS1 => Ok(ProductNamespace::Gs1),
50 protos::product_state::Product_ProductNamespace::UNSET_TYPE => {
51 Err(ProtoConversionError::InvalidTypeError(
52 "Cannot convert Product_ProductNamespace with type UNSET_TYPE".to_string(),
53 ))
54 }
55 }
56 }
57}
58
59impl FromNative<ProductNamespace> for protos::product_state::Product_ProductNamespace {
60 fn from_native(product_namespace: ProductNamespace) -> Result<Self, ProtoConversionError> {
61 match product_namespace {
62 ProductNamespace::Gs1 => Ok(protos::product_state::Product_ProductNamespace::GS1),
63 }
64 }
65}
66
67impl IntoProto<protos::product_state::Product_ProductNamespace> for ProductNamespace {}
68impl IntoNative<ProductNamespace> for protos::product_state::Product_ProductNamespace {}
69
70#[derive(Debug, Clone, PartialEq)]
74pub struct Product {
75 product_id: String,
76 product_namespace: ProductNamespace,
77 owner: String,
78 properties: Vec<PropertyValue>,
79}
80
81impl Product {
82 pub fn product_id(&self) -> &str {
83 &self.product_id
84 }
85
86 pub fn product_namespace(&self) -> &ProductNamespace {
87 &self.product_namespace
88 }
89
90 pub fn owner(&self) -> &str {
91 &self.owner
92 }
93
94 pub fn properties(&self) -> &[PropertyValue] {
95 &self.properties
96 }
97
98 pub fn into_builder(self) -> ProductBuilder {
99 ProductBuilder::new()
100 .with_product_id(self.product_id)
101 .with_product_namespace(self.product_namespace)
102 .with_owner(self.owner)
103 .with_properties(self.properties)
104 }
105}
106
107impl FromProto<protos::product_state::Product> for Product {
108 fn from_proto(product: protos::product_state::Product) -> Result<Self, ProtoConversionError> {
109 Ok(Product {
110 product_id: product.get_product_id().to_string(),
111 product_namespace: ProductNamespace::from_proto(product.get_product_namespace())?,
112 owner: product.get_owner().to_string(),
113 properties: product
114 .get_properties()
115 .iter()
116 .cloned()
117 .map(PropertyValue::from_proto)
118 .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
119 })
120 }
121}
122
123impl FromNative<Product> for protos::product_state::Product {
124 fn from_native(product: Product) -> Result<Self, ProtoConversionError> {
125 let mut proto = protos::product_state::Product::new();
126 proto.set_product_id(product.product_id().to_string());
127 proto.set_product_namespace(product.product_namespace().clone().into_proto()?);
128 proto.set_owner(product.owner().to_string());
129 proto.set_properties(RepeatedField::from_vec(
130 product
131 .properties()
132 .iter()
133 .cloned()
134 .map(PropertyValue::into_proto)
135 .collect::<Result<Vec<schema_state::PropertyValue>, ProtoConversionError>>()?,
136 ));
137 Ok(proto)
138 }
139}
140
141impl FromBytes<Product> for Product {
142 fn from_bytes(bytes: &[u8]) -> Result<Product, ProtoConversionError> {
143 let proto: protos::product_state::Product =
144 Message::parse_from_bytes(bytes).map_err(|_| {
145 ProtoConversionError::SerializationError(
146 "Unable to get Product from bytes".to_string(),
147 )
148 })?;
149 proto.into_native()
150 }
151}
152
153impl IntoBytes for Product {
154 fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
155 let proto = self.into_proto()?;
156 let bytes = proto.write_to_bytes().map_err(|_| {
157 ProtoConversionError::SerializationError("Unable to get bytes from Product".to_string())
158 })?;
159 Ok(bytes)
160 }
161}
162
163impl IntoProto<protos::product_state::Product> for Product {}
164impl IntoNative<Product> for protos::product_state::Product {}
165
166#[derive(Debug)]
169pub enum ProductBuildError {
170 MissingField(String),
171 EmptyVec(String),
172}
173
174impl StdError for ProductBuildError {
175 fn description(&self) -> &str {
176 match *self {
177 ProductBuildError::MissingField(ref msg) => msg,
178 ProductBuildError::EmptyVec(ref msg) => msg,
179 }
180 }
181
182 fn source(&self) -> Option<&(dyn StdError + 'static)> {
183 match *self {
184 ProductBuildError::MissingField(_) => None,
185 ProductBuildError::EmptyVec(_) => None,
186 }
187 }
188}
189
190impl std::fmt::Display for ProductBuildError {
191 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
192 match *self {
193 ProductBuildError::MissingField(ref s) => write!(f, "missing field \"{}\"", s),
194 ProductBuildError::EmptyVec(ref s) => write!(f, "\"{}\" must not be empty", s),
195 }
196 }
197}
198
199#[derive(Default, Clone, PartialEq)]
201pub struct ProductBuilder {
202 pub product_id: Option<String>,
203 pub product_namespace: Option<ProductNamespace>,
204 pub owner: Option<String>,
205 pub properties: Option<Vec<PropertyValue>>,
206}
207
208impl ProductBuilder {
209 pub fn new() -> Self {
210 ProductBuilder::default()
211 }
212
213 pub fn with_product_id(mut self, product_id: String) -> Self {
214 self.product_id = Some(product_id);
215 self
216 }
217
218 pub fn with_product_namespace(mut self, product_namespace: ProductNamespace) -> Self {
219 self.product_namespace = Some(product_namespace);
220 self
221 }
222
223 pub fn with_owner(mut self, owner: String) -> Self {
224 self.owner = Some(owner);
225 self
226 }
227
228 pub fn with_properties(mut self, properties: Vec<PropertyValue>) -> Self {
229 self.properties = Some(properties);
230 self
231 }
232
233 pub fn build(self) -> Result<Product, ProductBuildError> {
234 let product_id = self.product_id.ok_or_else(|| {
235 ProductBuildError::MissingField("'product_id' field is required".to_string())
236 })?;
237
238 let product_namespace = self.product_namespace.ok_or_else(|| {
239 ProductBuildError::MissingField("'product_namespace' field is required".to_string())
240 })?;
241
242 let owner = self.owner.ok_or_else(|| {
243 ProductBuildError::MissingField("'owner' field is required".to_string())
244 })?;
245
246 let properties = self.properties.ok_or_else(|| {
248 ProductBuildError::MissingField("'properties' field is required".to_string())
249 })?;
250
251 Ok(Product {
252 product_id,
253 product_namespace,
254 owner,
255 properties,
256 })
257 }
258}
259
260#[derive(Debug, Clone, PartialEq)]
262pub struct ProductList {
263 products: Vec<Product>,
264}
265
266impl ProductList {
267 pub fn products(&self) -> &[Product] {
268 &self.products
269 }
270
271 pub fn into_builder(self) -> ProductListBuilder {
272 ProductListBuilder::new().with_products(self.products)
273 }
274}
275
276impl FromProto<protos::product_state::ProductList> for ProductList {
277 fn from_proto(
278 product_list: protos::product_state::ProductList,
279 ) -> Result<Self, ProtoConversionError> {
280 Ok(ProductList {
281 products: product_list
282 .get_entries()
283 .iter()
284 .cloned()
285 .map(Product::from_proto)
286 .collect::<Result<Vec<Product>, ProtoConversionError>>()?,
287 })
288 }
289}
290
291impl FromNative<ProductList> for protos::product_state::ProductList {
292 fn from_native(product_list: ProductList) -> Result<Self, ProtoConversionError> {
293 let mut product_list_proto = protos::product_state::ProductList::new();
294
295 product_list_proto.set_entries(RepeatedField::from_vec(
296 product_list
297 .products()
298 .iter()
299 .cloned()
300 .map(Product::into_proto)
301 .collect::<Result<Vec<protos::product_state::Product>, ProtoConversionError>>()?,
302 ));
303
304 Ok(product_list_proto)
305 }
306}
307
308impl FromBytes<ProductList> for ProductList {
309 fn from_bytes(bytes: &[u8]) -> Result<ProductList, ProtoConversionError> {
310 let proto: protos::product_state::ProductList =
311 Message::parse_from_bytes(bytes).map_err(|_| {
312 ProtoConversionError::SerializationError(
313 "Unable to get ProductList from bytes".to_string(),
314 )
315 })?;
316 proto.into_native()
317 }
318}
319
320impl IntoBytes for ProductList {
321 fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
322 let proto = self.into_proto()?;
323 let bytes = proto.write_to_bytes().map_err(|_| {
324 ProtoConversionError::SerializationError(
325 "Unable to get bytes from ProductList".to_string(),
326 )
327 })?;
328 Ok(bytes)
329 }
330}
331
332impl IntoProto<protos::product_state::ProductList> for ProductList {}
333impl IntoNative<ProductList> for protos::product_state::ProductList {}
334
335#[derive(Debug)]
338pub enum ProductListBuildError {
339 MissingField(String),
340}
341
342impl StdError for ProductListBuildError {
343 fn description(&self) -> &str {
344 match *self {
345 ProductListBuildError::MissingField(ref msg) => msg,
346 }
347 }
348
349 fn source(&self) -> Option<&(dyn StdError + 'static)> {
350 match *self {
351 ProductListBuildError::MissingField(_) => None,
352 }
353 }
354}
355
356impl std::fmt::Display for ProductListBuildError {
357 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
358 match *self {
359 ProductListBuildError::MissingField(ref s) => write!(f, "missing field \"{}\"", s),
360 }
361 }
362}
363
364#[derive(Default, Clone)]
366pub struct ProductListBuilder {
367 pub products: Option<Vec<Product>>,
368}
369
370impl ProductListBuilder {
371 pub fn new() -> Self {
372 ProductListBuilder::default()
373 }
374
375 pub fn with_products(mut self, products: Vec<Product>) -> ProductListBuilder {
376 self.products = Some(products);
377 self
378 }
379
380 pub fn build(self) -> Result<ProductList, ProductListBuildError> {
381 let products = self.products.ok_or_else(|| {
383 ProductListBuildError::MissingField("'products' field is required".to_string())
384 })?;
385
386 let products = {
387 if products.is_empty() {
388 return Err(ProductListBuildError::MissingField(
389 "'products' cannot be empty".to_string(),
390 ));
391 } else {
392 products
393 }
394 };
395
396 Ok(ProductList { products })
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::protocol::schema::state::{DataType, PropertyValueBuilder};
404 use std::fmt::Debug;
405
406 #[test]
407 fn test_product_builder() {
409 let product = build_product();
410
411 assert_eq!(product.product_id(), "688955434684");
412 assert_eq!(*product.product_namespace(), ProductNamespace::Gs1);
413 assert_eq!(product.owner(), "Target");
414 assert_eq!(product.properties()[0].name(), "description");
415 assert_eq!(*product.properties()[0].data_type(), DataType::String);
416 assert_eq!(
417 product.properties()[0].string_value(),
418 "This is a product description"
419 );
420 assert_eq!(product.properties()[1].name(), "price");
421 assert_eq!(*product.properties()[1].data_type(), DataType::Number);
422 assert_eq!(*product.properties()[1].number_value(), 3);
423 }
424
425 #[test]
426 fn test_product_into_builder() {
428 let product = build_product();
429
430 let builder = product.into_builder();
431
432 assert_eq!(builder.product_id, Some("688955434684".to_string()));
433 assert_eq!(builder.product_namespace, Some(ProductNamespace::Gs1));
434 assert_eq!(builder.owner, Some("Target".to_string()));
435 assert_eq!(builder.properties, Some(make_properties()));
436 }
437
438 #[test]
439 fn test_product_into_bytes() {
442 let builder = ProductBuilder::new();
443 let original = builder
444 .with_product_id("688955434684".into())
445 .with_product_namespace(ProductNamespace::Gs1)
446 .with_owner("Target".into())
447 .with_properties(make_properties())
448 .build()
449 .unwrap();
450
451 test_from_bytes(original, Product::from_bytes);
452 }
453
454 #[test]
455 fn test_product_list_builder() {
457 let product_list = build_product_list();
458
459 assert_eq!(product_list.products.len(), 2);
460
461 assert_eq!(product_list.products[0].product_id(), "688955434684");
463 assert_eq!(
464 *product_list.products[0].product_namespace(),
465 ProductNamespace::Gs1
466 );
467 assert_eq!(product_list.products[0].owner(), "Target");
468 assert_eq!(
469 product_list.products[0].properties()[0].name(),
470 "description"
471 );
472 assert_eq!(
473 *product_list.products[0].properties()[0].data_type(),
474 DataType::String
475 );
476 assert_eq!(
477 product_list.products[0].properties()[0].string_value(),
478 "This is a product description"
479 );
480 assert_eq!(product_list.products[0].properties()[1].name(), "price");
481 assert_eq!(
482 *product_list.products[0].properties()[1].data_type(),
483 DataType::Number
484 );
485 assert_eq!(*product_list.products[0].properties()[1].number_value(), 3);
486
487 assert_eq!(product_list.products[1].product_id(), "688955434685");
489 assert_eq!(
490 *product_list.products[1].product_namespace(),
491 ProductNamespace::Gs1
492 );
493 assert_eq!(product_list.products[1].owner(), "Cargill");
494 assert_eq!(
495 product_list.products[1].properties()[0].name(),
496 "description"
497 );
498 assert_eq!(
499 *product_list.products[1].properties()[0].data_type(),
500 DataType::String
501 );
502 assert_eq!(
503 product_list.products[1].properties()[0].string_value(),
504 "This is a product description"
505 );
506 assert_eq!(product_list.products[1].properties()[1].name(), "price");
507 assert_eq!(
508 *product_list.products[1].properties()[1].data_type(),
509 DataType::Number
510 );
511 assert_eq!(*product_list.products[1].properties()[1].number_value(), 3);
512 }
513
514 #[test]
515 fn test_product_list_into_builder() {
517 let product_list = build_product_list();
518
519 let builder = product_list.into_builder();
520
521 assert_eq!(builder.products, Some(make_products()));
522 }
523
524 #[test]
525 fn test_product_list_into_bytes() {
528 let builder = ProductListBuilder::new();
529 let original = builder.with_products(make_products()).build().unwrap();
530
531 test_from_bytes(original, ProductList::from_bytes);
532 }
533
534 fn build_product() -> Product {
535 ProductBuilder::new()
536 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
538 .with_owner("Target".into())
539 .with_properties(make_properties())
540 .build()
541 .expect("Failed to build test product")
542 }
543
544 fn make_properties() -> Vec<PropertyValue> {
545 let property_value_description = PropertyValueBuilder::new()
546 .with_name("description".into())
547 .with_data_type(DataType::String)
548 .with_string_value("This is a product description".into())
549 .build()
550 .unwrap();
551 let property_value_price = PropertyValueBuilder::new()
552 .with_name("price".into())
553 .with_data_type(DataType::Number)
554 .with_number_value(3)
555 .build()
556 .unwrap();
557
558 vec![
559 property_value_description.clone(),
560 property_value_price.clone(),
561 ]
562 }
563
564 fn build_product_list() -> ProductList {
565 ProductListBuilder::new()
566 .with_products(make_products())
567 .build()
568 .expect("Failed to build test product list")
569 }
570
571 fn make_products() -> Vec<Product> {
572 vec![
573 ProductBuilder::new()
574 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
576 .with_owner("Target".into())
577 .with_properties(make_properties())
578 .build()
579 .expect("Failed to build test product"),
580 ProductBuilder::new()
581 .with_product_id("688955434685".into()) .with_product_namespace(ProductNamespace::Gs1)
583 .with_owner("Cargill".into())
584 .with_properties(make_properties())
585 .build()
586 .expect("Failed to build test product"),
587 ]
588 }
589
590 fn test_from_bytes<T: FromBytes<T> + Clone + PartialEq + IntoBytes + Debug, F>(
591 under_test: T,
592 from_bytes: F,
593 ) where
594 F: Fn(&[u8]) -> Result<T, ProtoConversionError>,
595 {
596 let bytes = under_test.clone().into_bytes().unwrap();
597 let created_from_bytes = from_bytes(&bytes).unwrap();
598 assert_eq!(under_test, created_from_bytes);
599 }
600}