ggen_core/codegen/
code_graph.rs

1//! Code graph operations
2//!
3//! Converts SPARQL query results into strongly-typed code graph entities
4//! that can be rendered by Tera templates.
5
6use ggen_utils::error::{Error, Result};
7use serde::Serialize;
8use std::collections::BTreeMap;
9
10/// Builder for code graph entities from SPARQL results
11pub struct CodeGraphBuilder {
12    /// Parsed structs
13    structs: Vec<CodeStruct>,
14    /// Parsed traits
15    traits: Vec<CodeTrait>,
16    /// Parsed impls
17    impls: Vec<CodeImpl>,
18    /// Parsed enums
19    enums: Vec<CodeEnum>,
20}
21
22impl CodeGraphBuilder {
23    /// Create a new code graph builder
24    pub fn new() -> Self {
25        Self {
26            structs: Vec::new(),
27            traits: Vec::new(),
28            impls: Vec::new(),
29            enums: Vec::new(),
30        }
31    }
32
33    /// Parse SPARQL SELECT results into CodeStruct objects
34    ///
35    /// # Arguments
36    /// * `results` - SPARQL query results as maps of variable bindings
37    ///
38    /// # Returns
39    /// * `Ok(Vec<CodeStruct>)` - Parsed structs
40    /// * `Err(Error)` - Parse error
41    pub fn from_sparql_results(results: &[BTreeMap<String, String>]) -> Result<Vec<CodeStruct>> {
42        let mut structs = Vec::new();
43
44        for row in results {
45            let name = row
46                .get("name")
47                .ok_or_else(|| Error::new("SPARQL result missing 'name' binding"))?;
48
49            let iri = row.get("iri").cloned().unwrap_or_default();
50
51            let struct_def = CodeStruct {
52                iri,
53                name: name.clone(),
54                visibility: row
55                    .get("visibility")
56                    .cloned()
57                    .unwrap_or_else(|| "pub".to_string()),
58                derives: Self::parse_derives(row.get("derives")),
59                generics: row.get("generics").cloned(),
60                fields: Vec::new(), // Populated by separate query
61                docstring: row.get("docstring").cloned(),
62                attributes: Vec::new(),
63                source_iri: row.get("source_iri").cloned(),
64            };
65
66            structs.push(struct_def);
67        }
68
69        Ok(structs)
70    }
71
72    /// Parse comma-separated derives into Vec
73    fn parse_derives(derives: Option<&String>) -> Vec<String> {
74        derives
75            .map(|d| d.split(',').map(|s| s.trim().to_string()).collect())
76            .unwrap_or_default()
77    }
78
79    /// Convert code graph entities to Tera context
80    pub fn to_tera_context(&self) -> tera::Context {
81        let mut ctx = tera::Context::new();
82        ctx.insert("structs", &self.structs);
83        ctx.insert("traits", &self.traits);
84        ctx.insert("impls", &self.impls);
85        ctx.insert("enums", &self.enums);
86        ctx
87    }
88
89    /// Add a struct to the code graph
90    pub fn add_struct(&mut self, s: CodeStruct) {
91        self.structs.push(s);
92    }
93
94    /// Add a trait to the code graph
95    pub fn add_trait(&mut self, t: CodeTrait) {
96        self.traits.push(t);
97    }
98
99    /// Add an impl to the code graph
100    pub fn add_impl(&mut self, i: CodeImpl) {
101        self.impls.push(i);
102    }
103
104    /// Build an impl block from a relationship definition
105    ///
106    /// # Arguments
107    /// * `source` - Source entity name (e.g., "User")
108    /// * `rel_type` - Relationship type (e.g., "has_many")
109    /// * `target` - Target entity name (e.g., "Order")
110    pub fn build_impl_from_relationship(source: &str, rel_type: &str, target: &str) -> CodeImpl {
111        let method_name = match rel_type {
112            "has_many" => format!("get_{}s", target.to_lowercase()),
113            "has_one" | "belongs_to" => format!("get_{}", target.to_lowercase()),
114            _ => format!("get_{}", target.to_lowercase()),
115        };
116
117        let return_type = match rel_type {
118            "has_many" => format!("Vec<{}>", target),
119            _ => target.to_string(),
120        };
121
122        CodeImpl {
123            iri: String::new(),
124            for_type: source.to_string(),
125            trait_name: None,
126            generics: None,
127            methods: vec![CodeMethod {
128                iri: String::new(),
129                name: method_name,
130                visibility: "pub".to_string(),
131                is_async: false,
132                self_param: Some("&self".to_string()),
133                params: Vec::new(),
134                return_type: Some(return_type),
135                body: Some("todo!()".to_string()),
136                docstring: Some(format!(
137                    "Get {} {}(s)",
138                    rel_type.replace('_', " "),
139                    target.to_lowercase()
140                )),
141            }],
142        }
143    }
144}
145
146impl Default for CodeGraphBuilder {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152// ============================================================================
153// Code Graph Entity Types
154// ============================================================================
155
156/// Represents a Rust module
157#[derive(Debug, Clone, Serialize)]
158pub struct CodeModule {
159    /// IRI identifying this module
160    pub iri: String,
161    /// Module name (e.g., "models")
162    pub name: String,
163    /// Visibility ("pub", "pub(crate)", "")
164    #[serde(default)]
165    pub visibility: String,
166    /// Use statements
167    #[serde(default)]
168    pub imports: Vec<CodeImport>,
169    /// Module items (structs, traits, impls)
170    #[serde(default)]
171    pub items: Vec<CodeItem>,
172    /// Module-level attributes
173    #[serde(default)]
174    pub attributes: Vec<String>,
175}
176
177/// A code item (struct, trait, impl, or enum)
178#[derive(Debug, Clone, Serialize)]
179#[serde(tag = "type")]
180pub enum CodeItem {
181    /// Rust struct
182    Struct(CodeStruct),
183    /// Rust trait
184    Trait(CodeTrait),
185    /// Rust impl block
186    Impl(CodeImpl),
187    /// Rust enum
188    Enum(CodeEnum),
189}
190
191/// Represents a Rust struct
192#[derive(Debug, Clone, Serialize)]
193pub struct CodeStruct {
194    /// IRI from code graph
195    pub iri: String,
196    /// Struct name (PascalCase)
197    pub name: String,
198    /// Visibility
199    #[serde(default)]
200    pub visibility: String,
201    /// Derive macros
202    #[serde(default)]
203    pub derives: Vec<String>,
204    /// Generic parameters
205    #[serde(default)]
206    pub generics: Option<String>,
207    /// Struct fields (ordered)
208    #[serde(default)]
209    pub fields: Vec<CodeField>,
210    /// Documentation string
211    #[serde(default)]
212    pub docstring: Option<String>,
213    /// Additional attributes
214    #[serde(default)]
215    pub attributes: Vec<String>,
216    /// Source traceability
217    #[serde(default)]
218    pub source_iri: Option<String>,
219}
220
221/// Represents a struct field
222#[derive(Debug, Clone, Serialize)]
223pub struct CodeField {
224    /// IRI from code graph
225    pub iri: String,
226    /// Field name (snake_case)
227    pub name: String,
228    /// Rust type
229    pub field_type: String,
230    /// Visibility
231    #[serde(default)]
232    pub visibility: String,
233    /// Documentation
234    #[serde(default)]
235    pub docstring: Option<String>,
236    /// Field attributes
237    #[serde(default)]
238    pub attributes: Vec<String>,
239    /// Default value expression
240    #[serde(default)]
241    pub default: Option<String>,
242}
243
244/// Represents a Rust trait
245#[derive(Debug, Clone, Serialize)]
246pub struct CodeTrait {
247    /// IRI from code graph
248    pub iri: String,
249    /// Trait name
250    pub name: String,
251    /// Visibility
252    #[serde(default)]
253    pub visibility: String,
254    /// Trait bounds
255    #[serde(default)]
256    pub bounds: Option<String>,
257    /// Trait methods
258    #[serde(default)]
259    pub methods: Vec<CodeMethod>,
260    /// Whether trait has async methods
261    #[serde(default)]
262    pub is_async: bool,
263    /// Documentation
264    #[serde(default)]
265    pub docstring: Option<String>,
266}
267
268/// Represents a method signature or implementation
269#[derive(Debug, Clone, Serialize)]
270pub struct CodeMethod {
271    /// IRI from code graph
272    pub iri: String,
273    /// Method name
274    pub name: String,
275    /// Visibility
276    #[serde(default)]
277    pub visibility: String,
278    /// Whether method is async
279    #[serde(default)]
280    pub is_async: bool,
281    /// Self parameter ("&self", "&mut self", "self", None)
282    #[serde(default)]
283    pub self_param: Option<String>,
284    /// Method parameters
285    #[serde(default)]
286    pub params: Vec<CodeParam>,
287    /// Return type
288    #[serde(default)]
289    pub return_type: Option<String>,
290    /// Method body (for impls)
291    #[serde(default)]
292    pub body: Option<String>,
293    /// Documentation
294    #[serde(default)]
295    pub docstring: Option<String>,
296}
297
298/// Represents a method parameter
299#[derive(Debug, Clone, Serialize)]
300pub struct CodeParam {
301    /// Parameter name
302    pub name: String,
303    /// Parameter type
304    pub param_type: String,
305}
306
307/// Represents a Rust impl block
308#[derive(Debug, Clone, Serialize)]
309pub struct CodeImpl {
310    /// IRI from code graph
311    pub iri: String,
312    /// Type being implemented for
313    pub for_type: String,
314    /// Trait being implemented (if any)
315    #[serde(default)]
316    pub trait_name: Option<String>,
317    /// Generic parameters
318    #[serde(default)]
319    pub generics: Option<String>,
320    /// Implemented methods
321    #[serde(default)]
322    pub methods: Vec<CodeMethod>,
323}
324
325/// Represents a Rust enum
326#[derive(Debug, Clone, Serialize)]
327pub struct CodeEnum {
328    /// IRI from code graph
329    pub iri: String,
330    /// Enum name
331    pub name: String,
332    /// Visibility
333    #[serde(default)]
334    pub visibility: String,
335    /// Derive macros
336    #[serde(default)]
337    pub derives: Vec<String>,
338    /// Enum variants
339    #[serde(default)]
340    pub variants: Vec<CodeVariant>,
341    /// Documentation
342    #[serde(default)]
343    pub docstring: Option<String>,
344}
345
346/// Represents an enum variant
347#[derive(Debug, Clone, Serialize)]
348pub struct CodeVariant {
349    /// Variant name
350    pub name: String,
351    /// Variant fields (for tuple/struct variants)
352    #[serde(default)]
353    pub fields: Vec<CodeField>,
354    /// Documentation
355    #[serde(default)]
356    pub docstring: Option<String>,
357}
358
359/// Represents a use/import statement
360#[derive(Debug, Clone, Serialize)]
361pub struct CodeImport {
362    /// Import path
363    pub path: String,
364    /// Optional alias
365    #[serde(default)]
366    pub alias: Option<String>,
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn test_parse_derives() {
375        let derives = Some("Debug, Clone, Serialize".to_string());
376        let result = CodeGraphBuilder::parse_derives(derives.as_ref());
377        assert_eq!(result, vec!["Debug", "Clone", "Serialize"]);
378    }
379
380    #[test]
381    fn test_build_impl_has_many() {
382        let impl_block =
383            CodeGraphBuilder::build_impl_from_relationship("User", "has_many", "Order");
384        assert_eq!(impl_block.for_type, "User");
385        assert_eq!(impl_block.methods.len(), 1);
386        assert_eq!(impl_block.methods[0].name, "get_orders");
387        assert_eq!(
388            impl_block.methods[0].return_type,
389            Some("Vec<Order>".to_string())
390        );
391    }
392
393    #[test]
394    fn test_build_impl_belongs_to() {
395        let impl_block =
396            CodeGraphBuilder::build_impl_from_relationship("Order", "belongs_to", "User");
397        assert_eq!(impl_block.methods[0].name, "get_user");
398        assert_eq!(impl_block.methods[0].return_type, Some("User".to_string()));
399    }
400
401    #[test]
402    fn test_from_sparql_results() {
403        let mut row = BTreeMap::new();
404        row.insert("name".to_string(), "User".to_string());
405        row.insert("iri".to_string(), "http://example.org/User".to_string());
406        row.insert("derives".to_string(), "Debug, Clone".to_string());
407        row.insert("docstring".to_string(), "A user entity".to_string());
408
409        let results = CodeGraphBuilder::from_sparql_results(&[row]).unwrap();
410        assert_eq!(results.len(), 1);
411        assert_eq!(results[0].name, "User");
412        assert_eq!(results[0].derives, vec!["Debug", "Clone"]);
413    }
414}