use crate::document::DocumentViewId;
use crate::operation::error::OperationBuilderError;
use crate::operation::plain::PlainFields;
use crate::operation::traits::{Actionable, AsOperation, Schematic};
use crate::operation::validate::validate_operation_format;
use crate::operation::{OperationAction, OperationFields, OperationValue, OperationVersion};
use crate::schema::SchemaId;
#[derive(Clone, Debug)]
pub struct OperationBuilder {
action: OperationAction,
schema_id: SchemaId,
previous: Option<DocumentViewId>,
fields: Option<OperationFields>,
}
impl OperationBuilder {
pub fn new(schema_id: &SchemaId) -> Self {
Self {
action: OperationAction::Create,
schema_id: schema_id.to_owned(),
previous: None,
fields: None,
}
}
pub fn action(mut self, action: OperationAction) -> Self {
self.action = action;
self
}
pub fn schema_id(mut self, schema_id: SchemaId) -> Self {
self.schema_id = schema_id;
self
}
pub fn previous(mut self, previous: &DocumentViewId) -> Self {
self.previous = Some(previous.to_owned());
self
}
pub fn fields(mut self, fields: &[(&str, OperationValue)]) -> Self {
let mut operation_fields = OperationFields::new();
for (field_name, field_value) in fields {
if operation_fields
.insert(field_name, field_value.to_owned())
.is_err()
{
}
}
self.fields = Some(operation_fields);
self
}
pub fn build(&self) -> Result<Operation, OperationBuilderError> {
let operation = Operation {
action: self.action,
version: OperationVersion::V1,
schema_id: self.schema_id.to_owned(),
previous: self.previous.to_owned(),
fields: self.fields.to_owned(),
};
validate_operation_format(&operation)?;
Ok(operation)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Operation {
pub(crate) version: OperationVersion,
pub(crate) action: OperationAction,
pub(crate) schema_id: SchemaId,
pub(crate) previous: Option<DocumentViewId>,
pub(crate) fields: Option<OperationFields>,
}
impl AsOperation for Operation {
fn version(&self) -> OperationVersion {
self.version.to_owned()
}
fn action(&self) -> OperationAction {
self.action.to_owned()
}
fn schema_id(&self) -> SchemaId {
self.schema_id.to_owned()
}
fn previous(&self) -> Option<DocumentViewId> {
self.previous.clone()
}
fn fields(&self) -> Option<OperationFields> {
self.fields.clone()
}
}
impl Actionable for Operation {
fn version(&self) -> OperationVersion {
self.version
}
fn action(&self) -> OperationAction {
self.action
}
fn previous(&self) -> Option<&DocumentViewId> {
self.previous.as_ref()
}
}
impl Schematic for Operation {
fn schema_id(&self) -> &SchemaId {
&self.schema_id
}
fn fields(&self) -> Option<PlainFields> {
self.fields.as_ref().map(PlainFields::from)
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use crate::document::DocumentViewId;
use crate::operation::traits::AsOperation;
use crate::operation::{OperationAction, OperationFields, OperationValue, OperationVersion};
use crate::schema::SchemaId;
use crate::test_utils::fixtures::{document_view_id, schema_id};
use super::OperationBuilder;
#[rstest]
fn operation_builder(schema_id: SchemaId, document_view_id: DocumentViewId) {
let fields = vec![
("firstname", "Peter".into()),
("lastname", "Panda".into()),
("year", 2020.into()),
];
let operation = OperationBuilder::new(&schema_id)
.action(OperationAction::Update)
.previous(&document_view_id)
.fields(&fields)
.build()
.unwrap();
assert_eq!(operation.action(), OperationAction::Update);
assert_eq!(operation.previous(), Some(document_view_id));
assert_eq!(operation.fields(), Some(fields.into()));
assert_eq!(operation.version(), OperationVersion::V1);
assert_eq!(operation.schema_id(), schema_id);
}
#[rstest]
fn operation_builder_validation(schema_id: SchemaId, document_view_id: DocumentViewId) {
assert!(OperationBuilder::new(&schema_id)
.fields(&[("year", 2020.into())])
.build()
.is_ok());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Create)
.fields(&[("year", 2020.into())])
.previous(&document_view_id)
.build()
.is_err());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Create)
.build()
.is_err());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Update)
.fields(&[("year", 2020.into())])
.previous(&document_view_id)
.build()
.is_ok());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Update)
.previous(&document_view_id)
.build()
.is_err());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Update)
.fields(&[("year", 2020.into())])
.build()
.is_err());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Delete)
.previous(&document_view_id)
.build()
.is_ok());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Delete)
.previous(&document_view_id)
.fields(&[("year", 2020.into())])
.build()
.is_err());
assert!(OperationBuilder::new(&schema_id)
.action(OperationAction::Update)
.build()
.is_err());
}
#[rstest]
fn field_ordering(schema_id: SchemaId) {
let operation_1 = OperationBuilder::new(&schema_id)
.fields(&[("a", "sloth".into()), ("b", "penguin".into())])
.build();
let operation_2 = OperationBuilder::new(&schema_id)
.fields(&[("b", "penguin".into()), ("a", "sloth".into())])
.build();
assert_eq!(operation_1.unwrap(), operation_2.unwrap());
}
#[test]
fn field_iteration() {
let mut fields = OperationFields::new();
fields
.insert("a", OperationValue::String("sloth".to_owned()))
.unwrap();
fields
.insert("b", OperationValue::String("penguin".to_owned()))
.unwrap();
let mut field_iterator = fields.iter();
assert_eq!(
field_iterator.next().unwrap().1,
&OperationValue::String("sloth".to_owned())
);
assert_eq!(
field_iterator.next().unwrap().1,
&OperationValue::String("penguin".to_owned())
);
}
}