1use thiserror::Error;
8
9use crate::update_graph_management_types::{
10 GraphManagementOp, GraphManagementResult, GraphManagementTarget,
11};
12
13#[derive(Debug, Error)]
19pub enum GraphManagementProtocolError {
20 #[error("parse error: {0}")]
22 Parse(String),
23
24 #[error("invalid IRI: {0}")]
26 InvalidIri(String),
27}
28
29#[derive(Debug, Clone)]
35pub struct GraphManagementHttpResponse {
36 pub status_code: u16,
38 pub body: String,
40 pub result: Option<GraphManagementResult>,
42}
43
44impl GraphManagementHttpResponse {
45 pub fn ok(result: GraphManagementResult) -> Self {
47 let body = format!(
48 "OK: {} triples affected, {} graphs affected",
49 result.triples_affected,
50 result.graphs_affected.len()
51 );
52 Self {
53 status_code: 200,
54 body,
55 result: Some(result),
56 }
57 }
58
59 pub fn error(status_code: u16, message: impl Into<String>) -> Self {
61 Self {
62 status_code,
63 body: message.into(),
64 result: None,
65 }
66 }
67
68 pub fn is_success(&self) -> bool {
70 self.status_code >= 200 && self.status_code < 300
71 }
72}
73
74pub struct GraphManagementParser;
95
96impl GraphManagementParser {
97 pub fn parse(input: &str) -> Result<GraphManagementOp, GraphManagementProtocolError> {
107 let tokens: Vec<&str> = input.split_whitespace().collect();
108 if tokens.is_empty() {
109 return Err(GraphManagementProtocolError::Parse(
110 "empty input".to_string(),
111 ));
112 }
113
114 let keyword = tokens[0].to_uppercase();
115 match keyword.as_str() {
116 "LOAD" => Self::parse_load(&tokens[1..]),
117 "CLEAR" => Self::parse_clear(&tokens[1..]),
118 "DROP" => Self::parse_drop(&tokens[1..]),
119 "CREATE" => Self::parse_create(&tokens[1..]),
120 "COPY" => Self::parse_copy(&tokens[1..]),
121 "MOVE" => Self::parse_move(&tokens[1..]),
122 "ADD" => Self::parse_add(&tokens[1..]),
123 other => Err(GraphManagementProtocolError::Parse(format!(
124 "unknown graph management keyword: {other}"
125 ))),
126 }
127 }
128
129 fn parse_load(tokens: &[&str]) -> Result<GraphManagementOp, GraphManagementProtocolError> {
134 let mut pos = 0;
135 let silent = Self::consume_silent(tokens, &mut pos);
136
137 let iri = Self::consume_iri(tokens, &mut pos)?;
138
139 let into_graph = if pos < tokens.len()
140 && tokens[pos].to_uppercase() == "INTO"
141 && pos + 1 < tokens.len()
142 && tokens[pos + 1].to_uppercase() == "GRAPH"
143 {
144 pos += 2; Some(Self::consume_iri(tokens, &mut pos)?)
146 } else {
147 None
148 };
149
150 Ok(GraphManagementOp::Load {
151 iri,
152 into_graph,
153 silent,
154 })
155 }
156
157 fn parse_clear(tokens: &[&str]) -> Result<GraphManagementOp, GraphManagementProtocolError> {
158 let mut pos = 0;
159 let silent = Self::consume_silent(tokens, &mut pos);
160 let target = Self::consume_graph_ref_all(tokens, &mut pos)?;
161 Ok(GraphManagementOp::Clear { target, silent })
162 }
163
164 fn parse_drop(tokens: &[&str]) -> Result<GraphManagementOp, GraphManagementProtocolError> {
165 let mut pos = 0;
166 let silent = Self::consume_silent(tokens, &mut pos);
167 let target = Self::consume_graph_ref_all(tokens, &mut pos)?;
168 Ok(GraphManagementOp::Drop { target, silent })
169 }
170
171 fn parse_create(tokens: &[&str]) -> Result<GraphManagementOp, GraphManagementProtocolError> {
172 let mut pos = 0;
173 let silent = Self::consume_silent(tokens, &mut pos);
174
175 if pos < tokens.len() && tokens[pos].to_uppercase() == "GRAPH" {
176 pos += 1;
177 }
178
179 let graph = Self::consume_iri(tokens, &mut pos)?;
180 Ok(GraphManagementOp::Create { graph, silent })
181 }
182
183 fn parse_copy(tokens: &[&str]) -> Result<GraphManagementOp, GraphManagementProtocolError> {
184 let mut pos = 0;
185 let silent = Self::consume_silent(tokens, &mut pos);
186 let source = Self::consume_graph_or_default(tokens, &mut pos)?;
187
188 if pos >= tokens.len() || tokens[pos].to_uppercase() != "TO" {
189 return Err(GraphManagementProtocolError::Parse(
190 "expected TO after source graph in COPY".to_string(),
191 ));
192 }
193 pos += 1;
194
195 let destination = Self::consume_graph_or_default(tokens, &mut pos)?;
196 Ok(GraphManagementOp::Copy {
197 source,
198 destination,
199 silent,
200 })
201 }
202
203 fn parse_move(tokens: &[&str]) -> Result<GraphManagementOp, GraphManagementProtocolError> {
204 let mut pos = 0;
205 let silent = Self::consume_silent(tokens, &mut pos);
206 let source = Self::consume_graph_or_default(tokens, &mut pos)?;
207
208 if pos >= tokens.len() || tokens[pos].to_uppercase() != "TO" {
209 return Err(GraphManagementProtocolError::Parse(
210 "expected TO after source graph in MOVE".to_string(),
211 ));
212 }
213 pos += 1;
214
215 let destination = Self::consume_graph_or_default(tokens, &mut pos)?;
216 Ok(GraphManagementOp::Move {
217 source,
218 destination,
219 silent,
220 })
221 }
222
223 fn parse_add(tokens: &[&str]) -> Result<GraphManagementOp, GraphManagementProtocolError> {
224 let mut pos = 0;
225 let silent = Self::consume_silent(tokens, &mut pos);
226 let source = Self::consume_graph_or_default(tokens, &mut pos)?;
227
228 if pos >= tokens.len() || tokens[pos].to_uppercase() != "TO" {
229 return Err(GraphManagementProtocolError::Parse(
230 "expected TO after source graph in ADD".to_string(),
231 ));
232 }
233 pos += 1;
234
235 let destination = Self::consume_graph_or_default(tokens, &mut pos)?;
236 Ok(GraphManagementOp::Add {
237 source,
238 destination,
239 silent,
240 })
241 }
242
243 fn consume_silent(tokens: &[&str], pos: &mut usize) -> bool {
249 if *pos < tokens.len() && tokens[*pos].to_uppercase() == "SILENT" {
250 *pos += 1;
251 true
252 } else {
253 false
254 }
255 }
256
257 fn consume_iri(
259 tokens: &[&str],
260 pos: &mut usize,
261 ) -> Result<String, GraphManagementProtocolError> {
262 if *pos >= tokens.len() {
263 return Err(GraphManagementProtocolError::Parse(
264 "expected IRI but found end of input".to_string(),
265 ));
266 }
267
268 let token = tokens[*pos];
269 if token.starts_with('<') && token.ends_with('>') && token.len() >= 2 {
270 *pos += 1;
271 Ok(token[1..token.len() - 1].to_owned())
272 } else {
273 Err(GraphManagementProtocolError::InvalidIri(format!(
274 "expected <iri>, got: {token}"
275 )))
276 }
277 }
278
279 fn consume_graph_ref_all(
281 tokens: &[&str],
282 pos: &mut usize,
283 ) -> Result<GraphManagementTarget, GraphManagementProtocolError> {
284 if *pos >= tokens.len() {
285 return Err(GraphManagementProtocolError::Parse(
286 "expected graph reference but found end of input".to_string(),
287 ));
288 }
289
290 match tokens[*pos].to_uppercase().as_str() {
291 "DEFAULT" => {
292 *pos += 1;
293 Ok(GraphManagementTarget::Default)
294 }
295 "NAMED" => {
296 *pos += 1;
297 Ok(GraphManagementTarget::AllNamed)
298 }
299 "ALL" => {
300 *pos += 1;
301 Ok(GraphManagementTarget::All)
302 }
303 "GRAPH" => {
304 *pos += 1;
305 let iri = Self::consume_iri(tokens, pos)?;
306 Ok(GraphManagementTarget::Named(iri))
307 }
308 other => Err(GraphManagementProtocolError::Parse(format!(
309 "expected DEFAULT | NAMED | ALL | GRAPH <iri>, got: {other}"
310 ))),
311 }
312 }
313
314 fn consume_graph_or_default(
316 tokens: &[&str],
317 pos: &mut usize,
318 ) -> Result<GraphManagementTarget, GraphManagementProtocolError> {
319 if *pos >= tokens.len() {
320 return Err(GraphManagementProtocolError::Parse(
321 "expected graph or DEFAULT but found end of input".to_string(),
322 ));
323 }
324
325 match tokens[*pos].to_uppercase().as_str() {
326 "DEFAULT" => {
327 *pos += 1;
328 Ok(GraphManagementTarget::Default)
329 }
330 "GRAPH" => {
331 *pos += 1;
332 let iri = Self::consume_iri(tokens, pos)?;
333 Ok(GraphManagementTarget::Named(iri))
334 }
335 _ => {
336 let iri = Self::consume_iri(tokens, pos)?;
338 Ok(GraphManagementTarget::Named(iri))
339 }
340 }
341 }
342}
343
344pub struct GraphManagementRequestHandler;
352
353impl GraphManagementRequestHandler {
354 pub fn handle(
359 input: &str,
360 dataset: &mut crate::update_graph_management_types::GraphManagementDataset,
361 ) -> GraphManagementHttpResponse {
362 let op = match GraphManagementParser::parse(input) {
363 Ok(op) => op,
364 Err(e) => {
365 return GraphManagementHttpResponse::error(400, format!("Bad Request: {e}"));
366 }
367 };
368
369 match crate::update_graph_management_ops::GraphManagementExecutor::execute(&op, dataset) {
370 Ok(result) => GraphManagementHttpResponse::ok(result),
371 Err(e) => {
372 GraphManagementHttpResponse::error(500, format!("Internal Server Error: {e}"))
373 }
374 }
375 }
376}