1use std::borrow::Cow;
6
7use crate::model::{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}
51
52#[derive(Debug, Clone, PartialEq, Default)]
58pub struct UpdateEntity<'a> {
59 pub id: Id,
61 pub set_properties: Vec<PropertyValue<'a>>,
63 pub unset_values: Vec<UnsetValue>,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum UnsetLanguage {
70 All,
72 English,
74 Specific(Id),
76}
77
78impl Default for UnsetLanguage {
79 fn default() -> Self {
80 Self::All
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Default)]
86pub struct UnsetValue {
87 pub property: Id,
89 pub language: UnsetLanguage,
94}
95
96impl UnsetValue {
97 pub fn all(property: Id) -> Self {
99 Self { property, language: UnsetLanguage::All }
100 }
101
102 pub fn english(property: Id) -> Self {
104 Self { property, language: UnsetLanguage::English }
105 }
106
107 pub fn language(property: Id, language: Id) -> Self {
109 Self { property, language: UnsetLanguage::Specific(language) }
110 }
111}
112
113impl<'a> UpdateEntity<'a> {
114 pub fn new(id: Id) -> Self {
116 Self {
117 id,
118 set_properties: Vec::new(),
119 unset_values: Vec::new(),
120 }
121 }
122
123 pub fn is_empty(&self) -> bool {
125 self.set_properties.is_empty() && self.unset_values.is_empty()
126 }
127}
128
129
130#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct DeleteEntity {
136 pub id: Id,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct RestoreEntity {
146 pub id: Id,
148}
149
150#[derive(Debug, Clone, PartialEq)]
154pub struct CreateRelation<'a> {
155 pub id: Id,
157 pub relation_type: Id,
159 pub from: Id,
161 pub from_is_value_ref: bool,
164 pub from_space: Option<Id>,
166 pub from_version: Option<Id>,
168 pub to: Id,
170 pub to_is_value_ref: bool,
173 pub to_space: Option<Id>,
175 pub to_version: Option<Id>,
177 pub entity: Option<Id>,
180 pub position: Option<Cow<'a, str>>,
182}
183
184impl CreateRelation<'_> {
185 pub fn entity_id(&self) -> Id {
190 use crate::model::id::relation_entity_id;
191 match self.entity {
192 Some(id) => id,
193 None => relation_entity_id(&self.id),
194 }
195 }
196
197 pub fn has_explicit_entity(&self) -> bool {
199 self.entity.is_some()
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
205pub enum UnsetRelationField {
206 FromSpace,
207 FromVersion,
208 ToSpace,
209 ToVersion,
210 Position,
211}
212
213#[derive(Debug, Clone, PartialEq, Default)]
218pub struct UpdateRelation<'a> {
219 pub id: Id,
221 pub from_space: Option<Id>,
223 pub from_version: Option<Id>,
225 pub to_space: Option<Id>,
227 pub to_version: Option<Id>,
229 pub position: Option<Cow<'a, str>>,
231 pub unset: Vec<UnsetRelationField>,
233}
234
235impl UpdateRelation<'_> {
236 pub fn new(id: Id) -> Self {
238 Self {
239 id,
240 from_space: None,
241 from_version: None,
242 to_space: None,
243 to_version: None,
244 position: None,
245 unset: Vec::new(),
246 }
247 }
248
249 pub fn is_empty(&self) -> bool {
251 self.from_space.is_none()
252 && self.from_version.is_none()
253 && self.to_space.is_none()
254 && self.to_version.is_none()
255 && self.position.is_none()
256 && self.unset.is_empty()
257 }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq)]
265pub struct DeleteRelation {
266 pub id: Id,
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
275pub struct RestoreRelation {
276 pub id: Id,
278}
279
280#[derive(Debug, Clone, PartialEq, Eq)]
285pub struct CreateValueRef {
286 pub id: Id,
288 pub entity: Id,
290 pub property: Id,
292 pub language: Option<Id>,
294 pub space: Option<Id>,
296}
297
298pub fn validate_position(pos: &str) -> Result<(), &'static str> {
305 if pos.is_empty() {
306 return Err("position cannot be empty");
307 }
308 if pos.len() > 64 {
309 return Err("position exceeds 64 characters");
310 }
311 for c in pos.chars() {
312 if !c.is_ascii_alphanumeric() {
313 return Err("position contains invalid character");
314 }
315 }
316 Ok(())
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn test_op_type_codes() {
325 assert_eq!(
326 Op::CreateEntity(CreateEntity {
327 id: [0; 16],
328 values: vec![]
329 })
330 .op_type(),
331 1
332 );
333 assert_eq!(Op::UpdateEntity(UpdateEntity::new([0; 16])).op_type(), 2);
334 assert_eq!(Op::DeleteEntity(DeleteEntity { id: [0; 16] }).op_type(), 3);
335 }
336
337 #[test]
338 fn test_validate_position() {
339 assert!(validate_position("abc123").is_ok());
340 assert!(validate_position("aV").is_ok());
341 assert!(validate_position("a").is_ok());
342
343 assert!(validate_position("").is_err());
345
346 assert!(validate_position("abc-123").is_err());
348 assert!(validate_position("abc_123").is_err());
349 assert!(validate_position("abc 123").is_err());
350
351 let long = "a".repeat(65);
353 assert!(validate_position(&long).is_err());
354
355 let exact = "a".repeat(64);
357 assert!(validate_position(&exact).is_ok());
358 }
359
360 #[test]
361 fn test_update_entity_is_empty() {
362 let update = UpdateEntity::new([0; 16]);
363 assert!(update.is_empty());
364
365 let mut update2 = UpdateEntity::new([0; 16]);
366 update2.set_properties.push(PropertyValue {
367 property: [1; 16],
368 value: crate::model::Value::Bool(true),
369 });
370 assert!(!update2.is_empty());
371 }
372
373 #[test]
374 fn test_entity_id_derivation() {
375 use crate::model::id::relation_entity_id;
376
377 let rel_id = [5u8; 16];
378 let from = [1u8; 16];
379 let to = [2u8; 16];
380 let rel_type = [3u8; 16];
381
382 let rel_auto = CreateRelation {
384 id: rel_id,
385 relation_type: rel_type,
386 from,
387 from_is_value_ref: false,
388 to,
389 to_is_value_ref: false,
390 entity: None,
391 position: None,
392 from_space: None,
393 from_version: None,
394 to_space: None,
395 to_version: None,
396 };
397 assert_eq!(rel_auto.entity_id(), relation_entity_id(&rel_id));
398 assert!(!rel_auto.has_explicit_entity());
399
400 let explicit_entity = [6u8; 16];
402 let rel_explicit = 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: Some(explicit_entity),
410 position: None,
411 from_space: None,
412 from_version: None,
413 to_space: None,
414 to_version: None,
415 };
416 assert_eq!(rel_explicit.entity_id(), explicit_entity);
417 assert!(rel_explicit.has_explicit_entity());
418 }
419
420 #[test]
421 fn test_update_relation_is_empty() {
422 let update = UpdateRelation::new([0; 16]);
423 assert!(update.is_empty());
424
425 let mut update2 = UpdateRelation::new([0; 16]);
426 update2.from_space = Some([1; 16]);
427 assert!(!update2.is_empty());
428
429 let mut update3 = UpdateRelation::new([0; 16]);
430 update3.unset.push(UnsetRelationField::Position);
431 assert!(!update3.is_empty());
432 }
433}