use crate::model::source_location::SourceLocation;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
pub type NodeId = usize;
pub type NodeRef = Rc<RefCell<ConfigNode>>;
#[derive(Debug, Clone)]
pub enum ConfigValue {
Integer(i64),
Float(f64),
Boolean(bool),
String(String),
}
impl fmt::Display for ConfigValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigValue::Integer(i) => write!(f, "{}", i),
ConfigValue::Float(fl) => write!(f, "{}", fl),
ConfigValue::Boolean(b) => write!(f, "{}", b),
ConfigValue::String(s) => write!(f, "\"{}\"", s),
}
}
}
#[derive(Debug)]
pub struct ConfigField {
pub name: String,
pub value: ConfigValue,
pub location: Option<SourceLocation>,
}
#[derive(Debug)]
pub struct EntityNode {
pub name: String, pub plural_name: Option<String>, pub parent: Option<NodeId>, pub children: Vec<NodeId>, pub fields: HashMap<String, ConfigValue>, pub location: Option<SourceLocation>, }
#[derive(Debug)]
pub enum ConfigNode {
Entity(EntityNode),
Field(ConfigField),
}
impl ConfigNode {
pub fn new_entity(
name: &str,
plural_name: Option<&str>,
parent: Option<NodeId>,
location: Option<SourceLocation>,
) -> Self {
ConfigNode::Entity(EntityNode {
name: name.to_string(),
plural_name: plural_name.map(|s| s.to_string()),
parent,
children: vec![],
fields: HashMap::new(),
location,
})
}
pub fn new_field(name: &str, value: ConfigValue, location: Option<SourceLocation>) -> Self {
ConfigNode::Field(ConfigField {
name: name.to_string(),
value,
location,
})
}
pub fn name(&self) -> &str {
match self {
ConfigNode::Entity(entity) => &entity.name,
ConfigNode::Field(field) => &field.name,
}
}
pub fn is_entity(&self) -> bool {
matches!(self, ConfigNode::Entity(_))
}
pub fn is_field(&self) -> bool {
matches!(self, ConfigNode::Field(_))
}
}
#[derive(Debug)]
pub struct ConfigModel {
nodes: HashMap<NodeId, NodeRef>,
root_id: NodeId,
original_entity_names: HashMap<String, String>, }
impl Default for ConfigModel {
fn default() -> Self {
Self::new()
}
}
impl ConfigModel {
pub fn new() -> Self {
let mut model = ConfigModel {
nodes: HashMap::new(),
root_id: 0,
original_entity_names: HashMap::new(),
};
let root_node = ConfigNode::new_entity("root", None, None, None);
let root_id = model.add_node(root_node);
model.root_id = root_id;
model
}
pub fn set_original_entity_names(&mut self, names: HashMap<String, String>) {
self.original_entity_names = names;
}
pub fn get_original_entity_name(&self, sanitized_name: &str) -> String {
if let Some(original) = self.original_entity_names.get(sanitized_name) {
original.clone()
} else {
sanitized_name.to_string()
}
}
pub fn add_node(&mut self, node: ConfigNode) -> NodeId {
let id = self.nodes.len();
self.nodes.insert(id, Rc::new(RefCell::new(node)));
id
}
pub fn get_node(&self, id: NodeId) -> Option<NodeRef> {
self.nodes.get(&id).cloned()
}
pub fn root_id(&self) -> NodeId {
self.root_id
}
pub fn add_child(&mut self, parent_id: NodeId, child_id: NodeId) -> Result<(), String> {
let parent_node = self
.get_node(parent_id)
.ok_or_else(|| format!("Parent node with ID {} not found", parent_id))?;
let child_node = self
.get_node(child_id)
.ok_or_else(|| format!("Child node with ID {} not found", child_id))?;
{
let mut parent_node_borrow = parent_node.borrow_mut();
if let ConfigNode::Entity(ref mut entity) = *parent_node_borrow {
entity.children.push(child_id);
} else {
return Err(format!(
"Parent node with ID {} is not an entity",
parent_id
));
}
}
{
let mut child_node_borrow = child_node.borrow_mut();
if let ConfigNode::Entity(ref mut entity) = *child_node_borrow {
entity.parent = Some(parent_id);
}
}
Ok(())
}
pub fn add_field_to_entity(
&mut self,
entity_id: NodeId,
field_name: &str,
value: ConfigValue,
) -> Result<(), String> {
let entity_node = self
.get_node(entity_id)
.ok_or_else(|| format!("Entity node with ID {} not found", entity_id))?;
let mut entity_node_borrow = entity_node.borrow_mut();
if let ConfigNode::Entity(ref mut entity) = *entity_node_borrow {
entity.fields.insert(field_name.to_string(), value);
Ok(())
} else {
Err(format!("Node with ID {} is not an entity", entity_id))
}
}
pub fn add_field_with_location(
&mut self,
entity_id: NodeId,
field_name: &str,
value: ConfigValue,
location: Option<SourceLocation>,
) -> Result<(), String> {
self.add_field_to_entity(entity_id, field_name, value.clone())?;
let field_node = ConfigNode::new_field(field_name, value, location);
let field_id = self.add_node(field_node);
self.add_child(entity_id, field_id)
}
pub fn find_entity_by_path(&self, path: &str) -> Option<NodeId> {
if path.is_empty() {
return Some(self.root_id);
}
let components: Vec<&str> = path.split('/').collect();
let mut current_id = self.root_id;
for component in components {
let found = {
let current_node = self.get_node(current_id)?;
let current_node_borrow = current_node.borrow();
if let ConfigNode::Entity(entity) = &*current_node_borrow {
let child_ids = &entity.children;
child_ids
.iter()
.find(|&&id| {
if let Some(child_node) = self.get_node(id) {
let child_borrow = child_node.borrow();
if let ConfigNode::Entity(child_entity) = &*child_borrow {
return child_entity.name == component;
}
}
false
})
.cloned()
} else {
None
}
};
current_id = found?;
}
Some(current_id)
}
pub fn create_entity_at_path(
&mut self,
path: &str,
name: &str,
plural_name: Option<&str>,
location: Option<SourceLocation>,
) -> Result<NodeId, String> {
if path.is_empty() {
let entity = ConfigNode::new_entity(name, plural_name, Some(self.root_id), location);
let entity_id = self.add_node(entity);
self.add_child(self.root_id, entity_id)?;
return Ok(entity_id);
}
let parent_id = self
.find_entity_by_path(path)
.ok_or_else(|| format!("Parent path '{}' not found", path))?;
let entity = ConfigNode::new_entity(name, plural_name, Some(parent_id), location);
let entity_id = self.add_node(entity);
self.add_child(parent_id, entity_id)?;
Ok(entity_id)
}
pub fn get_field_value(&self, entity_id: NodeId, field_name: &str) -> Option<ConfigValue> {
let entity_node = self.get_node(entity_id)?;
let entity_borrow = entity_node.borrow();
if let ConfigNode::Entity(entity) = &*entity_borrow {
entity.fields.get(field_name).cloned()
} else {
None
}
}
pub fn find_child_entity_by_name(&self, parent_id: NodeId, child_name: &str) -> Option<NodeId> {
let parent_node = self.get_node(parent_id)?;
let parent_borrow = parent_node.borrow();
if let ConfigNode::Entity(parent_entity) = &*parent_borrow {
for &child_id in &parent_entity.children {
if let Some(child_node) = self.get_node(child_id) {
let child_borrow = child_node.borrow();
if let ConfigNode::Entity(child_entity) = &*child_borrow
&& child_entity.name == child_name
{
return Some(child_id);
}
}
}
}
None
}
fn display_node(&self, id: NodeId, depth: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let node = match self.get_node(id) {
Some(n) => n,
None => return writeln!(f, "{}Node ID {} not found", " ".repeat(depth), id),
};
let node_borrow = node.borrow();
match &*node_borrow {
ConfigNode::Entity(entity) => {
if let Some(plural) = &entity.plural_name {
writeln!(
f,
"{}{} plural {}:",
" ".repeat(depth),
self.get_original_entity_name(&entity.name),
plural
)?;
} else {
writeln!(
f,
"{}{}:",
" ".repeat(depth),
self.get_original_entity_name(&entity.name)
)?;
}
for (name, value) in &entity.fields {
writeln!(f, "{}{}: {},", " ".repeat(depth + 1), name, value)?;
}
for &child_id in &entity.children {
self.display_node(child_id, depth + 1, f)?;
}
if entity.parent.is_some() {
writeln!(f, "{};", " ".repeat(depth))?;
}
}
ConfigNode::Field(field) => {
writeln!(f, "{}{}: {},", " ".repeat(depth), field.name, field.value)?;
}
}
Ok(())
}
pub fn pretty_display(&self) -> String {
let mut result = String::new();
self.pretty_display_node(self.root_id, &mut result, &mut vec![], false);
result
}
fn pretty_display_node(
&self,
id: NodeId,
result: &mut String,
prefix: &mut Vec<&'static str>,
is_last: bool,
) {
let node = match self.get_node(id) {
Some(n) => n,
None => {
result.push_str(&prefix.join(""));
result.push_str(&format!("Node ID {} not found\n", id));
return;
}
};
result.push_str(&prefix.join(""));
if is_last {
result.push_str("└── ");
} else {
result.push_str("├── ");
}
let node_borrow = node.borrow();
match &*node_borrow {
ConfigNode::Entity(entity) => {
let display_name = self.get_original_entity_name(&entity.name);
if let Some(plural) = &entity.plural_name {
result.push_str(&format!("{} plural {}\n", display_name, plural));
} else {
result.push_str(&format!("{}\n", display_name));
}
if is_last {
prefix.push(" ");
} else {
prefix.push("│ ");
}
let fields: Vec<(&String, &ConfigValue)> = entity.fields.iter().collect();
let num_fields = fields.len();
for (i, (name, value)) in fields.into_iter().enumerate() {
let is_field_last = i == num_fields - 1 && entity.children.is_empty();
result.push_str(&prefix.join(""));
if is_field_last {
result.push_str(&format!("└── {}: {}\n", name, value));
} else {
result.push_str(&format!("├── {}: {}\n", name, value));
}
}
let num_children = entity.children.len();
for (i, &child_id) in entity.children.iter().enumerate() {
let is_child_last = i == num_children - 1;
self.pretty_display_node(child_id, result, prefix, is_child_last);
}
prefix.pop();
}
ConfigNode::Field(field) => {
result.push_str(&format!("{}: {}\n", field.name, field.value));
}
}
}
}
impl fmt::Display for ConfigModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display_node(self.root_id, 0, f)?;
writeln!(f, ".")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_config_model() {
let mut model = ConfigModel::new();
let _llm_id = model
.create_entity_at_path("", "llm", Some("llms"), None)
.unwrap();
let openai_id = model
.create_entity_at_path("llm", "openai", None, None)
.unwrap();
model
.add_field_to_entity(
openai_id,
"api_key",
ConfigValue::String("test_key".to_string()),
)
.unwrap();
model
.add_field_to_entity(openai_id, "max_tokens", ConfigValue::Integer(1000))
.unwrap();
let _models_id = model
.create_entity_at_path("llm/openai", "model", Some("models"), None)
.unwrap();
let gpt4_id = model
.create_entity_at_path("llm/openai/model", "gpt-4", None, None)
.unwrap();
model
.add_field_to_entity(gpt4_id, "max_input_tokens", ConfigValue::Integer(8192))
.unwrap();
model
.add_field_to_entity(gpt4_id, "supports_vision", ConfigValue::Boolean(true))
.unwrap();
println!("{}", model);
let max_tokens = model.get_field_value(openai_id, "max_tokens").unwrap();
if let ConfigValue::Integer(val) = max_tokens {
assert_eq!(val, 1000);
} else {
panic!("Expected Integer value for max_tokens");
}
let found_gpt4_id = model.find_entity_by_path("llm/openai/model/gpt-4").unwrap();
assert_eq!(found_gpt4_id, gpt4_id);
}
}