Skip to main content

oxirs_shacl/
lib.rs

1//! # OxiRS SHACL - RDF Validation Engine
2//!
3//! [![Version](https://img.shields.io/badge/version-0.4.1-blue)](https://github.com/cool-japan/oxirs/releases)
4//! [![docs.rs](https://docs.rs/oxirs-shacl/badge.svg)](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}