1use crate::{Result, ShaclError};
9use oxirs_core::{
10 model::{Literal, NamedNode, Term},
11 Store,
12};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::sync::Arc;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub enum ParameterType {
20 Iri,
22 Literal,
24 RdfTerm,
26 Boolean,
28 Integer,
30 Decimal,
32 String,
34 Custom(String),
36 List(Box<ParameterType>),
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct FunctionParameter {
43 pub name: String,
45 pub param_type: ParameterType,
47 pub optional: bool,
49 pub order: usize,
51 pub default_value: Option<String>,
53 pub description: Option<String>,
55}
56
57impl FunctionParameter {
58 pub fn required(name: impl Into<String>, param_type: ParameterType, order: usize) -> Self {
60 Self {
61 name: name.into(),
62 param_type,
63 optional: false,
64 order,
65 default_value: None,
66 description: None,
67 }
68 }
69
70 pub fn optional(
72 name: impl Into<String>,
73 param_type: ParameterType,
74 order: usize,
75 default_value: Option<String>,
76 ) -> Self {
77 Self {
78 name: name.into(),
79 param_type,
80 optional: true,
81 order,
82 default_value,
83 description: None,
84 }
85 }
86
87 pub fn with_description(mut self, description: impl Into<String>) -> Self {
89 self.description = Some(description.into());
90 self
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96pub enum ReturnType {
97 Single(ParameterType),
99 List(ParameterType),
101 Multiple(Vec<ParameterType>),
103 Void,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, Default)]
109pub struct FunctionMetadata {
110 pub author: Option<String>,
112 pub version: Option<String>,
114 pub description: Option<String>,
116 pub documentation: Option<String>,
118 pub tags: Vec<String>,
120 pub custom: HashMap<String, String>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct ShaclFunction {
127 pub id: String,
129 pub name: String,
131 pub parameters: Vec<FunctionParameter>,
133 pub return_type: ReturnType,
135 pub metadata: FunctionMetadata,
137 pub has_side_effects: bool,
139 pub deterministic: bool,
141}
142
143impl ShaclFunction {
144 pub fn new(
146 id: impl Into<String>,
147 name: impl Into<String>,
148 parameters: Vec<FunctionParameter>,
149 return_type: ReturnType,
150 ) -> Self {
151 Self {
152 id: id.into(),
153 name: name.into(),
154 parameters,
155 return_type,
156 metadata: FunctionMetadata::default(),
157 has_side_effects: false,
158 deterministic: true,
159 }
160 }
161
162 pub fn with_metadata(mut self, metadata: FunctionMetadata) -> Self {
164 self.metadata = metadata;
165 self
166 }
167
168 pub fn with_side_effects(mut self) -> Self {
170 self.has_side_effects = true;
171 self
172 }
173
174 pub fn non_deterministic(mut self) -> Self {
176 self.deterministic = false;
177 self
178 }
179
180 pub fn validate_arguments(&self, arguments: &HashMap<String, Term>) -> Result<()> {
182 for param in &self.parameters {
184 if !param.optional && !arguments.contains_key(¶m.name) {
185 return Err(ShaclError::ValidationEngine(format!(
186 "Missing required parameter: {}",
187 param.name
188 )));
189 }
190 }
191
192 for arg_name in arguments.keys() {
194 if !self.parameters.iter().any(|p| &p.name == arg_name) {
195 return Err(ShaclError::ValidationEngine(format!(
196 "Unknown parameter: {}",
197 arg_name
198 )));
199 }
200 }
201
202 Ok(())
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct FunctionInvocation {
209 pub function_id: String,
211 pub arguments: HashMap<String, Term>,
213 pub context: FunctionContext,
215}
216
217#[derive(Debug, Clone, Default)]
219pub struct FunctionContext {
220 pub variables: HashMap<String, Term>,
222 pub depth: usize,
224 pub max_depth: usize,
226}
227
228impl FunctionContext {
229 pub fn new() -> Self {
231 Self {
232 variables: HashMap::new(),
233 depth: 0,
234 max_depth: 100,
235 }
236 }
237
238 pub fn increment_depth(&mut self) -> Result<()> {
240 self.depth += 1;
241 if self.depth > self.max_depth {
242 return Err(ShaclError::RecursionLimit(format!(
243 "Maximum function call depth exceeded: {}",
244 self.max_depth
245 )));
246 }
247 Ok(())
248 }
249
250 pub fn decrement_depth(&mut self) {
252 if self.depth > 0 {
253 self.depth -= 1;
254 }
255 }
256}
257
258#[derive(Debug, Clone)]
260pub enum FunctionResult {
261 Single(Option<Term>),
263 Multiple(Vec<Term>),
265 Error(String),
267}
268
269impl FunctionResult {
270 pub fn value(term: Term) -> Self {
272 FunctionResult::Single(Some(term))
273 }
274
275 pub fn none() -> Self {
277 FunctionResult::Single(None)
278 }
279
280 pub fn values(terms: Vec<Term>) -> Self {
282 FunctionResult::Multiple(terms)
283 }
284
285 pub fn error(message: impl Into<String>) -> Self {
287 FunctionResult::Error(message.into())
288 }
289
290 pub fn is_error(&self) -> bool {
292 matches!(self, FunctionResult::Error(_))
293 }
294
295 pub fn as_single(&self) -> Option<&Term> {
297 match self {
298 FunctionResult::Single(Some(term)) => Some(term),
299 _ => None,
300 }
301 }
302
303 pub fn as_multiple(&self) -> Option<&[Term]> {
305 match self {
306 FunctionResult::Multiple(terms) => Some(terms),
307 _ => None,
308 }
309 }
310}
311
312pub trait FunctionExecutor: Send + Sync {
314 fn execute(
316 &self,
317 function: &ShaclFunction,
318 invocation: &FunctionInvocation,
319 store: &dyn Store,
320 ) -> Result<FunctionResult>;
321
322 fn name(&self) -> &str;
324
325 fn can_execute(&self, function: &ShaclFunction) -> bool;
327}
328
329pub struct BuiltInFunctionExecutor;
331
332impl BuiltInFunctionExecutor {
333 pub fn new() -> Self {
335 Self
336 }
337
338 fn concat(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
340 let mut result = String::new();
341 let mut i = 0;
342 loop {
343 let key = format!("arg{}", i);
344 if let Some(term) = args.get(&key) {
345 match term {
346 Term::Literal(lit) => result.push_str(lit.value()),
347 _ => result.push_str(&term.to_string()),
348 }
349 i += 1;
350 } else {
351 break;
352 }
353 }
354 Ok(FunctionResult::value(Term::Literal(
355 Literal::new_simple_literal(result),
356 )))
357 }
358
359 fn upper_case(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
361 if let Some(Term::Literal(lit)) = args.get("input") {
362 let upper = lit.value().to_uppercase();
363 Ok(FunctionResult::value(Term::Literal(
364 Literal::new_simple_literal(upper),
365 )))
366 } else {
367 Err(ShaclError::ValidationEngine(
368 "upperCase requires a string literal argument".to_string(),
369 ))
370 }
371 }
372
373 fn lower_case(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
375 if let Some(Term::Literal(lit)) = args.get("input") {
376 let lower = lit.value().to_lowercase();
377 Ok(FunctionResult::value(Term::Literal(
378 Literal::new_simple_literal(lower),
379 )))
380 } else {
381 Err(ShaclError::ValidationEngine(
382 "lowerCase requires a string literal argument".to_string(),
383 ))
384 }
385 }
386
387 fn substring(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
389 if let Some(Term::Literal(lit)) = args.get("input") {
390 let start = match args.get("start") {
391 Some(Term::Literal(l)) => l
392 .value()
393 .parse::<usize>()
394 .map_err(|_| ShaclError::ValidationEngine("Invalid start index".to_string()))?,
395 _ => {
396 return Err(ShaclError::ValidationEngine(
397 "substring requires start index".to_string(),
398 ))
399 }
400 };
401
402 let value = lit.value();
403 let result: String = if let Some(Term::Literal(l)) = args.get("length") {
404 let length = l
405 .value()
406 .parse::<usize>()
407 .map_err(|_| ShaclError::ValidationEngine("Invalid length".to_string()))?;
408 value.chars().skip(start).take(length).collect()
409 } else {
410 value.chars().skip(start).collect()
411 };
412
413 Ok(FunctionResult::value(Term::Literal(
414 Literal::new_simple_literal(result),
415 )))
416 } else {
417 Err(ShaclError::ValidationEngine(
418 "substring requires a string literal input".to_string(),
419 ))
420 }
421 }
422
423 fn str_length(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
425 if let Some(Term::Literal(lit)) = args.get("input") {
426 let length = lit.value().chars().count();
427 Ok(FunctionResult::value(Term::Literal(
428 Literal::new_typed_literal(
429 length.to_string(),
430 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#integer"),
431 ),
432 )))
433 } else {
434 Err(ShaclError::ValidationEngine(
435 "strLength requires a string literal argument".to_string(),
436 ))
437 }
438 }
439
440 fn abs(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
442 if let Some(Term::Literal(lit)) = args.get("value") {
443 let value: f64 = lit.value().parse().map_err(|_| {
444 ShaclError::ValidationEngine("abs requires a numeric argument".to_string())
445 })?;
446 let result = value.abs();
447 Ok(FunctionResult::value(Term::Literal(
448 Literal::new_typed_literal(
449 result.to_string(),
450 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#decimal"),
451 ),
452 )))
453 } else {
454 Err(ShaclError::ValidationEngine(
455 "abs requires a numeric literal argument".to_string(),
456 ))
457 }
458 }
459
460 fn ceil(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
462 if let Some(Term::Literal(lit)) = args.get("value") {
463 let value: f64 = lit.value().parse().map_err(|_| {
464 ShaclError::ValidationEngine("ceil requires a numeric argument".to_string())
465 })?;
466 let result = value.ceil();
467 Ok(FunctionResult::value(Term::Literal(
468 Literal::new_typed_literal(
469 result.to_string(),
470 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#integer"),
471 ),
472 )))
473 } else {
474 Err(ShaclError::ValidationEngine(
475 "ceil requires a numeric literal argument".to_string(),
476 ))
477 }
478 }
479
480 fn floor(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
482 if let Some(Term::Literal(lit)) = args.get("value") {
483 let value: f64 = lit.value().parse().map_err(|_| {
484 ShaclError::ValidationEngine("floor requires a numeric argument".to_string())
485 })?;
486 let result = value.floor();
487 Ok(FunctionResult::value(Term::Literal(
488 Literal::new_typed_literal(
489 result.to_string(),
490 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#integer"),
491 ),
492 )))
493 } else {
494 Err(ShaclError::ValidationEngine(
495 "floor requires a numeric literal argument".to_string(),
496 ))
497 }
498 }
499
500 fn round(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
502 if let Some(Term::Literal(lit)) = args.get("value") {
503 let value: f64 = lit.value().parse().map_err(|_| {
504 ShaclError::ValidationEngine("round requires a numeric argument".to_string())
505 })?;
506 let result = value.round();
507 Ok(FunctionResult::value(Term::Literal(
508 Literal::new_typed_literal(
509 result.to_string(),
510 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#integer"),
511 ),
512 )))
513 } else {
514 Err(ShaclError::ValidationEngine(
515 "round requires a numeric literal argument".to_string(),
516 ))
517 }
518 }
519
520 fn contains(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
522 if let (Some(Term::Literal(haystack)), Some(Term::Literal(needle))) =
523 (args.get("string"), args.get("substring"))
524 {
525 let result = haystack.value().contains(needle.value());
526 Ok(FunctionResult::value(Term::Literal(
527 Literal::new_typed_literal(
528 result.to_string(),
529 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#boolean"),
530 ),
531 )))
532 } else {
533 Err(ShaclError::ValidationEngine(
534 "contains requires string and substring arguments".to_string(),
535 ))
536 }
537 }
538
539 fn starts_with(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
541 if let (Some(Term::Literal(string)), Some(Term::Literal(prefix))) =
542 (args.get("string"), args.get("prefix"))
543 {
544 let result = string.value().starts_with(prefix.value());
545 Ok(FunctionResult::value(Term::Literal(
546 Literal::new_typed_literal(
547 result.to_string(),
548 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#boolean"),
549 ),
550 )))
551 } else {
552 Err(ShaclError::ValidationEngine(
553 "startsWith requires string and prefix arguments".to_string(),
554 ))
555 }
556 }
557
558 fn ends_with(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
560 if let (Some(Term::Literal(string)), Some(Term::Literal(suffix))) =
561 (args.get("string"), args.get("suffix"))
562 {
563 let result = string.value().ends_with(suffix.value());
564 Ok(FunctionResult::value(Term::Literal(
565 Literal::new_typed_literal(
566 result.to_string(),
567 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#boolean"),
568 ),
569 )))
570 } else {
571 Err(ShaclError::ValidationEngine(
572 "endsWith requires string and suffix arguments".to_string(),
573 ))
574 }
575 }
576
577 fn trim(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
579 if let Some(Term::Literal(lit)) = args.get("input") {
580 let trimmed = lit.value().trim();
581 Ok(FunctionResult::value(Term::Literal(
582 Literal::new_simple_literal(trimmed),
583 )))
584 } else {
585 Err(ShaclError::ValidationEngine(
586 "trim requires a string literal argument".to_string(),
587 ))
588 }
589 }
590
591 fn replace(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
593 if let (
594 Some(Term::Literal(input)),
595 Some(Term::Literal(pattern)),
596 Some(Term::Literal(replacement)),
597 ) = (
598 args.get("input"),
599 args.get("pattern"),
600 args.get("replacement"),
601 ) {
602 let result = input.value().replace(pattern.value(), replacement.value());
603 Ok(FunctionResult::value(Term::Literal(
604 Literal::new_simple_literal(result),
605 )))
606 } else {
607 Err(ShaclError::ValidationEngine(
608 "replace requires input, pattern, and replacement arguments".to_string(),
609 ))
610 }
611 }
612
613 fn min(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
615 if let (Some(Term::Literal(val1)), Some(Term::Literal(val2))) =
616 (args.get("value1"), args.get("value2"))
617 {
618 let v1: f64 = val1.value().parse().map_err(|_| {
619 ShaclError::ValidationEngine("min requires numeric arguments".to_string())
620 })?;
621 let v2: f64 = val2.value().parse().map_err(|_| {
622 ShaclError::ValidationEngine("min requires numeric arguments".to_string())
623 })?;
624 let result = v1.min(v2);
625 Ok(FunctionResult::value(Term::Literal(
626 Literal::new_typed_literal(
627 result.to_string(),
628 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#decimal"),
629 ),
630 )))
631 } else {
632 Err(ShaclError::ValidationEngine(
633 "min requires two numeric literal arguments".to_string(),
634 ))
635 }
636 }
637
638 fn max(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
640 if let (Some(Term::Literal(val1)), Some(Term::Literal(val2))) =
641 (args.get("value1"), args.get("value2"))
642 {
643 let v1: f64 = val1.value().parse().map_err(|_| {
644 ShaclError::ValidationEngine("max requires numeric arguments".to_string())
645 })?;
646 let v2: f64 = val2.value().parse().map_err(|_| {
647 ShaclError::ValidationEngine("max requires numeric arguments".to_string())
648 })?;
649 let result = v1.max(v2);
650 Ok(FunctionResult::value(Term::Literal(
651 Literal::new_typed_literal(
652 result.to_string(),
653 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#decimal"),
654 ),
655 )))
656 } else {
657 Err(ShaclError::ValidationEngine(
658 "max requires two numeric literal arguments".to_string(),
659 ))
660 }
661 }
662
663 fn sqrt(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
665 if let Some(Term::Literal(lit)) = args.get("value") {
666 let value: f64 = lit.value().parse().map_err(|_| {
667 ShaclError::ValidationEngine("sqrt requires a numeric argument".to_string())
668 })?;
669 if value < 0.0 {
670 return Err(ShaclError::ValidationEngine(
671 "sqrt requires a non-negative argument".to_string(),
672 ));
673 }
674 let result = value.sqrt();
675 Ok(FunctionResult::value(Term::Literal(
676 Literal::new_typed_literal(
677 result.to_string(),
678 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#decimal"),
679 ),
680 )))
681 } else {
682 Err(ShaclError::ValidationEngine(
683 "sqrt requires a numeric literal argument".to_string(),
684 ))
685 }
686 }
687
688 fn pow(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
690 if let (Some(Term::Literal(base)), Some(Term::Literal(exponent))) =
691 (args.get("base"), args.get("exponent"))
692 {
693 let base_val: f64 = base.value().parse().map_err(|_| {
694 ShaclError::ValidationEngine("pow requires numeric arguments".to_string())
695 })?;
696 let exp_val: f64 = exponent.value().parse().map_err(|_| {
697 ShaclError::ValidationEngine("pow requires numeric arguments".to_string())
698 })?;
699 let result = base_val.powf(exp_val);
700 Ok(FunctionResult::value(Term::Literal(
701 Literal::new_typed_literal(
702 result.to_string(),
703 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#decimal"),
704 ),
705 )))
706 } else {
707 Err(ShaclError::ValidationEngine(
708 "pow requires two numeric literal arguments".to_string(),
709 ))
710 }
711 }
712
713 fn encode_uri(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
715 if let Some(Term::Literal(lit)) = args.get("input") {
716 use url::form_urlencoded;
717 let encoded: String = form_urlencoded::byte_serialize(lit.value().as_bytes()).collect();
718 Ok(FunctionResult::value(Term::Literal(
719 Literal::new_simple_literal(encoded),
720 )))
721 } else {
722 Err(ShaclError::ValidationEngine(
723 "encodeURI requires a string literal argument".to_string(),
724 ))
725 }
726 }
727
728 fn decode_uri(&self, args: &HashMap<String, Term>) -> Result<FunctionResult> {
730 if let Some(Term::Literal(lit)) = args.get("input") {
731 use url::form_urlencoded;
732 let decoded = form_urlencoded::parse(lit.value().as_bytes())
733 .map(|(key, _)| key)
734 .collect::<Vec<_>>()
735 .join("");
736 Ok(FunctionResult::value(Term::Literal(
737 Literal::new_simple_literal(decoded),
738 )))
739 } else {
740 Err(ShaclError::ValidationEngine(
741 "decodeURI requires a string literal argument".to_string(),
742 ))
743 }
744 }
745}
746
747impl Default for BuiltInFunctionExecutor {
748 fn default() -> Self {
749 Self::new()
750 }
751}
752
753impl FunctionExecutor for BuiltInFunctionExecutor {
754 fn execute(
755 &self,
756 function: &ShaclFunction,
757 invocation: &FunctionInvocation,
758 _store: &dyn Store,
759 ) -> Result<FunctionResult> {
760 function.validate_arguments(&invocation.arguments)?;
762
763 match function.name.as_str() {
765 "concat" => self.concat(&invocation.arguments),
767 "upperCase" => self.upper_case(&invocation.arguments),
768 "lowerCase" => self.lower_case(&invocation.arguments),
769 "substring" => self.substring(&invocation.arguments),
770 "strLength" => self.str_length(&invocation.arguments),
771 "contains" => self.contains(&invocation.arguments),
772 "startsWith" => self.starts_with(&invocation.arguments),
773 "endsWith" => self.ends_with(&invocation.arguments),
774 "trim" => self.trim(&invocation.arguments),
775 "replace" => self.replace(&invocation.arguments),
776 "abs" => self.abs(&invocation.arguments),
778 "ceil" => self.ceil(&invocation.arguments),
779 "floor" => self.floor(&invocation.arguments),
780 "round" => self.round(&invocation.arguments),
781 "min" => self.min(&invocation.arguments),
782 "max" => self.max(&invocation.arguments),
783 "sqrt" => self.sqrt(&invocation.arguments),
784 "pow" => self.pow(&invocation.arguments),
785 "encodeURI" => self.encode_uri(&invocation.arguments),
787 "decodeURI" => self.decode_uri(&invocation.arguments),
788 _ => Err(ShaclError::UnsupportedOperation(format!(
789 "Unknown built-in function: {}",
790 function.name
791 ))),
792 }
793 }
794
795 fn name(&self) -> &str {
796 "BuiltInFunctionExecutor"
797 }
798
799 fn can_execute(&self, function: &ShaclFunction) -> bool {
800 matches!(
801 function.name.as_str(),
802 "concat"
803 | "upperCase"
804 | "lowerCase"
805 | "substring"
806 | "strLength"
807 | "contains"
808 | "startsWith"
809 | "endsWith"
810 | "trim"
811 | "replace"
812 | "abs"
813 | "ceil"
814 | "floor"
815 | "round"
816 | "min"
817 | "max"
818 | "sqrt"
819 | "pow"
820 | "encodeURI"
821 | "decodeURI"
822 )
823 }
824}
825
826pub struct FunctionRegistry {
828 functions: HashMap<String, ShaclFunction>,
830 executors: Vec<Arc<dyn FunctionExecutor>>,
832}
833
834impl FunctionRegistry {
835 pub fn new() -> Self {
837 let mut registry = Self {
838 functions: HashMap::new(),
839 executors: Vec::new(),
840 };
841
842 registry.add_executor(Arc::new(BuiltInFunctionExecutor::new()));
844
845 registry.register_built_in_functions();
847
848 registry
849 }
850
851 fn register_built_in_functions(&mut self) {
853 self.register_function(ShaclFunction::new(
855 "http://www.w3.org/ns/shacl#concat",
856 "concat",
857 vec![],
858 ReturnType::Single(ParameterType::String),
859 ))
860 .ok();
861
862 self.register_function(ShaclFunction::new(
864 "http://www.w3.org/ns/shacl#upperCase",
865 "upperCase",
866 vec![FunctionParameter::required(
867 "input",
868 ParameterType::String,
869 0,
870 )],
871 ReturnType::Single(ParameterType::String),
872 ))
873 .ok();
874
875 self.register_function(ShaclFunction::new(
877 "http://www.w3.org/ns/shacl#lowerCase",
878 "lowerCase",
879 vec![FunctionParameter::required(
880 "input",
881 ParameterType::String,
882 0,
883 )],
884 ReturnType::Single(ParameterType::String),
885 ))
886 .ok();
887
888 self.register_function(ShaclFunction::new(
890 "http://www.w3.org/ns/shacl#substring",
891 "substring",
892 vec![
893 FunctionParameter::required("input", ParameterType::String, 0),
894 FunctionParameter::required("start", ParameterType::Integer, 1),
895 FunctionParameter::optional("length", ParameterType::Integer, 2, None),
896 ],
897 ReturnType::Single(ParameterType::String),
898 ))
899 .ok();
900
901 self.register_function(ShaclFunction::new(
903 "http://www.w3.org/ns/shacl#strLength",
904 "strLength",
905 vec![FunctionParameter::required(
906 "input",
907 ParameterType::String,
908 0,
909 )],
910 ReturnType::Single(ParameterType::Integer),
911 ))
912 .ok();
913
914 self.register_function(ShaclFunction::new(
916 "http://www.w3.org/ns/shacl#contains",
917 "contains",
918 vec![
919 FunctionParameter::required("string", ParameterType::String, 0),
920 FunctionParameter::required("substring", ParameterType::String, 1),
921 ],
922 ReturnType::Single(ParameterType::Boolean),
923 ))
924 .ok();
925
926 self.register_function(ShaclFunction::new(
928 "http://www.w3.org/ns/shacl#startsWith",
929 "startsWith",
930 vec![
931 FunctionParameter::required("string", ParameterType::String, 0),
932 FunctionParameter::required("prefix", ParameterType::String, 1),
933 ],
934 ReturnType::Single(ParameterType::Boolean),
935 ))
936 .ok();
937
938 self.register_function(ShaclFunction::new(
940 "http://www.w3.org/ns/shacl#endsWith",
941 "endsWith",
942 vec![
943 FunctionParameter::required("string", ParameterType::String, 0),
944 FunctionParameter::required("suffix", ParameterType::String, 1),
945 ],
946 ReturnType::Single(ParameterType::Boolean),
947 ))
948 .ok();
949
950 self.register_function(ShaclFunction::new(
954 "http://www.w3.org/ns/shacl#abs",
955 "abs",
956 vec![FunctionParameter::required(
957 "value",
958 ParameterType::Decimal,
959 0,
960 )],
961 ReturnType::Single(ParameterType::Decimal),
962 ))
963 .ok();
964
965 self.register_function(ShaclFunction::new(
967 "http://www.w3.org/ns/shacl#ceil",
968 "ceil",
969 vec![FunctionParameter::required(
970 "value",
971 ParameterType::Decimal,
972 0,
973 )],
974 ReturnType::Single(ParameterType::Integer),
975 ))
976 .ok();
977
978 self.register_function(ShaclFunction::new(
980 "http://www.w3.org/ns/shacl#floor",
981 "floor",
982 vec![FunctionParameter::required(
983 "value",
984 ParameterType::Decimal,
985 0,
986 )],
987 ReturnType::Single(ParameterType::Integer),
988 ))
989 .ok();
990
991 self.register_function(ShaclFunction::new(
993 "http://www.w3.org/ns/shacl#round",
994 "round",
995 vec![FunctionParameter::required(
996 "value",
997 ParameterType::Decimal,
998 0,
999 )],
1000 ReturnType::Single(ParameterType::Integer),
1001 ))
1002 .ok();
1003
1004 self.register_function(ShaclFunction::new(
1008 "http://www.w3.org/ns/shacl#trim",
1009 "trim",
1010 vec![FunctionParameter::required(
1011 "input",
1012 ParameterType::String,
1013 0,
1014 )],
1015 ReturnType::Single(ParameterType::String),
1016 ))
1017 .ok();
1018
1019 self.register_function(ShaclFunction::new(
1021 "http://www.w3.org/ns/shacl#replace",
1022 "replace",
1023 vec![
1024 FunctionParameter::required("input", ParameterType::String, 0),
1025 FunctionParameter::required("pattern", ParameterType::String, 1),
1026 FunctionParameter::required("replacement", ParameterType::String, 2),
1027 ],
1028 ReturnType::Single(ParameterType::String),
1029 ))
1030 .ok();
1031
1032 self.register_function(ShaclFunction::new(
1036 "http://www.w3.org/ns/shacl#min",
1037 "min",
1038 vec![
1039 FunctionParameter::required("value1", ParameterType::Decimal, 0),
1040 FunctionParameter::required("value2", ParameterType::Decimal, 1),
1041 ],
1042 ReturnType::Single(ParameterType::Decimal),
1043 ))
1044 .ok();
1045
1046 self.register_function(ShaclFunction::new(
1048 "http://www.w3.org/ns/shacl#max",
1049 "max",
1050 vec![
1051 FunctionParameter::required("value1", ParameterType::Decimal, 0),
1052 FunctionParameter::required("value2", ParameterType::Decimal, 1),
1053 ],
1054 ReturnType::Single(ParameterType::Decimal),
1055 ))
1056 .ok();
1057
1058 self.register_function(ShaclFunction::new(
1060 "http://www.w3.org/ns/shacl#sqrt",
1061 "sqrt",
1062 vec![FunctionParameter::required(
1063 "value",
1064 ParameterType::Decimal,
1065 0,
1066 )],
1067 ReturnType::Single(ParameterType::Decimal),
1068 ))
1069 .ok();
1070
1071 self.register_function(ShaclFunction::new(
1073 "http://www.w3.org/ns/shacl#pow",
1074 "pow",
1075 vec![
1076 FunctionParameter::required("base", ParameterType::Decimal, 0),
1077 FunctionParameter::required("exponent", ParameterType::Decimal, 1),
1078 ],
1079 ReturnType::Single(ParameterType::Decimal),
1080 ))
1081 .ok();
1082
1083 self.register_function(ShaclFunction::new(
1087 "http://www.w3.org/ns/shacl#encodeURI",
1088 "encodeURI",
1089 vec![FunctionParameter::required(
1090 "input",
1091 ParameterType::String,
1092 0,
1093 )],
1094 ReturnType::Single(ParameterType::String),
1095 ))
1096 .ok();
1097
1098 self.register_function(ShaclFunction::new(
1100 "http://www.w3.org/ns/shacl#decodeURI",
1101 "decodeURI",
1102 vec![FunctionParameter::required(
1103 "input",
1104 ParameterType::String,
1105 0,
1106 )],
1107 ReturnType::Single(ParameterType::String),
1108 ))
1109 .ok();
1110 }
1111
1112 pub fn register_function(&mut self, function: ShaclFunction) -> Result<()> {
1114 self.functions.insert(function.id.clone(), function);
1115 Ok(())
1116 }
1117
1118 pub fn get_function(&self, id: &str) -> Option<&ShaclFunction> {
1120 self.functions.get(id)
1121 }
1122
1123 pub fn add_executor(&mut self, executor: Arc<dyn FunctionExecutor>) {
1125 self.executors.push(executor);
1126 }
1127
1128 pub fn execute(
1130 &self,
1131 invocation: &FunctionInvocation,
1132 store: &dyn Store,
1133 ) -> Result<FunctionResult> {
1134 let function = self.get_function(&invocation.function_id).ok_or_else(|| {
1136 ShaclError::ValidationEngine(format!("Function not found: {}", invocation.function_id))
1137 })?;
1138
1139 for executor in &self.executors {
1141 if executor.can_execute(function) {
1142 return executor.execute(function, invocation, store);
1143 }
1144 }
1145
1146 Err(ShaclError::ValidationEngine(format!(
1147 "No executor found for function: {}",
1148 invocation.function_id
1149 )))
1150 }
1151
1152 pub fn list_functions(&self) -> Vec<&ShaclFunction> {
1154 self.functions.values().collect()
1155 }
1156
1157 pub fn function_count(&self) -> usize {
1159 self.functions.len()
1160 }
1161}
1162
1163impl Default for FunctionRegistry {
1164 fn default() -> Self {
1165 Self::new()
1166 }
1167}
1168
1169#[cfg(test)]
1170mod tests {
1171 use super::*;
1172
1173 #[test]
1174 fn test_function_parameter_creation() {
1175 let param = FunctionParameter::required("test", ParameterType::String, 0);
1176 assert_eq!(param.name, "test");
1177 assert!(!param.optional);
1178 assert_eq!(param.order, 0);
1179 }
1180
1181 #[test]
1182 fn test_function_registry_creation() {
1183 let registry = FunctionRegistry::new();
1184 assert!(registry.function_count() > 0);
1185 }
1186
1187 #[test]
1188 fn test_built_in_functions_registered() {
1189 let registry = FunctionRegistry::new();
1190 assert!(registry
1191 .get_function("http://www.w3.org/ns/shacl#concat")
1192 .is_some());
1193 assert!(registry
1194 .get_function("http://www.w3.org/ns/shacl#upperCase")
1195 .is_some());
1196 }
1197
1198 #[test]
1199 fn test_function_validation() {
1200 let function = ShaclFunction::new(
1201 "test:func",
1202 "testFunc",
1203 vec![FunctionParameter::required(
1204 "arg1",
1205 ParameterType::String,
1206 0,
1207 )],
1208 ReturnType::Single(ParameterType::String),
1209 );
1210
1211 let mut args = HashMap::new();
1212 args.insert(
1213 "arg1".to_string(),
1214 Term::Literal(Literal::new_simple_literal("test")),
1215 );
1216
1217 assert!(function.validate_arguments(&args).is_ok());
1218 }
1219
1220 #[test]
1221 fn test_function_validation_missing_required() {
1222 let function = ShaclFunction::new(
1223 "test:func",
1224 "testFunc",
1225 vec![FunctionParameter::required(
1226 "arg1",
1227 ParameterType::String,
1228 0,
1229 )],
1230 ReturnType::Single(ParameterType::String),
1231 );
1232
1233 let args = HashMap::new();
1234 assert!(function.validate_arguments(&args).is_err());
1235 }
1236}