1use std::borrow::Cow;
6
7use crate::model::{Context, Id, PropertyValue};
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum Op<'a> {
12 CreateEntity(CreateEntity<'a>),
13 UpdateEntity(UpdateEntity<'a>),
14 DeleteEntity(DeleteEntity),
15 RestoreEntity(RestoreEntity),
16 CreateRelation(CreateRelation<'a>),
17 UpdateRelation(UpdateRelation<'a>),
18 DeleteRelation(DeleteRelation),
19 RestoreRelation(RestoreRelation),
20 CreateValueRef(CreateValueRef),
21}
22
23impl Op<'_> {
24 pub fn op_type(&self) -> u8 {
26 match self {
27 Op::CreateEntity(_) => 1,
28 Op::UpdateEntity(_) => 2,
29 Op::DeleteEntity(_) => 3,
30 Op::RestoreEntity(_) => 4,
31 Op::CreateRelation(_) => 5,
32 Op::UpdateRelation(_) => 6,
33 Op::DeleteRelation(_) => 7,
34 Op::RestoreRelation(_) => 8,
35 Op::CreateValueRef(_) => 9,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq)]
45pub struct CreateEntity<'a> {
46 pub id: Id,
48 pub values: Vec<PropertyValue<'a>>,
50 pub context: Option<Context>,
52}
53
54#[derive(Debug, Clone, PartialEq, Default)]
60pub struct UpdateEntity<'a> {
61 pub id: Id,
63 pub set_properties: Vec<PropertyValue<'a>>,
65 pub unset_values: Vec<UnsetValue>,
67 pub context: Option<Context>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum UnsetLanguage {
74 All,
76 English,
78 Specific(Id),
80}
81
82impl Default for UnsetLanguage {
83 fn default() -> Self {
84 Self::All
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Default)]
90pub struct UnsetValue {
91 pub property: Id,
93 pub language: UnsetLanguage,
98}
99
100impl UnsetValue {
101 pub fn all(property: Id) -> Self {
103 Self { property, language: UnsetLanguage::All }
104 }
105
106 pub fn english(property: Id) -> Self {
108 Self { property, language: UnsetLanguage::English }
109 }
110
111 pub fn language(property: Id, language: Id) -> Self {
113 Self { property, language: UnsetLanguage::Specific(language) }
114 }
115}
116
117impl<'a> UpdateEntity<'a> {
118 pub fn new(id: Id) -> Self {
120 Self {
121 id,
122 set_properties: Vec::new(),
123 unset_values: Vec::new(),
124 context: None,
125 }
126 }
127
128 pub fn is_empty(&self) -> bool {
130 self.set_properties.is_empty() && self.unset_values.is_empty()
131 }
132}
133
134
135#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct DeleteEntity {
141 pub id: Id,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct RestoreEntity {
151 pub id: Id,
153}
154
155#[derive(Debug, Clone, PartialEq)]
159pub struct CreateRelation<'a> {
160 pub id: Id,
162 pub relation_type: Id,
164 pub from: Id,
166 pub from_is_value_ref: bool,
169 pub from_space: Option<Id>,
171 pub from_version: Option<Id>,
173 pub to: Id,
175 pub to_is_value_ref: bool,
178 pub to_space: Option<Id>,
180 pub to_version: Option<Id>,
182 pub entity: Option<Id>,
185 pub position: Option<Cow<'a, str>>,
187 pub context: Option<Context>,
189}
190
191impl CreateRelation<'_> {
192 pub fn entity_id(&self) -> Id {
197 use crate::model::id::relation_entity_id;
198 match self.entity {
199 Some(id) => id,
200 None => relation_entity_id(&self.id),
201 }
202 }
203
204 pub fn has_explicit_entity(&self) -> bool {
206 self.entity.is_some()
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
212pub enum UnsetRelationField {
213 FromSpace,
214 FromVersion,
215 ToSpace,
216 ToVersion,
217 Position,
218}
219
220#[derive(Debug, Clone, PartialEq, Default)]
225pub struct UpdateRelation<'a> {
226 pub id: Id,
228 pub from_space: Option<Id>,
230 pub from_version: Option<Id>,
232 pub to_space: Option<Id>,
234 pub to_version: Option<Id>,
236 pub position: Option<Cow<'a, str>>,
238 pub unset: Vec<UnsetRelationField>,
240 pub context: Option<Context>,
242}
243
244impl UpdateRelation<'_> {
245 pub fn new(id: Id) -> Self {
247 Self {
248 id,
249 from_space: None,
250 from_version: None,
251 to_space: None,
252 to_version: None,
253 position: None,
254 unset: Vec::new(),
255 context: None,
256 }
257 }
258
259 pub fn is_empty(&self) -> bool {
261 self.from_space.is_none()
262 && self.from_version.is_none()
263 && self.to_space.is_none()
264 && self.to_version.is_none()
265 && self.position.is_none()
266 && self.unset.is_empty()
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
275pub struct DeleteRelation {
276 pub id: Id,
278}
279
280#[derive(Debug, Clone, PartialEq, Eq)]
285pub struct RestoreRelation {
286 pub id: Id,
288}
289
290#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct CreateValueRef {
296 pub id: Id,
298 pub entity: Id,
300 pub property: Id,
302 pub language: Option<Id>,
304 pub space: Option<Id>,
306}
307
308pub fn validate_position(pos: &str) -> Result<(), &'static str> {
315 if pos.is_empty() {
316 return Err("position cannot be empty");
317 }
318 if pos.len() > 64 {
319 return Err("position exceeds 64 characters");
320 }
321 for c in pos.chars() {
322 if !c.is_ascii_alphanumeric() {
323 return Err("position contains invalid character");
324 }
325 }
326 Ok(())
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_op_type_codes() {
335 assert_eq!(
336 Op::CreateEntity(CreateEntity {
337 id: [0; 16],
338 values: vec![],
339 context: None,
340 })
341 .op_type(),
342 1
343 );
344 assert_eq!(Op::UpdateEntity(UpdateEntity::new([0; 16])).op_type(), 2);
345 assert_eq!(Op::DeleteEntity(DeleteEntity { id: [0; 16] }).op_type(), 3);
346 }
347
348 #[test]
349 fn test_validate_position() {
350 assert!(validate_position("abc123").is_ok());
351 assert!(validate_position("aV").is_ok());
352 assert!(validate_position("a").is_ok());
353
354 assert!(validate_position("").is_err());
356
357 assert!(validate_position("abc-123").is_err());
359 assert!(validate_position("abc_123").is_err());
360 assert!(validate_position("abc 123").is_err());
361
362 let long = "a".repeat(65);
364 assert!(validate_position(&long).is_err());
365
366 let exact = "a".repeat(64);
368 assert!(validate_position(&exact).is_ok());
369 }
370
371 #[test]
372 fn test_update_entity_is_empty() {
373 let update = UpdateEntity::new([0; 16]);
374 assert!(update.is_empty());
375
376 let mut update2 = UpdateEntity::new([0; 16]);
377 update2.set_properties.push(PropertyValue {
378 property: [1; 16],
379 value: crate::model::Value::Bool(true),
380 });
381 assert!(!update2.is_empty());
382 }
383
384 #[test]
385 fn test_entity_id_derivation() {
386 use crate::model::id::relation_entity_id;
387
388 let rel_id = [5u8; 16];
389 let from = [1u8; 16];
390 let to = [2u8; 16];
391 let rel_type = [3u8; 16];
392
393 let rel_auto = CreateRelation {
395 id: rel_id,
396 relation_type: rel_type,
397 from,
398 from_is_value_ref: false,
399 to,
400 to_is_value_ref: false,
401 entity: None,
402 position: None,
403 from_space: None,
404 from_version: None,
405 to_space: None,
406 to_version: None,
407 context: None,
408 };
409 assert_eq!(rel_auto.entity_id(), relation_entity_id(&rel_id));
410 assert!(!rel_auto.has_explicit_entity());
411
412 let explicit_entity = [6u8; 16];
414 let rel_explicit = CreateRelation {
415 id: rel_id,
416 relation_type: rel_type,
417 from,
418 from_is_value_ref: false,
419 to,
420 to_is_value_ref: false,
421 entity: Some(explicit_entity),
422 position: None,
423 from_space: None,
424 from_version: None,
425 to_space: None,
426 to_version: None,
427 context: None,
428 };
429 assert_eq!(rel_explicit.entity_id(), explicit_entity);
430 assert!(rel_explicit.has_explicit_entity());
431 }
432
433 #[test]
434 fn test_update_relation_is_empty() {
435 let update = UpdateRelation::new([0; 16]);
436 assert!(update.is_empty());
437
438 let mut update2 = UpdateRelation::new([0; 16]);
439 update2.from_space = Some([1; 16]);
440 assert!(!update2.is_empty());
441
442 let mut update3 = UpdateRelation::new([0; 16]);
443 update3.unset.push(UnsetRelationField::Position);
444 assert!(!update3.is_empty());
445 }
446}