ontocore_plugin_shacl/
lib.rs1use ontocore_catalog::OntologyCatalog;
2use ontocore_core::DiagnosticSeverity;
3use ontocore_plugin::{plugin_diagnostic, ValidatorPlugin};
4use std::path::{Path, PathBuf};
5
6pub const PLUGIN_ID: &str = "ontocode.shacl-validator";
7
8#[derive(Debug, Clone)]
9pub struct ShaclValidatorPlugin {
10 pub shapes_dir: PathBuf,
11}
12
13impl ShaclValidatorPlugin {
14 pub fn new(workspace: &Path, shapes_dir: Option<&str>) -> Self {
15 let rel = shapes_dir.unwrap_or("shapes");
16 Self { shapes_dir: workspace.join(rel) }
17 }
18}
19
20impl ValidatorPlugin for ShaclValidatorPlugin {
21 fn id(&self) -> &str {
22 PLUGIN_ID
23 }
24
25 fn validate(
26 &self,
27 _catalog: &OntologyCatalog,
28 workspace: &Path,
29 ) -> Vec<ontocore_core::Diagnostic> {
30 if !self.shapes_dir.is_dir() {
31 return vec![plugin_diagnostic(
32 PLUGIN_ID,
33 "shapes_missing",
34 DiagnosticSeverity::Info,
35 format!(
36 "SHACL shapes directory '{}' not found; add .ttl shape files to enable validation",
37 self.shapes_dir.display()
38 ),
39 workspace.to_path_buf(),
40 None,
41 )];
42 }
43 let shape_files: Vec<_> = std::fs::read_dir(&self.shapes_dir)
44 .ok()
45 .into_iter()
46 .flatten()
47 .filter_map(|e| e.ok())
48 .filter(|e| {
49 e.path()
50 .extension()
51 .and_then(|x| x.to_str())
52 .is_some_and(|ext| matches!(ext, "ttl" | "rdf" | "shacl"))
53 })
54 .collect();
55 if shape_files.is_empty() {
56 return vec![plugin_diagnostic(
57 PLUGIN_ID,
58 "shapes_empty",
59 DiagnosticSeverity::Info,
60 format!("No SHACL shape files in '{}'", self.shapes_dir.display()),
61 workspace.to_path_buf(),
62 None,
63 )];
64 }
65 vec![plugin_diagnostic(
66 PLUGIN_ID,
67 "shacl_pending",
68 DiagnosticSeverity::Info,
69 format!(
70 "Found {} SHACL shape file(s); full rudof validation ships in a future release",
71 shape_files.len()
72 ),
73 workspace.to_path_buf(),
74 None,
75 )]
76 }
77}