1use ggen_utils::error::{Error, Result};
7use serde::Serialize;
8use std::collections::BTreeMap;
9
10pub struct CodeGraphBuilder {
12 structs: Vec<CodeStruct>,
14 traits: Vec<CodeTrait>,
16 impls: Vec<CodeImpl>,
18 enums: Vec<CodeEnum>,
20}
21
22impl CodeGraphBuilder {
23 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 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(), 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 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 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 pub fn add_struct(&mut self, s: CodeStruct) {
91 self.structs.push(s);
92 }
93
94 pub fn add_trait(&mut self, t: CodeTrait) {
96 self.traits.push(t);
97 }
98
99 pub fn add_impl(&mut self, i: CodeImpl) {
101 self.impls.push(i);
102 }
103
104 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#[derive(Debug, Clone, Serialize)]
158pub struct CodeModule {
159 pub iri: String,
161 pub name: String,
163 #[serde(default)]
165 pub visibility: String,
166 #[serde(default)]
168 pub imports: Vec<CodeImport>,
169 #[serde(default)]
171 pub items: Vec<CodeItem>,
172 #[serde(default)]
174 pub attributes: Vec<String>,
175}
176
177#[derive(Debug, Clone, Serialize)]
179#[serde(tag = "type")]
180pub enum CodeItem {
181 Struct(CodeStruct),
183 Trait(CodeTrait),
185 Impl(CodeImpl),
187 Enum(CodeEnum),
189}
190
191#[derive(Debug, Clone, Serialize)]
193pub struct CodeStruct {
194 pub iri: String,
196 pub name: String,
198 #[serde(default)]
200 pub visibility: String,
201 #[serde(default)]
203 pub derives: Vec<String>,
204 #[serde(default)]
206 pub generics: Option<String>,
207 #[serde(default)]
209 pub fields: Vec<CodeField>,
210 #[serde(default)]
212 pub docstring: Option<String>,
213 #[serde(default)]
215 pub attributes: Vec<String>,
216 #[serde(default)]
218 pub source_iri: Option<String>,
219}
220
221#[derive(Debug, Clone, Serialize)]
223pub struct CodeField {
224 pub iri: String,
226 pub name: String,
228 pub field_type: String,
230 #[serde(default)]
232 pub visibility: String,
233 #[serde(default)]
235 pub docstring: Option<String>,
236 #[serde(default)]
238 pub attributes: Vec<String>,
239 #[serde(default)]
241 pub default: Option<String>,
242}
243
244#[derive(Debug, Clone, Serialize)]
246pub struct CodeTrait {
247 pub iri: String,
249 pub name: String,
251 #[serde(default)]
253 pub visibility: String,
254 #[serde(default)]
256 pub bounds: Option<String>,
257 #[serde(default)]
259 pub methods: Vec<CodeMethod>,
260 #[serde(default)]
262 pub is_async: bool,
263 #[serde(default)]
265 pub docstring: Option<String>,
266}
267
268#[derive(Debug, Clone, Serialize)]
270pub struct CodeMethod {
271 pub iri: String,
273 pub name: String,
275 #[serde(default)]
277 pub visibility: String,
278 #[serde(default)]
280 pub is_async: bool,
281 #[serde(default)]
283 pub self_param: Option<String>,
284 #[serde(default)]
286 pub params: Vec<CodeParam>,
287 #[serde(default)]
289 pub return_type: Option<String>,
290 #[serde(default)]
292 pub body: Option<String>,
293 #[serde(default)]
295 pub docstring: Option<String>,
296}
297
298#[derive(Debug, Clone, Serialize)]
300pub struct CodeParam {
301 pub name: String,
303 pub param_type: String,
305}
306
307#[derive(Debug, Clone, Serialize)]
309pub struct CodeImpl {
310 pub iri: String,
312 pub for_type: String,
314 #[serde(default)]
316 pub trait_name: Option<String>,
317 #[serde(default)]
319 pub generics: Option<String>,
320 #[serde(default)]
322 pub methods: Vec<CodeMethod>,
323}
324
325#[derive(Debug, Clone, Serialize)]
327pub struct CodeEnum {
328 pub iri: String,
330 pub name: String,
332 #[serde(default)]
334 pub visibility: String,
335 #[serde(default)]
337 pub derives: Vec<String>,
338 #[serde(default)]
340 pub variants: Vec<CodeVariant>,
341 #[serde(default)]
343 pub docstring: Option<String>,
344}
345
346#[derive(Debug, Clone, Serialize)]
348pub struct CodeVariant {
349 pub name: String,
351 #[serde(default)]
353 pub fields: Vec<CodeField>,
354 #[serde(default)]
356 pub docstring: Option<String>,
357}
358
359#[derive(Debug, Clone, Serialize)]
361pub struct CodeImport {
362 pub path: String,
364 #[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}