oxirs_shacl/
lib.rs

1//! # OxiRS SHACL - RDF Validation Engine
2//!
3//! [![Version](https://img.shields.io/badge/version-0.1.0-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.1.0)
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
15//! - **SHACL-SPARQL** - SPARQL-based constraints (experimental)
16//! - **Property Paths** - Full property path evaluation
17//! - **Logical Constraints** - sh:and, sh:or, sh:not, sh:xone
18//! - **Validation Reports** - Comprehensive violation reporting
19//! - **Performance** - Optimized validation engine
20//!
21//! ## See Also
22//!
23//! - [`oxirs-core`](https://docs.rs/oxirs-core) - RDF data model
24//! - [`oxirs-arq`](https://docs.rs/oxirs-arq) - SPARQL query engine
25//!
26//! ## Basic Usage
27//!
28//! ```rust
29//! use oxirs_shacl::{ValidationConfig, ValidationStrategy};
30//! use oxirs_core::{Store, model::*};
31//!
32//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
33//! // Create a validation configuration
34//! let config = ValidationConfig::default()
35//!     .with_strategy(ValidationStrategy::Optimized)
36//!     .with_inference_enabled(true);
37//!
38//! // Validation is typically performed using ValidationEngine
39//! // See examples/ directory for complete working examples
40//!
41//! # Ok(())
42//! # }
43//! ```
44//!
45//! ## Advanced Features
46//!
47//! For detailed examples and advanced usage patterns, including:
48//! - Custom constraint components
49//! - Parallel and incremental validation
50//! - Enterprise security and compliance features
51//! - Federated validation
52//! - Production deployment patterns
53//!
54//! Please refer to the individual module documentation and the examples
55//! directory in the repository.
56
57use std::collections::HashMap;
58use std::fmt;
59use std::hash::Hash;
60
61use indexmap::IndexMap;
62use once_cell::sync::Lazy;
63use serde::{Deserialize, Serialize};
64use uuid::Uuid;
65
66use oxirs_core::OxirsError;
67
68pub use crate::optimization::integration::ValidationStrategy;
69
70pub mod advanced_features;
71pub mod analytics;
72pub mod builders;
73pub mod constraints;
74pub mod custom_components;
75pub mod designer;
76pub mod federated_validation;
77pub mod incremental;
78pub mod integration;
79pub mod iri_resolver;
80#[cfg(feature = "lsp")]
81pub mod lsp;
82pub mod optimization;
83pub mod paths;
84pub mod report;
85pub mod scirs_graph_integration;
86pub mod security;
87pub mod shape_import;
88pub mod shape_inheritance;
89pub mod shape_versioning;
90pub mod shapes;
91pub mod sparql;
92pub mod targets;
93pub mod templates;
94pub mod testing;
95pub mod validation;
96pub mod visual_editor;
97pub mod vocabulary;
98pub mod w3c_test_suite;
99pub mod w3c_test_suite_enhanced;
100
101// Re-export key types for convenience - avoiding ambiguous glob re-exports
102pub use advanced_features::{
103    AdvancedTarget, AdvancedTargetSelector, ConditionalConstraint, ConditionalEvaluator,
104    ConditionalResult, FunctionInvocation, FunctionParameter, FunctionRegistry, FunctionResult,
105    InferenceStrategy, InferredShape, ParameterType, ReturnType, RuleEngine, RuleEngineStats,
106    RuleExecutionResult, ShaclFunction, ShaclRule, ShapeInferenceConfig, ShapeInferenceEngine,
107    ShapeRegistry,
108};
109pub use analytics::ValidationAnalytics;
110pub use builders::*;
111pub use constraints::{
112    Constraint, ConstraintContext, ConstraintEvaluationResult, NodeKindConstraint,
113    PropertyConstraint,
114};
115pub use custom_components::{
116    ComponentMetadata, CustomConstraint, CustomConstraintRegistry, EmailValidationComponent,
117    RangeConstraintComponent, RegexConstraintComponent,
118};
119pub use federated_validation::*;
120pub use incremental::{
121    Changeset, GraphChange, IncrementalConfig, IncrementalStats, IncrementalValidator,
122};
123pub use iri_resolver::*;
124pub use optimization::{
125    NegationOptimizer, OptimizationConfig, OptimizationResult, OptimizationStrategy,
126    ValidationOptimizationEngine,
127};
128pub use paths::*;
129pub use report::{
130    ReportFormat, ReportGenerator, ReportMetadata, ValidationReport, ValidationSummary,
131};
132pub use scirs_graph_integration::{
133    BasicMetrics, ConnectivityAnalysis, GraphValidationConfig, GraphValidationResult,
134    SciRS2GraphValidator,
135};
136pub use security::{SecureSparqlExecutor, SecurityConfig, SecurityPolicy};
137pub use shape_import::*;
138pub use shape_inheritance::*;
139// Import specific types from shapes to avoid conflicts
140pub use shapes::{
141    format_literal_for_sparql, format_term_for_sparql, ShapeCacheStats, ShapeFactory, ShapeParser,
142    ShapeParsingConfig, ShapeParsingContext, ShapeParsingStats, ShapeValidationReport,
143    ShapeValidator, SingleShapeValidationReport,
144};
145pub use sparql::*;
146// Import specific types from targets to avoid conflicts
147pub use targets::{
148    Target, TargetCacheStats, TargetOptimizationConfig, TargetSelectionStats, TargetSelector,
149};
150pub use testing::{
151    ShapeTestSuite, TestAssertions, TestCase, TestExpectation, TestResult, TestStatus,
152    TestSuiteResult, TestSummary,
153};
154pub use validation::{ValidationEngine, ValidationViolation};
155pub use visual_editor::{
156    ColorScheme, ExportFormat, LayoutDirection, ShapeVisualizer, VisualizerConfig,
157};
158pub use w3c_test_suite::*;
159
160// Re-export designer types
161pub use designer::{
162    ConstraintSpec, DesignIssue, DesignStep, DesignWizard, Domain, PropertyDesign, PropertyHint,
163    RecommendationEngine, ShapeDesign, ShapeDesigner,
164    ShapeInferenceEngine as DesignerInferenceEngine,
165};
166
167// Re-export optimization types (note: these are already imported above)
168
169/// SHACL namespace IRI
170pub static SHACL_NS: &str = "http://www.w3.org/ns/shacl#";
171
172/// SHACL vocabulary terms
173pub static SHACL_VOCAB: Lazy<vocabulary::ShaclVocabulary> =
174    Lazy::new(vocabulary::ShaclVocabulary::new);
175
176/// IRI resolver for validation and expansion
177pub use iri_resolver::IriResolver;
178
179/// Core error type for SHACL operations
180#[derive(Debug, Clone, thiserror::Error)]
181pub enum ShaclError {
182    #[error("Shape parsing error: {0}")]
183    ShapeParsing(String),
184
185    #[error("Constraint validation error: {0}")]
186    ConstraintValidation(String),
187
188    #[error("Target selection error: {0}")]
189    TargetSelection(String),
190
191    #[error("Property path error: {0}")]
192    PropertyPath(String),
193
194    #[error("Path evaluation error: {0}")]
195    PathEvaluationError(String),
196
197    #[error("SPARQL execution error: {0}")]
198    SparqlExecution(String),
199
200    #[error("Validation engine error: {0}")]
201    ValidationEngine(String),
202
203    #[error("Report generation error: {0}")]
204    ReportGeneration(String),
205
206    #[error("Configuration error: {0}")]
207    Configuration(String),
208
209    #[error("OxiRS core error: {0}")]
210    Core(#[from] OxirsError),
211
212    #[error("IO error: {0}")]
213    Io(String),
214
215    #[error("Regex error: {0}")]
216    Regex(#[from] regex::Error),
217
218    #[error("JSON error: {0}")]
219    Json(String),
220
221    #[error("IRI resolution error: {0}")]
222    IriResolution(#[from] crate::iri_resolver::IriResolutionError),
223
224    #[error("Security violation: {0}")]
225    SecurityViolation(String),
226
227    #[error("Shape validation error: {0}")]
228    ShapeValidation(String),
229
230    #[error("Validation timeout: {0}")]
231    Timeout(String),
232
233    #[error("Memory limit exceeded: {0}")]
234    MemoryLimit(String),
235
236    #[error("Recursion limit exceeded: {0}")]
237    RecursionLimit(String),
238
239    #[error("Memory pool error: {0}")]
240    MemoryPool(String),
241
242    #[error("Memory optimization error: {0}")]
243    MemoryOptimization(String),
244
245    #[error("Async operation error: {0}")]
246    AsyncOperation(String),
247
248    #[error("Unsupported operation: {0}")]
249    UnsupportedOperation(String),
250
251    #[error("Report error: {0}")]
252    ReportError(String),
253}
254
255impl From<serde_json::Error> for ShaclError {
256    fn from(err: serde_json::Error) -> Self {
257        ShaclError::Json(err.to_string())
258    }
259}
260
261impl From<std::io::Error> for ShaclError {
262    fn from(err: std::io::Error) -> Self {
263        ShaclError::Io(err.to_string())
264    }
265}
266
267impl From<serde_yaml::Error> for ShaclError {
268    fn from(err: serde_yaml::Error) -> Self {
269        ShaclError::Json(err.to_string())
270    }
271}
272
273impl From<anyhow::Error> for ShaclError {
274    fn from(err: anyhow::Error) -> Self {
275        ShaclError::ValidationEngine(err.to_string())
276    }
277}
278
279impl From<std::fmt::Error> for ShaclError {
280    fn from(err: std::fmt::Error) -> Self {
281        ShaclError::ReportGeneration(err.to_string())
282    }
283}
284
285/// Result type alias for SHACL operations
286pub type Result<T> = std::result::Result<T, ShaclError>;
287
288/// SHACL shape identifier
289#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
290pub struct ShapeId(pub String);
291
292impl ShapeId {
293    pub fn new(id: impl Into<String>) -> Self {
294        ShapeId(id.into())
295    }
296
297    pub fn as_str(&self) -> &str {
298        &self.0
299    }
300
301    pub fn generate() -> Self {
302        ShapeId(format!("shape_{}", Uuid::new_v4()))
303    }
304}
305
306impl fmt::Display for ShapeId {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        write!(f, "{}", self.0)
309    }
310}
311
312impl From<String> for ShapeId {
313    fn from(s: String) -> Self {
314        ShapeId(s)
315    }
316}
317
318impl From<&str> for ShapeId {
319    fn from(s: &str) -> Self {
320        ShapeId(s.to_string())
321    }
322}
323
324/// SHACL constraint component identifier
325#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
326pub struct ConstraintComponentId(pub String);
327
328impl ConstraintComponentId {
329    pub fn new(id: impl Into<String>) -> Self {
330        ConstraintComponentId(id.into())
331    }
332
333    pub fn as_str(&self) -> &str {
334        &self.0
335    }
336}
337
338impl fmt::Display for ConstraintComponentId {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        write!(f, "{}", self.0)
341    }
342}
343
344impl From<String> for ConstraintComponentId {
345    fn from(s: String) -> Self {
346        ConstraintComponentId(s)
347    }
348}
349
350impl From<&str> for ConstraintComponentId {
351    fn from(s: &str) -> Self {
352        ConstraintComponentId(s.to_string())
353    }
354}
355
356/// SHACL shape type
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
358pub enum ShapeType {
359    NodeShape,
360    PropertyShape,
361}
362
363/// SHACL shape representation
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct Shape {
366    /// Unique identifier for this shape
367    pub id: ShapeId,
368
369    /// Type of this shape (node or property)
370    pub shape_type: ShapeType,
371
372    /// Target definitions for this shape
373    pub targets: Vec<Target>,
374
375    /// Property path (for property shapes)
376    pub path: Option<PropertyPath>,
377
378    /// Constraints applied by this shape
379    pub constraints: IndexMap<ConstraintComponentId, Constraint>,
380
381    /// Whether this shape is deactivated
382    pub deactivated: bool,
383
384    /// Human-readable label
385    pub label: Option<String>,
386
387    /// Human-readable description
388    pub description: Option<String>,
389
390    /// Groups this shape belongs to
391    pub groups: Vec<String>,
392
393    /// Order for evaluation
394    pub order: Option<i32>,
395
396    /// Default severity for violations
397    pub severity: Severity,
398
399    /// Custom messages for this shape
400    pub messages: IndexMap<String, String>, // language -> message
401
402    /// --- Enhanced features ---
403
404    /// Parent shapes for inheritance (sh:extends)
405    pub extends: Vec<ShapeId>,
406
407    /// Property shapes linked via sh:property (for NodeShapes)
408    pub property_shapes: Vec<ShapeId>,
409
410    /// Priority for conflict resolution (higher value = higher priority)
411    pub priority: Option<i32>,
412
413    /// Additional metadata
414    pub metadata: ShapeMetadata,
415}
416
417impl Shape {
418    pub fn new(id: ShapeId, shape_type: ShapeType) -> Self {
419        Self {
420            id,
421            shape_type,
422            targets: Vec::new(),
423            path: None,
424            constraints: IndexMap::new(),
425            deactivated: false,
426            label: None,
427            description: None,
428            groups: Vec::new(),
429            order: None,
430            severity: Severity::Violation,
431            messages: IndexMap::new(),
432            extends: Vec::new(),
433            property_shapes: Vec::new(),
434            priority: None,
435            metadata: ShapeMetadata::default(),
436        }
437    }
438
439    pub fn node_shape(id: ShapeId) -> Self {
440        Self::new(id, ShapeType::NodeShape)
441    }
442
443    pub fn property_shape(id: ShapeId, path: PropertyPath) -> Self {
444        let mut shape = Self::new(id, ShapeType::PropertyShape);
445        shape.path = Some(path);
446        shape
447    }
448
449    pub fn add_constraint(&mut self, component_id: ConstraintComponentId, constraint: Constraint) {
450        self.constraints.insert(component_id, constraint);
451    }
452
453    pub fn add_target(&mut self, target: Target) {
454        self.targets.push(target);
455    }
456
457    pub fn is_active(&self) -> bool {
458        !self.deactivated
459    }
460
461    pub fn is_node_shape(&self) -> bool {
462        matches!(self.shape_type, ShapeType::NodeShape)
463    }
464
465    pub fn is_property_shape(&self) -> bool {
466        matches!(self.shape_type, ShapeType::PropertyShape)
467    }
468
469    /// Set shape inheritance
470    pub fn extends(&mut self, parent_shape_id: ShapeId) -> &mut Self {
471        self.extends.push(parent_shape_id);
472        self
473    }
474
475    /// Set shape priority
476    pub fn with_priority(&mut self, priority: i32) -> &mut Self {
477        self.priority = Some(priority);
478        self
479    }
480
481    /// Set shape metadata
482    pub fn with_metadata(&mut self, metadata: ShapeMetadata) -> &mut Self {
483        self.metadata = metadata;
484        self
485    }
486
487    /// Update metadata fields
488    pub fn update_metadata<F>(&mut self, updater: F) -> &mut Self
489    where
490        F: FnOnce(&mut ShapeMetadata),
491    {
492        updater(&mut self.metadata);
493        self
494    }
495
496    /// Get effective priority (defaults to 0 if not set)
497    pub fn effective_priority(&self) -> i32 {
498        self.priority.unwrap_or(0)
499    }
500
501    /// Check if this shape extends another shape
502    pub fn extends_shape(&self, shape_id: &ShapeId) -> bool {
503        self.extends.contains(shape_id)
504    }
505
506    /// Get all parent shape IDs
507    pub fn parent_shapes(&self) -> &[ShapeId] {
508        &self.extends
509    }
510}
511
512impl Default for Shape {
513    fn default() -> Self {
514        Self::new(ShapeId("default:shape".to_string()), ShapeType::NodeShape)
515    }
516}
517
518/// Shape metadata for tracking additional information
519#[derive(Debug, Clone, Serialize, Deserialize, Default)]
520pub struct ShapeMetadata {
521    /// Author of the shape
522    pub author: Option<String>,
523
524    /// Creation timestamp
525    pub created: Option<chrono::DateTime<chrono::Utc>>,
526
527    /// Last modification timestamp
528    pub modified: Option<chrono::DateTime<chrono::Utc>>,
529
530    /// Version string
531    pub version: Option<String>,
532
533    /// License information
534    pub license: Option<String>,
535
536    /// Tags for categorization
537    pub tags: Vec<String>,
538
539    /// Custom properties
540    pub custom: HashMap<String, String>,
541}
542
543/// Violation severity levels
544#[derive(
545    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
546)]
547pub enum Severity {
548    Info,
549    Warning,
550    #[default]
551    Violation,
552}
553
554impl fmt::Display for Severity {
555    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556        match self {
557            Severity::Info => write!(f, "Info"),
558            Severity::Warning => write!(f, "Warning"),
559            Severity::Violation => write!(f, "Violation"),
560        }
561    }
562}
563
564/// Validation configuration
565#[derive(Debug, Clone, Serialize, Deserialize)]
566pub struct ValidationConfig {
567    /// Maximum number of violations to report (0 = unlimited)
568    pub max_violations: usize,
569
570    /// Include violations with Info severity
571    pub include_info: bool,
572
573    /// Include violations with Warning severity
574    pub include_warnings: bool,
575
576    /// Stop validation on first violation
577    pub fail_fast: bool,
578
579    /// Maximum recursion depth for shape validation
580    pub max_recursion_depth: usize,
581
582    /// Timeout for validation in milliseconds
583    pub timeout_ms: Option<u64>,
584
585    /// Enable parallel validation
586    pub parallel: bool,
587
588    /// Custom validation context
589    pub context: HashMap<String, String>,
590
591    /// Validation strategy
592    pub strategy: ValidationStrategy,
593}
594
595impl Default for ValidationConfig {
596    fn default() -> Self {
597        Self {
598            max_violations: 0,
599            include_info: true,
600            include_warnings: true,
601            fail_fast: false,
602            max_recursion_depth: 50,
603            timeout_ms: None,
604            parallel: false,
605            context: HashMap::new(),
606            strategy: ValidationStrategy::default(),
607        }
608    }
609}
610
611impl ValidationConfig {
612    /// Set the validation strategy
613    pub fn with_strategy(mut self, strategy: ValidationStrategy) -> Self {
614        self.strategy = strategy;
615        self
616    }
617
618    /// Enable inference during validation
619    pub fn with_inference_enabled(mut self, enabled: bool) -> Self {
620        self.context
621            .insert("inference_enabled".to_string(), enabled.to_string());
622        self
623    }
624}
625
626/// OxiRS SHACL version
627pub const VERSION: &str = env!("CARGO_PKG_VERSION");
628// Validator module
629pub mod validator;
630
631// Re-export validator types
632pub use validator::{ValidationStats, Validator, ValidatorBuilder};
633
634/// Initialize OxiRS SHACL with default configuration
635pub fn init() -> Result<()> {
636    tracing::info!("Initializing OxiRS SHACL v{}", VERSION);
637    Ok(())
638}