use std::borrow::Cow;
use crate::model::{Context, Id, PropertyValue};
#[derive(Debug, Clone, PartialEq)]
pub enum Op<'a> {
CreateEntity(CreateEntity<'a>),
UpdateEntity(UpdateEntity<'a>),
DeleteEntity(DeleteEntity),
RestoreEntity(RestoreEntity),
CreateRelation(CreateRelation<'a>),
UpdateRelation(UpdateRelation<'a>),
DeleteRelation(DeleteRelation),
RestoreRelation(RestoreRelation),
CreateValueRef(CreateValueRef),
}
impl Op<'_> {
pub fn op_type(&self) -> u8 {
match self {
Op::CreateEntity(_) => 1,
Op::UpdateEntity(_) => 2,
Op::DeleteEntity(_) => 3,
Op::RestoreEntity(_) => 4,
Op::CreateRelation(_) => 5,
Op::UpdateRelation(_) => 6,
Op::DeleteRelation(_) => 7,
Op::RestoreRelation(_) => 8,
Op::CreateValueRef(_) => 9,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateEntity<'a> {
pub id: Id,
pub values: Vec<PropertyValue<'a>>,
pub context: Option<Context>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct UpdateEntity<'a> {
pub id: Id,
pub set_properties: Vec<PropertyValue<'a>>,
pub unset_values: Vec<UnsetValue>,
pub context: Option<Context>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnsetLanguage {
All,
English,
Specific(Id),
}
impl Default for UnsetLanguage {
fn default() -> Self {
Self::All
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct UnsetValue {
pub property: Id,
pub language: UnsetLanguage,
}
impl UnsetValue {
pub fn all(property: Id) -> Self {
Self { property, language: UnsetLanguage::All }
}
pub fn english(property: Id) -> Self {
Self { property, language: UnsetLanguage::English }
}
pub fn language(property: Id, language: Id) -> Self {
Self { property, language: UnsetLanguage::Specific(language) }
}
}
impl<'a> UpdateEntity<'a> {
pub fn new(id: Id) -> Self {
Self {
id,
set_properties: Vec::new(),
unset_values: Vec::new(),
context: None,
}
}
pub fn is_empty(&self) -> bool {
self.set_properties.is_empty() && self.unset_values.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteEntity {
pub id: Id,
pub context: Option<Context>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RestoreEntity {
pub id: Id,
pub context: Option<Context>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateRelation<'a> {
pub id: Id,
pub relation_type: Id,
pub from: Id,
pub from_is_value_ref: bool,
pub from_space: Option<Id>,
pub from_version: Option<Id>,
pub to: Id,
pub to_is_value_ref: bool,
pub to_space: Option<Id>,
pub to_version: Option<Id>,
pub entity: Option<Id>,
pub position: Option<Cow<'a, str>>,
pub context: Option<Context>,
}
impl CreateRelation<'_> {
pub fn entity_id(&self) -> Id {
use crate::model::id::relation_entity_id;
match self.entity {
Some(id) => id,
None => relation_entity_id(&self.id),
}
}
pub fn has_explicit_entity(&self) -> bool {
self.entity.is_some()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnsetRelationField {
FromSpace,
FromVersion,
ToSpace,
ToVersion,
Position,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct UpdateRelation<'a> {
pub id: Id,
pub from_space: Option<Id>,
pub from_version: Option<Id>,
pub to_space: Option<Id>,
pub to_version: Option<Id>,
pub position: Option<Cow<'a, str>>,
pub unset: Vec<UnsetRelationField>,
pub context: Option<Context>,
}
impl UpdateRelation<'_> {
pub fn new(id: Id) -> Self {
Self {
id,
from_space: None,
from_version: None,
to_space: None,
to_version: None,
position: None,
unset: Vec::new(),
context: None,
}
}
pub fn is_empty(&self) -> bool {
self.from_space.is_none()
&& self.from_version.is_none()
&& self.to_space.is_none()
&& self.to_version.is_none()
&& self.position.is_none()
&& self.unset.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteRelation {
pub id: Id,
pub context: Option<Context>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RestoreRelation {
pub id: Id,
pub context: Option<Context>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateValueRef {
pub id: Id,
pub entity: Id,
pub property: Id,
pub language: Option<Id>,
pub space: Option<Id>,
}
pub fn validate_position(pos: &str) -> Result<(), &'static str> {
if pos.is_empty() {
return Err("position cannot be empty");
}
if pos.len() > 64 {
return Err("position exceeds 64 characters");
}
for c in pos.chars() {
if !c.is_ascii_alphanumeric() {
return Err("position contains invalid character");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_op_type_codes() {
assert_eq!(
Op::CreateEntity(CreateEntity {
id: [0; 16],
values: vec![],
context: None,
})
.op_type(),
1
);
assert_eq!(Op::UpdateEntity(UpdateEntity::new([0; 16])).op_type(), 2);
assert_eq!(Op::DeleteEntity(DeleteEntity { id: [0; 16], context: None }).op_type(), 3);
}
#[test]
fn test_validate_position() {
assert!(validate_position("abc123").is_ok());
assert!(validate_position("aV").is_ok());
assert!(validate_position("a").is_ok());
assert!(validate_position("").is_err());
assert!(validate_position("abc-123").is_err());
assert!(validate_position("abc_123").is_err());
assert!(validate_position("abc 123").is_err());
let long = "a".repeat(65);
assert!(validate_position(&long).is_err());
let exact = "a".repeat(64);
assert!(validate_position(&exact).is_ok());
}
#[test]
fn test_update_entity_is_empty() {
let update = UpdateEntity::new([0; 16]);
assert!(update.is_empty());
let mut update2 = UpdateEntity::new([0; 16]);
update2.set_properties.push(PropertyValue {
property: [1; 16],
value: crate::model::Value::Boolean(true),
});
assert!(!update2.is_empty());
}
#[test]
fn test_entity_id_derivation() {
use crate::model::id::relation_entity_id;
let rel_id = [5u8; 16];
let from = [1u8; 16];
let to = [2u8; 16];
let rel_type = [3u8; 16];
let rel_auto = CreateRelation {
id: rel_id,
relation_type: rel_type,
from,
from_is_value_ref: false,
to,
to_is_value_ref: false,
entity: None,
position: None,
from_space: None,
from_version: None,
to_space: None,
to_version: None,
context: None,
};
assert_eq!(rel_auto.entity_id(), relation_entity_id(&rel_id));
assert!(!rel_auto.has_explicit_entity());
let explicit_entity = [6u8; 16];
let rel_explicit = CreateRelation {
id: rel_id,
relation_type: rel_type,
from,
from_is_value_ref: false,
to,
to_is_value_ref: false,
entity: Some(explicit_entity),
position: None,
from_space: None,
from_version: None,
to_space: None,
to_version: None,
context: None,
};
assert_eq!(rel_explicit.entity_id(), explicit_entity);
assert!(rel_explicit.has_explicit_entity());
}
#[test]
fn test_update_relation_is_empty() {
let update = UpdateRelation::new([0; 16]);
assert!(update.is_empty());
let mut update2 = UpdateRelation::new([0; 16]);
update2.from_space = Some([1; 16]);
assert!(!update2.is_empty());
let mut update3 = UpdateRelation::new([0; 16]);
update3.unset.push(UnsetRelationField::Position);
assert!(!update3.is_empty());
}
}