1use protobuf::Message;
18use protobuf::RepeatedField;
19
20use std::error::Error as StdError;
21
22use super::errors::BuilderError;
23
24use crate::protocol::{product::state::ProductNamespace, schema::state::PropertyValue};
25use crate::protos;
26use crate::protos::{product_payload, product_payload::ProductPayload_Action};
27use crate::protos::{
28 FromBytes, FromNative, FromProto, IntoBytes, IntoNative, IntoProto, ProtoConversionError,
29};
30
31#[derive(Debug, Clone, PartialEq)]
33pub enum Action {
34 ProductCreate(ProductCreateAction),
35 ProductUpdate(ProductUpdateAction),
36 ProductDelete(ProductDeleteAction),
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub struct ProductPayload {
42 action: Action,
43 timestamp: u64,
44}
45
46impl ProductPayload {
47 pub fn action(&self) -> &Action {
48 &self.action
49 }
50 pub fn timestamp(&self) -> &u64 {
51 &self.timestamp
52 }
53}
54
55impl FromProto<protos::product_payload::ProductPayload> for ProductPayload {
56 fn from_proto(
57 payload: protos::product_payload::ProductPayload,
58 ) -> Result<Self, ProtoConversionError> {
59 let action = match payload.get_action() {
60 ProductPayload_Action::PRODUCT_CREATE => Action::ProductCreate(
61 ProductCreateAction::from_proto(payload.get_product_create().clone())?,
62 ),
63 ProductPayload_Action::PRODUCT_UPDATE => Action::ProductUpdate(
64 ProductUpdateAction::from_proto(payload.get_product_update().clone())?,
65 ),
66 ProductPayload_Action::PRODUCT_DELETE => Action::ProductDelete(
67 ProductDeleteAction::from_proto(payload.get_product_delete().clone())?,
68 ),
69 ProductPayload_Action::UNSET_ACTION => {
70 return Err(ProtoConversionError::InvalidTypeError(
71 "Cannot convert ProductPayload_Action with type unset".to_string(),
72 ));
73 }
74 };
75 Ok(ProductPayload {
76 action,
77 timestamp: payload.get_timestamp(),
78 })
79 }
80}
81
82impl FromNative<ProductPayload> for protos::product_payload::ProductPayload {
83 fn from_native(native: ProductPayload) -> Result<Self, ProtoConversionError> {
84 let mut proto = product_payload::ProductPayload::new();
85
86 proto.set_timestamp(*native.timestamp());
87
88 match native.action() {
89 Action::ProductCreate(payload) => {
90 proto.set_action(ProductPayload_Action::PRODUCT_CREATE);
91 proto.set_product_create(payload.clone().into_proto()?);
92 }
93 Action::ProductUpdate(payload) => {
94 proto.set_action(ProductPayload_Action::PRODUCT_UPDATE);
95 proto.set_product_update(payload.clone().into_proto()?);
96 }
97 Action::ProductDelete(payload) => {
98 proto.set_action(ProductPayload_Action::PRODUCT_DELETE);
99 proto.set_product_delete(payload.clone().into_proto()?);
100 }
101 }
102
103 Ok(proto)
104 }
105}
106
107impl FromBytes<ProductPayload> for ProductPayload {
108 fn from_bytes(bytes: &[u8]) -> Result<ProductPayload, ProtoConversionError> {
109 let proto: product_payload::ProductPayload =
110 Message::parse_from_bytes(bytes).map_err(|_| {
111 ProtoConversionError::SerializationError(
112 "Unable to get ProductPayload from bytes".into(),
113 )
114 })?;
115 proto.into_native()
116 }
117}
118
119impl IntoBytes for ProductPayload {
120 fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
121 let proto = self.into_proto()?;
122 let bytes = proto.write_to_bytes().map_err(|_| {
123 ProtoConversionError::SerializationError(
124 "Unable to get ProductPayload from bytes".into(),
125 )
126 })?;
127 Ok(bytes)
128 }
129}
130
131impl IntoProto<protos::product_payload::ProductPayload> for ProductPayload {}
132impl IntoNative<ProductPayload> for protos::product_payload::ProductPayload {}
133
134#[derive(Debug)]
137pub enum ProductPayloadBuildError {
138 MissingField(String),
139}
140
141impl StdError for ProductPayloadBuildError {
142 fn description(&self) -> &str {
143 match *self {
144 ProductPayloadBuildError::MissingField(ref msg) => msg,
145 }
146 }
147
148 fn cause(&self) -> Option<&dyn StdError> {
149 match *self {
150 ProductPayloadBuildError::MissingField(_) => None,
151 }
152 }
153}
154
155impl std::fmt::Display for ProductPayloadBuildError {
156 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
157 match *self {
158 ProductPayloadBuildError::MissingField(ref s) => write!(f, "missing field \"{}\"", s),
159 }
160 }
161}
162
163#[derive(Default, Clone)]
165pub struct ProductPayloadBuilder {
166 action: Option<Action>,
167 timestamp: Option<u64>,
168}
169
170impl ProductPayloadBuilder {
171 pub fn new() -> Self {
172 ProductPayloadBuilder::default()
173 }
174 pub fn with_action(mut self, action: Action) -> Self {
175 self.action = Some(action);
176 self
177 }
178 pub fn with_timestamp(mut self, value: u64) -> Self {
179 self.timestamp = Some(value);
180 self
181 }
182 pub fn build(self) -> Result<ProductPayload, BuilderError> {
183 let action = self
184 .action
185 .ok_or_else(|| BuilderError::MissingField("'action' field is required".into()))?;
186 let timestamp = self
187 .timestamp
188 .ok_or_else(|| BuilderError::MissingField("'timestamp' field is required".into()))?;
189 Ok(ProductPayload { action, timestamp })
190 }
191}
192
193#[derive(Debug, Default, Clone, PartialEq)]
195pub struct ProductCreateAction {
196 product_namespace: ProductNamespace,
197 product_id: String,
198 owner: String,
199 properties: Vec<PropertyValue>,
200}
201
202impl ProductCreateAction {
203 pub fn product_namespace(&self) -> &ProductNamespace {
204 &self.product_namespace
205 }
206
207 pub fn product_id(&self) -> &str {
208 &self.product_id
209 }
210
211 pub fn owner(&self) -> &str {
212 &self.owner
213 }
214
215 pub fn properties(&self) -> &[PropertyValue] {
216 &self.properties
217 }
218}
219
220impl FromProto<product_payload::ProductCreateAction> for ProductCreateAction {
221 fn from_proto(
222 proto: product_payload::ProductCreateAction,
223 ) -> Result<Self, ProtoConversionError> {
224 Ok(ProductCreateAction {
225 product_namespace: ProductNamespace::from_proto(proto.get_product_namespace())?,
226 product_id: proto.get_product_id().to_string(),
227 owner: proto.get_owner().to_string(),
228 properties: proto
229 .get_properties()
230 .iter()
231 .cloned()
232 .map(PropertyValue::from_proto)
233 .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
234 })
235 }
236}
237
238impl FromNative<ProductCreateAction> for product_payload::ProductCreateAction {
239 fn from_native(native: ProductCreateAction) -> Result<Self, ProtoConversionError> {
240 let mut proto = protos::product_payload::ProductCreateAction::new();
241 proto.set_product_namespace(native.product_namespace().clone().into_proto()?);
242 proto.set_product_id(native.product_id().to_string());
243 proto.set_owner(native.owner().to_string());
244 proto.set_properties(RepeatedField::from_vec(
245 native
246 .properties()
247 .iter()
248 .cloned()
249 .map(PropertyValue::into_proto)
250 .collect::<Result<Vec<protos::schema_state::PropertyValue>, ProtoConversionError>>(
251 )?,
252 ));
253 Ok(proto)
254 }
255}
256
257impl FromBytes<ProductCreateAction> for ProductCreateAction {
258 fn from_bytes(bytes: &[u8]) -> Result<ProductCreateAction, ProtoConversionError> {
259 let proto: protos::product_payload::ProductCreateAction = Message::parse_from_bytes(bytes)
260 .map_err(|_| {
261 ProtoConversionError::SerializationError(
262 "Unable to get ProductCreateAction from bytes".to_string(),
263 )
264 })?;
265 proto.into_native()
266 }
267}
268
269impl IntoBytes for ProductCreateAction {
270 fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
271 let proto = self.into_proto()?;
272 let bytes = proto.write_to_bytes().map_err(|_| {
273 ProtoConversionError::SerializationError(
274 "Unable to get bytes from ProductCreateAction".to_string(),
275 )
276 })?;
277 Ok(bytes)
278 }
279}
280
281impl IntoProto<protos::product_payload::ProductCreateAction> for ProductCreateAction {}
282impl IntoNative<ProductCreateAction> for protos::product_payload::ProductCreateAction {}
283
284#[derive(Default, Debug)]
286pub struct ProductCreateActionBuilder {
287 product_namespace: Option<ProductNamespace>,
288 product_id: Option<String>,
289 owner: Option<String>,
290 properties: Option<Vec<PropertyValue>>,
291}
292
293impl ProductCreateActionBuilder {
294 pub fn new() -> Self {
295 ProductCreateActionBuilder::default()
296 }
297 pub fn with_product_namespace(mut self, value: ProductNamespace) -> Self {
298 self.product_namespace = Some(value);
299 self
300 }
301 pub fn with_product_id(mut self, value: String) -> Self {
302 self.product_id = Some(value);
303 self
304 }
305 pub fn with_owner(mut self, value: String) -> Self {
306 self.owner = Some(value);
307 self
308 }
309 pub fn with_properties(mut self, value: Vec<PropertyValue>) -> Self {
310 self.properties = Some(value);
311 self
312 }
313 pub fn build(self) -> Result<ProductCreateAction, BuilderError> {
314 let product_namespace = self.product_namespace.ok_or_else(|| {
315 BuilderError::MissingField("'product_namespace' field is required".to_string())
316 })?;
317 let product_id = self
318 .product_id
319 .ok_or_else(|| BuilderError::MissingField("'product_id' field is required".into()))?;
320 let owner = self
321 .owner
322 .ok_or_else(|| BuilderError::MissingField("'owner' field is required".into()))?;
323 let properties = self
324 .properties
325 .ok_or_else(|| BuilderError::MissingField("'properties' field is required".into()))?;
326 Ok(ProductCreateAction {
327 product_namespace,
328 product_id,
329 owner,
330 properties,
331 })
332 }
333}
334
335#[derive(Debug, Default, Clone, PartialEq)]
337pub struct ProductUpdateAction {
338 product_namespace: ProductNamespace,
339 product_id: String,
340 properties: Vec<PropertyValue>,
341}
342
343impl ProductUpdateAction {
344 pub fn product_namespace(&self) -> &ProductNamespace {
345 &self.product_namespace
346 }
347
348 pub fn product_id(&self) -> &str {
349 &self.product_id
350 }
351
352 pub fn properties(&self) -> &[PropertyValue] {
353 &self.properties
354 }
355}
356
357impl FromProto<protos::product_payload::ProductUpdateAction> for ProductUpdateAction {
358 fn from_proto(
359 proto: protos::product_payload::ProductUpdateAction,
360 ) -> Result<Self, ProtoConversionError> {
361 Ok(ProductUpdateAction {
362 product_namespace: ProductNamespace::from_proto(proto.get_product_namespace())?,
363 product_id: proto.get_product_id().to_string(),
364 properties: proto
365 .get_properties()
366 .iter()
367 .cloned()
368 .map(PropertyValue::from_proto)
369 .collect::<Result<Vec<PropertyValue>, ProtoConversionError>>()?,
370 })
371 }
372}
373
374impl FromNative<ProductUpdateAction> for protos::product_payload::ProductUpdateAction {
375 fn from_native(native: ProductUpdateAction) -> Result<Self, ProtoConversionError> {
376 let mut proto = protos::product_payload::ProductUpdateAction::new();
377 proto.set_product_namespace(native.product_namespace().clone().into_proto()?);
378 proto.set_product_id(native.product_id().to_string());
379 proto.set_properties(RepeatedField::from_vec(
380 native
381 .properties()
382 .iter()
383 .cloned()
384 .map(PropertyValue::into_proto)
385 .collect::<Result<Vec<protos::schema_state::PropertyValue>, ProtoConversionError>>(
386 )?,
387 ));
388
389 Ok(proto)
390 }
391}
392
393impl FromBytes<ProductUpdateAction> for ProductUpdateAction {
394 fn from_bytes(bytes: &[u8]) -> Result<ProductUpdateAction, ProtoConversionError> {
395 let proto: protos::product_payload::ProductUpdateAction = Message::parse_from_bytes(bytes)
396 .map_err(|_| {
397 ProtoConversionError::SerializationError(
398 "Unable to get ProductUpdateAction from bytes".to_string(),
399 )
400 })?;
401 proto.into_native()
402 }
403}
404
405impl IntoBytes for ProductUpdateAction {
406 fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
407 let proto = self.into_proto()?;
408 let bytes = proto.write_to_bytes().map_err(|_| {
409 ProtoConversionError::SerializationError(
410 "Unable to get bytes from ProductUpdateAction".to_string(),
411 )
412 })?;
413 Ok(bytes)
414 }
415}
416
417impl IntoProto<protos::product_payload::ProductUpdateAction> for ProductUpdateAction {}
418impl IntoNative<ProductUpdateAction> for protos::product_payload::ProductUpdateAction {}
419
420#[derive(Default, Clone)]
422pub struct ProductUpdateActionBuilder {
423 product_namespace: Option<ProductNamespace>,
424 product_id: Option<String>,
425 properties: Vec<PropertyValue>,
426}
427
428impl ProductUpdateActionBuilder {
429 pub fn new() -> Self {
430 ProductUpdateActionBuilder::default()
431 }
432
433 pub fn with_product_namespace(mut self, product_namespace: ProductNamespace) -> Self {
434 self.product_namespace = Some(product_namespace);
435 self
436 }
437
438 pub fn with_product_id(mut self, product_id: String) -> Self {
439 self.product_id = Some(product_id);
440 self
441 }
442
443 pub fn with_properties(mut self, properties: Vec<PropertyValue>) -> Self {
444 self.properties = properties;
445 self
446 }
447
448 pub fn build(self) -> Result<ProductUpdateAction, BuilderError> {
449 let product_namespace = self.product_namespace.ok_or_else(|| {
450 BuilderError::MissingField("'product_namespace' field is required".to_string())
451 })?;
452
453 let product_id = self.product_id.ok_or_else(|| {
454 BuilderError::MissingField("'product_id' field is required".to_string())
455 })?;
456
457 let properties = {
458 if !self.properties.is_empty() {
459 self.properties
460 } else {
461 return Err(BuilderError::MissingField(
462 "'properties' field is required".to_string(),
463 ));
464 }
465 };
466
467 Ok(ProductUpdateAction {
468 product_namespace,
469 product_id,
470 properties,
471 })
472 }
473}
474
475#[derive(Debug, Default, Clone, PartialEq)]
477pub struct ProductDeleteAction {
478 product_namespace: ProductNamespace,
479 product_id: String,
480}
481
482impl ProductDeleteAction {
483 pub fn product_namespace(&self) -> &ProductNamespace {
484 &self.product_namespace
485 }
486
487 pub fn product_id(&self) -> &str {
488 &self.product_id
489 }
490}
491
492impl FromProto<protos::product_payload::ProductDeleteAction> for ProductDeleteAction {
493 fn from_proto(
494 proto: protos::product_payload::ProductDeleteAction,
495 ) -> Result<Self, ProtoConversionError> {
496 Ok(ProductDeleteAction {
497 product_namespace: ProductNamespace::from_proto(proto.get_product_namespace())?,
498 product_id: proto.get_product_id().to_string(),
499 })
500 }
501}
502
503impl FromNative<ProductDeleteAction> for protos::product_payload::ProductDeleteAction {
504 fn from_native(native: ProductDeleteAction) -> Result<Self, ProtoConversionError> {
505 let mut proto = protos::product_payload::ProductDeleteAction::new();
506 proto.set_product_namespace(native.product_namespace().clone().into_proto()?);
507 proto.set_product_id(native.product_id().to_string());
508 Ok(proto)
509 }
510}
511
512impl FromBytes<ProductDeleteAction> for ProductDeleteAction {
513 fn from_bytes(bytes: &[u8]) -> Result<ProductDeleteAction, ProtoConversionError> {
514 let proto: protos::product_payload::ProductDeleteAction = Message::parse_from_bytes(bytes)
515 .map_err(|_| {
516 ProtoConversionError::SerializationError(
517 "Unable to get ProductDeleteAction from bytes".to_string(),
518 )
519 })?;
520 proto.into_native()
521 }
522}
523
524impl IntoBytes for ProductDeleteAction {
525 fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
526 let proto = self.into_proto()?;
527 let bytes = proto.write_to_bytes().map_err(|_| {
528 ProtoConversionError::SerializationError(
529 "Unable to get bytes from ProductDeleteAction".to_string(),
530 )
531 })?;
532 Ok(bytes)
533 }
534}
535
536impl IntoProto<protos::product_payload::ProductDeleteAction> for ProductDeleteAction {}
537impl IntoNative<ProductDeleteAction> for protos::product_payload::ProductDeleteAction {}
538
539#[derive(Default, Clone)]
541pub struct ProductDeleteActionBuilder {
542 product_namespace: Option<ProductNamespace>,
543 product_id: Option<String>,
544}
545
546impl ProductDeleteActionBuilder {
547 pub fn new() -> Self {
548 ProductDeleteActionBuilder::default()
549 }
550
551 pub fn with_product_namespace(mut self, product_namespace: ProductNamespace) -> Self {
552 self.product_namespace = Some(product_namespace);
553 self
554 }
555
556 pub fn with_product_id(mut self, product_id: String) -> Self {
557 self.product_id = Some(product_id);
558 self
559 }
560
561 pub fn build(self) -> Result<ProductDeleteAction, BuilderError> {
562 let product_namespace = self.product_namespace.ok_or_else(|| {
563 BuilderError::MissingField("'product_namespace' field is required".to_string())
564 })?;
565
566 let product_id = self.product_id.ok_or_else(|| {
567 BuilderError::MissingField("'product_id' field is required".to_string())
568 })?;
569
570 Ok(ProductDeleteAction {
571 product_namespace,
572 product_id,
573 })
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use crate::protocol::schema::state::{DataType, PropertyValueBuilder};
581 use std::fmt::Debug;
582
583 #[test]
584 fn test_product_create_builder() {
586 let action = ProductCreateActionBuilder::new()
587 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
589 .with_owner("Target".into())
590 .with_properties(make_properties())
591 .build()
592 .unwrap();
593
594 assert_eq!(action.product_id(), "688955434684");
595 assert_eq!(action.owner(), "Target");
596 assert_eq!(*action.product_namespace(), ProductNamespace::Gs1);
597 assert_eq!(action.properties()[0].name(), "description");
598 assert_eq!(*action.properties()[0].data_type(), DataType::String);
599 assert_eq!(
600 action.properties()[0].string_value(),
601 "This is a product description"
602 );
603 assert_eq!(action.properties()[1].name(), "price");
604 assert_eq!(*action.properties()[1].data_type(), DataType::Number);
605 assert_eq!(*action.properties()[1].number_value(), 3);
606 }
607
608 #[test]
609 fn test_product_create_into_bytes() {
612 let action = ProductCreateActionBuilder::new()
613 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
615 .with_owner("Target".into())
616 .with_properties(make_properties())
617 .build()
618 .unwrap();
619
620 test_from_bytes(action, ProductCreateAction::from_bytes);
621 }
622
623 #[test]
624 fn test_product_update_builder() {
626 let action = ProductUpdateActionBuilder::new()
627 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
629 .with_properties(make_properties())
630 .build()
631 .unwrap();
632
633 assert_eq!(action.product_id(), "688955434684");
634 assert_eq!(*action.product_namespace(), ProductNamespace::Gs1);
635 assert_eq!(action.properties()[0].name(), "description");
636 assert_eq!(*action.properties()[0].data_type(), DataType::String);
637 assert_eq!(
638 action.properties()[0].string_value(),
639 "This is a product description"
640 );
641 assert_eq!(action.properties()[1].name(), "price");
642 assert_eq!(*action.properties()[1].data_type(), DataType::Number);
643 assert_eq!(*action.properties()[1].number_value(), 3);
644 }
645
646 #[test]
647 fn test_product_update_into_bytes() {
650 let action = ProductUpdateActionBuilder::new()
651 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
653 .with_properties(make_properties())
654 .build()
655 .unwrap();
656
657 test_from_bytes(action, ProductUpdateAction::from_bytes);
658 }
659
660 #[test]
661 fn test_product_delete_builder() {
663 let action = ProductDeleteActionBuilder::new()
664 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
666 .build()
667 .unwrap();
668
669 assert_eq!(action.product_id(), "688955434684");
670 assert_eq!(*action.product_namespace(), ProductNamespace::Gs1);
671 }
672
673 #[test]
674 fn test_product_delete_into_bytes() {
677 let action = ProductDeleteActionBuilder::new()
678 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
680 .build()
681 .unwrap();
682
683 test_from_bytes(action, ProductDeleteAction::from_bytes);
684 }
685
686 #[test]
687 fn test_product_payload_builder() {
689 let action = ProductCreateActionBuilder::new()
690 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
692 .with_owner("Target".into())
693 .with_properties(make_properties())
694 .build()
695 .unwrap();
696
697 let payload = ProductPayloadBuilder::new()
698 .with_action(Action::ProductCreate(action.clone()))
699 .with_timestamp(0)
700 .build()
701 .unwrap();
702
703 assert_eq!(*payload.action(), Action::ProductCreate(action));
704 assert_eq!(*payload.timestamp(), 0);
705 }
706
707 #[test]
708 fn test_product_payload_bytes() {
711 let action = ProductCreateActionBuilder::new()
712 .with_product_id("688955434684".into()) .with_product_namespace(ProductNamespace::Gs1)
714 .with_owner("Target".into())
715 .with_properties(make_properties())
716 .build()
717 .unwrap();
718
719 let payload = ProductPayloadBuilder::new()
720 .with_action(Action::ProductCreate(action.clone()))
721 .with_timestamp(0)
722 .build()
723 .unwrap();
724
725 test_from_bytes(payload, ProductPayload::from_bytes);
726 }
727
728 fn make_properties() -> Vec<PropertyValue> {
729 let property_value_description = PropertyValueBuilder::new()
730 .with_name("description".into())
731 .with_data_type(DataType::String)
732 .with_string_value("This is a product description".into())
733 .build()
734 .unwrap();
735 let property_value_price = PropertyValueBuilder::new()
736 .with_name("price".into())
737 .with_data_type(DataType::Number)
738 .with_number_value(3)
739 .build()
740 .unwrap();
741
742 vec![
743 property_value_description.clone(),
744 property_value_price.clone(),
745 ]
746 }
747
748 fn test_from_bytes<T: FromBytes<T> + Clone + PartialEq + IntoBytes + Debug, F>(
749 under_test: T,
750 from_bytes: F,
751 ) where
752 F: Fn(&[u8]) -> Result<T, ProtoConversionError>,
753 {
754 let bytes = under_test.clone().into_bytes().unwrap();
755 let created_from_bytes = from_bytes(&bytes).unwrap();
756 assert_eq!(under_test, created_from_bytes);
757 }
758}