Initial commit: Rhai model builder with code generation

This commit is contained in:
Timur Gordon 2025-05-03 01:02:44 +02:00
commit 75f238cb4d
7 changed files with 457 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# Generated by Cargo
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# IDE files
.idea/
.vscode/
*.swp
*.swo
# Generated files
/src/gen/

15
Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "rhai_meeting_builder"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
rhai = "1"
quote = "1"
syn = { version = "2", features = ["full"] }
[build-dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"

83
README.md Normal file
View File

@ -0,0 +1,83 @@
# Generated Model Code
This README documents the automatically generated code for the data models.
## Table of Contents
- [Meeting](#meeting)
- [Task](#task)
## Meeting
### Fields
- `id`: String
- `title`: String
- `description`: String
- `participants`: Vec<String>
- `start`: String
### Builder Methods
- `with_title(value)`: Sets the title field (String)
- `with_description(value)`: Sets the description field (String)
- `with_participants(value)`: Sets the participants field (Vec<String>)
- `with_start(value)`: Sets the start field (String)
- `meeting()`: Creates a new Meeting builder
- `get_fields()`: Returns a Map of all set fields
### Database Operations
- `create_meeting_typed(fields)`: Creates a new Meeting in the database
- `find_meeting_by_id_typed(id)`: Finds a Meeting by ID
- `update_meeting_typed(id, fields)`: Updates a Meeting in the database
- `delete_meeting_typed(id)`: Deletes a Meeting from the database
- `list_meetings_typed()`: Lists all Meetings in the database
- `filter_meetings_typed(query)`: Filters Meetings based on query parameters
- `find_meeting_typed(query)`: Finds a specific Meeting based on query parameters
### Rhai Integration
- `create_meeting(fields)`: Creates a new Meeting in the database
- `find_meeting_by_id(id)`: Finds a Meeting by ID
- `update_meeting(id, fields)`: Updates a Meeting in the database
- `delete_meeting(id)`: Deletes a Meeting from the database
- `list_meetings()`: Lists all Meetings in the database
- `filter_meetings(query)`: Filters Meetings based on query parameters
- `find_meeting(query)`: Finds a specific Meeting based on query parameters
## Task
### Fields
- `id`: String
- `title`: String
- `description`: String
- `assignee`: String
- `due_date`: String
- `priority`: String
- `completed`: bool
### Builder Methods
- `with_title(value)`: Sets the title field (String)
- `with_description(value)`: Sets the description field (String)
- `with_assignee(value)`: Sets the assignee field (String)
- `with_due_date(value)`: Sets the due_date field (String)
- `with_priority(value)`: Sets the priority field (String)
- `with_completed(value)`: Sets the completed field (bool)
- `task()`: Creates a new Task builder
- `get_fields()`: Returns a Map of all set fields
### Database Operations
- `create_task_typed(fields)`: Creates a new Task in the database
- `find_task_by_id_typed(id)`: Finds a Task by ID
- `update_task_typed(id, fields)`: Updates a Task in the database
- `delete_task_typed(id)`: Deletes a Task from the database
- `list_tasks_typed()`: Lists all Tasks in the database
- `filter_tasks_typed(query)`: Filters Tasks based on query parameters
- `find_task_typed(query)`: Finds a specific Task based on query parameters
### Rhai Integration
- `create_task(fields)`: Creates a new Task in the database
- `find_task_by_id(id)`: Finds a Task by ID
- `update_task(id, fields)`: Updates a Task in the database
- `delete_task(id)`: Deletes a Task from the database
- `list_tasks()`: Lists all Tasks in the database
- `filter_tasks(query)`: Filters Tasks based on query parameters
- `find_task(query)`: Finds a specific Task based on query parameters

0
build.rs Normal file
View File

199
specs.md Normal file
View File

@ -0,0 +1,199 @@
Here's an updated full project specification including:
1. ✅ Unified builder for both creation and filtering
2. ✅ with_...() methods used for both creation and filtering
3. ✅ Auto-generated CRUD + list + filter + find methods
4. ✅ Database interface for persistence operations
5. ✅ Auto-generated registration code
6. ✅ 📜 Example Rhai script
7. ✅ 🔍 build.rs should output to src/gen/meeting_builder.rs
🧾 Project Spec: Rust → Rhai Builder Code Generator
🎯 Goal
Build a build.rs script that parses Rust structs annotated with #[builder] and generates Rhai-compatible unified builders with with_...() methods that populate a single field map used for both creation and filtering. Also generate CRUD operations that use a database interface.
📥 Input Example: src/models.rs
#[derive(Debug)]
pub struct Meeting {
pub id: String,
pub title: String,
pub description: String,
pub participants: Vec<String>,
pub start: String,
}
📤 Generated Code: src/gen/meeting_builder.rs
✅ 1. MeetingBuilder Struct
#[derive(Clone)]
pub struct MeetingBuilder {
pub fields: std::collections::HashMap<String, rhai::Dynamic>,
}
✅ 2. with_...() Methods (for each field)
impl MeetingBuilder {
pub fn with_title(mut self, value: &str) -> Self {
self.fields.insert("title".into(), value.into());
self
}
pub fn with_participants(mut self, value: Vec<rhai::Dynamic>) -> Self {
self.fields.insert("participants".into(), value.into());
self
}
pub fn get_fields(&mut self) -> rhai::Map {
self.fields.clone()
}
// CRUD operations
pub fn create(&mut self, db: &dyn Database) -> rhai::Dynamic {
db.create("meetings", self.get_fields())
}
pub fn find_by_id(&mut self, db: &dyn Database, id: &str) -> rhai::Dynamic {
db.find_by_id("meetings", id)
}
pub fn update(&mut self, db: &dyn Database, id: &str) -> rhai::Dynamic {
db.update("meetings", id, self.get_fields())
}
pub fn delete(&mut self, db: &dyn Database, id: &str) -> rhai::Dynamic {
db.delete("meetings", id)
}
pub fn list(&mut self, db: &dyn Database) -> rhai::Dynamic {
db.list("meetings")
}
pub fn filter(&mut self, db: &dyn Database) -> rhai::Dynamic {
db.filter("meetings", self.get_fields())
}
pub fn find(&mut self, db: &dyn Database) -> rhai::Dynamic {
db.find("meetings", self.get_fields())
}
}
✅ 3. Database Interface
pub trait Database {
fn create(&self, collection: &str, fields: rhai::Map) -> rhai::Dynamic;
fn find_by_id(&self, collection: &str, id: &str) -> rhai::Dynamic;
fn update(&self, collection: &str, id: &str, fields: rhai::Map) -> rhai::Dynamic;
fn delete(&self, collection: &str, id: &str) -> rhai::Dynamic;
fn list(&self, collection: &str) -> rhai::Dynamic;
fn filter(&self, collection: &str, query: rhai::Map) -> rhai::Dynamic;
fn find(&self, collection: &str, query: rhai::Map) -> rhai::Dynamic;
}
✅ 4. Rhai Registration
pub fn register_meeting_module(engine: &mut rhai::Engine) {
engine.register_type::<MeetingBuilder>();
engine.register_fn("meeting", MeetingBuilder::new);
engine.register_fn("with_title", MeetingBuilder::with_title);
engine.register_fn("with_participants", MeetingBuilder::with_participants);
engine.register_fn("get_fields", MeetingBuilder::get_fields);
// Register CRUD operations
engine.register_fn("create", MeetingBuilder::create);
engine.register_fn("find_by_id", MeetingBuilder::find_by_id);
engine.register_fn("update", MeetingBuilder::update);
engine.register_fn("delete", MeetingBuilder::delete);
engine.register_fn("list", MeetingBuilder::list);
engine.register_fn("filter", MeetingBuilder::filter);
engine.register_fn("find", MeetingBuilder::find);
}
✅ Example Rhai Script
// Create a meeting
let meeting_id = meeting()
.with_title("Project Sync")
.with_description("Check in with the team")
.with_participants(["timur", "eda"])
.with_start("2024-06-01T10:00")
.create(db);
// Find a meeting by ID
let meeting = meeting().find_by_id(db, meeting_id);
// Update a meeting
meeting()
.with_description("Updated description")
.update(db, meeting_id);
// Delete a meeting
meeting().delete(db, meeting_id);
// List all meetings
let all_meetings = meeting().list(db);
// Filter meetings by description
let filtered_meetings = meeting()
.with_description("Project Sync")
.filter(db);
// Find a specific meeting
let found_meeting = meeting()
.with_title("Project Sync")
.with_start("2024-06-01T10:00")
.find(db);
This script uses the same builder for all operations.
📂 Output File
Write generated code to:
src/gen/meeting_builder.rs
And include it in main.rs:
#[path = "gen/meeting_builder.rs"]
mod meeting_builder;
🔨 build.rs Responsibilities
• Use syn to parse src/models.rs
• Extract structs and fields
• Generate:
• MeetingBuilder struct with fields map
• All with_...() methods for Rhai
• get_fields() method
• CRUD + list + filter + find methods
• Database trait
• register_meeting_module(engine: &mut Engine) function
Output to src/gen/meeting_builder.rs.
✅ Success Criteria
• No duplication between filter/create logic
• Fluent API in Rhai
• Generated code compiles and integrates
• build.rs works as part of standard Rust build pipeline
• Easy to extend to more models (e.g., User, Task)
• Complete CRUD operations available
• Database abstraction for persistence

123
src/main.rs Normal file
View File

@ -0,0 +1,123 @@
mod models;
#[path = "gen/meeting_builder.rs"]
pub mod meeting_builder;
#[path = "gen/meeting_db.rs"]
pub mod meeting_db;
#[path = "gen/meeting_rhai.rs"]
pub mod meeting_rhai;
#[path = "gen/task_builder.rs"]
pub mod task_builder;
#[path = "gen/task_db.rs"]
pub mod task_db;
#[path = "gen/task_rhai.rs"]
pub mod task_rhai;
#[path = "gen/tests/mod.rs"]
#[cfg(test)]
mod tests;
use rhai::{Engine, Scope};
fn main() {
println!("Dynamic code generation example with CRUD operations");
// Create a new Rhai engine
let mut engine = Engine::new();
// Register the println function
engine.register_fn("println", |s: &str| println!("{}", s));
// Register the meeting builder module
meeting_builder::register_meeting_module(&mut engine);
// Register the meeting database module
meeting_rhai::register_meeting_db_module(&mut engine);
// Register the task builder module
task_builder::register_task_module(&mut engine);
// Register the task database module
task_rhai::register_task_db_module(&mut engine);
// Create a scope
let mut scope = Scope::new();
// Define the Rhai script that uses CRUD operations for both models
let script = r#"
// ===== Meeting CRUD operations =====
println("=== Meeting CRUD Operations ===");
// Create a meeting
let builder = meeting()
.with_title("Project Sync")
.with_description("Check in with the team")
.with_participants(["timur", "eda"])
.with_start("2024-06-01T10:00");
let meeting_id = create_meeting(builder.get_fields());
print(`Created meeting with ID: ${meeting_id}`);
// Find a meeting by ID
let meeting = find_meeting_by_id(meeting_id);
print(`Found meeting: ${meeting}`);
// Update a meeting
let update_builder = meeting()
.with_description("Updated meeting description");
let updated = update_meeting(meeting_id, update_builder.get_fields());
print(`Update result: ${updated}`);
// List all meetings
let all_meetings = list_meetings();
print(`All meetings: ${all_meetings}`);
// ===== Task CRUD operations =====
println("=== Task CRUD Operations ===");
// Create a task
let task_builder = task()
.with_title("Implement CRUD")
.with_description("Implement CRUD operations for all models")
.with_assignee("timur")
.with_due_date("2024-06-15")
.with_priority("high")
.with_completed(false);
let task_id = create_task(task_builder.get_fields());
print(`Created task with ID: ${task_id}`);
// Find a task by ID
let task = find_task_by_id(task_id);
print(`Found task: ${task}`);
// Update a task
let task_update_builder = task()
.with_description("Updated task description")
.with_completed(true);
let task_updated = update_task(task_id, task_update_builder.get_fields());
print(`Update task result: ${task_updated}`);
// List all tasks
let all_tasks = list_tasks();
print(`All tasks: ${all_tasks}`);
// Filter tasks by assignee
let filter_builder = task()
.with_assignee("timur");
let filtered_tasks = filter_tasks(filter_builder.get_fields());
print(`Filtered tasks by assignee: ${filtered_tasks}`);
// Delete a task
let task_deleted = delete_task(task_id);
print(`Delete task result: ${task_deleted}`);
// Verify deletion by listing all tasks
let remaining_tasks = list_tasks();
print(`Remaining tasks: ${remaining_tasks}`);
"#;
// Run the script
match engine.eval_with_scope::<rhai::Dynamic>(&mut scope, script) {
Ok(_) => println!("Script executed successfully"),
Err(e) => eprintln!("Script error: {}", e),
}
}

19
src/models.rs Normal file
View File

@ -0,0 +1,19 @@
#[derive(Debug, Clone)]
pub struct Meeting {
pub id: String,
pub title: String,
pub description: String,
pub participants: Vec<String>,
pub start: String,
}
#[derive(Debug, Clone)]
pub struct Task {
pub id: String,
pub title: String,
pub description: String,
pub assignee: String,
pub due_date: String,
pub priority: String,
pub completed: bool,
}