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)]
140pub struct DeleteEntity {
141 pub id: Id,
143 pub context: Option<Context>,
145}
146
147#[derive(Debug, Clone, PartialEq)]
152pub struct RestoreEntity {
153 pub id: Id,
155 pub context: Option<Context>,
157}
158
159#[derive(Debug, Clone, PartialEq)]
163pub struct CreateRelation<'a> {
164 pub id: Id,
166 pub relation_type: Id,
168 pub from: Id,
170 pub from_is_value_ref: bool,
173 pub from_space: Option<Id>,
175 pub from_version: Option<Id>,
177 pub to: Id,
179 pub to_is_value_ref: bool,
182 pub to_space: Option<Id>,
184 pub to_version: Option<Id>,
186 pub entity: Option<Id>,
189 pub position: Option<Cow<'a, str>>,
191 pub context: Option<Context>,
193}
194
195impl CreateRelation<'_> {
196 pub fn entity_id(&self) -> Id {
201 use crate::model::id::relation_entity_id;
202 match self.entity {
203 Some(id) => id,
204 None => relation_entity_id(&self.id),
205 }
206 }
207
208 pub fn has_explicit_entity(&self) -> bool {
210 self.entity.is_some()
211 }
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
216pub enum UnsetRelationField {
217 FromSpace,
218 FromVersion,
219 ToSpace,
220 ToVersion,
221 Position,
222}
223
224#[derive(Debug, Clone, PartialEq, Default)]
229pub struct UpdateRelation<'a> {
230 pub id: Id,
232 pub from_space: Option<Id>,
234 pub from_version: Option<Id>,
236 pub to_space: Option<Id>,
238 pub to_version: Option<Id>,
240 pub position: Option<Cow<'a, str>>,
242 pub unset: Vec<UnsetRelationField>,
244 pub context: Option<Context>,
246}
247
248impl UpdateRelation<'_> {
249 pub fn new(id: Id) -> Self {
251 Self {
252 id,
253 from_space: None,
254 from_version: None,
255 to_space: None,
256 to_version: None,
257 position: None,
258 unset: Vec::new(),
259 context: None,
260 }
261 }
262
263 pub fn is_empty(&self) -> bool {
265 self.from_space.is_none()
266 && self.from_version.is_none()
267 && self.to_space.is_none()
268 && self.to_version.is_none()
269 && self.position.is_none()
270 && self.unset.is_empty()
271 }
272}
273
274#[derive(Debug, Clone, PartialEq)]
279pub struct DeleteRelation {
280 pub id: Id,
282 pub context: Option<Context>,
284}
285
286#[derive(Debug, Clone, PartialEq)]
291pub struct RestoreRelation {
292 pub id: Id,
294 pub context: Option<Context>,
296}
297
298#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct CreateValueRef {
304 pub id: Id,
306 pub entity: Id,
308 pub property: Id,
310 pub language: Option<Id>,
312 pub space: Option<Id>,
314}
315
316pub fn validate_position(pos: &str) -> Result<(), &'static str> {
323 if pos.is_empty() {
324 return Err("position cannot be empty");
325 }
326 if pos.len() > 64 {
327 return Err("position exceeds 64 characters");
328 }
329 for c in pos.chars() {
330 if !c.is_ascii_alphanumeric() {
331 return Err("position contains invalid character");
332 }
333 }
334 Ok(())
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn test_op_type_codes() {
343 assert_eq!(
344 Op::CreateEntity(CreateEntity {
345 id: [0; 16],
346 values: vec![],
347 context: None,
348 })
349 .op_type(),
350 1
351 );
352 assert_eq!(Op::UpdateEntity(UpdateEntity::new([0; 16])).op_type(), 2);
353 assert_eq!(Op::DeleteEntity(DeleteEntity { id: [0; 16], context: None }).op_type(), 3);
354 }
355
356 #[test]
357 fn test_validate_position() {
358 assert!(validate_position("abc123").is_ok());
359 assert!(validate_position("aV").is_ok());
360 assert!(validate_position("a").is_ok());
361
362 assert!(validate_position("").is_err());
364
365 assert!(validate_position("abc-123").is_err());
367 assert!(validate_position("abc_123").is_err());
368 assert!(validate_position("abc 123").is_err());
369
370 let long = "a".repeat(65);
372 assert!(validate_position(&long).is_err());
373
374 let exact = "a".repeat(64);
376 assert!(validate_position(&exact).is_ok());
377 }
378
379 #[test]
380 fn test_update_entity_is_empty() {
381 let update = UpdateEntity::new([0; 16]);
382 assert!(update.is_empty());
383
384 let mut update2 = UpdateEntity::new([0; 16]);
385 update2.set_properties.push(PropertyValue {
386 property: [1; 16],
387 value: crate::model::Value::Bool(true),
388 });
389 assert!(!update2.is_empty());
390 }
391
392 #[test]
393 fn test_entity_id_derivation() {
394 use crate::model::id::relation_entity_id;
395
396 let rel_id = [5u8; 16];
397 let from = [1u8; 16];
398 let to = [2u8; 16];
399 let rel_type = [3u8; 16];
400
401 let rel_auto = CreateRelation {
403 id: rel_id,
404 relation_type: rel_type,
405 from,
406 from_is_value_ref: false,
407 to,
408 to_is_value_ref: false,
409 entity: None,
410 position: None,
411 from_space: None,
412 from_version: None,
413 to_space: None,
414 to_version: None,
415 context: None,
416 };
417 assert_eq!(rel_auto.entity_id(), relation_entity_id(&rel_id));
418 assert!(!rel_auto.has_explicit_entity());
419
420 let explicit_entity = [6u8; 16];
422 let rel_explicit = CreateRelation {
423 id: rel_id,
424 relation_type: rel_type,
425 from,
426 from_is_value_ref: false,
427 to,
428 to_is_value_ref: false,
429 entity: Some(explicit_entity),
430 position: None,
431 from_space: None,
432 from_version: None,
433 to_space: None,
434 to_version: None,
435 context: None,
436 };
437 assert_eq!(rel_explicit.entity_id(), explicit_entity);
438 assert!(rel_explicit.has_explicit_entity());
439 }
440
441 #[test]
442 fn test_update_relation_is_empty() {
443 let update = UpdateRelation::new([0; 16]);
444 assert!(update.is_empty());
445
446 let mut update2 = UpdateRelation::new([0; 16]);
447 update2.from_space = Some([1; 16]);
448 assert!(!update2.is_empty());
449
450 let mut update3 = UpdateRelation::new([0; 16]);
451 update3.unset.push(UnsetRelationField::Position);
452 assert!(!update3.is_empty());
453 }
454}