use crate::model::{BlankNode, Literal, NamedNode, StarGraph, StarQuad, StarTerm, StarTriple};
use crate::{StarError, StarResult};
use super::context::ParseContext;
pub(super) fn process_jsonld_star_value(
value: &serde_json::Value,
graph: &mut StarGraph,
context: &mut ParseContext,
) -> StarResult<()> {
match value {
serde_json::Value::Object(obj) => {
process_jsonld_star_object(obj, graph, context)?;
}
serde_json::Value::Array(arr) => {
for item in arr {
process_jsonld_star_value(item, graph, context)?;
}
}
_ => {
}
}
Ok(())
}
pub(super) fn process_jsonld_star_object(
obj: &serde_json::Map<String, serde_json::Value>,
graph: &mut StarGraph,
context: &mut ParseContext,
) -> StarResult<()> {
if obj.contains_key("@annotation") {
return process_quoted_triple_annotation(obj, graph, context);
}
let subject = if let Some(id_value) = obj.get("@id") {
match id_value {
serde_json::Value::String(id) => StarTerm::iri(id)?,
_ => return Err(StarError::parse_error("@id must be a string".to_string())),
}
} else {
StarTerm::BlankNode(BlankNode {
id: context.next_blank_node(),
})
};
for (key, value) in obj {
if key.starts_with('@') {
continue;
}
let predicate = StarTerm::iri(key)?;
process_property_values(&subject, &predicate, value, graph, context)?;
}
Ok(())
}
pub(super) fn process_property_values(
subject: &StarTerm,
predicate: &StarTerm,
value: &serde_json::Value,
graph: &mut StarGraph,
context: &mut ParseContext,
) -> StarResult<()> {
match value {
serde_json::Value::Array(arr) => {
for item in arr {
create_triple_from_value(subject, predicate, item, graph, context)?;
}
}
_ => {
create_triple_from_value(subject, predicate, value, graph, context)?;
}
}
Ok(())
}
pub(super) fn create_triple_from_value(
subject: &StarTerm,
predicate: &StarTerm,
value: &serde_json::Value,
graph: &mut StarGraph,
context: &mut ParseContext,
) -> StarResult<()> {
let object = match value {
serde_json::Value::String(s) => {
if s.starts_with("http://") || s.starts_with("https://") || s.contains(':') {
StarTerm::iri(s)?
} else {
StarTerm::Literal(Literal {
value: s.clone(),
datatype: None,
language: None,
})
}
}
serde_json::Value::Object(obj) => {
if let Some(id_value) = obj.get("@id") {
if let serde_json::Value::String(id) = id_value {
StarTerm::iri(id)?
} else {
return Err(StarError::parse_error("@id must be a string".to_string()));
}
} else if let Some(value_str) = obj.get("@value") {
let value_str = match value_str {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => return Err(StarError::parse_error("Invalid @value".to_string())),
};
let datatype = obj
.get("@type")
.and_then(|v| v.as_str())
.map(|s| NamedNode { iri: s.to_string() });
let language = obj
.get("@language")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
StarTerm::Literal(Literal {
value: value_str,
language,
datatype,
})
} else {
let blank_node = StarTerm::BlankNode(BlankNode {
id: context.next_blank_node(),
});
process_jsonld_star_object(obj, graph, context)?;
blank_node
}
}
serde_json::Value::Number(n) => StarTerm::Literal(Literal {
value: n.to_string(),
datatype: Some(NamedNode {
iri: "http://www.w3.org/2001/XMLSchema#decimal".to_string(),
}),
language: None,
}),
serde_json::Value::Bool(b) => StarTerm::Literal(Literal {
value: b.to_string(),
datatype: Some(NamedNode {
iri: "http://www.w3.org/2001/XMLSchema#boolean".to_string(),
}),
language: None,
}),
_ => {
return Err(StarError::parse_error(
"Unsupported JSON value type".to_string(),
));
}
};
let quad = StarQuad::new(subject.clone(), predicate.clone(), object, None);
graph.insert_quad(quad)?;
Ok(())
}
pub(super) fn process_quoted_triple_annotation(
obj: &serde_json::Map<String, serde_json::Value>,
graph: &mut StarGraph,
context: &mut ParseContext,
) -> StarResult<()> {
let annotation = obj
.get("@annotation")
.ok_or_else(|| StarError::parse_error("Missing @annotation".to_string()))?;
match annotation {
serde_json::Value::Object(ann_obj) => {
let subject_val = ann_obj.get("subject").ok_or_else(|| {
StarError::parse_error("Missing subject in annotation".to_string())
})?;
let predicate_val = ann_obj.get("predicate").ok_or_else(|| {
StarError::parse_error("Missing predicate in annotation".to_string())
})?;
let object_val = ann_obj.get("object").ok_or_else(|| {
StarError::parse_error("Missing object in annotation".to_string())
})?;
let subject = json_value_to_star_term(subject_val)?;
let predicate = json_value_to_star_term(predicate_val)?;
let object = json_value_to_star_term(object_val)?;
let quoted_triple =
StarTerm::QuotedTriple(Box::new(StarTriple::new(subject, predicate, object)));
for (prop_key, prop_value) in obj {
if prop_key == "@annotation" {
continue;
}
let annotation_predicate = StarTerm::iri(prop_key)?;
process_property_values(
"ed_triple,
&annotation_predicate,
prop_value,
graph,
context,
)?;
}
}
_ => {
return Err(StarError::parse_error(
"@annotation must be an object".to_string(),
));
}
}
Ok(())
}
pub(super) fn json_value_to_star_term(value: &serde_json::Value) -> StarResult<StarTerm> {
match value {
serde_json::Value::String(s) => {
if s.starts_with("_:") {
Ok(StarTerm::BlankNode(BlankNode { id: s.clone() }))
} else {
StarTerm::iri(s)
}
}
serde_json::Value::Object(obj) => {
if let Some(id) = obj.get("@id").and_then(|v| v.as_str()) {
StarTerm::iri(id)
} else if let Some(value) = obj.get("@value").and_then(|v| v.as_str()) {
let datatype = obj
.get("@type")
.and_then(|v| v.as_str())
.map(|s| NamedNode { iri: s.to_string() });
let language = obj
.get("@language")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Ok(StarTerm::Literal(Literal {
value: value.to_string(),
datatype,
language,
}))
} else {
Err(StarError::parse_error("Invalid object term".to_string()))
}
}
_ => Err(StarError::parse_error("Invalid term value".to_string())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_json_value_to_star_term_iri() {
let value = serde_json::Value::String("http://example.org/resource".to_string());
let term = json_value_to_star_term(&value).unwrap();
assert!(matches!(term, StarTerm::NamedNode(_)));
}
#[test]
fn test_json_value_to_star_term_blank_node() {
let value = serde_json::Value::String("_:b1".to_string());
let term = json_value_to_star_term(&value).unwrap();
assert!(matches!(term, StarTerm::BlankNode(_)));
}
#[test]
fn test_json_value_to_star_term_literal() {
let mut obj = serde_json::Map::new();
obj.insert(
"@value".to_string(),
serde_json::Value::String("test".to_string()),
);
let value = serde_json::Value::Object(obj);
let term = json_value_to_star_term(&value).unwrap();
assert!(matches!(term, StarTerm::Literal(_)));
}
}