oxirs_shacl/lib.rs
1//! # OxiRS SHACL - RDF Validation Engine
2//!
3//! [](https://github.com/cool-japan/oxirs/releases)
4//! [](https://docs.rs/oxirs-shacl)
5//!
6//! **Status**: Production Release (v0.4.1)
7//! **Stability**: Public APIs are stable. Production-ready with comprehensive testing.
8//!
9//! SHACL (Shapes Constraint Language) validation engine for RDF data.
10//! Provides comprehensive constraint validation with SHACL Core and SHACL-SPARQL support.
11//!
12//! ## Features
13//!
14//! - **SHACL Core** - Complete SHACL Core constraint validation (27/27 W3C constraint types)
15//! - **SHACL-SPARQL** - SPARQL-based constraints (experimental)
16//! - **SHACL-AF** - Advanced Features (SPARQL targets, ASK validators, target types)
17//! - **Property Paths** - Full property path evaluation including inverse, sequence, alternative,
18//! zero-or-more, one-or-more, zero-or-one operators
19//! - **Logical Constraints** - `sh:and`, `sh:or`, `sh:not`, `sh:xone`
20//! - **Validation Reports** - W3C-compliant violation reports with metadata
21//! - **Performance** - Optimized validation engine with caching, parallelism, and incremental modes
22//!
23//! ## Companion Documentation
24//!
25//! In addition to this rustdoc reference, two companion documents live at the crate root:
26//!
27//! - `COOKBOOK.md` — task-oriented patterns (cardinality, string, datatype, qualified value,
28//! target chains, SHACL-AF SPARQL constraints) with Turtle examples and Rust API snippets.
29//! - `SPEC_MAPPING.md` — exhaustive table mapping every SHACL Core / SHACL-AF construct to the
30//! Rust symbol that implements it.
31//!
32//! ## See Also
33//!
34//! - [`oxirs-core`](https://docs.rs/oxirs-core) - RDF data model
35//! - [`oxirs-arq`](https://docs.rs/oxirs-arq) - SPARQL query engine
36//!
37//! ## Basic Usage
38//!
39//! ```rust
40//! use oxirs_shacl::{ValidationConfig, ValidationStrategy};
41//! use oxirs_core::{Store, model::*};
42//!
43//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
44//! // Create a validation configuration
45//! let config = ValidationConfig::default()
46//! .with_strategy(ValidationStrategy::Optimized)
47//! .with_inference_enabled(true);
48//!
49//! // Validation is typically performed using ValidationEngine
50//! // See examples/ directory for complete working examples
51//!
52//! # Ok(())
53//! # }
54//! ```
55//!
56//! ## Crate Layout
57//!
58//! - [`constraints`] — every SHACL Core constraint component
59//! ([`MinCountConstraint`](constraints::cardinality_constraints::MinCountConstraint),
60//! [`PatternConstraint`](constraints::string_constraints::PatternConstraint),
61//! [`NodeKindConstraint`](constraints::value_constraints::NodeKindConstraint), and so on).
62//! - [`paths`] — property path evaluation and traversal.
63//! - [`shapes`] — shape parsing, factories, and shape-level validation.
64//! - [`targets`] — target selectors (`sh:targetClass`, `sh:targetSubjectsOf`, …).
65//! - [`validation`] — the validation engine and reporting pipeline.
66//! - [`sparql_af`] — SHACL Advanced Features built on SPARQL.
67//! - [`report`] — W3C validation report generation in multiple formats.
68//! - [`optimization`] — query rewriting and execution-plan optimisation.
69//! - [`cache`] — validation result and parallel-validation caches.
70//!
71//! ## Advanced Features
72//!
73//! For detailed examples and advanced usage patterns, including:
74//! - Custom constraint components
75//! - Parallel and incremental validation
76//! - Enterprise security and compliance features
77//! - Federated validation
78//! - Production deployment patterns
79//!
80//! Please refer to the individual module documentation and the examples
81//! directory in the repository.
82
83use std::collections::HashMap;
84use std::fmt;
85use std::hash::Hash;
86
87use indexmap::IndexMap;
88use once_cell::sync::Lazy;
89use serde::{Deserialize, Serialize};
90use uuid::Uuid;
91
92use oxirs_core::OxirsError;
93
94pub use crate::optimization::integration::ValidationStrategy;
95
96pub mod advanced_features;
97pub mod analytics;
98pub mod builders;
99pub mod cache;
100pub mod constraints;
101pub mod custom_components;
102pub mod designer;
103pub mod federated_validation;
104pub mod incremental;
105pub mod integration;
106pub mod iri_resolver;
107#[cfg(feature = "lsp")]
108pub mod lsp;
109pub mod node_expr_builtins;
110pub mod node_expr_evaluator;
111#[cfg(test)]
112mod node_expr_tests;
113pub mod node_expr_types;
114pub mod node_expressions;
115pub mod optimization;
116pub mod paths;
117pub mod report;
118pub mod schema_import;
119pub mod scirs_graph_integration;
120pub mod security;
121pub mod shaclc_parser;
122pub mod shape_import;
123pub mod shape_inheritance;
124pub mod shape_map;
125pub mod shape_versioning;
126pub mod shapes;
127pub mod sparql;
128pub mod sparql_af;
129pub mod targets;
130pub mod templates;
131pub mod testing;
132pub mod validation;
133pub mod visual_editor;
134pub mod vocabulary;
135pub mod w3c_test_suite;
136pub mod w3c_test_suite_enhanced;
137
138// Re-export key types for convenience - avoiding ambiguous glob re-exports
139pub use advanced_features::{
140 AdvancedTarget, AdvancedTargetSelector, ConditionalConstraint, ConditionalEvaluator,
141 ConditionalResult, FunctionInvocation, FunctionParameter, FunctionRegistry, FunctionResult,
142 InferenceStrategy, InferredShape, ParameterType, ReturnType, RuleEngine, RuleEngineStats,
143 RuleExecutionResult, ShaclFunction, ShaclRule, ShapeInferenceConfig, ShapeInferenceEngine,
144 ShapeRegistry,
145};
146pub use analytics::ValidationAnalytics;
147pub use builders::*;
148pub use cache::{
149 CacheStats, CachedValidationResult, ParallelConstraintConfig, ParallelConstraintOutcome,
150 ParallelConstraintValidator, ParallelValidationStats, ParallelValidationSummary, TripleKey,
151 ValidationCache, ValidationCacheKey,
152};
153pub use constraints::{
154 AdvancedSparqlConstraint, AlwaysViolatingEvaluator, Constraint, ConstraintContext,
155 ConstraintEvaluationResult, ExpressionConstraintComponent, ExpressionConstraintResult,
156 ExpressionContext, ExpressionEvaluator, FailingEvaluator, MockSparqlEvaluator,
157 NodeKindConstraint, PathResolver, PropertyConstraint, ShaclExpression, ShaclValue,
158 SparqlConstraintResult, SparqlConstraintSeverity, SparqlEvaluator,
159};
160pub use custom_components::{
161 ComponentMetadata, CustomConstraint, CustomConstraintRegistry, EmailValidationComponent,
162 RangeConstraintComponent, RegexConstraintComponent,
163};
164pub use federated_validation::*;
165pub use incremental::{
166 Changeset, GraphChange, IncrementalConfig, IncrementalStats, IncrementalValidator,
167};
168pub use iri_resolver::*;
169pub use optimization::{
170 NegationOptimizer, OptimizationConfig, OptimizationResult, OptimizationStrategy,
171 ValidationOptimizationEngine,
172};
173pub use paths::*;
174pub use report::{
175 ReportFormat, ReportGenerator, ReportMetadata, ValidationReport, ValidationSummary,
176};
177pub use scirs_graph_integration::{
178 BasicMetrics, ConnectivityAnalysis, GraphValidationConfig, GraphValidationResult,
179 SciRS2GraphValidator,
180};
181pub use security::{SecureSparqlExecutor, SecurityConfig, SecurityPolicy};
182pub use shape_import::*;
183pub use shape_inheritance::*;
184// Import specific types from shapes to avoid conflicts
185pub use shapes::{
186 format_literal_for_sparql, format_term_for_sparql, ShapeCacheStats, ShapeFactory, ShapeParser,
187 ShapeParsingConfig, ShapeParsingContext, ShapeParsingStats, ShapeValidationReport,
188 ShapeValidator, SingleShapeValidationReport,
189};
190pub use sparql::*;
191// Import specific types from targets to avoid conflicts
192pub use targets::{
193 Target, TargetCacheStats, TargetOptimizationConfig, TargetSelectionStats, TargetSelector,
194};
195pub use testing::{
196 ShapeTestSuite, TestAssertions, TestCase, TestExpectation, TestResult, TestStatus,
197 TestSuiteResult, TestSummary,
198};
199pub use validation::{ValidationEngine, ValidationViolation};
200pub use visual_editor::{
201 ColorScheme, ExportFormat, LayoutDirection, ShapeVisualizer, VisualizerConfig,
202};
203pub use w3c_test_suite::*;
204
205// Re-export SHACL-AF SPARQL target types
206pub use sparql_af::{
207 ask_validator::{
208 FailingAskExecutor, MockAskExecutor, SparqlAskExecutor, SparqlAskResult,
209 SparqlAskValidator, SparqlAskValidatorBuilder, SparqlAskViolation,
210 },
211 sparql_target::{
212 ParameterBinding, SparqlAfTarget, SparqlAfTargetResult, SparqlTargetEvaluator,
213 SparqlTargetMock,
214 },
215 target_type::{
216 SparqlTargetParameter, SparqlTargetType, SparqlTargetTypeInstance, SparqlTargetTypeRegistry,
217 },
218 PrefixMap, SubstitutionContext,
219};
220
221// Re-export designer types
222pub use designer::{
223 ConstraintSpec, DesignIssue, DesignStep, DesignWizard, Domain, PropertyDesign, PropertyHint,
224 RecommendationEngine, ShapeDesign, ShapeDesigner,
225 ShapeInferenceEngine as DesignerInferenceEngine,
226};
227
228// Re-export optimization types (note: these are already imported above)
229
230/// SHACL namespace IRI
231pub static SHACL_NS: &str = "http://www.w3.org/ns/shacl#";
232
233/// SHACL vocabulary terms
234pub static SHACL_VOCAB: Lazy<vocabulary::ShaclVocabulary> =
235 Lazy::new(vocabulary::ShaclVocabulary::new);
236
237/// IRI resolver for validation and expansion
238pub use iri_resolver::IriResolver;
239
240/// Core error type for SHACL operations.
241///
242/// Every fallible API in this crate returns [`Result<T>`](crate::Result), which is a
243/// type alias for `std::result::Result<T, ShaclError>`. The variants below cover the full
244/// surface area of the validation pipeline — parsing, constraint evaluation, property
245/// path traversal, SPARQL execution, reporting, and resource limits.
246///
247/// All variants derive [`thiserror::Error`] and provide a human-readable `Display`
248/// implementation suitable for inclusion in user-facing diagnostics. Errors that wrap
249/// foreign types (`oxirs_core::OxirsError`, `regex::Error`, `IriResolutionError`)
250/// implement `From` so the `?` operator works without boilerplate.
251#[derive(Debug, Clone, thiserror::Error)]
252pub enum ShaclError {
253 /// A shape document failed to parse (malformed Turtle/RDF, missing required terms,
254 /// or invalid SHACL syntax).
255 #[error("Shape parsing error: {0}")]
256 ShapeParsing(String),
257
258 /// A constraint definition was rejected during structural validation
259 /// (e.g. invalid `sh:pattern` regex, contradictory cardinalities).
260 #[error("Constraint validation error: {0}")]
261 ConstraintValidation(String),
262
263 /// Target selection failed — typically a malformed `sh:target*` declaration
264 /// or an inaccessible store.
265 #[error("Target selection error: {0}")]
266 TargetSelection(String),
267
268 /// A property path expression could not be parsed.
269 #[error("Property path error: {0}")]
270 PropertyPath(String),
271
272 /// A property path evaluated correctly but produced an unexpected result
273 /// during traversal (cycles, infinite paths beyond the recursion limit).
274 #[error("Path evaluation error: {0}")]
275 PathEvaluationError(String),
276
277 /// A SPARQL query (used by SHACL-SPARQL constraints, SHACL-AF targets,
278 /// or the optimizer) failed to execute.
279 #[error("SPARQL execution error: {0}")]
280 SparqlExecution(String),
281
282 /// The validation engine itself reported an internal error
283 /// (orchestration failure, scheduler error, etc.).
284 #[error("Validation engine error: {0}")]
285 ValidationEngine(String),
286
287 /// Producing a validation report failed (serialisation, IO, or formatter error).
288 #[error("Report generation error: {0}")]
289 ReportGeneration(String),
290
291 /// A user-supplied `ValidationConfig` or builder argument is invalid.
292 #[error("Configuration error: {0}")]
293 Configuration(String),
294
295 /// Wrapped error originating in the `oxirs-core` RDF data model.
296 #[error("OxiRS core error: {0}")]
297 Core(#[from] OxirsError),
298
299 /// Wrapped `std::io::Error`.
300 #[error("IO error: {0}")]
301 Io(String),
302
303 /// A `sh:pattern` regex failed to compile.
304 #[error("Regex error: {0}")]
305 Regex(#[from] regex::Error),
306
307 /// JSON serialisation/deserialisation error
308 /// (used by the JSON-LD report writer and YAML config loader).
309 #[error("JSON error: {0}")]
310 Json(String),
311
312 /// IRI resolution failed (relative IRI without a base, malformed prefix, etc.).
313 #[error("IRI resolution error: {0}")]
314 IriResolution(#[from] crate::iri_resolver::IriResolutionError),
315
316 /// A SHACL-SPARQL constraint was rejected by the security policy
317 /// (forbidden function, query too expensive, network egress denied).
318 #[error("Security violation: {0}")]
319 SecurityViolation(String),
320
321 /// A higher-level shape contract was violated
322 /// (e.g. inheritance cycle, conflicting `sh:property` definitions).
323 #[error("Shape validation error: {0}")]
324 ShapeValidation(String),
325
326 /// Validation exceeded the configured wall-clock timeout.
327 #[error("Validation timeout: {0}")]
328 Timeout(String),
329
330 /// Validation exceeded the configured memory budget.
331 #[error("Memory limit exceeded: {0}")]
332 MemoryLimit(String),
333
334 /// Recursive shape evaluation reached `ValidationConfig::max_recursion_depth`.
335 #[error("Recursion limit exceeded: {0}")]
336 RecursionLimit(String),
337
338 /// Internal pool used for short-lived allocations failed.
339 #[error("Memory pool error: {0}")]
340 MemoryPool(String),
341
342 /// Memory-aware optimisation pass aborted.
343 #[error("Memory optimization error: {0}")]
344 MemoryOptimization(String),
345
346 /// An async runtime task failed (only when the `async` feature is enabled).
347 #[error("Async operation error: {0}")]
348 AsyncOperation(String),
349
350 /// A construct was recognised but is not yet implemented in this build
351 /// (typically gated behind a Cargo feature).
352 #[error("Unsupported operation: {0}")]
353 UnsupportedOperation(String),
354
355 /// Generic report-related failure (writer IO, missing template, etc.).
356 #[error("Report error: {0}")]
357 ReportError(String),
358}
359
360impl From<serde_json::Error> for ShaclError {
361 fn from(err: serde_json::Error) -> Self {
362 ShaclError::Json(err.to_string())
363 }
364}
365
366impl From<std::io::Error> for ShaclError {
367 fn from(err: std::io::Error) -> Self {
368 ShaclError::Io(err.to_string())
369 }
370}
371
372impl From<serde_yaml::Error> for ShaclError {
373 fn from(err: serde_yaml::Error) -> Self {
374 ShaclError::Json(err.to_string())
375 }
376}
377
378impl From<anyhow::Error> for ShaclError {
379 fn from(err: anyhow::Error) -> Self {
380 ShaclError::ValidationEngine(err.to_string())
381 }
382}
383
384impl From<std::fmt::Error> for ShaclError {
385 fn from(err: std::fmt::Error) -> Self {
386 ShaclError::ReportGeneration(err.to_string())
387 }
388}
389
390/// Result type alias for SHACL operations
391pub type Result<T> = std::result::Result<T, ShaclError>;
392
393/// SHACL shape identifier.
394///
395/// A shape is identified by its IRI in the shapes graph; for blank-node shapes,
396/// implementations must mint a stable identifier. `ShapeId` is the canonical
397/// representation used throughout the engine to reference shapes in maps,
398/// inheritance chains, and validation reports.
399///
400/// Shapes are typically named with the `sh:NodeShape` or `sh:PropertyShape` IRI:
401///
402/// ```rust
403/// use oxirs_shacl::ShapeId;
404///
405/// let person = ShapeId::new("http://example.org/PersonShape");
406/// assert_eq!(person.as_str(), "http://example.org/PersonShape");
407/// ```
408///
409/// For blank-node shapes (anonymous shapes inlined in a property shape, etc.),
410/// use [`ShapeId::generate`] to mint a fresh UUID-based identifier.
411#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
412pub struct ShapeId(pub String);
413
414impl ShapeId {
415 /// Construct a shape identifier from anything convertible into `String`.
416 pub fn new(id: impl Into<String>) -> Self {
417 ShapeId(id.into())
418 }
419
420 /// Return the underlying IRI/string slice.
421 pub fn as_str(&self) -> &str {
422 &self.0
423 }
424
425 /// Generate a fresh, globally unique shape identifier (UUID-backed).
426 ///
427 /// Used for blank-node shapes and synthetic shapes constructed at runtime.
428 pub fn generate() -> Self {
429 ShapeId(format!("shape_{}", Uuid::new_v4()))
430 }
431}
432
433impl fmt::Display for ShapeId {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 write!(f, "{}", self.0)
436 }
437}
438
439impl From<String> for ShapeId {
440 fn from(s: String) -> Self {
441 ShapeId(s)
442 }
443}
444
445impl From<&str> for ShapeId {
446 fn from(s: &str) -> Self {
447 ShapeId(s.to_string())
448 }
449}
450
451/// SHACL constraint component identifier.
452///
453/// Every SHACL constraint is associated with a *constraint component* — the
454/// IRI that identifies which kind of constraint it is. For example, the
455/// `sh:minCount` parameter activates the constraint component whose ID is
456/// `sh:MinCountConstraintComponent`. These IDs surface in violation reports
457/// (`sh:sourceConstraintComponent`) and let consumers route or filter
458/// violations by constraint family.
459///
460/// ```rust
461/// use oxirs_shacl::ConstraintComponentId;
462///
463/// let id = ConstraintComponentId::new("sh:MinCountConstraintComponent");
464/// assert_eq!(id.as_str(), "sh:MinCountConstraintComponent");
465/// ```
466#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
467pub struct ConstraintComponentId(pub String);
468
469impl ConstraintComponentId {
470 /// Construct a constraint component ID from anything convertible into `String`.
471 pub fn new(id: impl Into<String>) -> Self {
472 ConstraintComponentId(id.into())
473 }
474
475 /// Return the underlying IRI/string slice.
476 pub fn as_str(&self) -> &str {
477 &self.0
478 }
479}
480
481impl fmt::Display for ConstraintComponentId {
482 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483 write!(f, "{}", self.0)
484 }
485}
486
487impl From<String> for ConstraintComponentId {
488 fn from(s: String) -> Self {
489 ConstraintComponentId(s)
490 }
491}
492
493impl From<&str> for ConstraintComponentId {
494 fn from(s: &str) -> Self {
495 ConstraintComponentId(s.to_string())
496 }
497}
498
499/// SHACL shape type.
500///
501/// SHACL distinguishes two shape kinds (SHACL Core §2.1):
502///
503/// - **Node shape** (`sh:NodeShape`) — places constraints on focus nodes themselves.
504/// - **Property shape** (`sh:PropertyShape`) — declares a `sh:path` and places
505/// constraints on the values reached via that path from a focus node.
506///
507/// The variant of this enum determines how a [`Shape`] is interpreted by the
508/// validation engine: property shapes always have a `path`, node shapes do not.
509#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
510pub enum ShapeType {
511 /// `sh:NodeShape` — constraints applied directly to focus nodes.
512 NodeShape,
513 /// `sh:PropertyShape` — constraints applied to values reachable from focus nodes
514 /// through the shape's `sh:path`.
515 PropertyShape,
516}
517
518/// SHACL shape representation.
519///
520/// A `Shape` is the in-memory model of a single SHACL shape — either a node shape
521/// (`sh:NodeShape`) or a property shape (`sh:PropertyShape`). Shapes group together
522/// the targets that select focus nodes, the path (for property shapes), the
523/// constraints to evaluate, and various metadata used for reporting and
524/// inheritance.
525///
526/// Construct via [`Shape::node_shape`] or [`Shape::property_shape`]:
527///
528/// ```rust
529/// use oxirs_shacl::{Shape, ShapeId};
530///
531/// let mut person = Shape::node_shape(ShapeId::new("http://example.org/PersonShape"));
532/// person.label = Some("Person".into());
533/// person.description = Some("A natural person".into());
534/// assert!(person.is_node_shape());
535/// ```
536#[derive(Debug, Clone, Serialize, Deserialize)]
537pub struct Shape {
538 /// Unique identifier for this shape
539 pub id: ShapeId,
540
541 /// Type of this shape (node or property)
542 pub shape_type: ShapeType,
543
544 /// Target definitions for this shape
545 pub targets: Vec<Target>,
546
547 /// Property path (for property shapes)
548 pub path: Option<PropertyPath>,
549
550 /// Constraints applied by this shape
551 pub constraints: IndexMap<ConstraintComponentId, Constraint>,
552
553 /// Whether this shape is deactivated
554 pub deactivated: bool,
555
556 /// Human-readable label
557 pub label: Option<String>,
558
559 /// Human-readable description
560 pub description: Option<String>,
561
562 /// Groups this shape belongs to
563 pub groups: Vec<String>,
564
565 /// Order for evaluation
566 pub order: Option<i32>,
567
568 /// Default severity for violations
569 pub severity: Severity,
570
571 /// Custom messages for this shape
572 pub messages: IndexMap<String, String>, // language -> message
573
574 /// --- Enhanced features ---
575
576 /// Parent shapes for inheritance (sh:extends)
577 pub extends: Vec<ShapeId>,
578
579 /// Property shapes linked via sh:property (for NodeShapes)
580 pub property_shapes: Vec<ShapeId>,
581
582 /// Priority for conflict resolution (higher value = higher priority)
583 pub priority: Option<i32>,
584
585 /// Additional metadata
586 pub metadata: ShapeMetadata,
587}
588
589impl Shape {
590 /// Construct a new shape with the given ID and type, no targets, and no
591 /// constraints. Defaults: severity = `Violation`, `deactivated = false`.
592 pub fn new(id: ShapeId, shape_type: ShapeType) -> Self {
593 Self {
594 id,
595 shape_type,
596 targets: Vec::new(),
597 path: None,
598 constraints: IndexMap::new(),
599 deactivated: false,
600 label: None,
601 description: None,
602 groups: Vec::new(),
603 order: None,
604 severity: Severity::Violation,
605 messages: IndexMap::new(),
606 extends: Vec::new(),
607 property_shapes: Vec::new(),
608 priority: None,
609 metadata: ShapeMetadata::default(),
610 }
611 }
612
613 /// Construct an empty `sh:NodeShape`.
614 pub fn node_shape(id: ShapeId) -> Self {
615 Self::new(id, ShapeType::NodeShape)
616 }
617
618 /// Construct an empty `sh:PropertyShape` with the given `sh:path`.
619 pub fn property_shape(id: ShapeId, path: PropertyPath) -> Self {
620 let mut shape = Self::new(id, ShapeType::PropertyShape);
621 shape.path = Some(path);
622 shape
623 }
624
625 /// Attach a constraint to this shape under the given component ID.
626 /// Inserting twice with the same `component_id` overwrites the previous value.
627 pub fn add_constraint(&mut self, component_id: ConstraintComponentId, constraint: Constraint) {
628 self.constraints.insert(component_id, constraint);
629 }
630
631 /// Append a target declaration (`sh:targetClass`, `sh:targetNode`, …) to this shape.
632 pub fn add_target(&mut self, target: Target) {
633 self.targets.push(target);
634 }
635
636 /// Whether this shape participates in validation. A shape with `sh:deactivated true`
637 /// is loaded into the shapes graph but skipped by the validation engine.
638 pub fn is_active(&self) -> bool {
639 !self.deactivated
640 }
641
642 /// True when [`shape_type`](Shape::shape_type) is [`ShapeType::NodeShape`].
643 pub fn is_node_shape(&self) -> bool {
644 matches!(self.shape_type, ShapeType::NodeShape)
645 }
646
647 /// True when [`shape_type`](Shape::shape_type) is [`ShapeType::PropertyShape`].
648 pub fn is_property_shape(&self) -> bool {
649 matches!(self.shape_type, ShapeType::PropertyShape)
650 }
651
652 /// Set shape inheritance
653 pub fn extends(&mut self, parent_shape_id: ShapeId) -> &mut Self {
654 self.extends.push(parent_shape_id);
655 self
656 }
657
658 /// Set shape priority
659 pub fn with_priority(&mut self, priority: i32) -> &mut Self {
660 self.priority = Some(priority);
661 self
662 }
663
664 /// Set shape metadata
665 pub fn with_metadata(&mut self, metadata: ShapeMetadata) -> &mut Self {
666 self.metadata = metadata;
667 self
668 }
669
670 /// Update metadata fields
671 pub fn update_metadata<F>(&mut self, updater: F) -> &mut Self
672 where
673 F: FnOnce(&mut ShapeMetadata),
674 {
675 updater(&mut self.metadata);
676 self
677 }
678
679 /// Get effective priority (defaults to 0 if not set)
680 pub fn effective_priority(&self) -> i32 {
681 self.priority.unwrap_or(0)
682 }
683
684 /// Check if this shape extends another shape
685 pub fn extends_shape(&self, shape_id: &ShapeId) -> bool {
686 self.extends.contains(shape_id)
687 }
688
689 /// Get all parent shape IDs
690 pub fn parent_shapes(&self) -> &[ShapeId] {
691 &self.extends
692 }
693}
694
695impl Default for Shape {
696 fn default() -> Self {
697 Self::new(ShapeId("default:shape".to_string()), ShapeType::NodeShape)
698 }
699}
700
701/// Shape metadata for tracking additional information
702#[derive(Debug, Clone, Serialize, Deserialize, Default)]
703pub struct ShapeMetadata {
704 /// Author of the shape
705 pub author: Option<String>,
706
707 /// Creation timestamp
708 pub created: Option<chrono::DateTime<chrono::Utc>>,
709
710 /// Last modification timestamp
711 pub modified: Option<chrono::DateTime<chrono::Utc>>,
712
713 /// Version string
714 pub version: Option<String>,
715
716 /// License information
717 pub license: Option<String>,
718
719 /// Tags for categorization
720 pub tags: Vec<String>,
721
722 /// Custom properties
723 pub custom: HashMap<String, String>,
724}
725
726/// Violation severity levels (SHACL Core §2.1.4 `sh:severity`).
727///
728/// SHACL allows shape authors to mark violations as informational, warning, or
729/// hard violation. The default is [`Severity::Violation`]. Tools (CI, IDE,
730/// linters) typically filter or color-code reports by severity.
731///
732/// Severity values are totally ordered: `Info < Warning < Violation`. This
733/// allows `max(severities)` to compute the worst severity in a report.
734#[derive(
735 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
736)]
737pub enum Severity {
738 /// `sh:Info` — purely informational; not a failure.
739 Info,
740 /// `sh:Warning` — soft violation; default-on but can be ignored.
741 Warning,
742 /// `sh:Violation` — hard violation (default for SHACL Core).
743 #[default]
744 Violation,
745}
746
747impl fmt::Display for Severity {
748 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
749 match self {
750 Severity::Info => write!(f, "Info"),
751 Severity::Warning => write!(f, "Warning"),
752 Severity::Violation => write!(f, "Violation"),
753 }
754 }
755}
756
757/// Validation configuration shared by all entry points into the engine.
758///
759/// `ValidationConfig` controls how the engine behaves during a single validation
760/// run: how many violations to emit, which severities to include, whether to
761/// stop after the first failure, recursion limits, time budgets, parallelism,
762/// and which optimisation strategy to use.
763///
764/// ```rust
765/// use oxirs_shacl::{ValidationConfig, ValidationStrategy, Severity};
766///
767/// let cfg = ValidationConfig::default()
768/// .with_strategy(ValidationStrategy::Optimized)
769/// .with_inference_enabled(true);
770/// assert!(cfg.include_warnings);
771/// assert_eq!(cfg.max_recursion_depth, 50);
772/// ```
773#[derive(Debug, Clone, Serialize, Deserialize)]
774pub struct ValidationConfig {
775 /// Maximum number of violations to report (0 = unlimited)
776 pub max_violations: usize,
777
778 /// Include violations with Info severity
779 pub include_info: bool,
780
781 /// Include violations with Warning severity
782 pub include_warnings: bool,
783
784 /// Stop validation on first violation
785 pub fail_fast: bool,
786
787 /// Maximum recursion depth for shape validation
788 pub max_recursion_depth: usize,
789
790 /// Timeout for validation in milliseconds
791 pub timeout_ms: Option<u64>,
792
793 /// Enable parallel validation
794 pub parallel: bool,
795
796 /// Custom validation context
797 pub context: HashMap<String, String>,
798
799 /// Validation strategy
800 pub strategy: ValidationStrategy,
801}
802
803impl Default for ValidationConfig {
804 fn default() -> Self {
805 Self {
806 max_violations: 0,
807 include_info: true,
808 include_warnings: true,
809 fail_fast: false,
810 max_recursion_depth: 50,
811 timeout_ms: None,
812 parallel: false,
813 context: HashMap::new(),
814 strategy: ValidationStrategy::default(),
815 }
816 }
817}
818
819impl ValidationConfig {
820 /// Set the validation strategy
821 pub fn with_strategy(mut self, strategy: ValidationStrategy) -> Self {
822 self.strategy = strategy;
823 self
824 }
825
826 /// Enable inference during validation
827 pub fn with_inference_enabled(mut self, enabled: bool) -> Self {
828 self.context
829 .insert("inference_enabled".to_string(), enabled.to_string());
830 self
831 }
832}
833
834/// OxiRS SHACL version
835pub const VERSION: &str = env!("CARGO_PKG_VERSION");
836// Validator module
837pub mod validator;
838// SHACL node expression evaluator (v1.1.0 round 5)
839pub mod node_expression_evaluator;
840
841// SHACL target declaration evaluator (v1.1.0 round 6)
842pub mod target_selector;
843
844// SHACL sh:message template interpolation (v1.1.0 round 7)
845pub mod message_formatter;
846
847// SHACL SPARQL-based constraint validation (sh:SPARQLConstraintComponent) (v1.1.0 round 8)
848pub mod sparql_constraint_validator;
849
850// SHACL property path constraint checking (v1.1.0 round 9)
851pub mod property_path_checker;
852
853// SHACL shape graph loader and indexer (v1.1.0 round 10)
854pub mod shape_graph_loader;
855
856// SHACL entailment regime support: RDFS + OWL Direct subsets (v1.1.0 round 11)
857pub mod entailment_regime;
858
859// Pattern-based SHACL shape matching (v1.1.0 round 12)
860pub mod shape_matcher;
861
862// SHACL constraint inheritance via sh:and/sh:or/sh:not/sh:xone (v1.1.0 round 13)
863pub mod constraint_inheritance;
864
865// SHACL severity level handling (v1.1.0 round 12)
866pub mod severity_handler;
867
868// SHACL focus node selection (v1.1.0 round 11)
869pub mod focus_node_selector;
870
871// SHACL property path execution — full path operator evaluation (v1.1.0 round 13)
872pub mod path_executor;
873
874// SHACL sh:parameter / parameterized constraint components (v1.1.0 round 14)
875pub mod constraint_parameter;
876
877// SHACL sh:datatype constraint checker (v1.1.0 round 15)
878pub mod datatype_checker;
879
880// SHACL sh:node constraint component (v1.1.0 round 16)
881pub mod node_constraint;
882
883// Re-export validator types
884pub use validator::{ValidationStats, Validator, ValidatorBuilder};
885
886/// Initialize OxiRS SHACL with default configuration
887pub fn init() -> Result<()> {
888 tracing::info!("Initializing OxiRS SHACL v{}", VERSION);
889 Ok(())
890}