Skip to main content

oxirs_shacl/custom_components/
mod.rs

1//! Custom SHACL Constraint Components
2//!
3//! This module provides comprehensive support for user-defined SHACL constraint components,
4//! allowing users to extend SHACL with domain-specific validation logic, parameter validation,
5//! component inheritance, performance optimization, and security features.
6
7#![allow(dead_code)]
8
9pub mod constraints;
10pub mod dsl;
11pub mod js_wasm;
12pub mod library;
13pub mod marketplace;
14pub mod metadata;
15pub mod performance;
16pub mod registry;
17pub mod security;
18pub mod standard;
19
20// Re-export key types
21pub use constraints::{
22    ComponentExecutionResult, CompositeConstraint, CompositionType, CustomConstraint,
23};
24pub use dsl::{
25    namespaces, patterns, xsd, ConstraintSpec, ConstraintTemplate, ParameterDef, ParameterType,
26    ShapeDSL, TemplateLibrary,
27};
28pub use js_wasm::{
29    interop, ExternalValidatorBuilder, JavaScriptValidator, RuntimeConfig, WasmValidator,
30};
31pub use library::{
32    AgeRangeValidator,
33    BicValidator,
34    BoundingBoxValidator,
35    BusinessHoursValidator,
36    ChemicalFormulaValidator,
37    // Semantic validators
38    ClassHierarchyValidator,
39    ConstraintLibrary,
40    // Geospatial validators
41    CoordinatesValidator,
42    CountryCodeValidator,
43    CreditCardValidator,
44    // Financial validators
45    CurrencyValidator,
46    // Temporal validators
47    DateRangeValidator,
48    DoiValidator,
49    DurationValidator,
50    GeoJsonValidator,
51    IbanValidator,
52    IriValidator,
53    IsbnValidator,
54    LibraryMetadata,
55    NamePatternValidator,
56    OntologyConsistencyValidator,
57    OrcidValidator,
58    // Personal validators
59    PhoneNumberValidator,
60    PostalCodeValidator,
61    PropertyDomainRangeValidator,
62    ScientificNotationValidator,
63    TimezoneValidator,
64    // Scientific validators
65    UnitValidator,
66    // Identity validators
67    UuidValidator,
68};
69pub use marketplace::{
70    AccessLevel, Author, Category, ComponentStats, ComponentVersion, ConstraintMarketplace,
71    Dependency, InstallOptions, InstallResult, MarketplaceCache, MarketplaceComponent,
72    MarketplaceConfig, MarketplaceIndex, MarketplaceStatistics, Permission, PublishOptions,
73    PublishResult, Review, SearchQuery, SearchQueryBuilder, SearchResults, SortBy, SortOrder,
74    UpdateInfo, UserSession,
75};
76pub use metadata::{
77    ComponentLibrary, ComponentMetadata, ParameterConstraint, ParameterDefinition,
78    ValidationCondition, ValidationRule, ValidationRuleType,
79};
80pub use performance::{
81    ComponentExecutionContext, ComponentPerformanceStats, ErrorStats, ErrorTrend, ExecutionMetrics,
82    MemoryUsageStats,
83};
84pub use registry::CustomConstraintRegistry;
85pub use security::{
86    ResourceQuotas, SandboxingLevel, SecurityPolicy, SecurityViolation, SecurityViolationType,
87    SparqlOperation,
88};
89pub use standard::{
90    EmailValidationComponent, RangeConstraintComponent, RegexConstraintComponent,
91    SparqlConstraintComponent, UrlValidationComponent,
92};
93
94use crate::{ConstraintComponentId, Result};
95use oxirs_core::model::Term;
96use std::collections::HashMap;
97
98/// Trait for custom constraint components
99pub trait CustomConstraintComponent: Send + Sync + std::fmt::Debug {
100    /// Get the component identifier
101    fn component_id(&self) -> &ConstraintComponentId;
102
103    /// Get component metadata
104    fn metadata(&self) -> &ComponentMetadata;
105
106    /// Validate the component configuration
107    fn validate_configuration(&self, parameters: &HashMap<String, Term>) -> Result<()>;
108
109    /// Create a constraint instance from parameters
110    fn create_constraint(&self, parameters: HashMap<String, Term>) -> Result<CustomConstraint>;
111
112    /// Get the SPARQL query template for this component (if SPARQL-based)
113    fn sparql_template(&self) -> Option<&str> {
114        None
115    }
116
117    /// Get additional prefixes needed for SPARQL queries
118    fn sparql_prefixes(&self) -> Option<&str> {
119        None
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use oxirs_core::model::Literal;
127
128    #[test]
129    fn test_registry_registration() {
130        let mut registry = CustomConstraintRegistry::new();
131        let component = std::sync::Arc::new(RegexConstraintComponent::new());
132        let component_id = component.component_id().clone();
133
134        assert!(registry.register_component(component).is_ok());
135        assert!(registry.get_component(&component_id).is_some());
136        assert!(registry.get_metadata(&component_id).is_some());
137    }
138
139    #[test]
140    fn test_regex_constraint_creation() {
141        let component = RegexConstraintComponent::new();
142        let mut parameters = HashMap::new();
143        parameters.insert(
144            "pattern".to_string(),
145            Term::Literal(Literal::new(r"^test\d+$")),
146        );
147
148        let constraint = component
149            .create_constraint(parameters)
150            .expect("training should succeed");
151        assert_eq!(
152            constraint.component_id.as_str(),
153            "ex:RegexConstraintComponent"
154        );
155        assert!(constraint.parameters.contains_key("pattern"));
156    }
157
158    #[test]
159    fn test_range_constraint_validation() {
160        let component = RangeConstraintComponent::new();
161
162        // Should fail without min or max
163        let empty_params = HashMap::new();
164        assert!(component.validate_configuration(&empty_params).is_err());
165
166        // Should succeed with min value
167        let mut params = HashMap::new();
168        params.insert("minValue".to_string(), Term::Literal(Literal::new("0")));
169        assert!(component.validate_configuration(&params).is_ok());
170    }
171
172    #[test]
173    fn test_standard_extensions_registration() {
174        let mut registry = CustomConstraintRegistry::new();
175        assert!(registry.register_standard_extensions().is_ok());
176
177        // Check that standard components are registered
178        assert!(registry
179            .get_component(&ConstraintComponentId(
180                "ex:RegexConstraintComponent".to_string()
181            ))
182            .is_some());
183        assert!(registry
184            .get_component(&ConstraintComponentId(
185                "ex:RangeConstraintComponent".to_string()
186            ))
187            .is_some());
188        assert!(registry
189            .get_component(&ConstraintComponentId(
190                "ex:UrlValidationComponent".to_string()
191            ))
192            .is_some());
193        assert!(registry
194            .get_component(&ConstraintComponentId(
195                "ex:EmailValidationComponent".to_string()
196            ))
197            .is_some());
198        assert!(registry
199            .get_component(&ConstraintComponentId(
200                "ex:SparqlConstraintComponent".to_string()
201            ))
202            .is_some());
203    }
204
205    #[test]
206    fn test_composite_constraint_creation() {
207        let mut registry = CustomConstraintRegistry::new();
208        registry
209            .register_standard_extensions()
210            .expect("training should succeed");
211
212        let regex_id = ConstraintComponentId("ex:RegexConstraintComponent".to_string());
213        let range_id = ConstraintComponentId("ex:RangeConstraintComponent".to_string());
214
215        let mut parameters = HashMap::new();
216        parameters.insert("pattern".to_string(), Term::Literal(Literal::new(r"^\d+$")));
217        parameters.insert("minValue".to_string(), Term::Literal(Literal::new("0")));
218
219        let composite = registry
220            .create_composite_constraint(&[regex_id, range_id], parameters)
221            .expect("training should succeed");
222
223        assert_eq!(composite.component_ids.len(), 2);
224        assert_eq!(composite.constraints.len(), 2);
225        assert!(matches!(composite.composition_type, CompositionType::And));
226    }
227}