use std::borrow::Cow;
use crate::model::{
CreateEntity, CreateRelation, DeleteEntity, DeleteRelation,
Edit, Id, Op, PropertyValue, RestoreEntity, RestoreRelation, UnsetRelationField,
UnsetLanguage, UnsetValue, UpdateEntity, UpdateRelation, Value,
};
#[derive(Debug, Clone)]
pub struct EditBuilder<'a> {
id: Id,
name: Cow<'a, str>,
authors: Vec<Id>,
created_at: i64,
ops: Vec<Op<'a>>,
}
impl<'a> EditBuilder<'a> {
pub fn new(id: Id) -> Self {
Self {
id,
name: Cow::Borrowed(""),
authors: Vec::new(),
created_at: 0,
ops: Vec::new(),
}
}
pub fn name(mut self, name: impl Into<Cow<'a, str>>) -> Self {
self.name = name.into();
self
}
pub fn author(mut self, author_id: Id) -> Self {
self.authors.push(author_id);
self
}
pub fn authors(mut self, author_ids: impl IntoIterator<Item = Id>) -> Self {
self.authors.extend(author_ids);
self
}
pub fn created_at(mut self, timestamp: i64) -> Self {
self.created_at = timestamp;
self
}
pub fn created_now(mut self) -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let micros = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_micros() as i64)
.unwrap_or(0);
self.created_at = micros;
self
}
pub fn create_entity<F>(mut self, id: Id, f: F) -> Self
where
F: FnOnce(EntityBuilder<'a>) -> EntityBuilder<'a>,
{
let builder = f(EntityBuilder::new());
self.ops.push(Op::CreateEntity(CreateEntity {
id,
values: builder.values,
context: None,
}));
self
}
pub fn create_empty_entity(mut self, id: Id) -> Self {
self.ops.push(Op::CreateEntity(CreateEntity {
id,
values: Vec::new(),
context: None,
}));
self
}
pub fn update_entity<F>(mut self, id: Id, f: F) -> Self
where
F: FnOnce(UpdateEntityBuilder<'a>) -> UpdateEntityBuilder<'a>,
{
let builder = f(UpdateEntityBuilder::new(id));
self.ops.push(Op::UpdateEntity(UpdateEntity {
id: builder.id,
set_properties: builder.set_properties,
unset_values: builder.unset_values,
context: None,
}));
self
}
pub fn delete_entity(mut self, id: Id) -> Self {
self.ops.push(Op::DeleteEntity(DeleteEntity { id, context: None }));
self
}
pub fn restore_entity(mut self, id: Id) -> Self {
self.ops.push(Op::RestoreEntity(RestoreEntity { id, context: None }));
self
}
pub fn create_relation_simple(
mut self,
id: Id,
from: Id,
to: Id,
relation_type: Id,
) -> Self {
self.ops.push(Op::CreateRelation(CreateRelation {
id,
relation_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,
}));
self
}
pub fn create_relation<F>(mut self, f: F) -> Self
where
F: FnOnce(RelationBuilder<'a>) -> RelationBuilder<'a>,
{
let builder = f(RelationBuilder::new());
if let Some(relation) = builder.build() {
self.ops.push(Op::CreateRelation(relation));
}
self
}
pub fn update_relation<F>(mut self, id: Id, f: F) -> Self
where
F: FnOnce(UpdateRelationBuilder<'a>) -> UpdateRelationBuilder<'a>,
{
let builder = f(UpdateRelationBuilder::new(id));
self.ops.push(Op::UpdateRelation(UpdateRelation {
id: builder.id,
from_space: builder.from_space,
from_version: builder.from_version,
to_space: builder.to_space,
to_version: builder.to_version,
position: builder.position,
unset: builder.unset,
context: None,
}));
self
}
pub fn update_relation_position(mut self, id: Id, position: Option<Cow<'a, str>>) -> Self {
self.ops.push(Op::UpdateRelation(UpdateRelation {
id,
from_space: None,
from_version: None,
to_space: None,
to_version: None,
position,
unset: vec![],
context: None,
}));
self
}
pub fn delete_relation(mut self, id: Id) -> Self {
self.ops.push(Op::DeleteRelation(DeleteRelation { id, context: None }));
self
}
pub fn restore_relation(mut self, id: Id) -> Self {
self.ops.push(Op::RestoreRelation(RestoreRelation { id, context: None }));
self
}
pub fn op(mut self, op: Op<'a>) -> Self {
self.ops.push(op);
self
}
pub fn ops(mut self, ops: impl IntoIterator<Item = Op<'a>>) -> Self {
self.ops.extend(ops);
self
}
pub fn build(self) -> Edit<'a> {
Edit {
id: self.id,
name: self.name,
authors: self.authors,
created_at: self.created_at,
ops: self.ops,
}
}
pub fn op_count(&self) -> usize {
self.ops.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct EntityBuilder<'a> {
values: Vec<PropertyValue<'a>>,
}
impl<'a> EntityBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn value(mut self, property: Id, value: Value<'a>) -> Self {
self.values.push(PropertyValue { property, value });
self
}
pub fn text(
mut self,
property: Id,
value: impl Into<Cow<'a, str>>,
language: Option<Id>,
) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Text {
value: value.into(),
language,
},
});
self
}
pub fn integer(mut self, property: Id, value: i64, unit: Option<Id>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Integer { value, unit },
});
self
}
pub fn float(mut self, property: Id, value: f64, unit: Option<Id>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Float { value, unit },
});
self
}
pub fn boolean(mut self, property: Id, value: bool) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Boolean(value),
});
self
}
pub fn bytes(mut self, property: Id, value: impl Into<Cow<'a, [u8]>>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Bytes(value.into()),
});
self
}
pub fn point(mut self, property: Id, lon: f64, lat: f64, alt: Option<f64>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Point { lon, lat, alt },
});
self
}
pub fn date(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Date(value.into()),
});
self
}
pub fn time(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Time(value.into()),
});
self
}
pub fn datetime(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Datetime(value.into()),
});
self
}
pub fn schedule(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Schedule(value.into()),
});
self
}
pub fn decimal(
mut self,
property: Id,
exponent: i32,
mantissa: crate::model::DecimalMantissa<'a>,
unit: Option<Id>,
) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Decimal { exponent, mantissa, unit },
});
self
}
pub fn embedding(
mut self,
property: Id,
sub_type: crate::model::EmbeddingSubType,
dims: usize,
data: impl Into<Cow<'a, [u8]>>,
) -> Self {
self.values.push(PropertyValue {
property,
value: Value::Embedding {
sub_type,
dims,
data: data.into(),
},
});
self
}
}
#[derive(Debug, Clone)]
pub struct UpdateEntityBuilder<'a> {
id: Id,
set_properties: Vec<PropertyValue<'a>>,
unset_values: Vec<UnsetValue>,
}
impl<'a> UpdateEntityBuilder<'a> {
pub fn new(id: Id) -> Self {
Self {
id,
set_properties: Vec::new(),
unset_values: Vec::new(),
}
}
pub fn set(mut self, property: Id, value: Value<'a>) -> Self {
self.set_properties.push(PropertyValue { property, value });
self
}
pub fn set_text(
mut self,
property: Id,
value: impl Into<Cow<'a, str>>,
language: Option<Id>,
) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Text {
value: value.into(),
language,
},
});
self
}
pub fn set_integer(mut self, property: Id, value: i64, unit: Option<Id>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Integer { value, unit },
});
self
}
pub fn set_float(mut self, property: Id, value: f64, unit: Option<Id>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Float { value, unit },
});
self
}
pub fn set_boolean(mut self, property: Id, value: bool) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Boolean(value),
});
self
}
pub fn set_point(mut self, property: Id, lon: f64, lat: f64, alt: Option<f64>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Point { lon, lat, alt },
});
self
}
pub fn set_date(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Date(value.into()),
});
self
}
pub fn set_time(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Time(value.into()),
});
self
}
pub fn set_datetime(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Datetime(value.into()),
});
self
}
pub fn set_schedule(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Schedule(value.into()),
});
self
}
pub fn set_bytes(mut self, property: Id, value: impl Into<Cow<'a, [u8]>>) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Bytes(value.into()),
});
self
}
pub fn set_decimal(
mut self,
property: Id,
exponent: i32,
mantissa: crate::model::DecimalMantissa<'a>,
unit: Option<Id>,
) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Decimal { exponent, mantissa, unit },
});
self
}
pub fn set_embedding(
mut self,
property: Id,
sub_type: crate::model::EmbeddingSubType,
dims: usize,
data: impl Into<Cow<'a, [u8]>>,
) -> Self {
self.set_properties.push(PropertyValue {
property,
value: Value::Embedding {
sub_type,
dims,
data: data.into(),
},
});
self
}
pub fn unset(mut self, property: Id, language: UnsetLanguage) -> Self {
self.unset_values.push(UnsetValue { property, language });
self
}
pub fn unset_all(mut self, property: Id) -> Self {
self.unset_values.push(UnsetValue {
property,
language: UnsetLanguage::All,
});
self
}
pub fn unset_english(mut self, property: Id) -> Self {
self.unset_values.push(UnsetValue {
property,
language: UnsetLanguage::English,
});
self
}
pub fn unset_language(mut self, property: Id, language: Id) -> Self {
self.unset_values.push(UnsetValue {
property,
language: UnsetLanguage::Specific(language),
});
self
}
}
#[derive(Debug, Clone, Default)]
pub struct RelationBuilder<'a> {
id: Option<Id>,
relation_type: Option<Id>,
from: Option<Id>,
from_is_value_ref: bool,
to: Option<Id>,
to_is_value_ref: bool,
entity: Option<Id>,
position: Option<Cow<'a, str>>,
from_space: Option<Id>,
from_version: Option<Id>,
to_space: Option<Id>,
to_version: Option<Id>,
}
impl<'a> RelationBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
}
pub fn relation_type(mut self, id: Id) -> Self {
self.relation_type = Some(id);
self
}
pub fn from(mut self, id: Id) -> Self {
self.from = Some(id);
self
}
pub fn to(mut self, id: Id) -> Self {
self.to = Some(id);
self
}
pub fn entity(mut self, id: Id) -> Self {
self.entity = Some(id);
self
}
pub fn position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
self.position = Some(pos.into());
self
}
pub fn from_space(mut self, space_id: Id) -> Self {
self.from_space = Some(space_id);
self
}
pub fn from_version(mut self, version_id: Id) -> Self {
self.from_version = Some(version_id);
self
}
pub fn to_space(mut self, space_id: Id) -> Self {
self.to_space = Some(space_id);
self
}
pub fn to_version(mut self, version_id: Id) -> Self {
self.to_version = Some(version_id);
self
}
pub fn build(self) -> Option<CreateRelation<'a>> {
Some(CreateRelation {
id: self.id?,
relation_type: self.relation_type?,
from: self.from?,
from_is_value_ref: self.from_is_value_ref,
to: self.to?,
to_is_value_ref: self.to_is_value_ref,
entity: self.entity,
position: self.position,
from_space: self.from_space,
from_version: self.from_version,
to_space: self.to_space,
to_version: self.to_version,
context: None,
})
}
pub fn from_value_ref(mut self, id: Id) -> Self {
self.from = Some(id);
self.from_is_value_ref = true;
self
}
pub fn to_value_ref(mut self, id: Id) -> Self {
self.to = Some(id);
self.to_is_value_ref = true;
self
}
}
#[derive(Debug, Clone)]
pub struct UpdateRelationBuilder<'a> {
id: Id,
from_space: Option<Id>,
from_version: Option<Id>,
to_space: Option<Id>,
to_version: Option<Id>,
position: Option<Cow<'a, str>>,
unset: Vec<UnsetRelationField>,
}
impl<'a> UpdateRelationBuilder<'a> {
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(),
}
}
pub fn set_from_space(mut self, space_id: Id) -> Self {
self.from_space = Some(space_id);
self
}
pub fn set_from_version(mut self, version_id: Id) -> Self {
self.from_version = Some(version_id);
self
}
pub fn set_to_space(mut self, space_id: Id) -> Self {
self.to_space = Some(space_id);
self
}
pub fn set_to_version(mut self, version_id: Id) -> Self {
self.to_version = Some(version_id);
self
}
pub fn set_position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
self.position = Some(pos.into());
self
}
pub fn unset_from_space(mut self) -> Self {
self.unset.push(UnsetRelationField::FromSpace);
self
}
pub fn unset_from_version(mut self) -> Self {
self.unset.push(UnsetRelationField::FromVersion);
self
}
pub fn unset_to_space(mut self) -> Self {
self.unset.push(UnsetRelationField::ToSpace);
self
}
pub fn unset_to_version(mut self) -> Self {
self.unset.push(UnsetRelationField::ToVersion);
self
}
pub fn unset_position(mut self) -> Self {
self.unset.push(UnsetRelationField::Position);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edit_builder_basic() {
let edit_id = [1u8; 16];
let author_id = [2u8; 16];
let entity_id = [3u8; 16];
let prop_id = [4u8; 16];
let edit = EditBuilder::new(edit_id)
.name("Test Edit")
.author(author_id)
.created_at(1234567890)
.create_entity(entity_id, |e| {
e.text(prop_id, "Hello", None)
.integer([5u8; 16], 42, None)
})
.build();
assert_eq!(edit.id, edit_id);
assert_eq!(edit.name, "Test Edit");
assert_eq!(edit.authors, vec![author_id]);
assert_eq!(edit.created_at, 1234567890);
assert_eq!(edit.ops.len(), 1);
match &edit.ops[0] {
Op::CreateEntity(ce) => {
assert_eq!(ce.id, entity_id);
assert_eq!(ce.values.len(), 2);
}
_ => panic!("Expected CreateEntity"),
}
}
#[test]
fn test_edit_builder_relations() {
let edit = EditBuilder::new([1u8; 16])
.create_relation_simple([5u8; 16], [2u8; 16], [3u8; 16], [4u8; 16])
.create_relation_simple([6u8; 16], [2u8; 16], [3u8; 16], [4u8; 16])
.build();
assert_eq!(edit.ops.len(), 2);
match &edit.ops[0] {
Op::CreateRelation(cr) => {
assert_eq!(cr.id, [5u8; 16]);
}
_ => panic!("Expected CreateRelation"),
}
match &edit.ops[1] {
Op::CreateRelation(cr) => {
assert_eq!(cr.id, [6u8; 16]);
}
_ => panic!("Expected CreateRelation"),
}
}
#[test]
fn test_update_entity_builder() {
let entity_id = [1u8; 16];
let prop_id = [2u8; 16];
let edit = EditBuilder::new([0u8; 16])
.update_entity(entity_id, |u| {
u.set_text(prop_id, "New value", None)
.unset_all([3u8; 16])
})
.build();
assert_eq!(edit.ops.len(), 1);
match &edit.ops[0] {
Op::UpdateEntity(ue) => {
assert_eq!(ue.id, entity_id);
assert_eq!(ue.set_properties.len(), 1);
assert_eq!(ue.unset_values.len(), 1);
}
_ => panic!("Expected UpdateEntity"),
}
}
#[test]
fn test_relation_builder_full() {
let edit = EditBuilder::new([0u8; 16])
.create_relation(|r| {
r.id([1u8; 16])
.from([2u8; 16])
.to([3u8; 16])
.relation_type([4u8; 16])
.entity([5u8; 16])
.position("aaa")
.from_space([6u8; 16])
})
.build();
assert_eq!(edit.ops.len(), 1);
match &edit.ops[0] {
Op::CreateRelation(cr) => {
assert_eq!(cr.id, [1u8; 16]);
assert_eq!(cr.entity, Some([5u8; 16]));
assert_eq!(cr.position.as_deref(), Some("aaa"));
assert_eq!(cr.from_space, Some([6u8; 16]));
}
_ => panic!("Expected CreateRelation"),
}
}
#[test]
fn test_entity_builder_all_types() {
let edit = EditBuilder::new([0u8; 16])
.create_entity([1u8; 16], |e| {
e.text([2u8; 16], "text", None)
.integer([3u8; 16], 123, None)
.float([4u8; 16], 3.14, None)
.boolean([5u8; 16], true)
.point([6u8; 16], -74.0060, 40.7128, None)
.date([7u8; 16], "2024-01-15Z") .time([10u8; 16], "14:30:00Z") .datetime([11u8; 16], "2024-01-15T14:30:00Z") .schedule([8u8; 16], "BEGIN:VEVENT\r\nDTSTART:20240315T090000Z\r\nEND:VEVENT")
.bytes([9u8; 16], vec![1, 2, 3, 4])
})
.build();
match &edit.ops[0] {
Op::CreateEntity(ce) => {
assert_eq!(ce.values.len(), 10);
}
_ => panic!("Expected CreateEntity"),
}
}
}