Skip to main content

ggen_cli_lib/cmds/
graph.rs

1//! Graph Commands - clap-noun-verb v3.4.0 Migration
2//!
3//! This module implements RDF graph operations using the v3.4.0 #[verb] pattern.
4
5use clap_noun_verb::Result;
6use clap_noun_verb_macros::verb;
7use serde::Serialize;
8use std::path::PathBuf;
9
10// ============================================================================
11// Output Types (all must derive Serialize for JSON output)
12// ============================================================================
13
14#[derive(Serialize)]
15struct LoadOutput {
16    triples_loaded: usize,
17    total_triples: usize,
18    format: String,
19    file_path: String,
20}
21
22#[derive(Serialize)]
23struct QueryOutput {
24    bindings: Vec<std::collections::HashMap<String, String>>,
25    variables: Vec<String>,
26    result_count: usize,
27}
28
29#[derive(Serialize)]
30struct ExportOutput {
31    output_path: String,
32    format: String,
33    triples_exported: usize,
34    file_size_bytes: usize,
35}
36
37#[derive(Serialize)]
38struct VisualizeOutput {
39    nodes_rendered: usize,
40    edges_rendered: usize,
41    output_path: String,
42    format: String,
43}
44
45#[derive(Serialize)]
46struct ValidateOutput {
47    is_valid: bool,
48    classes_count: usize,
49    properties_count: usize,
50    warnings: Vec<String>,
51    errors: Vec<String>,
52}
53
54// ============================================================================
55// Verb Functions (the actual CLI commands)
56// ============================================================================
57
58/// Validate ontology schema quality
59#[verb]
60fn validate(schema_file: String, strict: bool) -> Result<ValidateOutput> {
61    use ggen_core::domain::ontology;
62
63    let schema_path = PathBuf::from(&schema_file);
64
65    let (is_valid, warnings, errors, classes_count, properties_count) =
66        crate::runtime::block_on(async {
67            let schema = ontology::extract_ontology_schema(&schema_path, "http://example.org#")
68                .await
69                .map_err(|e| {
70                    ggen_core::utils::error::Error::new(&format!("Extraction failed: {}", e))
71                })?;
72
73            let (valid, warnings, errors) =
74                ontology::validate_ontology_schema(&schema, strict).await?;
75            Ok((
76                valid,
77                warnings,
78                errors,
79                schema.classes.len(),
80                schema.properties.len(),
81            ))
82        })
83        .map_err(|e: ggen_core::utils::Error| {
84            clap_noun_verb::NounVerbError::execution_error(format!("Runtime error: {}", e))
85        })?
86        .map_err(|e: ggen_core::utils::Error| {
87            clap_noun_verb::NounVerbError::execution_error(format!("Validation failed: {}", e))
88        })?;
89
90    Ok(ValidateOutput {
91        is_valid,
92        classes_count,
93        properties_count,
94        warnings,
95        errors,
96    })
97}
98
99/// Load RDF data into graph
100#[verb]
101fn load(file: String, format: Option<String>) -> Result<LoadOutput> {
102    use ggen_core::domain::graph::{execute_load, LoadInput};
103
104    let input = LoadInput {
105        file: PathBuf::from(file),
106        format,
107        base_iri: None,
108        merge: false,
109    };
110
111    let result = crate::runtime::block_on(async move {
112        execute_load(input)
113            .await
114            .map_err(|e| ggen_core::utils::error::Error::new(&format!("Load failed: {}", e)))
115    })
116    .map_err(|e: ggen_core::utils::Error| {
117        clap_noun_verb::NounVerbError::execution_error(e.to_string())
118    })?
119    .map_err(|e: ggen_core::utils::Error| {
120        clap_noun_verb::NounVerbError::execution_error(e.to_string())
121    })?;
122
123    Ok(LoadOutput {
124        triples_loaded: result.triples_loaded,
125        total_triples: result.total_triples,
126        format: result.format,
127        file_path: result.file_path,
128    })
129}
130
131/// Query graph with SPARQL
132#[verb]
133fn query(
134    sparql_query: String, graph_file: Option<String>, format: Option<String>,
135) -> Result<QueryOutput> {
136    use ggen_core::domain::graph::{execute_query, QueryInput};
137
138    let input = QueryInput {
139        query: sparql_query,
140        graph_file: graph_file.map(PathBuf::from),
141        format: format.unwrap_or_else(|| "json".to_string()),
142    };
143
144    let result = crate::runtime::block_on(async move {
145        execute_query(input)
146            .await
147            .map_err(|e| ggen_core::utils::error::Error::new(&format!("Query failed: {}", e)))
148    })
149    .map_err(|e: ggen_core::utils::Error| {
150        clap_noun_verb::NounVerbError::execution_error(e.to_string())
151    })?
152    .map_err(|e: ggen_core::utils::Error| {
153        clap_noun_verb::NounVerbError::execution_error(e.to_string())
154    })?;
155
156    Ok(QueryOutput {
157        bindings: result.bindings,
158        variables: result.variables,
159        result_count: result.result_count,
160    })
161}
162
163/// Export graph to file
164#[verb]
165fn export(input_file: String, output: String, format: String) -> Result<ExportOutput> {
166    use ggen_core::domain::graph::{execute_export, ExportInput};
167
168    let input_data = ExportInput {
169        input: PathBuf::from(input_file),
170        output: PathBuf::from(output),
171        format,
172        pretty: false,
173    };
174
175    let result = crate::runtime::block_on(async move {
176        execute_export(input_data)
177            .await
178            .map_err(|e| ggen_core::utils::error::Error::new(&format!("Export failed: {}", e)))
179    })
180    .map_err(|e: ggen_core::utils::Error| {
181        clap_noun_verb::NounVerbError::execution_error(e.to_string())
182    })?
183    .map_err(|e: ggen_core::utils::Error| {
184        clap_noun_verb::NounVerbError::execution_error(e.to_string())
185    })?;
186
187    Ok(ExportOutput {
188        output_path: result.output_path,
189        format: result.format,
190        triples_exported: result.triples_exported,
191        file_size_bytes: result.file_size_bytes,
192    })
193}
194
195/// Visualize graph structure
196#[verb]
197fn visualize(input_file: String, format: Option<String>) -> Result<VisualizeOutput> {
198    use ggen_core::domain::graph::{execute_visualize, VisualizeInput};
199
200    let input_data = VisualizeInput {
201        input: PathBuf::from(input_file),
202        output: None,
203        format: format.unwrap_or_else(|| "dot".to_string()),
204        labels: false,
205        max_depth: None,
206        subject: None,
207    };
208
209    let result = crate::runtime::block_on(async move {
210        execute_visualize(input_data)
211            .await
212            .map_err(|e| ggen_core::utils::error::Error::new(&format!("Visualize failed: {}", e)))
213    })
214    .map_err(|e: ggen_core::utils::Error| {
215        clap_noun_verb::NounVerbError::execution_error(e.to_string())
216    })?
217    .map_err(|e: ggen_core::utils::Error| {
218        clap_noun_verb::NounVerbError::execution_error(e.to_string())
219    })?;
220
221    Ok(VisualizeOutput {
222        nodes_rendered: result.nodes_rendered,
223        edges_rendered: result.edges_rendered,
224        output_path: result.output_path,
225        format: result.format,
226    })
227}