1use crate::model::{Literal, NamedNode, Object, Predicate, Subject, Triple};
12use crate::OxirsError;
13use std::collections::HashMap;
14
15pub const PROV_NS: &str = "http://www.w3.org/ns/prov#";
17
18pub const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema#";
20
21pub const RDF_NS: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
23
24fn prov_iri(local: &str) -> NamedNode {
26 NamedNode::new_unchecked(format!("{PROV_NS}{local}"))
27}
28
29fn xsd_iri(local: &str) -> NamedNode {
31 NamedNode::new_unchecked(format!("{XSD_NS}{local}"))
32}
33
34fn rdf_iri(local: &str) -> NamedNode {
36 NamedNode::new_unchecked(format!("{RDF_NS}{local}"))
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43pub enum AgentType {
44 SoftwareAgent,
46 Person,
48 Organization,
50}
51
52impl AgentType {
53 pub fn as_iri(&self) -> NamedNode {
55 match self {
56 AgentType::SoftwareAgent => prov_iri("SoftwareAgent"),
57 AgentType::Person => prov_iri("Person"),
58 AgentType::Organization => prov_iri("Organization"),
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct ProvEntity {
68 pub iri: NamedNode,
70 pub attributes: Vec<(NamedNode, Object)>,
72}
73
74impl ProvEntity {
75 pub fn new(iri: NamedNode) -> Self {
77 Self {
78 iri,
79 attributes: Vec::new(),
80 }
81 }
82
83 pub fn with_attributes(iri: NamedNode, attributes: Vec<(NamedNode, Object)>) -> Self {
85 Self { iri, attributes }
86 }
87
88 pub fn to_triples(&self) -> Vec<Triple> {
90 let mut triples = Vec::new();
91 triples.push(Triple::new(
93 self.iri.clone(),
94 rdf_iri("type"),
95 prov_iri("Entity"),
96 ));
97 for (pred, obj) in &self.attributes {
98 triples.push(Triple::new(self.iri.clone(), pred.clone(), obj.clone()));
99 }
100 triples
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct ProvActivity {
107 pub iri: NamedNode,
109 pub started_at: Option<String>,
111 pub ended_at: Option<String>,
113 pub attributes: Vec<(NamedNode, Object)>,
115}
116
117impl ProvActivity {
118 pub fn new(iri: NamedNode) -> Self {
120 Self {
121 iri,
122 started_at: None,
123 ended_at: None,
124 attributes: Vec::new(),
125 }
126 }
127
128 pub fn with_times(
130 iri: NamedNode,
131 started_at: Option<String>,
132 ended_at: Option<String>,
133 attributes: Vec<(NamedNode, Object)>,
134 ) -> Self {
135 Self {
136 iri,
137 started_at,
138 ended_at,
139 attributes,
140 }
141 }
142
143 pub fn to_triples(&self) -> Vec<Triple> {
145 let mut triples = Vec::new();
146 let xsd_datetime = xsd_iri("dateTime");
147
148 triples.push(Triple::new(
150 self.iri.clone(),
151 rdf_iri("type"),
152 prov_iri("Activity"),
153 ));
154
155 if let Some(ref start) = self.started_at {
156 triples.push(Triple::new(
157 self.iri.clone(),
158 prov_iri("startedAtTime"),
159 Literal::new_typed(start.as_str(), xsd_datetime.clone()),
160 ));
161 }
162
163 if let Some(ref end) = self.ended_at {
164 triples.push(Triple::new(
165 self.iri.clone(),
166 prov_iri("endedAtTime"),
167 Literal::new_typed(end.as_str(), xsd_datetime.clone()),
168 ));
169 }
170
171 for (pred, obj) in &self.attributes {
172 triples.push(Triple::new(self.iri.clone(), pred.clone(), obj.clone()));
173 }
174
175 triples
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct ProvAgent {
182 pub iri: NamedNode,
184 pub agent_type: AgentType,
186 pub attributes: Vec<(NamedNode, Object)>,
188}
189
190impl ProvAgent {
191 pub fn new(iri: NamedNode, agent_type: AgentType) -> Self {
193 Self {
194 iri,
195 agent_type,
196 attributes: Vec::new(),
197 }
198 }
199
200 pub fn with_attributes(
202 iri: NamedNode,
203 agent_type: AgentType,
204 attributes: Vec<(NamedNode, Object)>,
205 ) -> Self {
206 Self {
207 iri,
208 agent_type,
209 attributes,
210 }
211 }
212
213 pub fn to_triples(&self) -> Vec<Triple> {
215 let mut triples = Vec::new();
216
217 triples.push(Triple::new(
219 self.iri.clone(),
220 rdf_iri("type"),
221 prov_iri("Agent"),
222 ));
223
224 triples.push(Triple::new(
226 self.iri.clone(),
227 rdf_iri("type"),
228 self.agent_type.as_iri(),
229 ));
230
231 for (pred, obj) in &self.attributes {
232 triples.push(Triple::new(self.iri.clone(), pred.clone(), obj.clone()));
233 }
234
235 triples
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Hash)]
243pub enum ProvRelationKind {
244 WasGeneratedBy,
246 WasDerivedFrom,
248 WasAttributedTo,
250 Used,
252 WasAssociatedWith,
254 WasInformedBy,
256 ActedOnBehalfOf,
258}
259
260impl ProvRelationKind {
261 pub fn as_predicate(&self) -> NamedNode {
263 match self {
264 ProvRelationKind::WasGeneratedBy => prov_iri("wasGeneratedBy"),
265 ProvRelationKind::WasDerivedFrom => prov_iri("wasDerivedFrom"),
266 ProvRelationKind::WasAttributedTo => prov_iri("wasAttributedTo"),
267 ProvRelationKind::Used => prov_iri("used"),
268 ProvRelationKind::WasAssociatedWith => prov_iri("wasAssociatedWith"),
269 ProvRelationKind::WasInformedBy => prov_iri("wasInformedBy"),
270 ProvRelationKind::ActedOnBehalfOf => prov_iri("actedOnBehalfOf"),
271 }
272 }
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct ProvRelation {
278 pub kind: ProvRelationKind,
280 pub subject: NamedNode,
282 pub object: NamedNode,
284 pub qualifier: Option<NamedNode>,
286}
287
288impl ProvRelation {
289 pub fn new(kind: ProvRelationKind, subject: NamedNode, object: NamedNode) -> Self {
291 Self {
292 kind,
293 subject,
294 object,
295 qualifier: None,
296 }
297 }
298
299 pub fn with_qualifier(
301 kind: ProvRelationKind,
302 subject: NamedNode,
303 object: NamedNode,
304 qualifier: NamedNode,
305 ) -> Self {
306 Self {
307 kind,
308 subject,
309 object,
310 qualifier: Some(qualifier),
311 }
312 }
313
314 pub fn to_triple(&self) -> Triple {
316 Triple::new(
317 self.subject.clone(),
318 self.kind.as_predicate(),
319 self.object.clone(),
320 )
321 }
322}
323
324#[derive(Debug, Clone)]
331pub struct ProvBundle {
332 pub iri: NamedNode,
334 pub entities: Vec<ProvEntity>,
336 pub activities: Vec<ProvActivity>,
338 pub agents: Vec<ProvAgent>,
340 pub relations: Vec<ProvRelation>,
342}
343
344impl ProvBundle {
345 pub fn new(iri: NamedNode) -> Self {
347 Self {
348 iri,
349 entities: Vec::new(),
350 activities: Vec::new(),
351 agents: Vec::new(),
352 relations: Vec::new(),
353 }
354 }
355
356 pub fn add_entity(&mut self, entity: ProvEntity) {
358 self.entities.push(entity);
359 }
360
361 pub fn add_activity(&mut self, activity: ProvActivity) {
363 self.activities.push(activity);
364 }
365
366 pub fn add_agent(&mut self, agent: ProvAgent) {
368 self.agents.push(agent);
369 }
370
371 pub fn add_relation(&mut self, relation: ProvRelation) {
373 self.relations.push(relation);
374 }
375
376 pub fn to_rdf(&self) -> Vec<Triple> {
381 let mut triples = Vec::new();
382
383 triples.push(Triple::new(
385 self.iri.clone(),
386 rdf_iri("type"),
387 prov_iri("Bundle"),
388 ));
389
390 for entity in &self.entities {
391 triples.extend(entity.to_triples());
392 }
393 for activity in &self.activities {
394 triples.extend(activity.to_triples());
395 }
396 for agent in &self.agents {
397 triples.extend(agent.to_triples());
398 }
399 for relation in &self.relations {
400 triples.push(relation.to_triple());
401 }
402
403 triples
404 }
405
406 pub fn from_rdf(triples: &[Triple]) -> Result<Self, OxirsError> {
411 let mut by_subject: HashMap<String, Vec<&Triple>> = HashMap::new();
413 for triple in triples {
414 let key = match triple.subject() {
415 Subject::NamedNode(n) => n.as_str().to_string(),
416 Subject::BlankNode(b) => b.as_str().to_string(),
417 _ => continue,
418 };
419 by_subject.entry(key).or_default().push(triple);
420 }
421
422 let type_pred_full = format!("{RDF_NS}type");
423 let bundle_type_full = format!("{PROV_NS}Bundle");
424
425 let bundle_iri_str = triples
427 .iter()
428 .find(|t| {
429 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str() == type_pred_full)
430 && matches!(t.object(), Object::NamedNode(o) if o.as_str() == bundle_type_full)
431 })
432 .and_then(|t| match t.subject() {
433 Subject::NamedNode(n) => Some(n.as_str().to_string()),
434 _ => None,
435 })
436 .ok_or_else(|| OxirsError::Parse("No prov:Bundle declaration found".to_string()))?;
437
438 let bundle_iri = NamedNode::new_unchecked(bundle_iri_str.clone());
439
440 let entity_type = format!("{PROV_NS}Entity");
442 let activity_type = format!("{PROV_NS}Activity");
443 let agent_type_iri_str = format!("{PROV_NS}Agent");
444 let software_type = format!("{PROV_NS}SoftwareAgent");
445 let person_type = format!("{PROV_NS}Person");
446 let org_type = format!("{PROV_NS}Organization");
447
448 let mut entities: Vec<ProvEntity> = Vec::new();
449 let mut activities: Vec<ProvActivity> = Vec::new();
450 let mut agents: Vec<ProvAgent> = Vec::new();
451 let mut relations: Vec<ProvRelation> = Vec::new();
452
453 let relation_kind_map: HashMap<String, ProvRelationKind> = [
455 (
456 format!("{PROV_NS}wasGeneratedBy"),
457 ProvRelationKind::WasGeneratedBy,
458 ),
459 (
460 format!("{PROV_NS}wasDerivedFrom"),
461 ProvRelationKind::WasDerivedFrom,
462 ),
463 (
464 format!("{PROV_NS}wasAttributedTo"),
465 ProvRelationKind::WasAttributedTo,
466 ),
467 (format!("{PROV_NS}used"), ProvRelationKind::Used),
468 (
469 format!("{PROV_NS}wasAssociatedWith"),
470 ProvRelationKind::WasAssociatedWith,
471 ),
472 (
473 format!("{PROV_NS}wasInformedBy"),
474 ProvRelationKind::WasInformedBy,
475 ),
476 (
477 format!("{PROV_NS}actedOnBehalfOf"),
478 ProvRelationKind::ActedOnBehalfOf,
479 ),
480 ]
481 .into_iter()
482 .collect();
483
484 for triple in triples {
486 let subj_iri = match triple.subject() {
487 Subject::NamedNode(n) => n.clone(),
488 _ => continue,
489 };
490 let pred_str = match triple.predicate() {
491 Predicate::NamedNode(p) => p.as_str().to_string(),
492 _ => continue,
493 };
494 let obj_iri = match triple.object() {
495 Object::NamedNode(o) => o.clone(),
496 _ => continue,
497 };
498
499 if let Some(kind) = relation_kind_map.get(&pred_str) {
500 relations.push(ProvRelation::new(kind.clone(), subj_iri, obj_iri));
501 }
502 }
503
504 for (subj_str, subj_triples) in &by_subject {
506 if subj_str == &bundle_iri_str {
507 continue;
508 }
509
510 let types: Vec<String> = subj_triples
512 .iter()
513 .filter(|t| {
514 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str() == type_pred_full)
515 })
516 .filter_map(|t| match t.object() {
517 Object::NamedNode(o) => Some(o.as_str().to_string()),
518 _ => None,
519 })
520 .collect();
521
522 let iri = NamedNode::new_unchecked(subj_str.clone());
523
524 let attributes: Vec<(NamedNode, Object)> = subj_triples
526 .iter()
527 .filter_map(|t| {
528 if let Predicate::NamedNode(p) = t.predicate() {
529 let p_str = p.as_str();
530 if p_str == type_pred_full {
531 return None;
532 }
533 if relation_kind_map.contains_key(p_str) {
534 return None;
535 }
536 Some((p.clone(), t.object().clone()))
537 } else {
538 None
539 }
540 })
541 .collect();
542
543 if types.contains(&entity_type) {
544 entities.push(ProvEntity::with_attributes(iri, attributes));
545 } else if types.contains(&activity_type) {
546 let start_pred = format!("{PROV_NS}startedAtTime");
547 let end_pred = format!("{PROV_NS}endedAtTime");
548
549 let start = attributes
550 .iter()
551 .find(|(p, _)| p.as_str() == start_pred)
552 .and_then(|(_, o)| match o {
553 Object::Literal(l) => Some(l.value().to_string()),
554 _ => None,
555 });
556 let end = attributes
557 .iter()
558 .find(|(p, _)| p.as_str() == end_pred)
559 .and_then(|(_, o)| match o {
560 Object::Literal(l) => Some(l.value().to_string()),
561 _ => None,
562 });
563 let extra_attrs: Vec<(NamedNode, Object)> = attributes
564 .into_iter()
565 .filter(|(p, _)| p.as_str() != start_pred && p.as_str() != end_pred)
566 .collect();
567 activities.push(ProvActivity::with_times(iri, start, end, extra_attrs));
568 } else if types.contains(&agent_type_iri_str) {
569 let agent_kind = if types.contains(&software_type) {
570 AgentType::SoftwareAgent
571 } else if types.contains(&person_type) {
572 AgentType::Person
573 } else if types.contains(&org_type) {
574 AgentType::Organization
575 } else {
576 AgentType::Person
577 };
578 agents.push(ProvAgent::with_attributes(iri, agent_kind, attributes));
579 }
580 }
581
582 Ok(Self {
583 iri: bundle_iri,
584 entities,
585 activities,
586 agents,
587 relations,
588 })
589 }
590}
591
592#[derive(Debug, Clone)]
599pub struct QueryProvenanceTracker {
600 pub query_iri: NamedNode,
602 pub executed_at: String,
604 pub executed_by: NamedNode,
606 pub input_dataset: NamedNode,
608 pub result_dataset: NamedNode,
610 pub query_text: Option<String>,
612}
613
614impl QueryProvenanceTracker {
615 pub fn new(
617 query_iri: NamedNode,
618 executed_at: String,
619 executed_by: NamedNode,
620 input_dataset: NamedNode,
621 result_dataset: NamedNode,
622 ) -> Self {
623 Self {
624 query_iri,
625 executed_at,
626 executed_by,
627 input_dataset,
628 result_dataset,
629 query_text: None,
630 }
631 }
632
633 pub fn with_query_text(mut self, text: impl Into<String>) -> Self {
635 self.query_text = Some(text.into());
636 self
637 }
638
639 pub fn to_bundle(&self) -> ProvBundle {
647 let bundle_iri =
648 NamedNode::new_unchecked(format!("{}/provenance", self.query_iri.as_str()));
649 let mut bundle = ProvBundle::new(bundle_iri);
650
651 bundle.add_entity(ProvEntity::new(self.input_dataset.clone()));
653 bundle.add_entity(ProvEntity::new(self.result_dataset.clone()));
654
655 let mut activity_attrs: Vec<(NamedNode, Object)> = Vec::new();
657 if let Some(ref text) = self.query_text {
658 activity_attrs.push((
659 prov_iri("value"),
660 Object::Literal(Literal::new(text.as_str())),
661 ));
662 }
663
664 bundle.add_activity(ProvActivity::with_times(
665 self.query_iri.clone(),
666 Some(self.executed_at.clone()),
667 Some(self.executed_at.clone()),
668 activity_attrs,
669 ));
670
671 bundle.add_agent(ProvAgent::new(
673 self.executed_by.clone(),
674 AgentType::SoftwareAgent,
675 ));
676
677 bundle.add_relation(ProvRelation::new(
679 ProvRelationKind::WasGeneratedBy,
680 self.result_dataset.clone(),
681 self.query_iri.clone(),
682 ));
683 bundle.add_relation(ProvRelation::new(
684 ProvRelationKind::Used,
685 self.query_iri.clone(),
686 self.input_dataset.clone(),
687 ));
688 bundle.add_relation(ProvRelation::new(
689 ProvRelationKind::WasAssociatedWith,
690 self.query_iri.clone(),
691 self.executed_by.clone(),
692 ));
693 bundle.add_relation(ProvRelation::new(
694 ProvRelationKind::WasAttributedTo,
695 self.result_dataset.clone(),
696 self.executed_by.clone(),
697 ));
698
699 bundle
700 }
701}
702
703#[cfg(test)]
706mod tests {
707 use super::*;
708
709 fn nn(iri: &str) -> NamedNode {
710 NamedNode::new_unchecked(iri)
711 }
712
713 #[test]
716 fn test_agent_type_software_agent_iri() {
717 assert_eq!(
718 AgentType::SoftwareAgent.as_iri().as_str(),
719 "http://www.w3.org/ns/prov#SoftwareAgent"
720 );
721 }
722
723 #[test]
724 fn test_agent_type_person_iri() {
725 assert_eq!(
726 AgentType::Person.as_iri().as_str(),
727 "http://www.w3.org/ns/prov#Person"
728 );
729 }
730
731 #[test]
732 fn test_agent_type_organization_iri() {
733 assert_eq!(
734 AgentType::Organization.as_iri().as_str(),
735 "http://www.w3.org/ns/prov#Organization"
736 );
737 }
738
739 #[test]
740 fn test_agent_type_equality() {
741 assert_eq!(AgentType::Person, AgentType::Person);
742 assert_ne!(AgentType::Person, AgentType::Organization);
743 }
744
745 #[test]
748 fn test_entity_new_has_type_triple() {
749 let entity = ProvEntity::new(nn("http://example.org/data1"));
750 let triples = entity.to_triples();
751 assert!(
752 triples.iter().any(|t| {
753 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str().contains("type"))
754 && matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Entity"))
755 }),
756 "entity must have rdf:type prov:Entity triple"
757 );
758 }
759
760 #[test]
761 fn test_entity_new_no_extra_attributes() {
762 let entity = ProvEntity::new(nn("http://example.org/data1"));
763 assert_eq!(entity.to_triples().len(), 1);
765 }
766
767 #[test]
768 fn test_entity_with_attributes() {
769 let label_pred = nn("http://www.w3.org/2000/01/rdf-schema#label");
770 let entity = ProvEntity::with_attributes(
771 nn("http://example.org/data1"),
772 vec![(label_pred, Object::Literal(Literal::new("Dataset 1")))],
773 );
774 let triples = entity.to_triples();
775 assert_eq!(triples.len(), 2); }
777
778 #[test]
779 fn test_entity_attributes_are_emitted() {
780 let pred = nn("http://example.org/customPred");
781 let entity = ProvEntity::with_attributes(
782 nn("http://example.org/data1"),
783 vec![(pred.clone(), Object::Literal(Literal::new("custom value")))],
784 );
785 let triples = entity.to_triples();
786 assert!(triples.iter().any(|t| {
787 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str() == pred.as_str())
788 }));
789 }
790
791 #[test]
792 fn test_entity_iri_preserved() {
793 let iri = "http://example.org/myentity";
794 let entity = ProvEntity::new(nn(iri));
795 assert_eq!(entity.iri.as_str(), iri);
796 }
797
798 #[test]
799 fn test_entity_iri_is_subject_in_triples() {
800 let iri = "http://example.org/myentity";
801 let entity = ProvEntity::new(nn(iri));
802 let triples = entity.to_triples();
803 for triple in &triples {
804 assert!(
805 matches!(triple.subject(), Subject::NamedNode(s) if s.as_str() == iri),
806 "entity IRI must be subject of all its triples"
807 );
808 }
809 }
810
811 #[test]
814 fn test_activity_new_has_type_triple() {
815 let activity = ProvActivity::new(nn("http://example.org/query1"));
816 let triples = activity.to_triples();
817 assert!(
818 triples.iter().any(|t| {
819 matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Activity"))
820 }),
821 "activity must have rdf:type prov:Activity triple"
822 );
823 }
824
825 #[test]
826 fn test_activity_with_start_time() {
827 let activity = ProvActivity::with_times(
828 nn("http://example.org/query1"),
829 Some("2026-02-24T10:00:00Z".to_string()),
830 None,
831 vec![],
832 );
833 let triples = activity.to_triples();
834 assert!(triples.iter().any(|t| {
835 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str().contains("startedAtTime"))
836 }));
837 }
838
839 #[test]
840 fn test_activity_with_end_time() {
841 let activity = ProvActivity::with_times(
842 nn("http://example.org/query1"),
843 None,
844 Some("2026-02-24T10:05:00Z".to_string()),
845 vec![],
846 );
847 let triples = activity.to_triples();
848 assert!(triples.iter().any(|t| {
849 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str().contains("endedAtTime"))
850 }));
851 }
852
853 #[test]
854 fn test_activity_with_both_times() {
855 let activity = ProvActivity::with_times(
856 nn("http://example.org/query1"),
857 Some("2026-02-24T10:00:00Z".to_string()),
858 Some("2026-02-24T10:05:00Z".to_string()),
859 vec![],
860 );
861 let triples = activity.to_triples();
862 assert_eq!(triples.len(), 3);
864 }
865
866 #[test]
867 fn test_activity_no_times() {
868 let activity = ProvActivity::new(nn("http://example.org/query1"));
869 assert_eq!(activity.to_triples().len(), 1);
871 }
872
873 #[test]
874 fn test_activity_with_attributes() {
875 let activity = ProvActivity::with_times(
876 nn("http://example.org/query1"),
877 None,
878 None,
879 vec![(
880 nn("http://example.org/desc"),
881 Object::Literal(Literal::new("SPARQL query")),
882 )],
883 );
884 let triples = activity.to_triples();
885 assert_eq!(triples.len(), 2);
887 }
888
889 #[test]
890 fn test_activity_iri_is_subject() {
891 let iri = "http://example.org/query1";
892 let activity = ProvActivity::new(nn(iri));
893 let triples = activity.to_triples();
894 assert!(triples
895 .iter()
896 .all(|t| { matches!(t.subject(), Subject::NamedNode(s) if s.as_str() == iri) }));
897 }
898
899 #[test]
902 fn test_agent_new_has_type_agent_triple() {
903 let agent = ProvAgent::new(nn("http://example.org/oxirs"), AgentType::SoftwareAgent);
904 let triples = agent.to_triples();
905 assert!(triples.iter().any(|t| {
906 matches!(t.object(), Object::NamedNode(o) if o.as_str() == format!("{PROV_NS}Agent"))
907 }));
908 }
909
910 #[test]
911 fn test_agent_software_type_triple() {
912 let agent = ProvAgent::new(nn("http://example.org/oxirs"), AgentType::SoftwareAgent);
913 let triples = agent.to_triples();
914 assert!(triples.iter().any(|t| {
915 matches!(t.object(), Object::NamedNode(o) if o.as_str() == format!("{PROV_NS}SoftwareAgent"))
916 }));
917 }
918
919 #[test]
920 fn test_agent_person_type_triple() {
921 let agent = ProvAgent::new(nn("http://example.org/alice"), AgentType::Person);
922 let triples = agent.to_triples();
923 assert!(triples.iter().any(|t| {
924 matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Person"))
925 }));
926 }
927
928 #[test]
929 fn test_agent_organization_type_triple() {
930 let agent = ProvAgent::new(nn("http://example.org/acme"), AgentType::Organization);
931 let triples = agent.to_triples();
932 assert!(triples.iter().any(|t| {
933 matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Organization"))
934 }));
935 }
936
937 #[test]
938 fn test_agent_with_attributes() {
939 let agent = ProvAgent::with_attributes(
940 nn("http://example.org/oxirs"),
941 AgentType::SoftwareAgent,
942 vec![(
943 nn("http://example.org/version"),
944 Object::Literal(Literal::new("0.2.0")),
945 )],
946 );
947 let triples = agent.to_triples();
948 assert_eq!(triples.len(), 3);
950 }
951
952 #[test]
953 fn test_agent_iri_is_subject_in_triples() {
954 let iri = "http://example.org/myagent";
955 let agent = ProvAgent::new(nn(iri), AgentType::Organization);
956 let triples = agent.to_triples();
957 for triple in &triples {
958 assert!(
959 matches!(triple.subject(), Subject::NamedNode(s) if s.as_str() == iri),
960 "agent IRI must be subject of all its triples"
961 );
962 }
963 }
964
965 #[test]
968 fn test_relation_kind_was_generated_by_predicate() {
969 assert!(ProvRelationKind::WasGeneratedBy
970 .as_predicate()
971 .as_str()
972 .contains("wasGeneratedBy"));
973 }
974
975 #[test]
976 fn test_relation_kind_was_derived_from_predicate() {
977 assert!(ProvRelationKind::WasDerivedFrom
978 .as_predicate()
979 .as_str()
980 .contains("wasDerivedFrom"));
981 }
982
983 #[test]
984 fn test_relation_kind_was_attributed_to_predicate() {
985 assert!(ProvRelationKind::WasAttributedTo
986 .as_predicate()
987 .as_str()
988 .contains("wasAttributedTo"));
989 }
990
991 #[test]
992 fn test_relation_kind_used_predicate() {
993 assert!(ProvRelationKind::Used
994 .as_predicate()
995 .as_str()
996 .contains("used"));
997 }
998
999 #[test]
1000 fn test_relation_kind_was_associated_with_predicate() {
1001 assert!(ProvRelationKind::WasAssociatedWith
1002 .as_predicate()
1003 .as_str()
1004 .contains("wasAssociatedWith"));
1005 }
1006
1007 #[test]
1008 fn test_relation_kind_was_informed_by_predicate() {
1009 assert!(ProvRelationKind::WasInformedBy
1010 .as_predicate()
1011 .as_str()
1012 .contains("wasInformedBy"));
1013 }
1014
1015 #[test]
1016 fn test_relation_kind_acted_on_behalf_of_predicate() {
1017 assert!(ProvRelationKind::ActedOnBehalfOf
1018 .as_predicate()
1019 .as_str()
1020 .contains("actedOnBehalfOf"));
1021 }
1022
1023 #[test]
1024 fn test_all_seven_relation_kinds_produce_distinct_predicates() {
1025 let kinds = [
1026 ProvRelationKind::WasGeneratedBy,
1027 ProvRelationKind::WasDerivedFrom,
1028 ProvRelationKind::WasAttributedTo,
1029 ProvRelationKind::Used,
1030 ProvRelationKind::WasAssociatedWith,
1031 ProvRelationKind::WasInformedBy,
1032 ProvRelationKind::ActedOnBehalfOf,
1033 ];
1034 let predicates: Vec<String> = kinds
1035 .iter()
1036 .map(|k| k.as_predicate().as_str().to_string())
1037 .collect();
1038 let unique: std::collections::HashSet<_> = predicates.iter().collect();
1039 assert_eq!(
1040 unique.len(),
1041 7,
1042 "all 7 relation kinds must have unique predicates"
1043 );
1044 }
1045
1046 #[test]
1049 fn test_relation_to_triple_correct_predicate() {
1050 let relation = ProvRelation::new(
1051 ProvRelationKind::WasGeneratedBy,
1052 nn("http://example.org/result"),
1053 nn("http://example.org/query"),
1054 );
1055 let triple = relation.to_triple();
1056 assert!(
1057 matches!(triple.predicate(), Predicate::NamedNode(p) if p.as_str().contains("wasGeneratedBy"))
1058 );
1059 }
1060
1061 #[test]
1062 fn test_relation_to_triple_correct_subject() {
1063 let relation = ProvRelation::new(
1064 ProvRelationKind::Used,
1065 nn("http://example.org/query"),
1066 nn("http://example.org/input"),
1067 );
1068 let triple = relation.to_triple();
1069 assert!(
1070 matches!(triple.subject(), Subject::NamedNode(s) if s.as_str() == "http://example.org/query")
1071 );
1072 }
1073
1074 #[test]
1075 fn test_relation_to_triple_correct_object() {
1076 let relation = ProvRelation::new(
1077 ProvRelationKind::Used,
1078 nn("http://example.org/query"),
1079 nn("http://example.org/input"),
1080 );
1081 let triple = relation.to_triple();
1082 assert!(
1083 matches!(triple.object(), Object::NamedNode(o) if o.as_str() == "http://example.org/input")
1084 );
1085 }
1086
1087 #[test]
1088 fn test_relation_with_qualifier() {
1089 let relation = ProvRelation::with_qualifier(
1090 ProvRelationKind::WasGeneratedBy,
1091 nn("http://example.org/result"),
1092 nn("http://example.org/query"),
1093 nn("http://example.org/qual1"),
1094 );
1095 assert!(relation.qualifier.is_some());
1096 assert_eq!(
1097 relation
1098 .qualifier
1099 .as_ref()
1100 .expect("operation should succeed")
1101 .as_str(),
1102 "http://example.org/qual1"
1103 );
1104 }
1105
1106 #[test]
1107 fn test_relation_no_qualifier_by_default() {
1108 let relation = ProvRelation::new(
1109 ProvRelationKind::WasInformedBy,
1110 nn("http://example.org/q2"),
1111 nn("http://example.org/q1"),
1112 );
1113 assert!(relation.qualifier.is_none());
1114 }
1115
1116 #[test]
1119 fn test_bundle_new_is_empty() {
1120 let bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1121 assert!(bundle.entities.is_empty());
1122 assert!(bundle.activities.is_empty());
1123 assert!(bundle.agents.is_empty());
1124 assert!(bundle.relations.is_empty());
1125 }
1126
1127 #[test]
1128 fn test_bundle_to_rdf_includes_bundle_type() {
1129 let bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1130 let triples = bundle.to_rdf();
1131 assert!(triples.iter().any(|t| {
1132 matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Bundle"))
1133 }));
1134 }
1135
1136 #[test]
1137 fn test_bundle_add_entity() {
1138 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1139 bundle.add_entity(ProvEntity::new(nn("http://example.org/e1")));
1140 assert_eq!(bundle.entities.len(), 1);
1141 }
1142
1143 #[test]
1144 fn test_bundle_add_activity() {
1145 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1146 bundle.add_activity(ProvActivity::new(nn("http://example.org/a1")));
1147 assert_eq!(bundle.activities.len(), 1);
1148 }
1149
1150 #[test]
1151 fn test_bundle_add_agent() {
1152 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1153 bundle.add_agent(ProvAgent::new(
1154 nn("http://example.org/ag1"),
1155 AgentType::SoftwareAgent,
1156 ));
1157 assert_eq!(bundle.agents.len(), 1);
1158 }
1159
1160 #[test]
1161 fn test_bundle_add_relation() {
1162 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1163 bundle.add_relation(ProvRelation::new(
1164 ProvRelationKind::WasGeneratedBy,
1165 nn("http://example.org/r"),
1166 nn("http://example.org/a"),
1167 ));
1168 assert_eq!(bundle.relations.len(), 1);
1169 }
1170
1171 #[test]
1172 fn test_bundle_to_rdf_contains_entity_type() {
1173 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1174 bundle.add_entity(ProvEntity::new(nn("http://example.org/e1")));
1175 let triples = bundle.to_rdf();
1176 assert!(triples.iter().any(|t| {
1177 matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Entity"))
1178 }));
1179 }
1180
1181 #[test]
1182 fn test_bundle_to_rdf_contains_activity_type() {
1183 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1184 bundle.add_activity(ProvActivity::new(nn("http://example.org/a1")));
1185 let triples = bundle.to_rdf();
1186 assert!(triples.iter().any(|t| {
1187 matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Activity"))
1188 }));
1189 }
1190
1191 #[test]
1192 fn test_bundle_to_rdf_contains_agent_type() {
1193 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1194 bundle.add_agent(ProvAgent::new(
1195 nn("http://example.org/ag1"),
1196 AgentType::SoftwareAgent,
1197 ));
1198 let triples = bundle.to_rdf();
1199 assert!(triples.iter().any(|t| {
1200 matches!(t.object(), Object::NamedNode(o) if o.as_str().contains("Agent"))
1201 }));
1202 }
1203
1204 #[test]
1205 fn test_bundle_to_rdf_contains_relation_triple() {
1206 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1207 bundle.add_relation(ProvRelation::new(
1208 ProvRelationKind::WasGeneratedBy,
1209 nn("http://example.org/r"),
1210 nn("http://example.org/a"),
1211 ));
1212 let triples = bundle.to_rdf();
1213 assert!(triples.iter().any(|t| {
1214 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str().contains("wasGeneratedBy"))
1215 }));
1216 }
1217
1218 #[test]
1219 fn test_bundle_full_to_rdf_triple_count() {
1220 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1221 bundle.add_entity(ProvEntity::new(nn("http://example.org/e1")));
1222 bundle.add_activity(ProvActivity::new(nn("http://example.org/a1")));
1223 bundle.add_agent(ProvAgent::new(
1224 nn("http://example.org/ag1"),
1225 AgentType::Person,
1226 ));
1227 bundle.add_relation(ProvRelation::new(
1228 ProvRelationKind::WasGeneratedBy,
1229 nn("http://example.org/e1"),
1230 nn("http://example.org/a1"),
1231 ));
1232 let triples = bundle.to_rdf();
1233 assert!(
1235 triples.len() >= 6,
1236 "expected at least 6 triples, got {}",
1237 triples.len()
1238 );
1239 }
1240
1241 #[test]
1244 fn test_bundle_round_trip_entity() {
1245 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1246 bundle.add_entity(ProvEntity::new(nn("http://example.org/e1")));
1247 let triples = bundle.to_rdf();
1248 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1249 assert_eq!(restored.entities.len(), 1);
1250 }
1251
1252 #[test]
1253 fn test_bundle_round_trip_activity() {
1254 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1255 bundle.add_activity(ProvActivity::with_times(
1256 nn("http://example.org/a1"),
1257 Some("2026-02-24T10:00:00Z".to_string()),
1258 Some("2026-02-24T10:05:00Z".to_string()),
1259 vec![],
1260 ));
1261 let triples = bundle.to_rdf();
1262 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1263 assert_eq!(restored.activities.len(), 1);
1264 assert_eq!(
1265 restored.activities[0].started_at.as_deref(),
1266 Some("2026-02-24T10:00:00Z")
1267 );
1268 assert_eq!(
1269 restored.activities[0].ended_at.as_deref(),
1270 Some("2026-02-24T10:05:00Z")
1271 );
1272 }
1273
1274 #[test]
1275 fn test_bundle_round_trip_agent_software() {
1276 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1277 bundle.add_agent(ProvAgent::new(
1278 nn("http://example.org/ag1"),
1279 AgentType::SoftwareAgent,
1280 ));
1281 let triples = bundle.to_rdf();
1282 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1283 assert_eq!(restored.agents.len(), 1);
1284 assert_eq!(restored.agents[0].agent_type, AgentType::SoftwareAgent);
1285 }
1286
1287 #[test]
1288 fn test_bundle_round_trip_agent_person() {
1289 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1290 bundle.add_agent(ProvAgent::new(
1291 nn("http://example.org/alice"),
1292 AgentType::Person,
1293 ));
1294 let triples = bundle.to_rdf();
1295 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1296 assert_eq!(restored.agents.len(), 1);
1297 assert_eq!(restored.agents[0].agent_type, AgentType::Person);
1298 }
1299
1300 #[test]
1301 fn test_bundle_round_trip_agent_organization() {
1302 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1303 bundle.add_agent(ProvAgent::new(
1304 nn("http://example.org/acme"),
1305 AgentType::Organization,
1306 ));
1307 let triples = bundle.to_rdf();
1308 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1309 assert_eq!(restored.agents.len(), 1);
1310 assert_eq!(restored.agents[0].agent_type, AgentType::Organization);
1311 }
1312
1313 #[test]
1314 fn test_bundle_round_trip_relation() {
1315 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1316 bundle.add_entity(ProvEntity::new(nn("http://example.org/e1")));
1317 bundle.add_activity(ProvActivity::new(nn("http://example.org/a1")));
1318 bundle.add_relation(ProvRelation::new(
1319 ProvRelationKind::WasGeneratedBy,
1320 nn("http://example.org/e1"),
1321 nn("http://example.org/a1"),
1322 ));
1323 let triples = bundle.to_rdf();
1324 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1325 assert_eq!(restored.relations.len(), 1);
1326 assert_eq!(restored.relations[0].kind, ProvRelationKind::WasGeneratedBy);
1327 }
1328
1329 #[test]
1330 fn test_bundle_from_rdf_missing_bundle_declaration() {
1331 let triples = vec![Triple::new(
1333 nn("http://example.org/e1"),
1334 nn("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
1335 nn("http://www.w3.org/ns/prov#Entity"),
1336 )];
1337 let result = ProvBundle::from_rdf(&triples);
1338 assert!(result.is_err());
1339 }
1340
1341 #[test]
1342 fn test_bundle_from_rdf_bundle_iri_preserved() {
1343 let bundle = ProvBundle::new(nn("http://example.org/mybundle"));
1344 let triples = bundle.to_rdf();
1345 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1346 assert_eq!(restored.iri.as_str(), "http://example.org/mybundle");
1347 }
1348
1349 #[test]
1350 fn test_bundle_round_trip_all_relation_kinds() {
1351 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1352 let kinds = vec![
1353 ProvRelationKind::WasGeneratedBy,
1354 ProvRelationKind::WasDerivedFrom,
1355 ProvRelationKind::WasAttributedTo,
1356 ProvRelationKind::Used,
1357 ProvRelationKind::WasAssociatedWith,
1358 ProvRelationKind::WasInformedBy,
1359 ProvRelationKind::ActedOnBehalfOf,
1360 ];
1361 for kind in &kinds {
1362 bundle.add_relation(ProvRelation::new(
1363 kind.clone(),
1364 nn("http://example.org/s"),
1365 nn("http://example.org/o"),
1366 ));
1367 }
1368 let triples = bundle.to_rdf();
1369 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1370 assert_eq!(restored.relations.len(), kinds.len());
1371 }
1372
1373 #[test]
1374 fn test_bundle_multiple_entities() {
1375 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1376 for i in 0..5 {
1377 bundle.add_entity(ProvEntity::new(nn(&format!("http://example.org/e{i}"))));
1378 }
1379 let triples = bundle.to_rdf();
1380 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1381 assert_eq!(restored.entities.len(), 5);
1382 }
1383
1384 #[test]
1385 fn test_bundle_multiple_activities() {
1386 let mut bundle = ProvBundle::new(nn("http://example.org/bundle1"));
1387 for i in 0..3 {
1388 bundle.add_activity(ProvActivity::new(nn(&format!("http://example.org/a{i}"))));
1389 }
1390 let triples = bundle.to_rdf();
1391 let restored = ProvBundle::from_rdf(&triples).expect("from_rdf should succeed");
1392 assert_eq!(restored.activities.len(), 3);
1393 }
1394
1395 #[test]
1398 fn test_query_tracker_to_bundle_has_entities() {
1399 let tracker = QueryProvenanceTracker::new(
1400 nn("http://example.org/query1"),
1401 "2026-02-24T10:00:00Z".to_string(),
1402 nn("http://example.org/oxirs"),
1403 nn("http://example.org/dataset"),
1404 nn("http://example.org/result"),
1405 );
1406 let bundle = tracker.to_bundle();
1407 assert_eq!(bundle.entities.len(), 2);
1408 }
1409
1410 #[test]
1411 fn test_query_tracker_to_bundle_has_activity() {
1412 let tracker = QueryProvenanceTracker::new(
1413 nn("http://example.org/query1"),
1414 "2026-02-24T10:00:00Z".to_string(),
1415 nn("http://example.org/oxirs"),
1416 nn("http://example.org/dataset"),
1417 nn("http://example.org/result"),
1418 );
1419 let bundle = tracker.to_bundle();
1420 assert_eq!(bundle.activities.len(), 1);
1421 }
1422
1423 #[test]
1424 fn test_query_tracker_to_bundle_has_software_agent() {
1425 let tracker = QueryProvenanceTracker::new(
1426 nn("http://example.org/query1"),
1427 "2026-02-24T10:00:00Z".to_string(),
1428 nn("http://example.org/oxirs"),
1429 nn("http://example.org/dataset"),
1430 nn("http://example.org/result"),
1431 );
1432 let bundle = tracker.to_bundle();
1433 assert_eq!(bundle.agents.len(), 1);
1434 assert_eq!(bundle.agents[0].agent_type, AgentType::SoftwareAgent);
1435 }
1436
1437 #[test]
1438 fn test_query_tracker_to_bundle_has_four_relations() {
1439 let tracker = QueryProvenanceTracker::new(
1440 nn("http://example.org/query1"),
1441 "2026-02-24T10:00:00Z".to_string(),
1442 nn("http://example.org/oxirs"),
1443 nn("http://example.org/dataset"),
1444 nn("http://example.org/result"),
1445 );
1446 let bundle = tracker.to_bundle();
1447 assert_eq!(bundle.relations.len(), 4);
1448 }
1449
1450 #[test]
1451 fn test_query_tracker_to_bundle_was_generated_by() {
1452 let tracker = QueryProvenanceTracker::new(
1453 nn("http://example.org/query1"),
1454 "2026-02-24T10:00:00Z".to_string(),
1455 nn("http://example.org/oxirs"),
1456 nn("http://example.org/dataset"),
1457 nn("http://example.org/result"),
1458 );
1459 let bundle = tracker.to_bundle();
1460 assert!(bundle
1461 .relations
1462 .iter()
1463 .any(|r| r.kind == ProvRelationKind::WasGeneratedBy));
1464 }
1465
1466 #[test]
1467 fn test_query_tracker_to_bundle_used() {
1468 let tracker = QueryProvenanceTracker::new(
1469 nn("http://example.org/query1"),
1470 "2026-02-24T10:00:00Z".to_string(),
1471 nn("http://example.org/oxirs"),
1472 nn("http://example.org/dataset"),
1473 nn("http://example.org/result"),
1474 );
1475 let bundle = tracker.to_bundle();
1476 assert!(bundle
1477 .relations
1478 .iter()
1479 .any(|r| r.kind == ProvRelationKind::Used));
1480 }
1481
1482 #[test]
1483 fn test_query_tracker_to_bundle_was_associated_with() {
1484 let tracker = QueryProvenanceTracker::new(
1485 nn("http://example.org/query1"),
1486 "2026-02-24T10:00:00Z".to_string(),
1487 nn("http://example.org/oxirs"),
1488 nn("http://example.org/dataset"),
1489 nn("http://example.org/result"),
1490 );
1491 let bundle = tracker.to_bundle();
1492 assert!(bundle
1493 .relations
1494 .iter()
1495 .any(|r| r.kind == ProvRelationKind::WasAssociatedWith));
1496 }
1497
1498 #[test]
1499 fn test_query_tracker_to_bundle_was_attributed_to() {
1500 let tracker = QueryProvenanceTracker::new(
1501 nn("http://example.org/query1"),
1502 "2026-02-24T10:00:00Z".to_string(),
1503 nn("http://example.org/oxirs"),
1504 nn("http://example.org/dataset"),
1505 nn("http://example.org/result"),
1506 );
1507 let bundle = tracker.to_bundle();
1508 assert!(bundle
1509 .relations
1510 .iter()
1511 .any(|r| r.kind == ProvRelationKind::WasAttributedTo));
1512 }
1513
1514 #[test]
1515 fn test_query_tracker_with_query_text() {
1516 let tracker = QueryProvenanceTracker::new(
1517 nn("http://example.org/query1"),
1518 "2026-02-24T10:00:00Z".to_string(),
1519 nn("http://example.org/oxirs"),
1520 nn("http://example.org/dataset"),
1521 nn("http://example.org/result"),
1522 )
1523 .with_query_text("SELECT * WHERE { ?s ?p ?o }");
1524 assert!(tracker.query_text.is_some());
1525 let bundle = tracker.to_bundle();
1526 assert!(bundle.activities[0]
1527 .attributes
1528 .iter()
1529 .any(|(_, v)| { matches!(v, Object::Literal(l) if l.value().contains("SELECT")) }));
1530 }
1531
1532 #[test]
1533 fn test_query_tracker_to_bundle_to_rdf() {
1534 let tracker = QueryProvenanceTracker::new(
1535 nn("http://example.org/query1"),
1536 "2026-02-24T10:00:00Z".to_string(),
1537 nn("http://example.org/oxirs"),
1538 nn("http://example.org/dataset"),
1539 nn("http://example.org/result"),
1540 );
1541 let bundle = tracker.to_bundle();
1542 let triples = bundle.to_rdf();
1543 assert!(triples.len() > 5);
1544 }
1545
1546 #[test]
1547 fn test_query_tracker_executed_at_in_activity() {
1548 let tracker = QueryProvenanceTracker::new(
1549 nn("http://example.org/query1"),
1550 "2026-02-24T10:00:00Z".to_string(),
1551 nn("http://example.org/oxirs"),
1552 nn("http://example.org/dataset"),
1553 nn("http://example.org/result"),
1554 );
1555 let bundle = tracker.to_bundle();
1556 let activity = &bundle.activities[0];
1557 assert_eq!(activity.started_at.as_deref(), Some("2026-02-24T10:00:00Z"));
1558 }
1559
1560 #[test]
1563 fn test_prov_ns_constant() {
1564 assert_eq!(PROV_NS, "http://www.w3.org/ns/prov#");
1565 }
1566
1567 #[test]
1568 fn test_xsd_ns_constant() {
1569 assert_eq!(XSD_NS, "http://www.w3.org/2001/XMLSchema#");
1570 }
1571
1572 #[test]
1573 fn test_rdf_ns_constant() {
1574 assert_eq!(RDF_NS, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
1575 }
1576}