use std::collections::HashMap;
use std::fmt;
use serde::{Deserialize, Serialize};
use tracing::{debug, span, Level};
use crate::model::{StarTerm, StarTriple};
use crate::{StarError, StarResult};
pub mod vocab {
pub const RDF_TYPE: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
pub const RDF_STATEMENT: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement";
pub const RDF_SUBJECT: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#subject";
pub const RDF_PREDICATE: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate";
pub const RDF_OBJECT: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#object";
pub const SP_SINGLETON_PROPERTY_OF: &str =
"http://www.w3.org/ns/singletonProperty#singletonPropertyOf";
pub const NP_NANOPUBLICATION: &str = "http://www.nanopub.org/nschema#Nanopublication";
pub const NP_HAS_ASSERTION: &str = "http://www.nanopub.org/nschema#hasAssertion";
pub const NP_HAS_PROVENANCE: &str = "http://www.nanopub.org/nschema#hasProvenance";
pub const NP_HAS_PUBLICATION_INFO: &str = "http://www.nanopub.org/nschema#hasPublicationInfo";
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProfileConfig {
pub base_iri: String,
pub counter_seed: u64,
pub include_type_triple: bool,
}
impl Default for ProfileConfig {
fn default() -> Self {
Self {
base_iri: "http://example.org/stmt/".to_string(),
counter_seed: 1,
include_type_triple: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExpandedAnnotation {
pub triples: Vec<StarTriple>,
pub statement_node: String,
}
impl ExpandedAnnotation {
pub fn triple_count(&self) -> usize {
self.triples.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReificationProfile {
config: ProfileConfig,
counter: u64,
}
impl ReificationProfile {
pub fn new() -> Self {
Self {
config: ProfileConfig::default(),
counter: 0,
}
}
pub fn with_config(config: ProfileConfig) -> Self {
let counter = config.counter_seed;
Self { config, counter }
}
pub fn expand(
&mut self,
quoted: &StarTerm,
annotations: &[(StarTerm, StarTerm)],
) -> StarResult<ExpandedAnnotation> {
let span = span!(Level::DEBUG, "ReificationProfile::expand");
let _enter = span.enter();
let inner = match quoted {
StarTerm::QuotedTriple(t) => t.as_ref(),
other => {
return Err(StarError::invalid_term_type(format!(
"Expected QuotedTriple, got {:?}",
other
)))
}
};
self.counter += 1;
let stmt_iri = format!("{}{}", self.config.base_iri, self.counter);
let stmt_node = StarTerm::iri(&stmt_iri)?;
let mut triples: Vec<StarTriple> = Vec::new();
if self.config.include_type_triple {
triples.push(StarTriple::new(
stmt_node.clone(),
StarTerm::iri(vocab::RDF_TYPE)?,
StarTerm::iri(vocab::RDF_STATEMENT)?,
));
}
triples.push(StarTriple::new(
stmt_node.clone(),
StarTerm::iri(vocab::RDF_SUBJECT)?,
inner.subject.clone(),
));
triples.push(StarTriple::new(
stmt_node.clone(),
StarTerm::iri(vocab::RDF_PREDICATE)?,
inner.predicate.clone(),
));
triples.push(StarTriple::new(
stmt_node.clone(),
StarTerm::iri(vocab::RDF_OBJECT)?,
inner.object.clone(),
));
for (pred, obj) in annotations {
triples.push(StarTriple::new(
stmt_node.clone(),
pred.clone(),
obj.clone(),
));
}
debug!(
stmt_iri = %stmt_iri,
annotation_count = annotations.len(),
"Reification expanded"
);
Ok(ExpandedAnnotation {
triples,
statement_node: stmt_iri,
})
}
pub fn collapse(
&self,
triples: &[StarTriple],
statement_node_iri: &str,
) -> StarResult<StarTriple> {
let span = span!(Level::DEBUG, "ReificationProfile::collapse");
let _enter = span.enter();
let stmt_term = StarTerm::iri(statement_node_iri)?;
let find = |pred_iri: &str| -> StarResult<StarTerm> {
triples
.iter()
.find(|t| {
t.subject == stmt_term
&& matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == pred_iri)
})
.map(|t| t.object.clone())
.ok_or_else(|| {
StarError::reification_error(format!(
"Missing triple for {pred_iri} on statement node {statement_node_iri}"
))
})
};
let subj = find(vocab::RDF_SUBJECT)?;
let pred = find(vocab::RDF_PREDICATE)?;
let obj = find(vocab::RDF_OBJECT)?;
let inner = StarTriple::new(subj, pred, obj);
debug!(statement_node_iri = %statement_node_iri, "Reification collapsed");
Ok(StarTriple::new(
StarTerm::quoted_triple(inner),
StarTerm::iri(vocab::RDF_TYPE)?,
stmt_term,
))
}
}
impl Default for ReificationProfile {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SingletonProfile {
config: ProfileConfig,
counter: u64,
singleton_map: HashMap<String, Vec<String>>,
}
impl SingletonProfile {
pub fn new() -> Self {
Self {
config: ProfileConfig::default(),
counter: 0,
singleton_map: HashMap::new(),
}
}
pub fn with_config(config: ProfileConfig) -> Self {
let counter = config.counter_seed;
Self {
config,
counter,
singleton_map: HashMap::new(),
}
}
pub fn expand(
&mut self,
quoted: &StarTerm,
annotations: &[(StarTerm, StarTerm)],
) -> StarResult<ExpandedAnnotation> {
let span = span!(Level::DEBUG, "SingletonProfile::expand");
let _enter = span.enter();
let inner = match quoted {
StarTerm::QuotedTriple(t) => t.as_ref(),
other => {
return Err(StarError::invalid_term_type(format!(
"Expected QuotedTriple, got {:?}",
other
)))
}
};
let original_pred = match &inner.predicate {
StarTerm::NamedNode(n) => n.iri.clone(),
other => {
return Err(StarError::invalid_term_type(format!(
"Quoted triple predicate must be a NamedNode for SingletonProfile, got {:?}",
other
)))
}
};
self.counter += 1;
let singleton_iri = format!("{}#singleton_{}", original_pred, self.counter);
self.singleton_map
.entry(original_pred.clone())
.or_default()
.push(singleton_iri.clone());
let singleton_pred = StarTerm::iri(&singleton_iri)?;
let mut triples: Vec<StarTriple> = Vec::new();
triples.push(StarTriple::new(
inner.subject.clone(),
singleton_pred.clone(),
inner.object.clone(),
));
triples.push(StarTriple::new(
singleton_pred.clone(),
StarTerm::iri(vocab::SP_SINGLETON_PROPERTY_OF)?,
StarTerm::iri(&original_pred)?,
));
for (pred, obj) in annotations {
triples.push(StarTriple::new(
singleton_pred.clone(),
pred.clone(),
obj.clone(),
));
}
debug!(
singleton_iri = %singleton_iri,
original_pred = %original_pred,
"Singleton property pattern expanded"
);
Ok(ExpandedAnnotation {
triples,
statement_node: singleton_iri,
})
}
pub fn singletons_for(&self, predicate_iri: &str) -> &[String] {
self.singleton_map
.get(predicate_iri)
.map(Vec::as_slice)
.unwrap_or(&[])
}
}
impl Default for SingletonProfile {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NanopubProfile {
config: ProfileConfig,
counter: u64,
}
impl NanopubProfile {
pub fn new() -> Self {
Self {
config: ProfileConfig::default(),
counter: 0,
}
}
pub fn with_config(config: ProfileConfig) -> Self {
let counter = config.counter_seed;
Self { config, counter }
}
pub fn expand(
&mut self,
quoted: &StarTerm,
provenance_annotations: &[(StarTerm, StarTerm)],
) -> StarResult<ExpandedAnnotation> {
let span = span!(Level::DEBUG, "NanopubProfile::expand");
let _enter = span.enter();
let inner = match quoted {
StarTerm::QuotedTriple(t) => t.as_ref(),
other => {
return Err(StarError::invalid_term_type(format!(
"Expected QuotedTriple, got {:?}",
other
)))
}
};
self.counter += 1;
let np_iri = format!("{}{}", self.config.base_iri, self.counter);
let assertion_iri = format!("{}_assertion", np_iri);
let prov_iri = format!("{}_prov", np_iri);
let pubinfo_iri = format!("{}_pubinfo", np_iri);
let np_node = StarTerm::iri(&np_iri)?;
let assertion_node = StarTerm::iri(&assertion_iri)?;
let prov_node = StarTerm::iri(&prov_iri)?;
let pubinfo_node = StarTerm::iri(&pubinfo_iri)?;
let mut triples: Vec<StarTriple> = Vec::new();
triples.push(StarTriple::new(
np_node.clone(),
StarTerm::iri(vocab::NP_HAS_ASSERTION)?,
assertion_node.clone(),
));
triples.push(StarTriple::new(
np_node.clone(),
StarTerm::iri(vocab::NP_HAS_PROVENANCE)?,
prov_node.clone(),
));
triples.push(StarTriple::new(
np_node.clone(),
StarTerm::iri(vocab::NP_HAS_PUBLICATION_INFO)?,
pubinfo_node.clone(),
));
if self.config.include_type_triple {
triples.push(StarTriple::new(
np_node.clone(),
StarTerm::iri(vocab::RDF_TYPE)?,
StarTerm::iri(vocab::NP_NANOPUBLICATION)?,
));
}
triples.push(StarTriple::new(
assertion_node.clone(),
StarTerm::iri(vocab::RDF_SUBJECT)?,
inner.subject.clone(),
));
triples.push(StarTriple::new(
assertion_node.clone(),
StarTerm::iri(vocab::RDF_PREDICATE)?,
inner.predicate.clone(),
));
triples.push(StarTriple::new(
assertion_node.clone(),
StarTerm::iri(vocab::RDF_OBJECT)?,
inner.object.clone(),
));
for (pred, obj) in provenance_annotations {
triples.push(StarTriple::new(
prov_node.clone(),
pred.clone(),
obj.clone(),
));
}
debug!(
np_iri = %np_iri,
provenance_count = provenance_annotations.len(),
"Nanopublication expanded"
);
Ok(ExpandedAnnotation {
triples,
statement_node: np_iri,
})
}
pub fn assertion_iri(np_head_iri: &str) -> String {
format!("{}_assertion", np_head_iri)
}
pub fn provenance_iri(np_head_iri: &str) -> String {
format!("{}_prov", np_head_iri)
}
pub fn pubinfo_iri(np_head_iri: &str) -> String {
format!("{}_pubinfo", np_head_iri)
}
}
impl Default for NanopubProfile {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnnotationProfile {
Reification(ReificationProfile),
Singleton(SingletonProfile),
Nanopub(NanopubProfile),
}
impl AnnotationProfile {
pub fn reification() -> Self {
Self::Reification(ReificationProfile::new())
}
pub fn singleton() -> Self {
Self::Singleton(SingletonProfile::new())
}
pub fn nanopub() -> Self {
Self::Nanopub(NanopubProfile::new())
}
pub fn expand(
&mut self,
quoted: &StarTerm,
annotations: &[(StarTerm, StarTerm)],
) -> StarResult<ExpandedAnnotation> {
match self {
Self::Reification(p) => p.expand(quoted, annotations),
Self::Singleton(p) => p.expand(quoted, annotations),
Self::Nanopub(p) => p.expand(quoted, annotations),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Reification(_) => "reification",
Self::Singleton(_) => "singleton",
Self::Nanopub(_) => "nanopub",
}
}
pub fn convert_to(
source_expansion: &ExpandedAnnotation,
target: &mut AnnotationProfile,
) -> StarResult<ExpandedAnnotation> {
let find_obj = |pred_iri: &str| -> StarResult<StarTerm> {
source_expansion
.triples
.iter()
.find(|t| matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == pred_iri))
.map(|t| t.object.clone())
.ok_or_else(|| {
StarError::reification_error(format!(
"Cannot find {pred_iri} triple in source expansion for profile conversion"
))
})
};
let subj = find_obj(vocab::RDF_SUBJECT)?;
let pred = find_obj(vocab::RDF_PREDICATE)?;
let obj = find_obj(vocab::RDF_OBJECT)?;
let inner = StarTriple::new(subj, pred, obj);
let quoted = StarTerm::quoted_triple(inner);
let structural_iris = [
vocab::RDF_TYPE,
vocab::RDF_SUBJECT,
vocab::RDF_PREDICATE,
vocab::RDF_OBJECT,
vocab::NP_HAS_ASSERTION,
vocab::NP_HAS_PROVENANCE,
vocab::NP_HAS_PUBLICATION_INFO,
vocab::SP_SINGLETON_PROPERTY_OF,
];
let annotations: Vec<(StarTerm, StarTerm)> = source_expansion
.triples
.iter()
.filter(|t| {
!matches!(&t.predicate, StarTerm::NamedNode(n) if structural_iris.contains(&n.iri.as_str()))
})
.map(|t| (t.predicate.clone(), t.object.clone()))
.collect();
target.expand("ed, &annotations)
}
}
impl fmt::Display for AnnotationProfile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AnnotationProfile::{}", self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::StarTerm;
fn make_quoted_triple() -> StarTerm {
let inner = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("30").unwrap(),
);
StarTerm::quoted_triple(inner)
}
fn certainty_annotation() -> (StarTerm, StarTerm) {
(
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
)
}
#[test]
fn test_reification_profile_new() {
let profile = ReificationProfile::new();
assert_eq!(profile.counter, 0);
assert!(profile.config.include_type_triple);
}
#[test]
fn test_reification_expand_produces_correct_triple_count() {
let mut profile = ReificationProfile::new();
let quoted = make_quoted_triple();
let annotations = [certainty_annotation()];
let result = profile.expand("ed, &annotations).unwrap();
assert_eq!(result.triple_count(), 5);
}
#[test]
fn test_reification_expand_no_type_triple() {
let mut profile = ReificationProfile::with_config(ProfileConfig {
include_type_triple: false,
..Default::default()
});
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
assert_eq!(result.triple_count(), 3);
}
#[test]
fn test_reification_statement_node_iri_increments() {
let mut profile = ReificationProfile::new();
let quoted = make_quoted_triple();
let r1 = profile.expand("ed, &[]).unwrap();
let r2 = profile.expand("ed, &[]).unwrap();
assert_ne!(r1.statement_node, r2.statement_node);
}
#[test]
fn test_reification_expand_error_on_non_quoted_term() {
let mut profile = ReificationProfile::new();
let not_quoted = StarTerm::iri("http://example.org/plain").unwrap();
let result = profile.expand(¬_quoted, &[]);
assert!(result.is_err());
}
#[test]
fn test_reification_collapse_roundtrip() {
let mut profile = ReificationProfile::new();
let quoted = make_quoted_triple();
let expanded = profile.expand("ed, &[]).unwrap();
let stmt_iri = expanded.statement_node.clone();
let collapsed = profile.collapse(&expanded.triples, &stmt_iri).unwrap();
assert!(matches!(collapsed.subject, StarTerm::QuotedTriple(_)));
}
#[test]
fn test_reification_collapse_missing_predicate_triple() {
let profile = ReificationProfile::new();
let result = profile.collapse(&[], "http://example.org/stmt/1");
assert!(result.is_err());
}
#[test]
fn test_reification_rdf_subject_is_alice() {
let mut profile = ReificationProfile::new();
let quoted = make_quoted_triple();
let expanded = profile.expand("ed, &[]).unwrap();
let subj_triple = expanded
.triples
.iter()
.find(|t| matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == vocab::RDF_SUBJECT))
.unwrap();
assert_eq!(
subj_triple.object,
StarTerm::iri("http://example.org/alice").unwrap()
);
}
#[test]
fn test_singleton_profile_new() {
let profile = SingletonProfile::new();
assert!(profile.singleton_map.is_empty());
}
#[test]
fn test_singleton_expand_basic() {
let mut profile = SingletonProfile::new();
let quoted = make_quoted_triple();
let annotations = [certainty_annotation()];
let result = profile.expand("ed, &annotations).unwrap();
assert_eq!(result.triple_count(), 3);
}
#[test]
fn test_singleton_expand_increments_counter() {
let mut profile = SingletonProfile::new();
let quoted = make_quoted_triple();
let r1 = profile.expand("ed, &[]).unwrap();
let r2 = profile.expand("ed, &[]).unwrap();
assert_ne!(r1.statement_node, r2.statement_node);
}
#[test]
fn test_singleton_singletons_for_tracks_correctly() {
let mut profile = SingletonProfile::new();
let quoted = make_quoted_triple();
profile.expand("ed, &[]).unwrap();
profile.expand("ed, &[]).unwrap();
let singletons = profile.singletons_for("http://example.org/age");
assert_eq!(singletons.len(), 2);
}
#[test]
fn test_singleton_expand_error_on_non_named_node_predicate() {
let mut profile = SingletonProfile::new();
let inner = StarTriple::new(
StarTerm::iri("http://example.org/s").unwrap(),
StarTerm::blank_node("p_blank").unwrap(),
StarTerm::iri("http://example.org/o").unwrap(),
);
let quoted = StarTerm::quoted_triple(inner);
let result = profile.expand("ed, &[]);
assert!(result.is_err());
}
#[test]
fn test_singleton_expand_error_on_non_quoted() {
let mut profile = SingletonProfile::new();
let plain = StarTerm::iri("http://example.org/plain").unwrap();
assert!(profile.expand(&plain, &[]).is_err());
}
#[test]
fn test_singleton_sp_singleton_property_of_link_exists() {
let mut profile = SingletonProfile::new();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
let sp_triple = result.triples.iter().find(|t| {
matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == vocab::SP_SINGLETON_PROPERTY_OF)
});
assert!(sp_triple.is_some());
if let Some(t) = sp_triple {
assert_eq!(t.object, StarTerm::iri("http://example.org/age").unwrap());
}
}
#[test]
fn test_nanopub_profile_new() {
let profile = NanopubProfile::new();
assert_eq!(profile.counter, 0);
}
#[test]
fn test_nanopub_expand_basic_count() {
let mut profile = NanopubProfile::new();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
assert_eq!(result.triple_count(), 7);
}
#[test]
fn test_nanopub_expand_with_provenance() {
let mut profile = NanopubProfile::new();
let quoted = make_quoted_triple();
let prov = [(
StarTerm::iri("http://purl.org/dc/terms/creator").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
)];
let result = profile.expand("ed, &prov).unwrap();
assert_eq!(result.triple_count(), 8); }
#[test]
fn test_nanopub_expand_increments_counter() {
let mut profile = NanopubProfile::new();
let quoted = make_quoted_triple();
let r1 = profile.expand("ed, &[]).unwrap();
let r2 = profile.expand("ed, &[]).unwrap();
assert_ne!(r1.statement_node, r2.statement_node);
}
#[test]
fn test_nanopub_has_assertion_triple() {
let mut profile = NanopubProfile::new();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
let has_assertion = result.triples.iter().find(
|t| matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == vocab::NP_HAS_ASSERTION),
);
assert!(has_assertion.is_some());
}
#[test]
fn test_nanopub_has_provenance_triple() {
let mut profile = NanopubProfile::new();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
let has_prov = result.triples.iter().find(
|t| matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == vocab::NP_HAS_PROVENANCE),
);
assert!(has_prov.is_some());
}
#[test]
fn test_nanopub_has_publication_info_triple() {
let mut profile = NanopubProfile::new();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
let has_pubinfo = result.triples.iter().find(|t| {
matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == vocab::NP_HAS_PUBLICATION_INFO)
});
assert!(has_pubinfo.is_some());
}
#[test]
fn test_nanopub_assertion_iri_format() {
assert_eq!(
NanopubProfile::assertion_iri("http://example.org/stmt/1"),
"http://example.org/stmt/1_assertion"
);
}
#[test]
fn test_nanopub_provenance_iri_format() {
assert_eq!(
NanopubProfile::provenance_iri("http://example.org/stmt/1"),
"http://example.org/stmt/1_prov"
);
}
#[test]
fn test_nanopub_pubinfo_iri_format() {
assert_eq!(
NanopubProfile::pubinfo_iri("http://example.org/stmt/1"),
"http://example.org/stmt/1_pubinfo"
);
}
#[test]
fn test_nanopub_type_triple_present() {
let mut profile = NanopubProfile::new();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
let type_triple = result.triples.iter().find(|t| {
matches!(&t.predicate, StarTerm::NamedNode(n) if n.iri == vocab::RDF_TYPE)
&& matches!(&t.object, StarTerm::NamedNode(n) if n.iri == vocab::NP_NANOPUBLICATION)
});
assert!(type_triple.is_some());
}
#[test]
fn test_nanopub_expand_error_on_non_quoted() {
let mut profile = NanopubProfile::new();
let plain = StarTerm::iri("http://example.org/plain").unwrap();
assert!(profile.expand(&plain, &[]).is_err());
}
#[test]
fn test_annotation_profile_name_reification() {
let profile = AnnotationProfile::reification();
assert_eq!(profile.name(), "reification");
}
#[test]
fn test_annotation_profile_name_singleton() {
let profile = AnnotationProfile::singleton();
assert_eq!(profile.name(), "singleton");
}
#[test]
fn test_annotation_profile_name_nanopub() {
let profile = AnnotationProfile::nanopub();
assert_eq!(profile.name(), "nanopub");
}
#[test]
fn test_annotation_profile_reification_expand() {
let mut profile = AnnotationProfile::reification();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
assert!(!result.triples.is_empty());
}
#[test]
fn test_annotation_profile_singleton_expand() {
let mut profile = AnnotationProfile::singleton();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
assert!(!result.triples.is_empty());
}
#[test]
fn test_annotation_profile_nanopub_expand() {
let mut profile = AnnotationProfile::nanopub();
let quoted = make_quoted_triple();
let result = profile.expand("ed, &[]).unwrap();
assert!(!result.triples.is_empty());
}
#[test]
fn test_annotation_profile_display() {
let profile = AnnotationProfile::reification();
let s = format!("{}", profile);
assert!(s.contains("reification"));
}
#[test]
fn test_annotation_profile_convert_reification_to_singleton() {
let mut source_profile = AnnotationProfile::reification();
let quoted = make_quoted_triple();
let annotations = [certainty_annotation()];
let expanded = source_profile.expand("ed, &annotations).unwrap();
let mut target = AnnotationProfile::singleton();
let converted = AnnotationProfile::convert_to(&expanded, &mut target).unwrap();
assert!(!converted.triples.is_empty());
}
#[test]
fn test_annotation_profile_convert_reification_to_nanopub() {
let mut source_profile = AnnotationProfile::reification();
let quoted = make_quoted_triple();
let expanded = source_profile.expand("ed, &[]).unwrap();
let mut target = AnnotationProfile::nanopub();
let converted = AnnotationProfile::convert_to(&expanded, &mut target).unwrap();
assert!(converted.triple_count() >= 7);
}
#[test]
fn test_profile_config_default() {
let config = ProfileConfig::default();
assert_eq!(config.base_iri, "http://example.org/stmt/");
assert_eq!(config.counter_seed, 1);
assert!(config.include_type_triple);
}
#[test]
fn test_reification_with_multiple_annotations() {
let mut profile = ReificationProfile::new();
let quoted = make_quoted_triple();
let annotations = [
(
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
),
(
StarTerm::iri("http://example.org/source").unwrap(),
StarTerm::iri("http://example.org/study").unwrap(),
),
(
StarTerm::iri("http://example.org/year").unwrap(),
StarTerm::literal("2024").unwrap(),
),
];
let result = profile.expand("ed, &annotations).unwrap();
assert_eq!(result.triple_count(), 7);
}
#[test]
fn test_singleton_with_multiple_annotations() {
let mut profile = SingletonProfile::new();
let quoted = make_quoted_triple();
let annotations = [
certainty_annotation(),
(
StarTerm::iri("http://example.org/source").unwrap(),
StarTerm::iri("http://example.org/census").unwrap(),
),
];
let result = profile.expand("ed, &annotations).unwrap();
assert_eq!(result.triple_count(), 4);
}
}