Skip to main content

grid_sdk/protocol/schema/
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 Schema transaction payloads
16
17use protobuf::Message;
18use protobuf::RepeatedField;
19
20use std::error::Error as StdError;
21
22use crate::protocol::schema::state::PropertyDefinition;
23use crate::protos;
24use crate::protos::{
25    FromBytes, FromNative, FromProto, IntoBytes, IntoNative, IntoProto, ProtoConversionError,
26};
27
28/// Schema payload's action envelope
29#[derive(Debug, Clone, PartialEq)]
30pub enum Action {
31    SchemaCreate(SchemaCreateAction),
32    SchemaUpdate(SchemaUpdateAction),
33}
34
35/// Native representation of a Schema transaction payload
36#[derive(Debug, Clone, PartialEq)]
37pub struct SchemaPayload {
38    action: Action,
39}
40
41impl SchemaPayload {
42    pub fn action(&self) -> &Action {
43        &self.action
44    }
45}
46
47impl FromProto<protos::schema_payload::SchemaPayload> for SchemaPayload {
48    fn from_proto(
49        payload: protos::schema_payload::SchemaPayload,
50    ) -> Result<Self, ProtoConversionError> {
51        let action = match payload.get_action() {
52            protos::schema_payload::SchemaPayload_Action::SCHEMA_CREATE => Action::SchemaCreate(
53                SchemaCreateAction::from_proto(payload.get_schema_create().clone())?,
54            ),
55            protos::schema_payload::SchemaPayload_Action::SCHEMA_UPDATE => Action::SchemaUpdate(
56                SchemaUpdateAction::from_proto(payload.get_schema_update().clone())?,
57            ),
58            protos::schema_payload::SchemaPayload_Action::UNSET_ACTION => {
59                return Err(ProtoConversionError::InvalidTypeError(
60                    "Cannot convert SchemaPayload_Action with type unset.".to_string(),
61                ));
62            }
63        };
64        Ok(SchemaPayload { action })
65    }
66}
67
68impl FromNative<SchemaPayload> for protos::schema_payload::SchemaPayload {
69    fn from_native(payload: SchemaPayload) -> Result<Self, ProtoConversionError> {
70        let mut proto_payload = protos::schema_payload::SchemaPayload::new();
71        match payload.action() {
72            Action::SchemaCreate(payload) => {
73                proto_payload
74                    .set_action(protos::schema_payload::SchemaPayload_Action::SCHEMA_CREATE);
75                proto_payload.set_schema_create(payload.clone().into_proto()?);
76            }
77            Action::SchemaUpdate(payload) => {
78                proto_payload
79                    .set_action(protos::schema_payload::SchemaPayload_Action::SCHEMA_UPDATE);
80                proto_payload.set_schema_update(payload.clone().into_proto()?);
81            }
82        }
83        Ok(proto_payload)
84    }
85}
86
87impl FromBytes<SchemaPayload> for SchemaPayload {
88    fn from_bytes(bytes: &[u8]) -> Result<SchemaPayload, ProtoConversionError> {
89        let proto: protos::schema_payload::SchemaPayload = Message::parse_from_bytes(bytes)
90            .map_err(|_| {
91                ProtoConversionError::SerializationError(
92                    "Unable to get SchemaPayload from bytes".to_string(),
93                )
94            })?;
95        proto.into_native()
96    }
97}
98
99impl IntoBytes for SchemaPayload {
100    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
101        let proto = self.into_proto()?;
102        let bytes = proto.write_to_bytes().map_err(|_| {
103            ProtoConversionError::SerializationError(
104                "Unable to get bytes from SchemaPayload".to_string(),
105            )
106        })?;
107        Ok(bytes)
108    }
109}
110
111impl IntoProto<protos::schema_payload::SchemaPayload> for SchemaPayload {}
112impl IntoNative<SchemaPayload> for protos::schema_payload::SchemaPayload {}
113
114/// Returned if any required fields in a `SchemaPayload` are not present when being
115/// converted from the corresponding builder
116#[derive(Debug)]
117pub enum SchemaPayloadBuildError {
118    MissingField(String),
119}
120
121impl StdError for SchemaPayloadBuildError {
122    fn description(&self) -> &str {
123        match *self {
124            SchemaPayloadBuildError::MissingField(ref msg) => msg,
125        }
126    }
127
128    fn cause(&self) -> Option<&dyn StdError> {
129        match *self {
130            SchemaPayloadBuildError::MissingField(_) => None,
131        }
132    }
133}
134
135impl std::fmt::Display for SchemaPayloadBuildError {
136    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
137        match *self {
138            SchemaPayloadBuildError::MissingField(ref s) => write!(f, "MissingField: {}", s),
139        }
140    }
141}
142
143/// Builder used to create a Schema payload
144#[derive(Default, Clone)]
145pub struct SchemaPayloadBuilder {
146    action: Option<Action>,
147}
148
149impl SchemaPayloadBuilder {
150    pub fn new() -> Self {
151        SchemaPayloadBuilder::default()
152    }
153
154    pub fn with_action(mut self, action: Action) -> SchemaPayloadBuilder {
155        self.action = Some(action);
156        self
157    }
158
159    pub fn build(self) -> Result<SchemaPayload, SchemaPayloadBuildError> {
160        let action = self.action.ok_or_else(|| {
161            SchemaPayloadBuildError::MissingField("'action' field is required".to_string())
162        })?;
163        Ok(SchemaPayload { action })
164    }
165}
166
167/// Native implementation for the "create schema" action
168#[derive(Debug, Default, Clone, PartialEq)]
169pub struct SchemaCreateAction {
170    schema_name: String,
171    owner: String,
172    description: String,
173    properties: Vec<PropertyDefinition>,
174}
175
176impl SchemaCreateAction {
177    pub fn schema_name(&self) -> &str {
178        &self.schema_name
179    }
180
181    pub fn owner(&self) -> &str {
182        &self.owner
183    }
184
185    pub fn description(&self) -> &str {
186        &self.description
187    }
188
189    pub fn properties(&self) -> &[PropertyDefinition] {
190        &self.properties
191    }
192}
193
194impl FromProto<protos::schema_payload::SchemaCreateAction> for SchemaCreateAction {
195    fn from_proto(
196        schema_create: protos::schema_payload::SchemaCreateAction,
197    ) -> Result<Self, ProtoConversionError> {
198        Ok(SchemaCreateAction {
199            schema_name: schema_create.get_schema_name().to_string(),
200            owner: schema_create.get_owner().to_string(),
201            description: schema_create.get_description().to_string(),
202            properties: schema_create
203                .get_properties()
204                .iter()
205                .cloned()
206                .map(PropertyDefinition::from_proto)
207                .collect::<Result<Vec<PropertyDefinition>, ProtoConversionError>>()?,
208        })
209    }
210}
211
212impl FromNative<SchemaCreateAction> for protos::schema_payload::SchemaCreateAction {
213    fn from_native(schema_create: SchemaCreateAction) -> Result<Self, ProtoConversionError> {
214        let mut proto_schema_create = protos::schema_payload::SchemaCreateAction::new();
215
216        proto_schema_create.set_schema_name(schema_create.schema_name().to_string());
217        proto_schema_create.set_owner(schema_create.owner().to_string());
218        proto_schema_create.set_description(schema_create.description().to_string());
219        proto_schema_create.set_properties(
220            RepeatedField::from_vec(
221            schema_create.properties().iter().cloned()
222            .map(PropertyDefinition::into_proto)
223            .collect::<Result<Vec<protos::schema_state::PropertyDefinition>, ProtoConversionError>>()?,));
224
225        Ok(proto_schema_create)
226    }
227}
228
229impl FromBytes<SchemaCreateAction> for SchemaCreateAction {
230    fn from_bytes(bytes: &[u8]) -> Result<SchemaCreateAction, ProtoConversionError> {
231        let proto: protos::schema_payload::SchemaCreateAction = Message::parse_from_bytes(bytes)
232            .map_err(|_| {
233                ProtoConversionError::SerializationError(
234                    "Unable to get SchemaCreateAction from bytes".to_string(),
235                )
236            })?;
237        proto.into_native()
238    }
239}
240
241impl IntoBytes for SchemaCreateAction {
242    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
243        let proto = self.into_proto()?;
244        let bytes = proto.write_to_bytes().map_err(|_| {
245            ProtoConversionError::SerializationError(
246                "Unable to get bytes from SchemaCreateAction".to_string(),
247            )
248        })?;
249        Ok(bytes)
250    }
251}
252
253impl IntoProto<protos::schema_payload::SchemaCreateAction> for SchemaCreateAction {}
254impl IntoNative<SchemaCreateAction> for protos::schema_payload::SchemaCreateAction {}
255
256/// Returned if any required fields in a `SchemaCreateAction` are not present when being
257/// converted from the corresponding builder
258#[derive(Debug)]
259pub enum SchemaCreateBuildError {
260    MissingField(String),
261}
262
263impl StdError for SchemaCreateBuildError {
264    fn description(&self) -> &str {
265        match *self {
266            SchemaCreateBuildError::MissingField(ref msg) => msg,
267        }
268    }
269
270    fn cause(&self) -> Option<&dyn StdError> {
271        match *self {
272            SchemaCreateBuildError::MissingField(_) => None,
273        }
274    }
275}
276
277impl std::fmt::Display for SchemaCreateBuildError {
278    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
279        match *self {
280            SchemaCreateBuildError::MissingField(ref s) => write!(f, "MissingField: {}", s),
281        }
282    }
283}
284
285/// Builder used to create a "create schema" action
286#[derive(Default, Clone)]
287pub struct SchemaCreateBuilder {
288    schema_name: Option<String>,
289    owner: Option<String>,
290    description: Option<String>,
291    properties: Vec<PropertyDefinition>,
292}
293
294impl SchemaCreateBuilder {
295    pub fn new() -> Self {
296        SchemaCreateBuilder::default()
297    }
298
299    pub fn with_schema_name(mut self, schema_name: String) -> SchemaCreateBuilder {
300        self.schema_name = Some(schema_name);
301        self
302    }
303
304    pub fn with_owner(mut self, owner: String) -> SchemaCreateBuilder {
305        self.owner = Some(owner);
306        self
307    }
308
309    pub fn with_description(mut self, description: String) -> SchemaCreateBuilder {
310        self.description = Some(description);
311        self
312    }
313
314    pub fn with_properties(mut self, properties: Vec<PropertyDefinition>) -> SchemaCreateBuilder {
315        self.properties = properties;
316        self
317    }
318
319    pub fn build(self) -> Result<SchemaCreateAction, SchemaCreateBuildError> {
320        let schema_name = self.schema_name.ok_or_else(|| {
321            SchemaCreateBuildError::MissingField("'schema_name' field is required".to_string())
322        })?;
323
324        let owner = self.owner.ok_or_else(|| {
325            SchemaCreateBuildError::MissingField("'owner' field is required".to_string())
326        })?;
327
328        let description = self.description.unwrap_or_default();
329
330        let properties = {
331            if !self.properties.is_empty() {
332                self.properties
333            } else {
334                return Err(SchemaCreateBuildError::MissingField(
335                    "'properties' field is required".to_string(),
336                ));
337            }
338        };
339
340        Ok(SchemaCreateAction {
341            schema_name,
342            owner,
343            description,
344            properties,
345        })
346    }
347}
348
349/// Native representation of an "update schema" action
350#[derive(Debug, Default, Clone, PartialEq)]
351pub struct SchemaUpdateAction {
352    schema_name: String,
353    owner: String,
354    properties: Vec<PropertyDefinition>,
355}
356
357impl SchemaUpdateAction {
358    pub fn schema_name(&self) -> &str {
359        &self.schema_name
360    }
361
362    pub fn owner(&self) -> &str {
363        &self.owner
364    }
365
366    pub fn properties(&self) -> &[PropertyDefinition] {
367        &self.properties
368    }
369}
370
371impl FromProto<protos::schema_payload::SchemaUpdateAction> for SchemaUpdateAction {
372    fn from_proto(
373        schema_update: protos::schema_payload::SchemaUpdateAction,
374    ) -> Result<Self, ProtoConversionError> {
375        Ok(SchemaUpdateAction {
376            schema_name: schema_update.get_schema_name().to_string(),
377            owner: schema_update.get_owner().to_string(),
378            properties: schema_update
379                .get_properties()
380                .iter()
381                .cloned()
382                .map(PropertyDefinition::from_proto)
383                .collect::<Result<Vec<PropertyDefinition>, ProtoConversionError>>()?,
384        })
385    }
386}
387
388impl FromNative<SchemaUpdateAction> for protos::schema_payload::SchemaUpdateAction {
389    fn from_native(schema_update: SchemaUpdateAction) -> Result<Self, ProtoConversionError> {
390        let mut proto_schema_update = protos::schema_payload::SchemaUpdateAction::new();
391
392        proto_schema_update.set_schema_name(schema_update.schema_name().to_string());
393        proto_schema_update.set_owner(schema_update.owner().to_string());
394        proto_schema_update.set_properties(
395            RepeatedField::from_vec(
396            schema_update.properties().iter().cloned()
397            .map(PropertyDefinition::into_proto)
398            .collect::<Result<Vec<protos::schema_state::PropertyDefinition>, ProtoConversionError>>()?,));
399
400        Ok(proto_schema_update)
401    }
402}
403
404impl FromBytes<SchemaUpdateAction> for SchemaUpdateAction {
405    fn from_bytes(bytes: &[u8]) -> Result<SchemaUpdateAction, ProtoConversionError> {
406        let proto: protos::schema_payload::SchemaUpdateAction = Message::parse_from_bytes(bytes)
407            .map_err(|_| {
408                ProtoConversionError::SerializationError(
409                    "Unable to get SchemaUpdateAction from bytes".to_string(),
410                )
411            })?;
412        proto.into_native()
413    }
414}
415
416impl IntoBytes for SchemaUpdateAction {
417    fn into_bytes(self) -> Result<Vec<u8>, ProtoConversionError> {
418        let proto = self.into_proto()?;
419        let bytes = proto.write_to_bytes().map_err(|_| {
420            ProtoConversionError::SerializationError(
421                "Unable to get bytes from SchemaUpdateAction".to_string(),
422            )
423        })?;
424        Ok(bytes)
425    }
426}
427
428impl IntoProto<protos::schema_payload::SchemaUpdateAction> for SchemaUpdateAction {}
429impl IntoNative<SchemaUpdateAction> for protos::schema_payload::SchemaUpdateAction {}
430
431/// Returned if any required fields in a `SchemaUpdateAction` are not present when being
432/// converted from the corresponding builder
433#[derive(Debug)]
434pub enum SchemaUpdateBuildError {
435    MissingField(String),
436}
437
438impl StdError for SchemaUpdateBuildError {
439    fn description(&self) -> &str {
440        match *self {
441            SchemaUpdateBuildError::MissingField(ref msg) => msg,
442        }
443    }
444
445    fn cause(&self) -> Option<&dyn StdError> {
446        match *self {
447            SchemaUpdateBuildError::MissingField(_) => None,
448        }
449    }
450}
451
452impl std::fmt::Display for SchemaUpdateBuildError {
453    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
454        match *self {
455            SchemaUpdateBuildError::MissingField(ref s) => write!(f, "MissingField: {}", s),
456        }
457    }
458}
459
460/// Builder used to create an "update schema" action
461#[derive(Default, Clone)]
462pub struct SchemaUpdateBuilder {
463    schema_name: Option<String>,
464    owner: Option<String>,
465    properties: Vec<PropertyDefinition>,
466}
467
468impl SchemaUpdateBuilder {
469    pub fn new() -> Self {
470        SchemaUpdateBuilder::default()
471    }
472
473    pub fn with_schema_name(mut self, schema_name: String) -> SchemaUpdateBuilder {
474        self.schema_name = Some(schema_name);
475        self
476    }
477
478    pub fn with_owner(mut self, owner: String) -> SchemaUpdateBuilder {
479        self.owner = Some(owner);
480        self
481    }
482
483    pub fn with_properties(mut self, properties: Vec<PropertyDefinition>) -> SchemaUpdateBuilder {
484        self.properties = properties;
485        self
486    }
487
488    pub fn build(self) -> Result<SchemaUpdateAction, SchemaUpdateBuildError> {
489        let schema_name = self.schema_name.ok_or_else(|| {
490            SchemaUpdateBuildError::MissingField("'schema field is required".to_string())
491        })?;
492
493        let owner = self.owner.ok_or_else(|| {
494            SchemaUpdateBuildError::MissingField("'owner field is required".to_string())
495        })?;
496
497        let properties = {
498            if !self.properties.is_empty() {
499                self.properties
500            } else {
501                return Err(SchemaUpdateBuildError::MissingField(
502                    "'properties' field is required".to_string(),
503                ));
504            }
505        };
506
507        Ok(SchemaUpdateAction {
508            schema_name,
509            owner,
510            properties,
511        })
512    }
513}
514
515#[cfg(test)]
516mod tests {
517    use super::*;
518    use crate::protocol::schema::state::{DataType, PropertyDefinitionBuilder};
519
520    #[test]
521    /// Validate that a `SchemaCreateAction` is built correctly
522    fn check_schema_create_action() {
523        let builder = PropertyDefinitionBuilder::new();
524        let property_definition = builder
525            .with_name("TEST".to_string())
526            .with_data_type(DataType::String)
527            .with_description("Optional".to_string())
528            .build()
529            .unwrap();
530
531        let builder = SchemaCreateBuilder::new();
532        let action = builder
533            .with_schema_name("TestSchema".to_string())
534            .with_owner("test_org".to_string())
535            .with_description("Test Schema".to_string())
536            .with_properties(vec![property_definition.clone()])
537            .build()
538            .unwrap();
539
540        assert_eq!(action.schema_name, "TestSchema");
541        assert_eq!(action.description, "Test Schema");
542        assert_eq!(action.properties, vec![property_definition]);
543    }
544
545    #[test]
546    /// Validate that a `SchemaCreateAction` may be converted into bytes and back to its native
547    /// representation successfully
548    fn check_schema_create_bytes() {
549        let builder = PropertyDefinitionBuilder::new();
550        let property_definition = builder
551            .with_name("TEST".to_string())
552            .with_data_type(DataType::String)
553            .with_description("Optional".to_string())
554            .build()
555            .unwrap();
556
557        let builder = SchemaCreateBuilder::new();
558        let original = builder
559            .with_schema_name("TestSchema".to_string())
560            .with_owner("test_org".to_string())
561            .with_description("Test Schema".to_string())
562            .with_properties(vec![property_definition.clone()])
563            .build()
564            .unwrap();
565
566        let bytes = original.clone().into_bytes().unwrap();
567
568        let create = SchemaCreateAction::from_bytes(&bytes).unwrap();
569        assert_eq!(create, original);
570    }
571
572    #[test]
573    /// Validate that a `SchemaUpdateAction` is built correctly
574    fn check_schema_update_action() {
575        let builder = PropertyDefinitionBuilder::new();
576        let property_definition = builder
577            .with_name("TEST".to_string())
578            .with_data_type(DataType::String)
579            .with_description("Optional".to_string())
580            .build()
581            .unwrap();
582
583        let builder = SchemaUpdateBuilder::new();
584        let action = builder
585            .with_schema_name("TestSchema".to_string())
586            .with_owner("test_org".to_string())
587            .with_properties(vec![property_definition.clone()])
588            .build()
589            .unwrap();
590
591        assert_eq!(action.schema_name, "TestSchema");
592        assert_eq!(action.properties, vec![property_definition]);
593    }
594
595    #[test]
596    /// Validate that a `SchemaUpdateAction` may be converted into bytes and back to its native
597    /// representation successfully
598    fn check_schema_update_bytes() {
599        let builder = PropertyDefinitionBuilder::new();
600        let property_definition = builder
601            .with_name("TEST".to_string())
602            .with_data_type(DataType::String)
603            .with_description("Optional".to_string())
604            .build()
605            .unwrap();
606
607        let builder = SchemaUpdateBuilder::new();
608        let original = builder
609            .with_schema_name("TestSchema".to_string())
610            .with_owner("test_org".to_string())
611            .with_properties(vec![property_definition.clone()])
612            .build()
613            .unwrap();
614
615        let bytes = original.clone().into_bytes().unwrap();
616
617        let update = SchemaUpdateAction::from_bytes(&bytes).unwrap();
618        assert_eq!(update, original);
619    }
620
621    #[test]
622    /// Validate that a `SchemaPayload` is built correctly with a `SchemaCreateAction`
623    fn check_schema_create_action_payload() {
624        let builder = PropertyDefinitionBuilder::new();
625        let property_definition = builder
626            .with_name("TEST".to_string())
627            .with_data_type(DataType::String)
628            .with_description("Optional".to_string())
629            .build()
630            .unwrap();
631
632        let builder = SchemaCreateBuilder::new();
633        let action = builder
634            .with_schema_name("TestSchema".to_string())
635            .with_owner("test_org".to_string())
636            .with_description("Test Schema".to_string())
637            .with_properties(vec![property_definition.clone()])
638            .build()
639            .unwrap();
640
641        let builder = SchemaPayloadBuilder::new();
642        let payload = builder
643            .with_action(Action::SchemaCreate(action.clone()))
644            .build()
645            .unwrap();
646
647        assert_eq!(payload.action, Action::SchemaCreate(action));
648    }
649
650    #[test]
651    /// Validate that a `SchemaPayload` is built correctly with a `SchemaUpdateAction`
652    fn check_schema_update_action_payload() {
653        let builder = PropertyDefinitionBuilder::new();
654        let property_definition = builder
655            .with_name("TEST".to_string())
656            .with_data_type(DataType::String)
657            .with_description("Optional".to_string())
658            .build()
659            .unwrap();
660
661        let builder = SchemaUpdateBuilder::new();
662        let action = builder
663            .with_schema_name("TestSchema".to_string())
664            .with_owner("test_org".to_string())
665            .with_properties(vec![property_definition.clone()])
666            .build()
667            .unwrap();
668
669        let builder = SchemaPayloadBuilder::new();
670        let payload = builder
671            .with_action(Action::SchemaUpdate(action.clone()))
672            .build()
673            .unwrap();
674
675        assert_eq!(payload.action, Action::SchemaUpdate(action));
676    }
677
678    #[test]
679    /// Validate that a `SchemaPayload` with a `SchemaCreateAction` may be converted into bytes and
680    /// back to its native representation successfully
681    fn check_schema_payload_bytes() {
682        let builder = PropertyDefinitionBuilder::new();
683        let property_definition = builder
684            .with_name("TEST".to_string())
685            .with_data_type(DataType::String)
686            .with_description("Optional".to_string())
687            .build()
688            .unwrap();
689
690        let builder = SchemaUpdateBuilder::new();
691        let action = builder
692            .with_schema_name("TestSchema".to_string())
693            .with_owner("test_org".to_string())
694            .with_properties(vec![property_definition.clone()])
695            .build()
696            .unwrap();
697
698        let builder = SchemaPayloadBuilder::new();
699        let original = builder
700            .with_action(Action::SchemaUpdate(action))
701            .build()
702            .unwrap();
703
704        let bytes = original.clone().into_bytes().unwrap();
705
706        let payload = SchemaPayload::from_bytes(&bytes).unwrap();
707        assert_eq!(payload, original);
708    }
709}