1use crate::change::SemanticChange;
2use crate::error::{EditError, Result};
3use strixonomy_obo::OboPatchOp;
4use strixonomy_owl::PatchOp;
5
6pub fn invert_patch_op(op: &PatchOp) -> Result<PatchOp> {
7 Ok(match op {
8 PatchOp::AddPrefix { prefix, namespace_iri: _ } => {
9 PatchOp::RemovePrefix { prefix: prefix.clone() }
10 }
11 PatchOp::RemovePrefix { .. } => {
12 return Err(EditError::NotInvertible(
13 "remove_prefix inverse requires prior namespace IRI".into(),
14 ));
15 }
16 PatchOp::SetPrefix { .. } => {
17 return Err(EditError::NotInvertible("set_prefix requires prior value".into()));
18 }
19 PatchOp::SetOntologyIri { .. } | PatchOp::SetVersionIri { .. } => {
20 return Err(EditError::NotInvertible("ontology IRI set requires prior value".into()));
21 }
22 PatchOp::AddOntologyAnnotation { ontology_iri, predicate, value } => {
23 PatchOp::RemoveOntologyAnnotation {
24 ontology_iri: ontology_iri.clone(),
25 predicate: predicate.clone(),
26 value: value.clone(),
27 }
28 }
29 PatchOp::RemoveOntologyAnnotation { ontology_iri, predicate, value } => {
30 PatchOp::AddOntologyAnnotation {
31 ontology_iri: ontology_iri.clone(),
32 predicate: predicate.clone(),
33 value: value.clone(),
34 }
35 }
36 PatchOp::CreateEntity { entity_iri, kind: _ } => {
37 PatchOp::DeleteEntity { entity_iri: entity_iri.clone() }
38 }
39 PatchOp::DeleteEntity { entity_iri: _ } => {
40 return Err(EditError::NotInvertible(
41 "delete_entity inverse requires entity kind".into(),
42 ));
43 }
44 PatchOp::SetLabel { .. }
45 | PatchOp::SetComment { .. }
46 | PatchOp::SetEquivalentClass { .. } => {
47 return Err(EditError::NotInvertible("set_* requires prior value".into()));
48 }
49 PatchOp::AddLabel { entity_iri, value } => {
50 PatchOp::RemoveLabel { entity_iri: entity_iri.clone(), value: value.clone() }
51 }
52 PatchOp::RemoveLabel { entity_iri, value } => {
53 PatchOp::AddLabel { entity_iri: entity_iri.clone(), value: value.clone() }
54 }
55 PatchOp::AddComment { entity_iri, value } => {
56 PatchOp::RemoveComment { entity_iri: entity_iri.clone(), value: value.clone() }
57 }
58 PatchOp::RemoveComment { entity_iri, value } => {
59 PatchOp::AddComment { entity_iri: entity_iri.clone(), value: value.clone() }
60 }
61 PatchOp::AddSubClassOf { entity_iri, parent_iri } => PatchOp::RemoveSubClassOf {
62 entity_iri: entity_iri.clone(),
63 parent_iri: parent_iri.clone(),
64 },
65 PatchOp::RemoveSubClassOf { entity_iri, parent_iri } => PatchOp::AddSubClassOf {
66 entity_iri: entity_iri.clone(),
67 parent_iri: parent_iri.clone(),
68 },
69 PatchOp::AddComplexSubClassOf { entity_iri, manchester } => {
70 PatchOp::RemoveComplexSubClassOf {
71 entity_iri: entity_iri.clone(),
72 manchester: manchester.clone(),
73 }
74 }
75 PatchOp::RemoveComplexSubClassOf { entity_iri, manchester } => {
76 PatchOp::AddComplexSubClassOf {
77 entity_iri: entity_iri.clone(),
78 manchester: manchester.clone(),
79 }
80 }
81 PatchOp::AddEquivalentClass { entity_iri, manchester } => PatchOp::RemoveEquivalentClass {
82 entity_iri: entity_iri.clone(),
83 manchester: manchester.clone(),
84 },
85 PatchOp::RemoveEquivalentClass { entity_iri, manchester } => PatchOp::AddEquivalentClass {
86 entity_iri: entity_iri.clone(),
87 manchester: manchester.clone(),
88 },
89 PatchOp::SetDeprecated { entity_iri, value } => {
90 if !*value {
91 return Err(EditError::NotInvertible(
92 "set_deprecated false inverse would invent deprecation without prior state"
93 .into(),
94 ));
95 }
96 PatchOp::SetDeprecated { entity_iri: entity_iri.clone(), value: false }
97 }
98 PatchOp::AddDisjointClass { entity_iri, other_iri } => PatchOp::RemoveDisjointClass {
99 entity_iri: entity_iri.clone(),
100 other_iri: other_iri.clone(),
101 },
102 PatchOp::RemoveDisjointClass { entity_iri, other_iri } => PatchOp::AddDisjointClass {
103 entity_iri: entity_iri.clone(),
104 other_iri: other_iri.clone(),
105 },
106 PatchOp::AddImport { ontology_iri, import_iri } => PatchOp::RemoveImport {
107 ontology_iri: ontology_iri.clone(),
108 import_iri: import_iri.clone(),
109 },
110 PatchOp::RemoveImport { ontology_iri, import_iri } => PatchOp::AddImport {
111 ontology_iri: ontology_iri.clone(),
112 import_iri: import_iri.clone(),
113 },
114 PatchOp::AddDomain { entity_iri, class_iri } => {
115 PatchOp::RemoveDomain { entity_iri: entity_iri.clone(), class_iri: class_iri.clone() }
116 }
117 PatchOp::RemoveDomain { entity_iri, class_iri } => {
118 PatchOp::AddDomain { entity_iri: entity_iri.clone(), class_iri: class_iri.clone() }
119 }
120 PatchOp::AddRange { entity_iri, range_iri } => {
121 PatchOp::RemoveRange { entity_iri: entity_iri.clone(), range_iri: range_iri.clone() }
122 }
123 PatchOp::RemoveRange { entity_iri, range_iri } => {
124 PatchOp::AddRange { entity_iri: entity_iri.clone(), range_iri: range_iri.clone() }
125 }
126 PatchOp::SetFunctional { .. }
127 | PatchOp::SetInverseFunctional { .. }
128 | PatchOp::SetTransitive { .. }
129 | PatchOp::SetSymmetric { .. }
130 | PatchOp::SetAsymmetric { .. }
131 | PatchOp::SetReflexive { .. }
132 | PatchOp::SetIrreflexive { .. } => {
133 let value = match op {
134 PatchOp::SetFunctional { value, .. }
135 | PatchOp::SetInverseFunctional { value, .. }
136 | PatchOp::SetTransitive { value, .. }
137 | PatchOp::SetSymmetric { value, .. }
138 | PatchOp::SetAsymmetric { value, .. }
139 | PatchOp::SetReflexive { value, .. }
140 | PatchOp::SetIrreflexive { value, .. } => *value,
141 _ => unreachable!(),
142 };
143 if !value {
144 return Err(EditError::NotInvertible(
145 "clearing a property characteristic is not invertible without prior state"
146 .into(),
147 ));
148 }
149 match op {
150 PatchOp::SetFunctional { entity_iri, .. } => {
151 PatchOp::SetFunctional { entity_iri: entity_iri.clone(), value: false }
152 }
153 PatchOp::SetInverseFunctional { entity_iri, .. } => {
154 PatchOp::SetInverseFunctional { entity_iri: entity_iri.clone(), value: false }
155 }
156 PatchOp::SetTransitive { entity_iri, .. } => {
157 PatchOp::SetTransitive { entity_iri: entity_iri.clone(), value: false }
158 }
159 PatchOp::SetSymmetric { entity_iri, .. } => {
160 PatchOp::SetSymmetric { entity_iri: entity_iri.clone(), value: false }
161 }
162 PatchOp::SetAsymmetric { entity_iri, .. } => {
163 PatchOp::SetAsymmetric { entity_iri: entity_iri.clone(), value: false }
164 }
165 PatchOp::SetReflexive { entity_iri, .. } => {
166 PatchOp::SetReflexive { entity_iri: entity_iri.clone(), value: false }
167 }
168 PatchOp::SetIrreflexive { entity_iri, .. } => {
169 PatchOp::SetIrreflexive { entity_iri: entity_iri.clone(), value: false }
170 }
171 _ => unreachable!(),
172 }
173 }
174 PatchOp::AddPropertyChain { entity_iri, properties } => PatchOp::RemovePropertyChain {
175 entity_iri: entity_iri.clone(),
176 properties: properties.clone(),
177 },
178 PatchOp::RemovePropertyChain { entity_iri, properties } => PatchOp::AddPropertyChain {
179 entity_iri: entity_iri.clone(),
180 properties: properties.clone(),
181 },
182 PatchOp::AddClassAssertion { entity_iri, class_iri } => PatchOp::RemoveClassAssertion {
183 entity_iri: entity_iri.clone(),
184 class_iri: class_iri.clone(),
185 },
186 PatchOp::RemoveClassAssertion { entity_iri, class_iri } => PatchOp::AddClassAssertion {
187 entity_iri: entity_iri.clone(),
188 class_iri: class_iri.clone(),
189 },
190 PatchOp::AddObjectPropertyAssertion { entity_iri, property_iri, target_iri } => {
191 PatchOp::RemoveObjectPropertyAssertion {
192 entity_iri: entity_iri.clone(),
193 property_iri: property_iri.clone(),
194 target_iri: target_iri.clone(),
195 }
196 }
197 PatchOp::RemoveObjectPropertyAssertion { entity_iri, property_iri, target_iri } => {
198 PatchOp::AddObjectPropertyAssertion {
199 entity_iri: entity_iri.clone(),
200 property_iri: property_iri.clone(),
201 target_iri: target_iri.clone(),
202 }
203 }
204 PatchOp::AddDataPropertyAssertion { entity_iri, property_iri, value } => {
205 PatchOp::RemoveDataPropertyAssertion {
206 entity_iri: entity_iri.clone(),
207 property_iri: property_iri.clone(),
208 value: value.clone(),
209 }
210 }
211 PatchOp::RemoveDataPropertyAssertion { entity_iri, property_iri, value } => {
212 PatchOp::AddDataPropertyAssertion {
213 entity_iri: entity_iri.clone(),
214 property_iri: property_iri.clone(),
215 value: value.clone(),
216 }
217 }
218 PatchOp::AddAnnotation { entity_iri, predicate, value } => PatchOp::RemoveAnnotation {
219 entity_iri: entity_iri.clone(),
220 predicate: predicate.clone(),
221 value: value.clone(),
222 },
223 PatchOp::RemoveAnnotation { entity_iri, predicate, value } => PatchOp::AddAnnotation {
224 entity_iri: entity_iri.clone(),
225 predicate: predicate.clone(),
226 value: value.clone(),
227 },
228 PatchOp::AddHasKey { class_iri, properties } => {
229 PatchOp::RemoveHasKey { class_iri: class_iri.clone(), properties: properties.clone() }
230 }
231 PatchOp::RemoveHasKey { class_iri, properties } => {
232 PatchOp::AddHasKey { class_iri: class_iri.clone(), properties: properties.clone() }
233 }
234 PatchOp::AddDisjointUnion { class_iri, members } => {
235 PatchOp::RemoveDisjointUnion { class_iri: class_iri.clone(), members: members.clone() }
236 }
237 PatchOp::RemoveDisjointUnion { class_iri, members } => {
238 PatchOp::AddDisjointUnion { class_iri: class_iri.clone(), members: members.clone() }
239 }
240 PatchOp::AddInverseObjectProperties { property_iri, inverse_iri } => {
241 PatchOp::RemoveInverseObjectProperties {
242 property_iri: property_iri.clone(),
243 inverse_iri: inverse_iri.clone(),
244 }
245 }
246 PatchOp::RemoveInverseObjectProperties { property_iri, inverse_iri } => {
247 PatchOp::AddInverseObjectProperties {
248 property_iri: property_iri.clone(),
249 inverse_iri: inverse_iri.clone(),
250 }
251 }
252 PatchOp::AddEquivalentObjectProperties { properties } => {
253 PatchOp::RemoveEquivalentObjectProperties { properties: properties.clone() }
254 }
255 PatchOp::RemoveEquivalentObjectProperties { properties } => {
256 PatchOp::AddEquivalentObjectProperties { properties: properties.clone() }
257 }
258 PatchOp::AddDisjointObjectProperties { properties } => {
259 PatchOp::RemoveDisjointObjectProperties { properties: properties.clone() }
260 }
261 PatchOp::RemoveDisjointObjectProperties { properties } => {
262 PatchOp::AddDisjointObjectProperties { properties: properties.clone() }
263 }
264 PatchOp::AddEquivalentDataProperties { properties } => {
265 PatchOp::RemoveEquivalentDataProperties { properties: properties.clone() }
266 }
267 PatchOp::RemoveEquivalentDataProperties { properties } => {
268 PatchOp::AddEquivalentDataProperties { properties: properties.clone() }
269 }
270 PatchOp::AddDisjointDataProperties { properties } => {
271 PatchOp::RemoveDisjointDataProperties { properties: properties.clone() }
272 }
273 PatchOp::RemoveDisjointDataProperties { properties } => {
274 PatchOp::AddDisjointDataProperties { properties: properties.clone() }
275 }
276 PatchOp::AddSubObjectPropertyOf { property_iri, parent_iri } => {
277 PatchOp::RemoveSubObjectPropertyOf {
278 property_iri: property_iri.clone(),
279 parent_iri: parent_iri.clone(),
280 }
281 }
282 PatchOp::RemoveSubObjectPropertyOf { property_iri, parent_iri } => {
283 PatchOp::AddSubObjectPropertyOf {
284 property_iri: property_iri.clone(),
285 parent_iri: parent_iri.clone(),
286 }
287 }
288 PatchOp::AddSubDataPropertyOf { property_iri, parent_iri } => {
289 PatchOp::RemoveSubDataPropertyOf {
290 property_iri: property_iri.clone(),
291 parent_iri: parent_iri.clone(),
292 }
293 }
294 PatchOp::RemoveSubDataPropertyOf { property_iri, parent_iri } => {
295 PatchOp::AddSubDataPropertyOf {
296 property_iri: property_iri.clone(),
297 parent_iri: parent_iri.clone(),
298 }
299 }
300 PatchOp::AddNegativeObjectPropertyAssertion { entity_iri, property_iri, target_iri } => {
301 PatchOp::RemoveNegativeObjectPropertyAssertion {
302 entity_iri: entity_iri.clone(),
303 property_iri: property_iri.clone(),
304 target_iri: target_iri.clone(),
305 }
306 }
307 PatchOp::RemoveNegativeObjectPropertyAssertion { entity_iri, property_iri, target_iri } => {
308 PatchOp::AddNegativeObjectPropertyAssertion {
309 entity_iri: entity_iri.clone(),
310 property_iri: property_iri.clone(),
311 target_iri: target_iri.clone(),
312 }
313 }
314 PatchOp::AddNegativeDataPropertyAssertion { entity_iri, property_iri, value } => {
315 PatchOp::RemoveNegativeDataPropertyAssertion {
316 entity_iri: entity_iri.clone(),
317 property_iri: property_iri.clone(),
318 value: value.clone(),
319 }
320 }
321 PatchOp::RemoveNegativeDataPropertyAssertion { entity_iri, property_iri, value } => {
322 PatchOp::AddNegativeDataPropertyAssertion {
323 entity_iri: entity_iri.clone(),
324 property_iri: property_iri.clone(),
325 value: value.clone(),
326 }
327 }
328 PatchOp::AddSameIndividual { individuals } => {
329 PatchOp::RemoveSameIndividual { individuals: individuals.clone() }
330 }
331 PatchOp::RemoveSameIndividual { individuals } => {
332 PatchOp::AddSameIndividual { individuals: individuals.clone() }
333 }
334 PatchOp::AddDifferentIndividuals { individuals } => {
335 PatchOp::RemoveDifferentIndividuals { individuals: individuals.clone() }
336 }
337 PatchOp::RemoveDifferentIndividuals { individuals } => {
338 PatchOp::AddDifferentIndividuals { individuals: individuals.clone() }
339 }
340 PatchOp::AddDatatypeDefinition { datatype_iri, manchester } => {
341 PatchOp::RemoveDatatypeDefinition {
342 datatype_iri: datatype_iri.clone(),
343 manchester: manchester.clone(),
344 }
345 }
346 PatchOp::RemoveDatatypeDefinition { datatype_iri, manchester } => {
347 PatchOp::AddDatatypeDefinition {
348 datatype_iri: datatype_iri.clone(),
349 manchester: manchester.clone(),
350 }
351 }
352 PatchOp::AddAxiomAnnotation { axiom_op, subject_iri, related_iri, predicate, value } => {
353 PatchOp::RemoveAxiomAnnotation {
354 axiom_op: axiom_op.clone(),
355 subject_iri: subject_iri.clone(),
356 related_iri: related_iri.clone(),
357 predicate: predicate.clone(),
358 value: value.clone(),
359 }
360 }
361 PatchOp::RemoveAxiomAnnotation { axiom_op, subject_iri, related_iri, predicate, value } => {
362 PatchOp::AddAxiomAnnotation {
363 axiom_op: axiom_op.clone(),
364 subject_iri: subject_iri.clone(),
365 related_iri: related_iri.clone(),
366 predicate: predicate.clone(),
367 value: value.clone(),
368 }
369 }
370 PatchOp::AddSwrlRule { ontology_iri, rule_json } => PatchOp::RemoveSwrlRule {
371 ontology_iri: ontology_iri.clone(),
372 rule_json: rule_json.clone(),
373 },
374 PatchOp::RemoveSwrlRule { ontology_iri, rule_json } => PatchOp::AddSwrlRule {
375 ontology_iri: ontology_iri.clone(),
376 rule_json: rule_json.clone(),
377 },
378 PatchOp::ReplaceSwrlRule { ontology_iri, old_rule_json, new_rule_json } => {
379 PatchOp::ReplaceSwrlRule {
380 ontology_iri: ontology_iri.clone(),
381 old_rule_json: new_rule_json.clone(),
382 new_rule_json: old_rule_json.clone(),
383 }
384 }
385 })
386}
387
388pub fn invert_obo_patch_op(op: &OboPatchOp) -> Result<OboPatchOp> {
389 Ok(match op {
390 OboPatchOp::SetName { .. } | OboPatchOp::SetNamespace { .. } => {
391 return Err(EditError::NotInvertible("obo set_* requires prior value".into()));
392 }
393 OboPatchOp::AddSynonym { term_id, value, scope } => OboPatchOp::RemoveSynonym {
394 term_id: term_id.clone(),
395 value: value.clone(),
396 scope: Some(scope.clone()),
397 },
398 OboPatchOp::RemoveSynonym { term_id, value, scope: Some(scope) } => {
399 OboPatchOp::AddSynonym {
400 term_id: term_id.clone(),
401 value: value.clone(),
402 scope: scope.clone(),
403 }
404 }
405 OboPatchOp::RemoveSynonym { scope: None, .. } => {
406 return Err(EditError::NotInvertible(
407 "remove_synonym inverse requires recorded prior scope".into(),
408 ));
409 }
410 OboPatchOp::AddDef { .. } => {
411 return Err(EditError::NotInvertible(
412 "add_def may replace a prior def; inverse requires prior value".into(),
413 ));
414 }
415 OboPatchOp::RemoveDef { term_id: _ } => {
416 return Err(EditError::NotInvertible("remove_def inverse requires prior def".into()));
417 }
418 OboPatchOp::AddXref { term_id, xref } => {
419 OboPatchOp::RemoveXref { term_id: term_id.clone(), xref: xref.clone() }
420 }
421 OboPatchOp::RemoveXref { term_id, xref } => {
422 OboPatchOp::AddXref { term_id: term_id.clone(), xref: xref.clone() }
423 }
424 OboPatchOp::SetDeprecated { term_id, value } => {
425 if !*value {
426 return Err(EditError::NotInvertible(
427 "set_deprecated false inverse would invent obsolescence without prior state"
428 .into(),
429 ));
430 }
431 OboPatchOp::SetDeprecated { term_id: term_id.clone(), value: false }
432 }
433 OboPatchOp::AddIsA { term_id, parent_id } => {
434 OboPatchOp::RemoveIsA { term_id: term_id.clone(), parent_id: parent_id.clone() }
435 }
436 OboPatchOp::RemoveIsA { term_id, parent_id } => {
437 OboPatchOp::AddIsA { term_id: term_id.clone(), parent_id: parent_id.clone() }
438 }
439 })
440}
441
442pub fn invert_change(change: &SemanticChange) -> Result<SemanticChange> {
443 Ok(match change {
444 SemanticChange::Turtle { change } => {
445 SemanticChange::Turtle { change: invert_patch_op(change)? }
446 }
447 SemanticChange::Obo { change } => {
448 SemanticChange::Obo { change: invert_obo_patch_op(change)? }
449 }
450 })
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456
457 #[test]
458 fn remove_prefix_is_not_invertible() {
459 let err = invert_patch_op(&PatchOp::RemovePrefix { prefix: "ex".into() }).unwrap_err();
460 assert!(matches!(err, EditError::NotInvertible(_)));
461 }
462
463 #[test]
464 fn add_synonym_inverts_to_scoped_remove() {
465 let inverted = invert_obo_patch_op(&OboPatchOp::AddSynonym {
466 term_id: "EX:1".into(),
467 value: "alias".into(),
468 scope: "RELATED".into(),
469 })
470 .expect("invert");
471 assert_eq!(
472 inverted,
473 OboPatchOp::RemoveSynonym {
474 term_id: "EX:1".into(),
475 value: "alias".into(),
476 scope: Some("RELATED".into()),
477 }
478 );
479 }
480
481 #[test]
482 fn remove_synonym_with_scope_inverts_to_add() {
483 let inverted = invert_obo_patch_op(&OboPatchOp::RemoveSynonym {
484 term_id: "EX:1".into(),
485 value: "alias".into(),
486 scope: Some("RELATED".into()),
487 })
488 .expect("invert");
489 assert_eq!(
490 inverted,
491 OboPatchOp::AddSynonym {
492 term_id: "EX:1".into(),
493 value: "alias".into(),
494 scope: "RELATED".into(),
495 }
496 );
497 }
498
499 #[test]
500 fn remove_synonym_without_scope_and_add_def_are_not_invertible() {
501 assert!(matches!(
502 invert_obo_patch_op(&OboPatchOp::RemoveSynonym {
503 term_id: "EX:1".into(),
504 value: "alias".into(),
505 scope: None,
506 }),
507 Err(EditError::NotInvertible(_))
508 ));
509 assert!(matches!(
510 invert_obo_patch_op(&OboPatchOp::AddDef {
511 term_id: "EX:1".into(),
512 value: "new".into(),
513 }),
514 Err(EditError::NotInvertible(_))
515 ));
516 }
517
518 #[test]
519 fn set_deprecated_false_is_not_invertible() {
520 assert!(matches!(
521 invert_patch_op(&PatchOp::SetDeprecated {
522 entity_iri: "http://example.org/A".into(),
523 value: false,
524 }),
525 Err(EditError::NotInvertible(_))
526 ));
527 assert!(matches!(
528 invert_obo_patch_op(&OboPatchOp::SetDeprecated {
529 term_id: "EX:1".into(),
530 value: false,
531 }),
532 Err(EditError::NotInvertible(_))
533 ));
534 }
535
536 #[test]
537 fn set_deprecated_true_inverts_to_false() {
538 let inverted = invert_patch_op(&PatchOp::SetDeprecated {
539 entity_iri: "http://example.org/A".into(),
540 value: true,
541 })
542 .expect("invert");
543 assert_eq!(
544 inverted,
545 PatchOp::SetDeprecated { entity_iri: "http://example.org/A".into(), value: false }
546 );
547 }
548
549 #[test]
550 fn clearing_boolean_characteristic_is_not_invertible() {
551 assert!(matches!(
552 invert_patch_op(&PatchOp::SetFunctional {
553 entity_iri: "http://example.org/p".into(),
554 value: false,
555 }),
556 Err(EditError::NotInvertible(_))
557 ));
558 let inverted = invert_patch_op(&PatchOp::SetFunctional {
559 entity_iri: "http://example.org/p".into(),
560 value: true,
561 })
562 .expect("invert set true");
563 assert_eq!(
564 inverted,
565 PatchOp::SetFunctional { entity_iri: "http://example.org/p".into(), value: false }
566 );
567 }
568
569 #[test]
570 fn has_key_and_inverse_invert() {
571 let inverted = invert_patch_op(&PatchOp::AddHasKey {
572 class_iri: "http://example.org/C".into(),
573 properties: vec!["http://example.org/p".into()],
574 })
575 .expect("invert has key");
576 assert_eq!(
577 inverted,
578 PatchOp::RemoveHasKey {
579 class_iri: "http://example.org/C".into(),
580 properties: vec!["http://example.org/p".into()],
581 }
582 );
583 let inverted = invert_patch_op(&PatchOp::AddInverseObjectProperties {
584 property_iri: "http://example.org/p".into(),
585 inverse_iri: "http://example.org/q".into(),
586 })
587 .expect("invert inverse");
588 assert_eq!(
589 inverted,
590 PatchOp::RemoveInverseObjectProperties {
591 property_iri: "http://example.org/p".into(),
592 inverse_iri: "http://example.org/q".into(),
593 }
594 );
595 }
596
597 #[test]
598 fn negative_assertion_and_same_individual_invert() {
599 let inverted = invert_patch_op(&PatchOp::AddNegativeObjectPropertyAssertion {
600 entity_iri: "http://example.org/a".into(),
601 property_iri: "http://example.org/p".into(),
602 target_iri: "http://example.org/b".into(),
603 })
604 .expect("invert nopa");
605 assert!(matches!(inverted, PatchOp::RemoveNegativeObjectPropertyAssertion { .. }));
606 let inverted = invert_patch_op(&PatchOp::AddSameIndividual {
607 individuals: vec!["http://example.org/a".into(), "http://example.org/b".into()],
608 })
609 .expect("invert same");
610 assert_eq!(
611 inverted,
612 PatchOp::RemoveSameIndividual {
613 individuals: vec!["http://example.org/a".into(), "http://example.org/b".into()],
614 }
615 );
616 }
617}