rudof_lib

rudof_lib is the centralized, programmatic facade for the entire Rudof ecosystem. It exposes a stable entry point (Rudof) that hides the complexity of dealing with numerous specialized underlying subcrates (shacl_validation, shex_validation, rudof_rdf, pgschema, etc.).

At a high level, it provides robust, builder-style interfaces for:

  • Loading and serializing RDF and PG data.
  • Loading, checking, serializing and validating ShEx schemas.
  • Loading, serializing and validating SHACL shapes.
  • Loading, serializing and validating PGSchemas.
  • Loading, running and serializing SPARQL queries and query results.
  • Converting and comparing schemas between supported formats.
  • Loading and serializing DCTAP and Service Descriptions.
  • Generating synthetic data from schemas.

Primary goals:

  • Provide a stable, ergonomic entry point (Rudof) that acts as a facade over specialized crates.
  • Expose operations via builder-style (fluent) interfaces.
  • Keep a clear separation between the public API, concrete implementations, and shared types/formats/errors.

Architecture and Structure

The package structure is organized to make the public API small and stable while keeping implementations modular and testable. Below is a more detailed description of the main files and folders, what to expect inside them and the conventions used by the crate.

  • src/lib.rs: Core entry, exposing the essential (Rudof, RudofConfig, errors and formats).
  • src/rudof.rs: Houses the central Rudof state container. Contains methods grouped by domain returning configured builder instances.
  • src/config/rudof.rs: Houses RudofConfig for configuration handling, layered discovery, and cross-section resolution.
  • src/api/: The functional brain of the library. Divided by domain (data, shex, shacl, etc.). Each domain contains three subparts:
    • builders/: The public builder types (*Builder) that callers instantiate via Rudof methods. Builders configure the operation and finally call an implementation function when executed.
    • implementations/: Concrete operation code that performs the logic using Rudof and the configured inputs.
    • trait definition (in the domain module): describe the operations offered.
  • src/formats/: Enums for input/serialization formats (for example SchemaFormat, InputSpec, or ConversionFormat).
  • src/types/: Shared domain types (for example Data, QueryResult, shex_statistics, and other cross-cutting structs).
  • src/errors/: Domain-specific error types and the unified RudofError. Errors are defined per-domain and composed into a top-level error to provide predictable error handling for callers.
  • src/utils/: Internal utility helpers.

Operations on the Rudof Struct

Every functionality in the library operates by initially calling a method on the Rudof instance. This creates a specific operation builder. Modifiers chain upon this builder, culminating in an .execute() call.

Below is an exhaustive breakdown grouped by domain:

1. Core

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};

// Initialize Rudof with default config
let mut rudof = Rudof::new(RudofConfig::default());

// Get the current version
let version = rudof.version().execute();
println!("Rudof version: {}", version);

// Update the configuration
rudof.update_config(RudofConfig::default());

// Reset all internal state
rudof.reset_all().execute();
}

2. Data

2.1 RDF Data

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{
    InputSpec, DataFormat, DataReaderMode, ResultDataFormat, NodeInspectionMode
    };
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let rdf_data_input = vec![InputSpec::from_str(
    r#"
        prefix ex: <http://example.org/> . 
        ex:alice a ex:Person ;
            ex:age 30 .
    "#
).unwrap()];

// Load RDF data into Rudof's state
rudof.load_data()
    .with_data(&data_input)
    .with_data_format(&DataFormat::Turtle)
    .with_reader_mode(&DataReaderMode::Lax)
    .with_merge(false)
    .execute()
    .unwrap();

// Serialize the loaded data to standard output
rudof.serialize_data(&mut std::io::stdout())
    .with_result_data_format(&ResultDataFormat.NTriples)
    .execute()
    .unwrap();

// Serialize the information of the node "ex:alice" to standard output
let node = "ex:alice";
let predicates = vec!["ex:age".to_string()];
rudof.show_node_info(&node, &mut std::io::stdout())
    .with_show_node_mode(&NodeInspectionMode::Outgoing)
    .with_predicates(&predicates)
    .with_depth(1)
    .with_show_colors(false)
    .execute()
    .unwrap();

// Reset data state
rudof.reset_data().execute()
}

2.2 Node Neighborhood

show_node_info renders the neighborhood of a node as an ASCII tree. When the traversal itself is what you need (instead of its textual rendering), node_neighborhood exposes the very same traversal as a lazy iterator of NeighborArc values, so callers get the structure without having to parse box-drawing characters.

The builder accepts the same options as show_node_info (predicates, inspection mode, depth and IRI normalization), and the resulting NodeNeighborhood yields one Result<NeighborArc> per visited arc, in depth-first order, for every node matched by the node selector. Each NeighborArc carries:

  • root: the matched node the traversal started from.
  • direction: ArcDirection::Outgoing (the node is the subject) or ArcDirection::Incoming (the node is the object).
  • depth: distance from root, starting at 1.
  • node, predicate, neighbor: the arc itself.
  • is_last: whether this is the last arc among the siblings of node, which is what the tree renderer uses to draw the branch corners.

Because the iterator borrows the loaded graph, the Rudof instance cannot be mutated while it is alive.

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{InputSpec, IriNormalizationMode, NodeInspectionMode};
use rudof_lib::types::{ArcDirection, NeighborArc};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let rdf_data_input = vec![InputSpec::from_str(
    r#"
        prefix ex: <http://example.org/>
        ex:alice ex:knows ex:bob ;
            ex:age 30 .
        ex:bob   ex:knows ex:carol .
        ex:dave  ex:knows ex:alice .
    "#
).unwrap()];

// Load RDF data into Rudof's state
rudof.load_data().with_data(&rdf_data_input).execute().unwrap();

// Traverse the neighborhood of "ex:alice" instead of rendering it
let predicates = vec!["ex:knows".to_string()];
let neighborhood = rudof.node_neighborhood("ex:alice")
    .with_mode(&NodeInspectionMode::Both)
    .with_predicates(&predicates)
    .with_depth(2)
    .with_iri_mode(IriNormalizationMode::Lax)
    .execute()
    .unwrap();

for arc in neighborhood {
    let NeighborArc { depth, direction, node, predicate, neighbor, .. } = arc.unwrap();
    let arrow = match direction {
        ArcDirection::Outgoing => "-->",
        ArcDirection::Incoming => "<--",
    };
    println!("{:indent$}{node} {arrow} {predicate} {arrow} {neighbor}", "", indent = (depth - 1) * 2);
}

// Prints:
// http://example.org/alice --> http://example.org/knows --> http://example.org/bob
//   http://example.org/bob --> http://example.org/knows --> http://example.org/carol
// http://example.org/alice <-- http://example.org/knows <-- http://example.org/dave
}

2.3 Triples

add_triple, remove_triple and triples work on the loaded graph one triple at a time, which is what building a graph programmatically (or applying the triples a validation report says are missing) takes, without serializing the graph, editing the text and reading it back.

Terms are given in the textual form the rest of the API accepts, so callers do not build RDF terms by hand: <http://example.org/alice>, a prefixed name (ex:alice), a blank node label (_:b1) or a literal ("Alice", "Alice"@en, 23, "23"^^xsd:integer). Prefixed names are resolved against the prefixes of the loaded data, filled in with the session’s default prefixes for the aliases the data does not declare itself; an alias neither of them declares is an error rather than an unresolved term.

triples takes a pattern: every position left unset matches any term, so a builder with none of them set walks the whole graph. Like node_neighborhood it borrows the loaded data, which therefore cannot be modified while the triples are being walked, and yields one Result<Triple> per match.

Only the in-memory graph can be modified. A SPARQL endpoint and the QLever backend are read-only, and so is data federated with SPARQL endpoints: the local graph of such a session is deliberately not written to instead, since triples reads it and the endpoints’ as one. triples itself works on all of them, but an unconstrained pattern against a remote backend pulls its whole graph.

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::InputSpec;
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let rdf_data_input = vec![InputSpec::from_str(
    r#"
        prefix ex: <http://example.org/>
        ex:alice ex:name "Alice" .
    "#
).unwrap()];

rudof.load_data().with_data(&rdf_data_input).execute().unwrap();

// Change the graph in place
rudof.add_triple("ex:alice", "ex:knows", "ex:bob").execute().unwrap();
rudof.add_triple("ex:bob", "ex:name", "\"Bob\"").execute().unwrap();
rudof.remove_triple("ex:alice", "ex:name", "\"Alice\"").execute().unwrap();

// Walk the triples matching a pattern (here: everything ex:bob is the subject of)
for triple in rudof.triples().with_subject("ex:bob").execute().unwrap() {
    println!("{}", triple.unwrap());
}

// Prints:
// http://example.org/bob <http://example.org/name> "Bob" .
}

2.4 Property Graph Data

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{InputSpec, DataFormat, DataReaderMode, ResultDataFormat};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let pg_data_input = vec![InputSpec::from_str(
    r#"
        (alice {Person} [ name: "Alice", age: 23, aliases: "Ally" ])
        (bob   {Person} [ name: "Robert", aliases: ["Bob", "Bobby"] ])
    "#,
).unwrap()];

// Load Property Graph data into Rudof's state (without merging)
rudof.load_data()
    .with_data(&pg_data_input)
    .with_data_format(&DataFormat::Pg)
    .execute()
    .unwrap();

// Serialize the loaded data to standard output
rudof.serialize_data(&mut std::io::stdout()).execute().unwrap();
}

2.5 SPARQL Endpoint

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};

let mut rudof = Rudof::new(RudofConfig::default());

// Lists the registered SPARQL endpoints
let endpoints = rudof.list_endpoints().execute();
for (name, url) in endpoints {
    println!("{}, {}", name, url);
}

// Focus SPARQL endpoint
let endpoint = "http://example.org/sparql";
rudof.load_data()
    .with_endpoint(&endpoint)
    .execute()
    .unwrap();
}

2.6 Service Description

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{
    InputSpec, DataFormat, DataReaderMode, ResultServiceFormat
};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let service_description_input = InputSpec::from_str(
    r#"
        prefix sd: <http://www.w3.org/ns/sparql-service-description#> .
        prefix ent: <http://www.w3.org/ns/entailment/> .
        
        <http://example.org/sparql> a sd:Service ;
            sd:endpoint <http://example.org/sparql> ;
            sd:supportedLanguage sd:SPARQL11Query ;
            sd:defaultEntailmentRegime ent:Simple .
    "#;
    ).unwrap();

// Load a Service Description into Rudof's state
let base = "http://example.org/";
rudof.load_service_description(&service_description)
    .with_data_format(&DataFormat::Turtle)
    .with_reader_mode(&DataReaderMode::Strict)
    .with_base(&base)
    .execute()
    .unwrap();

// Serialize the loaded service description to standard output
rudof.serialize_service_description(&mut std::io::stdout())
    .with_result_service_format(&ResultServiceFormat::Json)
    .execute()
    .unwrap();
}

3. ShEx

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{
    InputSpec, ShExFormat, ShapeMapFormat, ShExValidationSortByMode,

};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let rdf_data_input = vec![InputSpec::from_str(
    r#"
        <alice> <name> "Alice" ;
            <age> 30 .
    "#
    ).unwrap()];
let base_nodes = "http://example.org";

// Load RDF data into Rudof's state
rudof.load_data()
    .with_data(&rdf_data_input)
    .with_base(&base_nodes)
    .execute()
    .unwrap();

let shex_schema_input = InputSpec::from_str(
    r#"
        PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
        <PersonShape> {
            <name> xsd:string ;
            <age> xsd:integer
        }
    "#,
    ).unwrap();
let base_shapes = "http://example.org";

// Check ShEx schema well-formedness and check for negative cycles in the dependency graph,
// serializing the checking results to standard output
rudof.check_shex_schema(&shex_schema_input, &mut std::io::stdout())
    .with_shex_schema_format(&ShExFormat::ShExC)
    .with_base(&base_shapes)
    .execute()
    .unwrap();

// Load ShEx schema into Rudof's state
rudof.load_shex_schema(&shex_schema_input)
    .with_shex_schema_format(&ShExFormat::ShExC)
    .with_base(&base_shapes)
    .execute()
    .unwrap();

// Serialize the loaded ShEx schema to standard output
rudof.serialize_shex_schema(&mut std::io::stdout())
    .with_shape(&"http://example.org/PersonShape")
    .with_show_statistics(true)
    .with_show_dependencies(true)
    .with_show_time(true)
    .with_result_shex_format(&ShExFormat::ShExJ)
    .execute()
    .unwrap();

let shapemap_input = InputSpec::from_str(
    r#"
        <alice>@<PersonShape>
    "#
    ).unwrap();

// Load shapemap into Rudof's state
rudof.load_shex_schema(&shapemap_input)
    .with_shapemap_format(&ShapeMapFormat::Compact)
    .with_base_nodes(&base_nodes)
    .with_base_shapes(&base_shapes)
    .execute()
    .unwrap();

// Serialize the loaded shapemap to standard output
rudof.serialize_shex_schema(&mut std::io::stdout())
    .with_result_shapemap_format(&ShapeMapFormat::Compact)
    .with_show_colors(false)
    .execute()
    .unwrap();

// Run validation storing the result into Rudof´s state
rudof.validate_shex().execute().unwrap();

// Serialize the validation results to standard output
rudof.serialize_shex_validation_results(&mut std::io::stdout())
    .with_shex_validation_sort_order_mode(&ShExValidationSortByMode::Node)
    .with_result_shex_validation_format(&ResultShExValidationFormat::Details)
    .execute()
    .unwrap();

// Reset ShEx schema state
rudof.reset_shex_schema().execute();
// Reset shapemap state
rudof.reset_shapemap().execute();
// Reset ShEx validation state (ShEx schema, shapemap and validation results)
rudof.reset_shex().execute();
}

4. SHACL

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{
    InputSpec, DataFormat, ShaclFormat, ShaclValidationSortByMode
};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let data_with_shapes_input = InputSpec::from_str(r#"
        prefix ex: <http://example.org/> .
        prefix sh: <http://www.w3.org/ns/shacl#> .
        prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

        SHACL Shape
        ex:PersonShape
            a sh:NodeShape ;
            sh:targetClass ex:Person ;
            sh:property [
                sh:path ex:name ;
                sh:datatype xsd:string ;
                sh:minCount 1 ;
            ] .

        Instance data
        ex:Alice
            a ex:Person ;
            ex:name "Alice Smith" ;
            ex:age 30 .

        ex:Bob
            a ex:Person ;
            ex:name "Bob Jones" ;
            ex:age 25 .
    "#).unwrap();

// Load RDF data containing SHACL shapes
rudof.load_data()
    .with_data(&[data_with_shapes_input])
    .with_data_format(&DataFormat::Turtle)
    .execute()
    .unwrap();

// Extract SHACL shapes from the loaded data and load them into Rudof's state (you can also separate data and shapes into two fields and use ".with_shacl_schema()")
rudof.load_shacl_shapes().execute().unwrap();

// Serialize the loaded shapes to standard output
rudof.serialize_shacl_shapes(&mut std::io::stdout())
    .with_shacl_result_format(&ShaclFormat::NTriples)
    .execute()
    .unwrap();

// Run validation storing the result into Rudof´s state
rudof.validate_shacl().execute().unwrap();

// Serialize the validation results to standard output
rudof.serialize_shacl_validation_results(&mut std::io::stdout())
    .with_shacl_validation_sort_order_mode(&ShaclValidationSortByMode::Node)
    .with_result_shacl_validation_format(&ResultShaclValidationFormat::Details)
    .execute()
    .unwrap();

// Reset SHACL shapes state
rudof.reset_shacl_shapes().execute();
// Reset SHACL validation state (SHACL schema and validation results)
rudof.reset_shacl().execute();
}

5. Property Graph Schema

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{InputSpec, DataFormat, PgSchemaFormat, ResultPgSchemaValidationFormat};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let pg_data_input = vec![InputSpec::from_str(
    r#"
        (n1 {"Student"}["name": "Alice", "age": 23])
        (n2_wrong {"Student"}["name": "Bob", "age": 12])
        (n3_wrong {"Student"}["name": "Carol", "age": "unknown"])
    "#,
).unwrap()];

// Load Property Graph data into Rudof's state
rudof.load_data()
    .with_data(&pg_data_input)
    .with_data_format(&DataFormat::Pg)
    .execute()
    .unwrap();

let pg_schema_input = InputSpec::from_str(
    r#"
        CREATE NODE TYPE ( AdultStudentType: Student {
            name: STRING ,
            age: INTEGER CHECK > 18
        })
    "#).unwrap();

// Load Property Graph Schema into Rudof's state
rudof.load_pgschema(&pg_schema_input)
    .with_pg_schema_format(&PgSchemaFormat::PgSchemaC)
    .execute()
    .unwrap();

// Serialize the loaded Property Graph Schema to standard output
rudof.serialize_pgschema(&mut std::io::stdout())
    .with_result_pg_schema_format(&PgSchemaFormat::PgSchemaC)
    .execute()
    .unwrap();

let typemap_input = InputSpec::from_str(
    r#"
        n1: AdultStudentType,
        n2_wrong: AdultStudentType,
        n3_wrong: AdultStudentType
    "#,).unwrap();

// Load typemap into Rudof's state
rudof.load_typemap(&typemap_input).execute().unwrap();

// Run validation storing the result into Rudof´s state
rudof.validate_pgschema().execute().unwrap();

// Serialize the validation results to standard output
rudof.serialize_pgschema_validation_results(&mut std::io::stdout())
    .with_result_pg_schema_validation_format(&ResultPgSchemaValidationFormat::Details)
    .with_show_colors(false)
    .execute()
    .unwrap()

// Reset Property Graph schema shapes state
rudof.reset_pgschema().execute()
// Reset typemap state
rudof.reset_typemap().execute()
// Reset Property Graph schema validation state (PG schema, typemap and validation results)
rudof.reset_pgschema_validation().execute()
}

6. Query

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{
    InputSpec, DataFormat, ResultQueryFormat, QueryType
};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let data_input = InputSpec::from_str(r#"
        prefix : <http://example.org/>
        prefix schema: <http://schema.org/>

        :a schema:name  "Alice" ;
        :status      :Active ;
        schema:knows :a, :b  .

        :b schema:name  "Bob"    ;
        :status      :Waiting ;
        schema:knows :c       .

        :c schema:name  "Carol"  .

        :d schema:name  23      .  # Should fail

        :e schema:name  "Emily" ;  # Should fail
        schema:knows :d      .
    "#).unwrap();

// Load RDF data
rudof.load_data()
    .with_data(&[data_with_shapes_input])
    .with_data_format(&DataFormat::Turtle)
    .execute()
    .unwrap();

let query_input = InputSpec::from_str(
        r#"
            prefix : <http://example.org/>
            prefix schema: <http://schema.org/>

            select ?person ?name ?status where {
            ?person schema:name ?name ;
                    :status ?status .
            }
        "#,
    )
    .unwrap();

// Load query into Rudof's state (this is what the `sparql` command uses)
rudof.load_sparql_query(&query_input)
    .with_query_type(&QueryType::Select)
    .execute()
    .unwrap(); 

// Serialize the query to standard output (this is what the `sparql` command uses)
rudof.serialize_sparql_query(&mut std::io::stdout()).execute().unwrap();

// Run the query (this is what the `query` command uses)
rudof.run_query().execute();

// Serialize the query results to standard output
rudof.serialize_query_results(&mut std::io::stdout())
    .with_result_query_format(&ResultQueryFormat::Csv)
    .execute()
    .unwrap();

// Reset query state (just the loaded query)
rudof.reset_sparql_query().execute()
// Reset query execution state (query and query results)
rudof.reset_query_results().execute()

}

7. DCTAP

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{InputSpec, DCTapFormat, ResultDCTapFormat};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let dctap_input = InputSpec::from_str(
    r#"
        shapeID,propertyID,mandatory,repeatable,valueDataType
        :Person,rdf:type,true,false,
        :Person,schema:name,true,false,xsd:string
        :Person,schema:age,false,false,xsd:integer
        :Person,schema:memberOf,false,true,:Organization
        :Organization,rdf:type,true,false,
        :Organization,schema:name,true,false,xsd:string
        :Organization,schema:location,false,false,xsd:string
    "#
).unwrap();

// Load a DCTAP into Rudof's state
rudof.load_dctap(&dctap_input)
    .with_dctap_format(&DCTapFormat::Csv)
    .execute()
    .unwrap();

// Serialize the loaded DCTAP to standard output
rudof.serialize_dctap(&mut std::io::stdout())
    .with_result_dctap_format(&ResultDCTapFormat::Json)
    .execute()
    .unwrap();

// Reset DCTAP state
rudof.reset_dctap().execute();
}

8. RDF_Config (DBCLS)

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{InputSpec, RdfConfigFormat, ResultRdfConfigFormat};
use std::str::FromStr;

let mut rudof = Rudof::new(RudofConfig::default());
let config_input = InputSpec::from_str(
    r#"
        - Person ex:person1 ex:person2:
        - a: ex:Person
        - rdfs:label:
            - name: "Alice"
        - ex:age?:
            - age_value: 32
        - ex:memberOf:
            - organization: Organization
        - Organization ex:org1:
        - a: ex:Organization
        - rdfs:label:
            - org_name: "Example Org"
        - ex:location:
            - city: "Oviedo" 
    "#).unwrap();

// Load RDF config
rudof.load_rdf_config(&config_input)
    .with_rdf_config_format(&RdfConfigFormat::Yaml)
    .execute()
    .unwrap();

// Serialize the loaded RDF config to standard output
rudof.serialize_rdf_config(&mut std::io::stdout())
    .with_rdf_config_format(&ResultRdfConfigFormat::Yaml)
    .execute()
    .unwrap();

// Reset RDF config state
rudof.reset_rdf_config().execute();
}

9. Generation

#![allow(unused)]
fn main() {
use rudof_lib::{Rudof, RudofConfig};
use rudof_lib::formats::{DataFormat, InputSpec, GenerationSchemaFormat};
use std::str::FromStr;

let rudof = Rudof::new(RudofConfig::default());
let schema_input = InputSpec::from_str(
    r#"
        prefix : <http://example.org/>
        :User { :name . }
    "#).unwrap();

// Generate synthetic data asynchronously
// Note: Requires a Tokio runtime or similar async environment
rudof.generate_data(&schema_input, &GenerationSchemaFormat::ShEx, 10)
    .with_output(&mut std::io::stdout())
    .with_parallel(4)
    .with_result_generation_format(&DataFormat::Turtle)
    .with_seed(42)
    .execute()
    .await
    .unwrap();
}