use crate::utils::error::{Error, Result};
use serde::Serialize;
use std::collections::BTreeMap;
pub struct CodeGraphBuilder {
structs: Vec<CodeStruct>,
traits: Vec<CodeTrait>,
impls: Vec<CodeImpl>,
enums: Vec<CodeEnum>,
}
impl CodeGraphBuilder {
pub fn new() -> Self {
Self {
structs: Vec::new(),
traits: Vec::new(),
impls: Vec::new(),
enums: Vec::new(),
}
}
pub fn from_sparql_results(results: &[BTreeMap<String, String>]) -> Result<Vec<CodeStruct>> {
let mut structs = Vec::new();
for row in results {
let name = row
.get("name")
.ok_or_else(|| Error::new("SPARQL result missing 'name' binding"))?;
let iri = row.get("iri").cloned().unwrap_or_default();
let struct_def = CodeStruct {
iri,
name: name.clone(),
visibility: row
.get("visibility")
.cloned()
.unwrap_or_else(|| "pub".to_string()),
derives: Self::parse_derives(row.get("derives")),
generics: row.get("generics").cloned(),
fields: Vec::new(), docstring: row.get("docstring").cloned(),
attributes: Vec::new(),
source_iri: row.get("source_iri").cloned(),
};
structs.push(struct_def);
}
Ok(structs)
}
fn parse_derives(derives: Option<&String>) -> Vec<String> {
derives
.map(|d| d.split(',').map(|s| s.trim().to_string()).collect())
.unwrap_or_default()
}
pub fn to_tera_context(&self) -> tera::Context {
let mut ctx = tera::Context::new();
ctx.insert("structs", &self.structs);
ctx.insert("traits", &self.traits);
ctx.insert("impls", &self.impls);
ctx.insert("enums", &self.enums);
ctx
}
pub fn add_struct(&mut self, s: CodeStruct) {
self.structs.push(s);
}
pub fn add_trait(&mut self, t: CodeTrait) {
self.traits.push(t);
}
pub fn add_impl(&mut self, i: CodeImpl) {
self.impls.push(i);
}
pub fn build_impl_from_relationship(source: &str, rel_type: &str, target: &str) -> CodeImpl {
let method_name = match rel_type {
"has_many" => format!("get_{}s", target.to_lowercase()),
"has_one" | "belongs_to" => format!("get_{}", target.to_lowercase()),
_ => format!("get_{}", target.to_lowercase()),
};
let return_type = match rel_type {
"has_many" => format!("Vec<{}>", target),
_ => target.to_string(),
};
CodeImpl {
iri: String::new(),
for_type: source.to_string(),
trait_name: None,
generics: None,
methods: vec![CodeMethod {
iri: String::new(),
name: method_name,
visibility: "pub".to_string(),
is_async: false,
self_param: Some("&self".to_string()),
params: Vec::new(),
return_type: Some(return_type.clone()),
body: Some(if return_type.starts_with("Vec<") {
"Vec::new()".to_string()
} else if return_type.starts_with("Option<") {
"None".to_string()
} else if return_type.starts_with("Result<") {
"Err(crate::utils::error::Error::new(\"Not implemented\"))".to_string()
} else {
"Default::default()".to_string()
}),
docstring: Some(format!(
"Get {} {}(s)",
rel_type.replace('_', " "),
target.to_lowercase()
)),
}],
}
}
}
impl Default for CodeGraphBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeModule {
pub iri: String,
pub name: String,
#[serde(default)]
pub visibility: String,
#[serde(default)]
pub imports: Vec<CodeImport>,
#[serde(default)]
pub items: Vec<CodeItem>,
#[serde(default)]
pub attributes: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum CodeItem {
Struct(CodeStruct),
Trait(CodeTrait),
Impl(CodeImpl),
Enum(CodeEnum),
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeStruct {
pub iri: String,
pub name: String,
#[serde(default)]
pub visibility: String,
#[serde(default)]
pub derives: Vec<String>,
#[serde(default)]
pub generics: Option<String>,
#[serde(default)]
pub fields: Vec<CodeField>,
#[serde(default)]
pub docstring: Option<String>,
#[serde(default)]
pub attributes: Vec<String>,
#[serde(default)]
pub source_iri: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeField {
pub iri: String,
pub name: String,
pub field_type: String,
#[serde(default)]
pub visibility: String,
#[serde(default)]
pub docstring: Option<String>,
#[serde(default)]
pub attributes: Vec<String>,
#[serde(default)]
pub default: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeTrait {
pub iri: String,
pub name: String,
#[serde(default)]
pub visibility: String,
#[serde(default)]
pub bounds: Option<String>,
#[serde(default)]
pub methods: Vec<CodeMethod>,
#[serde(default)]
pub is_async: bool,
#[serde(default)]
pub docstring: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeMethod {
pub iri: String,
pub name: String,
#[serde(default)]
pub visibility: String,
#[serde(default)]
pub is_async: bool,
#[serde(default)]
pub self_param: Option<String>,
#[serde(default)]
pub params: Vec<CodeParam>,
#[serde(default)]
pub return_type: Option<String>,
#[serde(default)]
pub body: Option<String>,
#[serde(default)]
pub docstring: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeParam {
pub name: String,
pub param_type: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeImpl {
pub iri: String,
pub for_type: String,
#[serde(default)]
pub trait_name: Option<String>,
#[serde(default)]
pub generics: Option<String>,
#[serde(default)]
pub methods: Vec<CodeMethod>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeEnum {
pub iri: String,
pub name: String,
#[serde(default)]
pub visibility: String,
#[serde(default)]
pub derives: Vec<String>,
#[serde(default)]
pub variants: Vec<CodeVariant>,
#[serde(default)]
pub docstring: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeVariant {
pub name: String,
#[serde(default)]
pub fields: Vec<CodeField>,
#[serde(default)]
pub docstring: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CodeImport {
pub path: String,
#[serde(default)]
pub alias: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_derives() {
let derives = Some("Debug, Clone, Serialize".to_string());
let result = CodeGraphBuilder::parse_derives(derives.as_ref());
assert_eq!(result, vec!["Debug", "Clone", "Serialize"]);
}
#[test]
fn test_build_impl_has_many() {
let impl_block =
CodeGraphBuilder::build_impl_from_relationship("User", "has_many", "Order");
assert_eq!(impl_block.for_type, "User");
assert_eq!(impl_block.methods.len(), 1);
assert_eq!(impl_block.methods[0].name, "get_orders");
assert_eq!(
impl_block.methods[0].return_type,
Some("Vec<Order>".to_string())
);
}
#[test]
fn test_build_impl_belongs_to() {
let impl_block =
CodeGraphBuilder::build_impl_from_relationship("Order", "belongs_to", "User");
assert_eq!(impl_block.methods[0].name, "get_user");
assert_eq!(impl_block.methods[0].return_type, Some("User".to_string()));
}
#[test]
fn test_from_sparql_results() {
let mut row = BTreeMap::new();
row.insert("name".to_string(), "User".to_string());
row.insert("iri".to_string(), "http://example.org/User".to_string());
row.insert("derives".to_string(), "Debug, Clone".to_string());
row.insert("docstring".to_string(), "A user entity".to_string());
let results = CodeGraphBuilder::from_sparql_results(&[row]).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].name, "User");
assert_eq!(results[0].derives, vec!["Debug", "Clone"]);
}
}