use super::super::config::SerializationOptions;
use super::StarSerializer;
use crate::model::{StarGraph, StarTerm, StarTriple};
use crate::parser::StarFormat;
use crate::{StarError, StarResult};
impl StarSerializer {
pub fn can_pretty_print(&self, graph: &StarGraph) -> bool {
graph.len() < 1000 && graph.max_nesting_depth() < 3
}
pub fn estimate_size(&self, graph: &StarGraph, format: StarFormat) -> usize {
let base_size_per_triple = match format {
StarFormat::TurtleStar => 50, StarFormat::NTriplesStar => 80, StarFormat::TrigStar => 60, StarFormat::NQuadsStar => 90, StarFormat::JsonLdStar => 120, };
let quoted_triple_multiplier = 1.5;
let mut total_size = graph.len() * base_size_per_triple;
let quoted_count = graph.count_quoted_triples();
total_size +=
(quoted_count as f64 * quoted_triple_multiplier * base_size_per_triple as f64) as usize;
total_size
}
pub fn validate_for_format(&self, graph: &StarGraph, format: StarFormat) -> StarResult<()> {
let max_depth = graph.max_nesting_depth();
if max_depth > self.config.max_nesting_depth {
return Err(StarError::serialization_error(format!(
"Graph nesting depth {} exceeds maximum {}",
max_depth, self.config.max_nesting_depth
)));
}
match format {
StarFormat::TurtleStar | StarFormat::NTriplesStar => {
Ok(())
}
StarFormat::TrigStar | StarFormat::NQuadsStar => {
self.validate_quad_constraints(graph, format)
}
StarFormat::JsonLdStar => {
Ok(())
}
}
}
fn validate_quad_constraints(&self, graph: &StarGraph, format: StarFormat) -> StarResult<()> {
match format {
StarFormat::NQuadsStar => {
for quad in graph.quads() {
match &quad.subject {
StarTerm::NamedNode(_) | StarTerm::BlankNode(_) => {}
StarTerm::QuotedTriple(inner_triple) => {
self.validate_quoted_triple_structure(inner_triple)?;
}
StarTerm::Literal(_) => {
return Err(StarError::serialization_error(
"N-Quads-star: Literals cannot be subjects in quads".to_string(),
));
}
StarTerm::Variable(_) => {
return Err(StarError::serialization_error(
"N-Quads-star: Variables cannot be serialized in concrete data"
.to_string(),
));
}
}
match &quad.predicate {
StarTerm::NamedNode(_) => {}
_ => {
return Err(StarError::serialization_error(
"N-Quads-star: Predicates must be IRIs".to_string(),
));
}
}
match &quad.object {
StarTerm::QuotedTriple(inner_triple) => {
self.validate_quoted_triple_structure(inner_triple)?;
}
StarTerm::Variable(_) => {
return Err(StarError::serialization_error(
"N-Quads-star: Variables cannot be serialized in concrete data"
.to_string(),
));
}
_ => {} }
if let Some(ref graph_term) = quad.graph {
match graph_term {
StarTerm::NamedNode(_) | StarTerm::BlankNode(_) => {}
StarTerm::QuotedTriple(_) => {
return Err(StarError::serialization_error(
"N-Quads-star: Quoted triples cannot be used as graph names"
.to_string(),
));
}
StarTerm::Literal(_) => {
return Err(StarError::serialization_error(
"N-Quads-star: Literals cannot be used as graph names"
.to_string(),
));
}
StarTerm::Variable(_) => {
return Err(StarError::serialization_error(
"N-Quads-star: Variables cannot be serialized in concrete data"
.to_string(),
));
}
}
}
}
}
StarFormat::TrigStar => {
for graph_name in graph.named_graph_names() {
if graph_name.is_empty() {
return Err(StarError::serialization_error(
"TriG-star: Empty graph names are not allowed".to_string(),
));
}
if !graph_name.starts_with("http://")
&& !graph_name.starts_with("https://")
&& !graph_name.starts_with("_:")
&& !graph_name.starts_with("urn:")
{
return Err(StarError::serialization_error(format!(
"TriG-star: Invalid graph name format: {graph_name}"
)));
}
}
for triple in graph.triples() {
self.validate_triple_for_trig(triple)?;
}
for graph_name in graph.named_graph_names() {
if let Some(named_triples) = graph.named_graph_triples(graph_name) {
for triple in named_triples {
self.validate_triple_for_trig(triple)?;
}
}
}
let graph_count = graph.named_graph_names().len();
if graph_count > 1000 {
return Err(StarError::serialization_error(
format!("TriG-star: Too many named graphs ({graph_count}), consider using streaming serialization")
));
}
}
_ => {
return Err(StarError::serialization_error(
"Internal error: validate_quad_constraints called for non-quad format"
.to_string(),
));
}
}
Ok(())
}
#[allow(clippy::only_used_in_recursion)]
fn validate_quoted_triple_structure(&self, triple: &StarTriple) -> StarResult<()> {
match &triple.subject {
StarTerm::Literal(_) => {
return Err(StarError::serialization_error(
"Quoted triple: Literals cannot be subjects".to_string(),
));
}
StarTerm::Variable(_) => {
return Err(StarError::serialization_error(
"Quoted triple: Variables cannot be serialized in concrete data".to_string(),
));
}
StarTerm::QuotedTriple(nested) => {
self.validate_quoted_triple_structure(nested)?;
}
_ => {} }
match &triple.predicate {
StarTerm::NamedNode(_) => {}
_ => {
return Err(StarError::serialization_error(
"Quoted triple: Predicates must be IRIs".to_string(),
));
}
}
match &triple.object {
StarTerm::Variable(_) => {
return Err(StarError::serialization_error(
"Quoted triple: Variables cannot be serialized in concrete data".to_string(),
));
}
StarTerm::QuotedTriple(nested) => {
self.validate_quoted_triple_structure(nested)?;
}
_ => {} }
Ok(())
}
fn validate_triple_for_trig(&self, triple: &StarTriple) -> StarResult<()> {
match &triple.subject {
StarTerm::Literal(_) => {
return Err(StarError::serialization_error(
"TriG-star: Literals cannot be subjects".to_string(),
));
}
StarTerm::Variable(_) => {
return Err(StarError::serialization_error(
"TriG-star: Variables cannot be serialized in concrete data".to_string(),
));
}
StarTerm::QuotedTriple(inner) => {
self.validate_quoted_triple_structure(inner)?;
}
_ => {} }
match &triple.predicate {
StarTerm::NamedNode(_) => {}
_ => {
return Err(StarError::serialization_error(
"TriG-star: Predicates must be IRIs".to_string(),
));
}
}
match &triple.object {
StarTerm::Variable(_) => {
return Err(StarError::serialization_error(
"TriG-star: Variables cannot be serialized in concrete data".to_string(),
));
}
StarTerm::QuotedTriple(inner) => {
self.validate_quoted_triple_structure(inner)?;
}
_ => {} }
Ok(())
}
pub fn serialize_graph(
&self,
graph: &StarGraph,
format: StarFormat,
_options: &SerializationOptions,
) -> StarResult<String> {
self.serialize_to_string(graph, format)
}
}
pub struct ChunkedIterator<I: Iterator> {
inner: I,
chunk_size: usize,
}
impl<I: Iterator> ChunkedIterator<I> {
pub fn new(iter: I, chunk_size: usize) -> Self {
assert!(chunk_size > 0, "chunk_size must be greater than 0");
Self {
inner: iter,
chunk_size,
}
}
pub fn chunk_size(&self) -> usize {
self.chunk_size
}
}
impl<I: Iterator> Iterator for ChunkedIterator<I> {
type Item = Vec<I::Item>;
fn next(&mut self) -> Option<Self::Item> {
let mut chunk = Vec::with_capacity(self.chunk_size);
for _ in 0..self.chunk_size {
match self.inner.next() {
Some(item) => chunk.push(item),
None => break,
}
}
if chunk.is_empty() {
None
} else {
Some(chunk)
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (lower, upper) = self.inner.size_hint();
let lower_chunks = (lower + self.chunk_size - 1) / self.chunk_size;
let upper_chunks = upper.map(|u| (u + self.chunk_size - 1) / self.chunk_size);
(lower_chunks, upper_chunks)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::StarTerm;
use crate::parser::StarParser;
use crate::serializer::CompressionType;
use crate::StarQuad;
#[test]
fn test_simple_triple_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
);
graph.insert(triple).unwrap();
let result = serializer
.serialize_to_string(&graph, StarFormat::NTriplesStar)
.unwrap();
assert!(result.contains("<http://example.org/alice>"));
assert!(result.contains("<http://example.org/knows>"));
assert!(result.contains("<http://example.org/bob>"));
assert!(result.ends_with(" .\n"));
let result = serializer
.serialize_to_string(&graph, StarFormat::TurtleStar)
.unwrap();
assert!(result.contains("@prefix"));
}
#[test]
fn test_quoted_triple_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let inner = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("25").unwrap(),
);
let outer = StarTriple::new(
StarTerm::quoted_triple(inner),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
graph.insert(outer).unwrap();
let result = serializer
.serialize_to_string(&graph, StarFormat::NTriplesStar)
.unwrap();
assert!(result.contains("<<"));
assert!(result.contains(">>"));
assert!(result.contains("\"25\""));
assert!(result.contains("\"0.9\""));
}
#[test]
fn test_literal_escaping() {
let test_cases = vec![
("simple", "simple"),
("with\nnewline", "with\\nnewline"),
("with\ttab", "with\\ttab"),
("with\"quote", "with\\\"quote"),
("with\\backslash", "with\\\\backslash"),
];
for (input, expected) in test_cases {
let escaped = StarSerializer::escape_literal_static(input);
assert_eq!(escaped, expected);
}
}
#[test]
fn test_literal_with_language_and_datatype() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let triple1 = StarTriple::new(
StarTerm::iri("http://example.org/resource").unwrap(),
StarTerm::iri("http://example.org/label").unwrap(),
StarTerm::literal_with_language("Hello", "en").unwrap(),
);
let triple2 = StarTriple::new(
StarTerm::iri("http://example.org/resource").unwrap(),
StarTerm::iri("http://example.org/count").unwrap(),
StarTerm::literal_with_datatype("42", "http://www.w3.org/2001/XMLSchema#integer")
.unwrap(),
);
graph.insert(triple1).unwrap();
graph.insert(triple2).unwrap();
let result = serializer
.serialize_to_string(&graph, StarFormat::NTriplesStar)
.unwrap();
assert!(result.contains("\"Hello\"@en"));
assert!(result.contains("\"42\"^^<http://www.w3.org/2001/XMLSchema#integer>"));
}
#[test]
fn test_format_validation() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let mut current_triple = StarTriple::new(
StarTerm::iri("http://example.org/s").unwrap(),
StarTerm::iri("http://example.org/p").unwrap(),
StarTerm::iri("http://example.org/o").unwrap(),
);
for _ in 0..15 {
current_triple = StarTriple::new(
StarTerm::quoted_triple(current_triple),
StarTerm::iri("http://example.org/meta").unwrap(),
StarTerm::literal("value").unwrap(),
);
}
graph.insert(current_triple).unwrap();
let result = serializer.validate_for_format(&graph, StarFormat::NTriplesStar);
assert!(result.is_err());
}
#[test]
fn test_size_estimation() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let triple = StarTriple::new(
StarTerm::iri("http://example.org/s").unwrap(),
StarTerm::iri("http://example.org/p").unwrap(),
StarTerm::iri("http://example.org/o").unwrap(),
);
graph.insert(triple).unwrap();
let turtle_size = serializer.estimate_size(&graph, StarFormat::TurtleStar);
let ntriples_size = serializer.estimate_size(&graph, StarFormat::NTriplesStar);
assert!(ntriples_size > turtle_size);
}
#[test]
fn test_enhanced_trig_star_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let default_triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
);
graph.insert(default_triple).unwrap();
let named_quad = StarQuad::new(
StarTerm::iri("http://example.org/charlie").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("30").unwrap(),
Some(StarTerm::iri("http://example.org/graph1").unwrap()),
);
graph.insert_quad(named_quad).unwrap();
let result = serializer
.serialize_to_string(&graph, StarFormat::TrigStar)
.unwrap();
assert!(result.contains("@prefix"));
assert!(result.contains("{"));
assert!(result.contains("alice"));
assert!(result.contains("http://example.org/graph1"));
assert!(result.contains("charlie"));
}
#[test]
fn test_enhanced_nquads_star_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let default_triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
);
graph.insert(default_triple).unwrap();
let named_quad = StarQuad::new(
StarTerm::iri("http://example.org/charlie").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("30").unwrap(),
Some(StarTerm::iri("http://example.org/graph1").unwrap()),
);
graph.insert_quad(named_quad).unwrap();
let result = serializer
.serialize_to_string(&graph, StarFormat::NQuadsStar)
.unwrap();
assert!(result.contains(
"<http://example.org/alice> <http://example.org/knows> <http://example.org/bob> ."
));
assert!(result.contains("<http://example.org/charlie> <http://example.org/age> \"30\" <http://example.org/graph1> ."));
}
#[test]
fn test_quoted_triple_serialization_roundtrip() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let inner = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/says").unwrap(),
StarTerm::literal("Hello").unwrap(),
);
let outer = StarTriple::new(
StarTerm::quoted_triple(inner),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.95").unwrap(),
);
graph.insert(outer).unwrap();
for format in [
StarFormat::TurtleStar,
StarFormat::NTriplesStar,
StarFormat::TrigStar,
StarFormat::NQuadsStar,
] {
let serialized = serializer.serialize_to_string(&graph, format).unwrap();
assert!(serialized.contains("<<"));
assert!(serialized.contains(">>"));
assert!(serialized.contains("alice"));
assert!(serialized.contains("says"));
assert!(serialized.contains("Hello"));
assert!(serialized.contains("certainty"));
}
}
#[test]
fn test_nquads_star_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let triple1 = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
);
graph.insert(triple1).unwrap();
let quad1 = StarQuad::new(
StarTerm::iri("http://example.org/charlie").unwrap(),
StarTerm::iri("http://example.org/likes").unwrap(),
StarTerm::iri("http://example.org/dave").unwrap(),
Some(StarTerm::iri("http://example.org/graph1").unwrap()),
);
graph.insert_quad(quad1).unwrap();
let quoted = StarTriple::new(
StarTerm::iri("http://example.org/eve").unwrap(),
StarTerm::iri("http://example.org/says").unwrap(),
StarTerm::literal("hello").unwrap(),
);
let quad2 = StarQuad::new(
StarTerm::quoted_triple(quoted),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.8").unwrap(),
Some(StarTerm::iri("http://example.org/graph2").unwrap()),
);
graph.insert_quad(quad2).unwrap();
let serialized = serializer
.serialize_to_string(&graph, StarFormat::NQuadsStar)
.unwrap();
let lines: Vec<&str> = serialized
.lines()
.filter(|l| !l.trim().is_empty())
.collect();
assert_eq!(lines.len(), 3);
assert!(serialized.contains("<http://example.org/graph1>"));
assert!(serialized.contains("<http://example.org/graph2>"));
assert!(serialized.contains("<< "));
assert!(serialized.contains(" >>"));
}
#[test]
fn test_trig_star_serialization_with_multiple_graphs() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let triple1 = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/age").unwrap(),
StarTerm::literal("30").unwrap(),
);
graph.insert(triple1).unwrap();
let quad1 = StarQuad::new(
StarTerm::iri("http://example.org/bob").unwrap(),
StarTerm::iri("http://example.org/likes").unwrap(),
StarTerm::iri("http://example.org/coffee").unwrap(),
Some(StarTerm::iri("http://example.org/preferences").unwrap()),
);
graph.insert_quad(quad1).unwrap();
let inner = StarTriple::new(
StarTerm::iri("http://example.org/charlie").unwrap(),
StarTerm::iri("http://example.org/believes").unwrap(),
StarTerm::literal("earth is round").unwrap(),
);
let quad2 = StarQuad::new(
StarTerm::quoted_triple(inner),
StarTerm::iri("http://example.org/confidence").unwrap(),
StarTerm::literal("1.0").unwrap(),
Some(StarTerm::iri("http://example.org/beliefs").unwrap()),
);
graph.insert_quad(quad2).unwrap();
let serialized = serializer
.serialize_to_string(&graph, StarFormat::TrigStar)
.unwrap();
assert!(serialized.contains("@prefix"));
assert!(serialized.contains("{\n"));
assert!(serialized.contains("alice"));
assert!(serialized.contains("<http://example.org/preferences> {"));
assert!(serialized.contains("<http://example.org/beliefs> {"));
assert!(serialized.contains("<<"));
assert!(serialized.contains(">>"));
}
#[test]
fn test_streaming_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
for i in 0..1000 {
let triple = StarTriple::new(
StarTerm::iri(&format!("http://example.org/s{i}")).unwrap(),
StarTerm::iri("http://example.org/p").unwrap(),
StarTerm::literal(&format!("value{i}")).unwrap(),
);
graph.insert(triple).unwrap();
}
let mut output = Vec::new();
serializer
.serialize_streaming(&graph, &mut output, StarFormat::NTriplesStar, 100)
.unwrap();
let output_str = String::from_utf8(output).unwrap();
let lines: Vec<&str> = output_str
.lines()
.filter(|l| !l.trim().is_empty())
.collect();
assert_eq!(lines.len(), 1000);
for line in lines {
assert!(line.ends_with(" ."));
assert!(line.contains("http://example.org/"));
}
}
#[test]
fn test_parallel_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
for i in 0..500 {
let triple = StarTriple::new(
StarTerm::iri(&format!("http://example.org/subject{i}")).unwrap(),
StarTerm::iri("http://example.org/predicate").unwrap(),
StarTerm::iri(&format!("http://example.org/object{i}")).unwrap(),
);
graph.insert(triple).unwrap();
}
let output = Box::leak(Box::new(Vec::new()));
let output_ptr = output as *const Vec<u8>;
serializer
.serialize_parallel(&graph, output, StarFormat::NTriplesStar, 4, 100)
.unwrap();
let output_data = unsafe { &*output_ptr };
let output_str = String::from_utf8(output_data.clone()).unwrap();
let lines: Vec<&str> = output_str
.lines()
.filter(|l| !l.trim().is_empty())
.collect();
assert_eq!(lines.len(), 500);
}
#[test]
fn test_serialization_with_options() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
for i in 0..100 {
let triple = StarTriple::new(
StarTerm::iri(&format!("http://example.org/s{i}")).unwrap(),
StarTerm::iri("http://example.org/p").unwrap(),
StarTerm::literal(&format!("test{i}")).unwrap(),
);
graph.insert(triple).unwrap();
}
let options = SerializationOptions {
streaming: true,
batch_size: 25,
buffer_size: 1024,
..Default::default()
};
let output = Box::leak(Box::new(Vec::new()));
let output_ptr = output as *const Vec<u8>;
serializer
.serialize_with_options(&graph, output, StarFormat::NTriplesStar, &options)
.unwrap();
let output_data = unsafe { &*output_ptr };
let output_str = String::from_utf8(output_data.clone()).unwrap();
let lines: Vec<&str> = output_str
.lines()
.filter(|l| !l.trim().is_empty())
.collect();
assert_eq!(lines.len(), 100);
}
#[test]
fn test_optimized_serialization() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
for i in 0..50 {
let inner = StarTriple::new(
StarTerm::iri(&format!("http://example.org/alice{i}")).unwrap(),
StarTerm::iri("http://example.org/says").unwrap(),
StarTerm::literal(&format!("statement{i}")).unwrap(),
);
let outer = StarTriple::new(
StarTerm::quoted_triple(inner),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
graph.insert(outer).unwrap();
}
let output = Box::leak(Box::new(Vec::new()));
let output_ptr = output as *const Vec<u8>;
serializer
.serialize_optimized(&graph, output, StarFormat::NTriplesStar)
.unwrap();
let output_data = unsafe { &*output_ptr };
let output_str = String::from_utf8(output_data.clone()).unwrap();
assert!(output_str.contains("<<"));
assert!(output_str.contains(">>"));
let lines: Vec<&str> = output_str
.lines()
.filter(|l| !l.trim().is_empty())
.collect();
assert_eq!(lines.len(), 50);
}
#[test]
fn test_memory_usage_estimation() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
for i in 0..100 {
let triple = StarTriple::new(
StarTerm::iri(&format!("http://example.org/s{i}")).unwrap(),
StarTerm::iri("http://example.org/p").unwrap(),
StarTerm::literal(&format!("value{i}")).unwrap(),
);
graph.insert(triple).unwrap();
}
let options = SerializationOptions::default();
let memory_estimate =
serializer.estimate_memory_usage(&graph, StarFormat::NTriplesStar, &options);
assert!(memory_estimate > 1000);
assert!(memory_estimate < 10_000_000);
let streaming_options = SerializationOptions {
streaming: true,
..Default::default()
};
let streaming_estimate =
serializer.estimate_memory_usage(&graph, StarFormat::NTriplesStar, &streaming_options);
assert!(streaming_estimate > 0);
assert!(streaming_estimate < 10_000_000);
}
#[test]
fn test_chunked_iterator() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let chunked = ChunkedIterator::new(data.into_iter(), 3);
let chunks: Vec<_> = chunked.collect();
assert_eq!(chunks.len(), 4);
assert_eq!(chunks[0], vec![1, 2, 3]);
assert_eq!(chunks[1], vec![4, 5, 6]);
assert_eq!(chunks[2], vec![7, 8, 9]);
assert_eq!(chunks[3], vec![10]);
}
#[test]
fn test_chunked_iterator_exact_chunks() {
let data = vec![1, 2, 3, 4, 5, 6];
let chunked = ChunkedIterator::new(data.into_iter(), 2);
let chunks: Vec<_> = chunked.collect();
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0], vec![1, 2]);
assert_eq!(chunks[1], vec![3, 4]);
assert_eq!(chunks[2], vec![5, 6]);
}
#[test]
fn test_chunked_iterator_single_item() {
let data = vec![42];
let chunked = ChunkedIterator::new(data.into_iter(), 10);
let chunks: Vec<_> = chunked.collect();
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], vec![42]);
}
#[test]
fn test_chunked_iterator_empty() {
let data: Vec<i32> = vec![];
let chunked = ChunkedIterator::new(data.into_iter(), 5);
let chunks: Vec<_> = chunked.collect();
assert_eq!(chunks.len(), 0);
}
#[test]
fn test_chunked_iterator_size_hint() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let chunked = ChunkedIterator::new(data.into_iter(), 3);
let (lower, upper) = chunked.size_hint();
assert_eq!(lower, 4); assert_eq!(upper, Some(4));
}
#[test]
#[should_panic(expected = "chunk_size must be greater than 0")]
fn test_chunked_iterator_zero_chunk_size() {
let data = vec![1, 2, 3];
let _chunked = ChunkedIterator::new(data.into_iter(), 0);
}
#[test]
fn test_compression_type_selection() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let triple = StarTriple::new(
StarTerm::iri("http://example.org/s").unwrap(),
StarTerm::iri("http://example.org/p").unwrap(),
StarTerm::literal("test").unwrap(),
);
graph.insert(triple).unwrap();
for compression in [
CompressionType::None,
CompressionType::Gzip,
CompressionType::Zstd,
CompressionType::Lz4,
] {
let options = SerializationOptions {
compression,
..Default::default()
};
let output = Box::leak(Box::new(Vec::new()));
let result = serializer.serialize_with_options(
&graph,
output,
StarFormat::NTriplesStar,
&options,
);
assert!(result.is_ok());
}
}
#[test]
fn test_serialization_roundtrip() {
let parser = StarParser::new();
let serializer = StarSerializer::new();
let mut original_graph = StarGraph::new();
let simple = StarTriple::new(
StarTerm::iri("http://example.org/s1").unwrap(),
StarTerm::iri("http://example.org/p1").unwrap(),
StarTerm::literal("test").unwrap(),
);
original_graph.insert(simple).unwrap();
let inner = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/says").unwrap(),
StarTerm::literal("hello").unwrap(),
);
let quoted = StarTriple::new(
StarTerm::quoted_triple(inner),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
original_graph.insert(quoted).unwrap();
for format in [
StarFormat::TurtleStar,
StarFormat::NTriplesStar,
StarFormat::TrigStar,
StarFormat::NQuadsStar,
] {
let serialized = serializer
.serialize_to_string(&original_graph, format)
.unwrap();
let parsed_graph = parser.parse_str(&serialized, format).unwrap();
assert_eq!(
original_graph.total_len(),
parsed_graph.total_len(),
"Roundtrip failed for format {format:?}"
);
}
}
#[test]
fn test_quad_validation() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let valid_quad = StarQuad::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/knows").unwrap(),
StarTerm::iri("http://example.org/bob").unwrap(),
Some(StarTerm::iri("http://example.org/graph1").unwrap()),
);
graph.insert_quad(valid_quad).unwrap();
assert!(serializer
.validate_for_format(&graph, StarFormat::NQuadsStar)
.is_ok());
assert!(serializer
.validate_for_format(&graph, StarFormat::TrigStar)
.is_ok());
let invalid_inner = StarTriple::new(
StarTerm::literal("invalid_subject").unwrap(), StarTerm::iri("http://example.org/predicate").unwrap(),
StarTerm::literal("object").unwrap(),
);
let result = serializer.validate_quoted_triple_structure(&invalid_inner);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Literals cannot be subjects"));
let invalid_predicate_triple = StarTriple::new(
StarTerm::iri("http://example.org/subject").unwrap(),
StarTerm::literal("invalid_predicate").unwrap(), StarTerm::literal("object").unwrap(),
);
let result = serializer.validate_quoted_triple_structure(&invalid_predicate_triple);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Predicates must be IRIs"));
let invalid_trig_triple = StarTriple::new(
StarTerm::variable("invalid_subject").unwrap(), StarTerm::iri("http://example.org/predicate").unwrap(),
StarTerm::literal("object").unwrap(),
);
let result = serializer.validate_triple_for_trig(&invalid_trig_triple);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Variables cannot be serialized"));
}
#[test]
fn test_quoted_triple_validation() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let inner_triple = StarTriple::new(
StarTerm::iri("http://example.org/alice").unwrap(),
StarTerm::iri("http://example.org/says").unwrap(),
StarTerm::literal("hello").unwrap(),
);
let quoted_triple = StarTriple::new(
StarTerm::quoted_triple(inner_triple),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
graph.insert(quoted_triple).unwrap();
assert!(serializer
.validate_for_format(&graph, StarFormat::TurtleStar)
.is_ok());
assert!(serializer
.validate_for_format(&graph, StarFormat::NTriplesStar)
.is_ok());
assert!(serializer
.validate_for_format(&graph, StarFormat::TrigStar)
.is_ok());
assert!(serializer
.validate_for_format(&graph, StarFormat::NQuadsStar)
.is_ok());
let mut invalid_graph = StarGraph::new();
let invalid_inner = StarTriple::new(
StarTerm::literal("invalid_subject").unwrap(), StarTerm::iri("http://example.org/predicate").unwrap(),
StarTerm::literal("object").unwrap(),
);
let invalid_quoted = StarTriple::new(
StarTerm::quoted_triple(invalid_inner),
StarTerm::iri("http://example.org/certainty").unwrap(),
StarTerm::literal("0.9").unwrap(),
);
invalid_graph.insert(invalid_quoted).unwrap();
let result = serializer.validate_for_format(&invalid_graph, StarFormat::NQuadsStar);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Literals cannot be subjects"));
}
#[test]
fn test_trig_star_graph_name_validation() {
let serializer = StarSerializer::new();
let mut graph = StarGraph::new();
let valid_quad = StarQuad::new(
StarTerm::iri("http://example.org/subject").unwrap(),
StarTerm::iri("http://example.org/predicate").unwrap(),
StarTerm::iri("http://example.org/object").unwrap(),
Some(StarTerm::iri("http://example.org/valid_graph").unwrap()),
);
graph.insert_quad(valid_quad).unwrap();
assert!(serializer
.validate_for_format(&graph, StarFormat::TrigStar)
.is_ok());
let mut graph_with_bnode = StarGraph::new();
let bnode_quad = StarQuad::new(
StarTerm::iri("http://example.org/subject").unwrap(),
StarTerm::iri("http://example.org/predicate").unwrap(),
StarTerm::iri("http://example.org/object").unwrap(),
Some(StarTerm::blank_node("graph1").unwrap()),
);
graph_with_bnode.insert_quad(bnode_quad).unwrap();
assert!(serializer
.validate_for_format(&graph_with_bnode, StarFormat::TrigStar)
.is_ok());
}
}