lsp_max/runtime/control_plane/admission/
validation.rs1use super::types::GraphAdmissionError;
2
3pub fn resolve_db_path() -> std::path::PathBuf {
4 if let Ok(path_str) = std::env::var("LSP_MAX_DB_PATH") {
5 if !path_str.trim().is_empty() {
6 return std::path::PathBuf::from(path_str);
7 }
8 }
9
10 let config_path = if let Ok(path_str) = std::env::var("LSP_MAX_CONFIG") {
11 Some(std::path::PathBuf::from(path_str))
12 } else if let Ok(home) = std::env::var("HOME") {
13 Some(std::path::PathBuf::from(home).join(".lsp-max-config.json"))
14 } else {
15 Some(std::path::PathBuf::from(".lsp-max-config.json"))
16 };
17
18 if let Some(path) = config_path {
19 if path.exists() {
20 if let Ok(content) = std::fs::read_to_string(path) {
21 if let Ok(value) = serde_json::from_str::<serde_json::Value>(&content) {
22 if let Some(db_path_val) = value.get("database_path").and_then(|v| v.as_str()) {
23 if !db_path_val.trim().is_empty() {
24 return std::path::PathBuf::from(db_path_val);
25 }
26 }
27 }
28 }
29 }
30 }
31
32 let is_test =
33 std::env::var("CARGO_MANIFEST_DIR").is_ok() || std::env::var("LSP_MAX_TEST").is_ok();
34
35 if is_test {
36 let temp_dir = std::env::temp_dir();
37 temp_dir.join(format!("lsp-max-db-{}", uuid::Uuid::new_v4()))
38 } else if let Ok(home) = std::env::var("HOME") {
39 std::path::PathBuf::from(home).join(".local/share/lsp-max/db")
40 } else {
41 std::env::temp_dir().join("lsp-max-db")
42 }
43}
44
45pub fn validate_shacl_shapes(quads: &[oxigraph::model::Quad]) -> Result<(), GraphAdmissionError> {
46 for quad in quads {
47 let predicate_str = quad.predicate.as_str();
48 let object_str = match &quad.object {
49 oxigraph::model::Term::Literal(l) => l.value(),
50 oxigraph::model::Term::NamedNode(n) => n.as_str(),
51 oxigraph::model::Term::BlankNode(b) => b.as_str(),
52 _ => continue, };
54
55 if predicate_str == "urn:lsp-max:core:severity"
57 && object_str != "error"
58 && object_str != "warning"
59 && object_str != "info"
60 && object_str != "hint"
61 {
62 return Err(GraphAdmissionError::NamespaceViolation(format!(
63 "SHACL PropertyShape violation: invalid severity value '{}'",
64 object_str
65 )));
66 }
67
68 if (predicate_str == "urn:lsp-max:core:startLine"
70 || predicate_str == "urn:lsp-max:core:startCharacter"
71 || predicate_str == "urn:lsp-max:core:endLine"
72 || predicate_str == "urn:lsp-max:core:endCharacter")
73 && object_str.parse::<u32>().is_err()
74 {
75 return Err(GraphAdmissionError::ParsingFailed(format!(
76 "SHACL PropertyShape violation: expected integer for {}, got '{}'",
77 predicate_str, object_str
78 )));
79 }
80
81 if predicate_str == "urn:lsp-max:core:positionEncoding"
83 && object_str != "utf-8"
84 && object_str != "utf-16"
85 && object_str != "utf-32"
86 {
87 return Err(GraphAdmissionError::NamespaceViolation(format!(
88 "SHACL PropertyShape violation: invalid positionEncoding '{}'",
89 object_str
90 )));
91 }
92
93 if predicate_str == "urn:lsp-max:core:version" && object_str.is_empty() {
95 return Err(GraphAdmissionError::NamespaceViolation(
96 "SHACL PropertyShape violation: version is empty".to_string(),
97 ));
98 }
99
100 if predicate_str == "urn:lsp-max:core:projectRoot" && object_str.is_empty() {
102 return Err(GraphAdmissionError::NamespaceViolation(
103 "SHACL PropertyShape violation: projectRoot is empty".to_string(),
104 ));
105 }
106
107 if predicate_str == "urn:lsp-max:core:uri" && object_str.is_empty() {
109 return Err(GraphAdmissionError::NamespaceViolation(
110 "SHACL PropertyShape violation: uri is empty".to_string(),
111 ));
112 }
113
114 if predicate_str == "urn:lsp-max:core:languageId" && object_str.is_empty() {
116 return Err(GraphAdmissionError::NamespaceViolation(
117 "SHACL PropertyShape violation: languageId is empty".to_string(),
118 ));
119 }
120 }
121 Ok(())
122}
123
124pub struct StoreFactory;
125
126impl StoreFactory {
127 pub fn open() -> Result<oxigraph::store::Store, String> {
128 let path = resolve_db_path();
129 if let Some(parent) = path.parent() {
130 let _ = std::fs::create_dir_all(parent);
131 }
132 let _ = std::fs::create_dir_all(&path);
133 oxigraph::store::Store::new().map_err(|e| e.to_string())
134 }
135}