rust and rhai mcp improvements
This commit is contained in:
@@ -11,6 +11,22 @@ pub mut:
|
||||
max_tokens int
|
||||
}
|
||||
|
||||
// Create model configs
|
||||
const claude_3_sonnet = escalayer.ModelConfig{
|
||||
name: 'anthropic/claude-3.7-sonnet'
|
||||
provider: 'anthropic'
|
||||
temperature: 0.7
|
||||
max_tokens: 25000
|
||||
}
|
||||
|
||||
const gpt4 = escalayer.ModelConfig{
|
||||
name: 'gpt-4'
|
||||
provider: 'openai'
|
||||
temperature: 0.7
|
||||
max_tokens: 25000
|
||||
}
|
||||
|
||||
|
||||
// Call an AI model using OpenRouter
|
||||
fn call_ai_model(prompt string, model ModelConfig)! string {
|
||||
// Get OpenAI client (configured for OpenRouter)
|
||||
|
||||
3
lib/ai/mcp/README2.md
Normal file
3
lib/ai/mcp/README2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
If logic is implemented in mcp module, than structure with folders logic and mcp, where logic residers in /logic and mcp related code (like tool and prompt handlers and server code) in /mcp
|
||||
@@ -23,6 +23,9 @@ interface Backend {
|
||||
tool_get(name string) !Tool
|
||||
tool_list() ![]Tool
|
||||
tool_call(name string, arguments map[string]json2.Any) !ToolCallResult
|
||||
|
||||
// Sampling methods
|
||||
sampling_create_message(params map[string]json2.Any) !SamplingCreateMessageResult
|
||||
mut:
|
||||
resource_subscribe(uri string) !
|
||||
resource_unsubscribe(uri string) !
|
||||
|
||||
@@ -18,12 +18,17 @@ pub mut:
|
||||
// Tool related fields
|
||||
tools map[string]Tool
|
||||
tool_handlers map[string]ToolHandler
|
||||
|
||||
// Sampling related fields
|
||||
sampling_handler SamplingHandler
|
||||
}
|
||||
|
||||
pub type ToolHandler = fn (arguments map[string]json2.Any) !ToolCallResult
|
||||
|
||||
pub type PromptHandler = fn (arguments []string) ![]PromptMessage
|
||||
|
||||
pub type SamplingHandler = fn (params map[string]json2.Any) !SamplingCreateMessageResult
|
||||
|
||||
fn (b &MemoryBackend) resource_exists(uri string) !bool {
|
||||
return uri in b.resources
|
||||
}
|
||||
@@ -151,3 +156,30 @@ fn (b &MemoryBackend) tool_call(name string, arguments map[string]json2.Any) !To
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sampling related methods
|
||||
|
||||
fn (b &MemoryBackend) sampling_create_message(params map[string]json2.Any) !SamplingCreateMessageResult {
|
||||
// Check if a sampling handler is registered
|
||||
if isnil(b.sampling_handler) {
|
||||
// Return a default implementation that just echoes back a message
|
||||
// indicating that no sampling handler is registered
|
||||
return SamplingCreateMessageResult{
|
||||
model: 'default'
|
||||
stop_reason: 'endTurn'
|
||||
role: 'assistant'
|
||||
content: MessageContent{
|
||||
typ: 'text'
|
||||
text: 'Sampling is not configured on this server. Please register a sampling handler.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the sampling handler with the provided parameters
|
||||
return b.sampling_handler(params)!
|
||||
}
|
||||
|
||||
// register_sampling_handler registers a handler for sampling requests
|
||||
pub fn (mut b MemoryBackend) register_sampling_handler(handler SamplingHandler) {
|
||||
b.sampling_handler = handler
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import freeflowuniverse.herolib.osal
|
||||
// import freeflowuniverse.herolib.ai.mcp.mcpgen
|
||||
// import freeflowuniverse.herolib.ai.mcp.baobab
|
||||
import freeflowuniverse.herolib.ai.mcp.rhai.mcp as rhai_mcp
|
||||
import freeflowuniverse.herolib.ai.mcp.rust
|
||||
|
||||
fn main() {
|
||||
do() or { panic(err) }
|
||||
@@ -69,6 +70,7 @@ mcp
|
||||
|
||||
|
||||
cmd_mcp.add_command(rhai_mcp.command)
|
||||
cmd_mcp.add_command(rust.command)
|
||||
// cmd_mcp.add_command(baobab.command)
|
||||
// cmd_mcp.add_command(vcode.command)
|
||||
cmd_mcp.add_command(cmd_inspector)
|
||||
|
||||
@@ -41,7 +41,10 @@ pub fn new_server(backend Backend, params ServerParams) !&Server {
|
||||
|
||||
// Tool handlers
|
||||
'tools/list': server.tools_list_handler,
|
||||
'tools/call': server.tools_call_handler
|
||||
'tools/call': server.tools_call_handler,
|
||||
|
||||
// Sampling handlers
|
||||
'sampling/createMessage': server.sampling_create_message_handler
|
||||
}
|
||||
})!
|
||||
|
||||
|
||||
@@ -2,22 +2,22 @@ module mcp
|
||||
|
||||
|
||||
pub fn result_to_mcp_tool_contents[T](result T) []ToolContent {
|
||||
return [result_to_mcp_tool_content(result)]
|
||||
return [result_to_mcp_tool_content[T](result)]
|
||||
}
|
||||
|
||||
pub fn result_to_mcp_tool_content[T](result T) ToolContent {
|
||||
return $if T is string {
|
||||
ToolContent{
|
||||
$if T is string {
|
||||
return ToolContent{
|
||||
typ: 'text'
|
||||
text: result.str()
|
||||
}
|
||||
} $else $if T is int {
|
||||
ToolContent{
|
||||
return ToolContent{
|
||||
typ: 'number'
|
||||
number: result.int()
|
||||
}
|
||||
} $else $if T is bool {
|
||||
ToolContent{
|
||||
return ToolContent{
|
||||
typ: 'boolean'
|
||||
boolean: result.bool()
|
||||
}
|
||||
|
||||
145
lib/ai/mcp/handler_sampling.v
Normal file
145
lib/ai/mcp/handler_sampling.v
Normal file
@@ -0,0 +1,145 @@
|
||||
module mcp
|
||||
|
||||
import time
|
||||
import os
|
||||
import log
|
||||
import x.json2
|
||||
import json
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// Sampling related structs
|
||||
|
||||
pub struct MessageContent {
|
||||
pub:
|
||||
typ string @[json: 'type']
|
||||
text string
|
||||
data string
|
||||
mimetype string @[json: 'mimeType']
|
||||
}
|
||||
|
||||
pub struct Message {
|
||||
pub:
|
||||
role string
|
||||
content MessageContent
|
||||
}
|
||||
|
||||
pub struct ModelHint {
|
||||
pub:
|
||||
name string
|
||||
}
|
||||
|
||||
pub struct ModelPreferences {
|
||||
pub:
|
||||
hints []ModelHint
|
||||
cost_priority f32 @[json: 'costPriority']
|
||||
speed_priority f32 @[json: 'speedPriority']
|
||||
intelligence_priority f32 @[json: 'intelligencePriority']
|
||||
}
|
||||
|
||||
pub struct SamplingCreateMessageParams {
|
||||
pub:
|
||||
messages []Message
|
||||
model_preferences ModelPreferences @[json: 'modelPreferences']
|
||||
system_prompt string @[json: 'systemPrompt']
|
||||
include_context string @[json: 'includeContext']
|
||||
temperature f32
|
||||
max_tokens int @[json: 'maxTokens']
|
||||
stop_sequences []string @[json: 'stopSequences']
|
||||
metadata map[string]json2.Any
|
||||
}
|
||||
|
||||
pub struct SamplingCreateMessageResult {
|
||||
pub:
|
||||
model string
|
||||
stop_reason string @[json: 'stopReason']
|
||||
role string
|
||||
content MessageContent
|
||||
}
|
||||
|
||||
// sampling_create_message_handler handles the sampling/createMessage request
|
||||
// This request is used to request LLM completions through the client
|
||||
fn (mut s Server) sampling_create_message_handler(data string) !string {
|
||||
// Decode the request
|
||||
request_map := json2.raw_decode(data)!.as_map()
|
||||
id := request_map['id'].int()
|
||||
params_map := request_map['params'].as_map()
|
||||
|
||||
// Validate required parameters
|
||||
if 'messages' !in params_map {
|
||||
return jsonrpc.new_error_response(id, missing_required_argument('messages')).encode()
|
||||
}
|
||||
|
||||
if 'maxTokens' !in params_map {
|
||||
return jsonrpc.new_error_response(id, missing_required_argument('maxTokens')).encode()
|
||||
}
|
||||
|
||||
// Call the backend to handle the sampling request
|
||||
result := s.backend.sampling_create_message(params_map) or {
|
||||
return jsonrpc.new_error_response(id, sampling_error(err.msg())).encode()
|
||||
}
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response(id, json.encode(result))
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Helper function to convert JSON messages to our Message struct format
|
||||
fn parse_messages(messages_json json2.Any) ![]Message {
|
||||
messages_arr := messages_json.arr()
|
||||
mut result := []Message{cap: messages_arr.len}
|
||||
|
||||
for msg_json in messages_arr {
|
||||
msg_map := msg_json.as_map()
|
||||
|
||||
if 'role' !in msg_map {
|
||||
return error('Missing role in message')
|
||||
}
|
||||
|
||||
if 'content' !in msg_map {
|
||||
return error('Missing content in message')
|
||||
}
|
||||
|
||||
role := msg_map['role'].str()
|
||||
content_map := msg_map['content'].as_map()
|
||||
|
||||
if 'type' !in content_map {
|
||||
return error('Missing type in message content')
|
||||
}
|
||||
|
||||
typ := content_map['type'].str()
|
||||
mut text := ''
|
||||
mut data := ''
|
||||
mut mimetype := ''
|
||||
|
||||
if typ == 'text' {
|
||||
if 'text' !in content_map {
|
||||
return error('Missing text in text content')
|
||||
}
|
||||
text = content_map['text'].str()
|
||||
} else if typ == 'image' {
|
||||
if 'data' !in content_map {
|
||||
return error('Missing data in image content')
|
||||
}
|
||||
data = content_map['data'].str()
|
||||
|
||||
if 'mimeType' !in content_map {
|
||||
return error('Missing mimeType in image content')
|
||||
}
|
||||
mimetype = content_map['mimeType'].str()
|
||||
} else {
|
||||
return error('Unsupported content type: ${typ}')
|
||||
}
|
||||
|
||||
result << Message{
|
||||
role: role
|
||||
content: MessageContent{
|
||||
typ: typ
|
||||
text: text
|
||||
data: data
|
||||
mimetype: mimetype
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -33,3 +33,10 @@ fn tool_not_found(name string) jsonrpc.RPCError {
|
||||
message: 'Tool not found: ${name}'
|
||||
}
|
||||
}
|
||||
|
||||
fn sampling_error(message string) jsonrpc.RPCError {
|
||||
return jsonrpc.RPCError{
|
||||
code: -32603 // Internal error
|
||||
message: 'Sampling error: ${message}'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,15 @@ import freeflowuniverse.herolib.ai.utils
|
||||
import os
|
||||
|
||||
pub fn generate_rhai_wrapper(name string, source_path string) !string {
|
||||
prompt := rhai_wrapper_generation_prompt(name, source_path) or {panic(err)}
|
||||
// Detect source package and module information
|
||||
source_pkg_info := rust.detect_source_package(source_path)!
|
||||
source_code := rust.read_source_code(source_path)!
|
||||
prompt := rhai_wrapper_generation_prompt(name, source_code, source_pkg_info)!
|
||||
return run_wrapper_generation_task(prompt, RhaiGen{
|
||||
name: name
|
||||
dir: source_path
|
||||
}) or {panic(err)}
|
||||
source_pkg_info: source_pkg_info
|
||||
})!
|
||||
}
|
||||
|
||||
// Runs the task to generate Rhai wrappers
|
||||
@@ -56,11 +60,11 @@ pub fn run_wrapper_generation_task(prompt_content string, gen RhaiGen) !string {
|
||||
}
|
||||
|
||||
// Define a Rhai wrapper generator function for Container functions
|
||||
pub fn rhai_wrapper_generation_prompt(name string, source_code string) !string {
|
||||
pub fn rhai_wrapper_generation_prompt(name string, source_code string, source_pkg_info rust.SourcePackageInfo) !string {
|
||||
current_dir := os.dir(@FILE)
|
||||
example_rhai := os.read_file('${current_dir}/prompts/example_script.md') or {panic(err)}
|
||||
wrapper_md := os.read_file('${current_dir}/prompts/wrapper.md') or {panic(err)}
|
||||
errors_md := os.read_file('${current_dir}/prompts/errors.md') or {panic(err)}
|
||||
example_rhai := os.read_file('${current_dir}/prompts/example_script.md')!
|
||||
wrapper_md := os.read_file('${current_dir}/prompts/wrapper.md')!
|
||||
errors_md := os.read_file('${current_dir}/prompts/errors.md')!
|
||||
|
||||
// Load all required template and guide files
|
||||
guides := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')!
|
||||
@@ -120,6 +124,12 @@ pub fn write_rhai_wrapper_module(wrapper WrapperModule, name string, path string
|
||||
os.write_file('${project_dir}/src/lib.rs', wrapper.lib_rs) or {
|
||||
return error('Failed to write lib.rs: ${err}')
|
||||
}
|
||||
} else {
|
||||
// Use default lib.rs template if none provided
|
||||
lib_rs_content := $tmpl('./templates/lib.rs')
|
||||
os.write_file('${project_dir}/src/lib.rs', lib_rs_content) or {
|
||||
return error('Failed to write lib.rs: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
// Write the wrapper.rs file
|
||||
@@ -141,6 +151,12 @@ pub fn write_rhai_wrapper_module(wrapper WrapperModule, name string, path string
|
||||
os.write_file('${examples_dir}/example.rs', wrapper.example_rs) or {
|
||||
return error('Failed to write example.rs: ${err}')
|
||||
}
|
||||
} else {
|
||||
// Use default example.rs template if none provided
|
||||
example_rs_content := $tmpl('./templates/example.rs')
|
||||
os.write_file('${examples_dir}/example.rs', example_rs_content) or {
|
||||
return error('Failed to write example.rs: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
// Write the engine.rs file if provided
|
||||
@@ -199,39 +215,26 @@ fn extract_module_name(code string) string {
|
||||
struct RhaiGen {
|
||||
name string
|
||||
dir string
|
||||
source_pkg_info rust.SourcePackageInfo
|
||||
}
|
||||
|
||||
// Process the AI response and compile the generated code
|
||||
fn (gen RhaiGen) process_rhai_wrappers(response string)! string {
|
||||
// Extract code blocks from the response
|
||||
code_blocks := extract_code_blocks(response) or {
|
||||
return err
|
||||
}
|
||||
|
||||
name := gen.name
|
||||
|
||||
// Create a WrapperModule struct with the extracted content
|
||||
wrapper := WrapperModule{
|
||||
lib_rs: $tmpl('./templates/lib.rs')
|
||||
wrapper_rs: code_blocks.wrapper_rs
|
||||
example_rs: $tmpl('./templates/example.rs')
|
||||
engine_rs: code_blocks.engine_rs
|
||||
pub fn (gen RhaiGen) process_rhai_wrappers(input string) !string {
|
||||
blocks := extract_code_blocks(input)!
|
||||
source_pkg_info := gen.source_pkg_info
|
||||
// Create the module structure
|
||||
mod := WrapperModule{
|
||||
lib_rs: blocks.lib_rs
|
||||
engine_rs: blocks.engine_rs
|
||||
example_rhai: blocks.example_rhai
|
||||
generic_wrapper_rs: $tmpl('./templates/generic_wrapper.rs')
|
||||
cargo_toml: $tmpl('./templates/cargo.toml')
|
||||
example_rhai: code_blocks.example_rhai
|
||||
wrapper_rs: blocks.wrapper_rs
|
||||
}
|
||||
|
||||
// Create the wrapper module
|
||||
project_dir := write_rhai_wrapper_module(wrapper, gen.name, gen.dir) or {
|
||||
return error('Failed to create wrapper module: ${err}')
|
||||
}
|
||||
// Write the module files
|
||||
project_dir := write_rhai_wrapper_module(mod, gen.name, gen.dir)!
|
||||
|
||||
// Build and run the project
|
||||
build_output, run_output := rust.run_example(project_dir, 'example') or {
|
||||
return err
|
||||
}
|
||||
|
||||
return format_success_message(project_dir, build_output, run_output)
|
||||
return project_dir
|
||||
}
|
||||
|
||||
// CodeBlocks struct to hold extracted code blocks
|
||||
@@ -239,6 +242,7 @@ struct CodeBlocks {
|
||||
wrapper_rs string
|
||||
engine_rs string
|
||||
example_rhai string
|
||||
lib_rs string
|
||||
}
|
||||
|
||||
// Extract code blocks from the AI response
|
||||
@@ -266,10 +270,17 @@ fn extract_code_blocks(response string)! CodeBlocks {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract lib.rs content
|
||||
lib_rs_content := utils.extract_code_block(response, 'lib.rs', 'rust')
|
||||
if lib_rs_content == '' {
|
||||
return error('Failed to extract lib.rs content from response. Please ensure your code is properly formatted inside a code block that starts with ```rust\n// lib.rs and ends with ```')
|
||||
}
|
||||
|
||||
return CodeBlocks{
|
||||
wrapper_rs: wrapper_rs_content
|
||||
engine_rs: engine_rs_content
|
||||
example_rhai: example_rhai_content
|
||||
lib_rs: lib_rs_content
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
260
lib/ai/mcp/rhai/logic/logic_sampling.v
Normal file
260
lib/ai/mcp/rhai/logic/logic_sampling.v
Normal file
@@ -0,0 +1,260 @@
|
||||
module logic
|
||||
|
||||
import freeflowuniverse.herolib.ai.escalayer
|
||||
import freeflowuniverse.herolib.lang.rust
|
||||
import freeflowuniverse.herolib.ai.utils
|
||||
import os
|
||||
|
||||
// pub fn generate_rhai_wrapper_sampling(name string, source_path string) !string {
|
||||
// prompt := rhai_wrapper_generation_prompt(name, source_path) or {panic(err)}
|
||||
// return run_wrapper_generation_task_sampling(prompt, RhaiGen{
|
||||
// name: name
|
||||
// dir: source_path
|
||||
// }) or {panic(err)}
|
||||
// }
|
||||
|
||||
// // Runs the task to generate Rhai wrappers
|
||||
// pub fn run_wrapper_generation_task_sampling(prompt_content string, gen RhaiGen) !string {
|
||||
// // Create a new task
|
||||
// mut task := escalayer.new_task(
|
||||
// name: 'rhai_wrapper_creator.escalayer'
|
||||
// description: 'Create Rhai wrappers for Rust functions that follow builder pattern and create examples corresponding to the provided example file'
|
||||
// )
|
||||
|
||||
// // Create model configs
|
||||
// sonnet_model := escalayer.ModelConfig{
|
||||
// name: 'anthropic/claude-3.7-sonnet'
|
||||
// provider: 'anthropic'
|
||||
// temperature: 0.7
|
||||
// max_tokens: 25000
|
||||
// }
|
||||
|
||||
// gpt4_model := escalayer.ModelConfig{
|
||||
// name: 'gpt-4'
|
||||
// provider: 'openai'
|
||||
// temperature: 0.7
|
||||
// max_tokens: 25000
|
||||
// }
|
||||
|
||||
// // Create a prompt function that returns the prepared content
|
||||
// prompt_function := fn [prompt_content] (input string) string {
|
||||
// return prompt_content
|
||||
// }
|
||||
|
||||
// // Define a single unit task that handles everything
|
||||
// task.new_unit_task(
|
||||
// name: 'create_rhai_wrappers'
|
||||
// prompt_function: prompt_function
|
||||
// callback_function: gen.process_rhai_wrappers
|
||||
// base_model: sonnet_model
|
||||
// retry_model: gpt4_model
|
||||
// retry_count: 1
|
||||
// )
|
||||
|
||||
// // Initiate the task
|
||||
// return task.initiate('')
|
||||
// }
|
||||
|
||||
// @[params]
|
||||
// pub struct WrapperModule {
|
||||
// pub:
|
||||
// lib_rs string
|
||||
// example_rs string
|
||||
// engine_rs string
|
||||
// cargo_toml string
|
||||
// example_rhai string
|
||||
// generic_wrapper_rs string
|
||||
// wrapper_rs string
|
||||
// }
|
||||
|
||||
// // functions is a list of function names that AI should extract and pass in
|
||||
// pub fn write_rhai_wrapper_module(wrapper WrapperModule, name string, path string)! string {
|
||||
|
||||
// // Define project directory paths
|
||||
// project_dir := '${path}/rhai'
|
||||
|
||||
// // Create the project using cargo new --lib
|
||||
// if os.exists(project_dir) {
|
||||
// os.rmdir_all(project_dir) or {
|
||||
// return error('Failed to clean existing project directory: ${err}')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Run cargo new --lib to create the project
|
||||
// os.chdir(path) or {
|
||||
// return error('Failed to change directory to base directory: ${err}')
|
||||
// }
|
||||
|
||||
// cargo_new_result := os.execute('cargo new --lib rhai')
|
||||
// if cargo_new_result.exit_code != 0 {
|
||||
// return error('Failed to create new library project: ${cargo_new_result.output}')
|
||||
// }
|
||||
|
||||
// // Create examples directory
|
||||
// examples_dir := '${project_dir}/examples'
|
||||
// os.mkdir_all(examples_dir) or {
|
||||
// return error('Failed to create examples directory: ${err}')
|
||||
// }
|
||||
|
||||
// // Write the lib.rs file
|
||||
// if wrapper.lib_rs != '' {
|
||||
// os.write_file('${project_dir}/src/lib.rs', wrapper.lib_rs) or {
|
||||
// return error('Failed to write lib.rs: ${err}')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Write the wrapper.rs file
|
||||
// if wrapper.wrapper_rs != '' {
|
||||
// os.write_file('${project_dir}/src/wrapper.rs', wrapper.wrapper_rs) or {
|
||||
// return error('Failed to write wrapper.rs: ${err}')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Write the generic wrapper.rs file
|
||||
// if wrapper.generic_wrapper_rs != '' {
|
||||
// os.write_file('${project_dir}/src/generic_wrapper.rs', wrapper.generic_wrapper_rs) or {
|
||||
// return error('Failed to write generic wrapper.rs: ${err}')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Write the example.rs file
|
||||
// if wrapper.example_rs != '' {
|
||||
// os.write_file('${examples_dir}/example.rs', wrapper.example_rs) or {
|
||||
// return error('Failed to write example.rs: ${err}')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Write the engine.rs file if provided
|
||||
// if wrapper.engine_rs != '' {
|
||||
// os.write_file('${project_dir}/src/engine.rs', wrapper.engine_rs) or {
|
||||
// return error('Failed to write engine.rs: ${err}')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Write the Cargo.toml file
|
||||
// os.write_file('${project_dir}/Cargo.toml', wrapper.cargo_toml) or {
|
||||
// return error('Failed to write Cargo.toml: ${err}')
|
||||
// }
|
||||
|
||||
// // Write the example.rhai file
|
||||
// os.write_file('${examples_dir}/example.rhai', wrapper.example_rhai) or {
|
||||
// return error('Failed to write example.rhai: ${err}')
|
||||
// }
|
||||
|
||||
// return project_dir
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// // Extract module name from wrapper code
|
||||
// fn extract_module_name(code string) string {
|
||||
// lines := code.split('\n')
|
||||
|
||||
// for line in lines {
|
||||
// // Look for pub mod or mod declarations
|
||||
// if line.contains('pub mod ') || line.contains('mod ') {
|
||||
// // Extract module name
|
||||
// mut parts := []string{}
|
||||
// if line.contains('pub mod ') {
|
||||
// parts = line.split('pub mod ')
|
||||
// } else {
|
||||
// parts = line.split('mod ')
|
||||
// }
|
||||
|
||||
// if parts.len > 1 {
|
||||
// // Extract the module name and remove any trailing characters
|
||||
// mut name := parts[1].trim_space()
|
||||
// // Remove any trailing { or ; or whitespace
|
||||
// name = name.trim_right('{').trim_right(';').trim_space()
|
||||
// if name != '' {
|
||||
// return name
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return ''
|
||||
// }
|
||||
|
||||
// // RhaiGen struct for generating Rhai wrappers
|
||||
// struct RhaiGen {
|
||||
// name string
|
||||
// dir string
|
||||
// }
|
||||
|
||||
// // Process the AI response and compile the generated code
|
||||
// fn (gen RhaiGen) process_rhai_wrappers(response string)! string {
|
||||
// // Extract code blocks from the response
|
||||
// code_blocks := extract_code_blocks(response) or {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// name := gen.name
|
||||
|
||||
// // Create a WrapperModule struct with the extracted content
|
||||
// wrapper := WrapperModule{
|
||||
// lib_rs: $tmpl('./templates/lib.rs')
|
||||
// wrapper_rs: code_blocks.wrapper_rs
|
||||
// example_rs: $tmpl('./templates/example.rs')
|
||||
// engine_rs: code_blocks.engine_rs
|
||||
// generic_wrapper_rs: $tmpl('./templates/generic_wrapper.rs')
|
||||
// cargo_toml: $tmpl('./templates/cargo.toml')
|
||||
// example_rhai: code_blocks.example_rhai
|
||||
// }
|
||||
|
||||
// // Create the wrapper module
|
||||
// project_dir := write_rhai_wrapper_module(wrapper, gen.name, gen.dir) or {
|
||||
// return error('Failed to create wrapper module: ${err}')
|
||||
// }
|
||||
|
||||
// // Build and run the project
|
||||
// build_output, run_output := rust.run_example(project_dir, 'example') or {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return format_success_message(project_dir, build_output, run_output)
|
||||
// }
|
||||
|
||||
// // CodeBlocks struct to hold extracted code blocks
|
||||
// struct CodeBlocks {
|
||||
// wrapper_rs string
|
||||
// engine_rs string
|
||||
// example_rhai string
|
||||
// }
|
||||
|
||||
// // Extract code blocks from the AI response
|
||||
// fn extract_code_blocks(response string)! CodeBlocks {
|
||||
// // Extract wrapper.rs content
|
||||
// wrapper_rs_content := utils.extract_code_block(response, 'wrapper.rs', 'rust')
|
||||
// if wrapper_rs_content == '' {
|
||||
// return error('Failed to extract wrapper.rs content from response. Please ensure your code is properly formatted inside a code block that starts with ```rust\n// wrapper.rs and ends with ```')
|
||||
// }
|
||||
|
||||
// // Extract engine.rs content
|
||||
// mut engine_rs_content := utils.extract_code_block(response, 'engine.rs', 'rust')
|
||||
// if engine_rs_content == '' {
|
||||
// // Try to extract from the response without explicit language marker
|
||||
// engine_rs_content = utils.extract_code_block(response, 'engine.rs', '')
|
||||
// }
|
||||
|
||||
// // Extract example.rhai content
|
||||
// mut example_rhai_content := utils.extract_code_block(response, 'example.rhai', 'rhai')
|
||||
// if example_rhai_content == '' {
|
||||
// // Try to extract from the response without explicit language marker
|
||||
// example_rhai_content = utils.extract_code_block(response, 'example.rhai', '')
|
||||
// if example_rhai_content == '' {
|
||||
// return error('Failed to extract example.rhai content from response. Please ensure your code is properly formatted inside a code block that starts with ```rhai\n// example.rhai and ends with ```')
|
||||
// }
|
||||
// }
|
||||
|
||||
// return CodeBlocks{
|
||||
// wrapper_rs: wrapper_rs_content
|
||||
// engine_rs: engine_rs_content
|
||||
// example_rhai: example_rhai_content
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Format success message
|
||||
// fn format_success_message(project_dir string, build_output string, run_output string) string {
|
||||
// return 'Successfully generated Rhai wrappers and ran the example!\n\nProject created at: ${project_dir}\n\nBuild output:\n${build_output}\n\nRun output:\n${run_output}'
|
||||
// }
|
||||
@@ -25,9 +25,9 @@ IMPORTANT NOTES:
|
||||
- rhai = "1.21.0"
|
||||
- serde = { version = "1.0", features = ["derive"] }
|
||||
- serde_json = "1.0"
|
||||
- sal = { path = "../../../" }
|
||||
- @{source_pkg_info.name} = { path = "@{source_pkg_info.path}" }
|
||||
|
||||
3. For the wrapper: `use sal::@{name};` this way you can access the module functions and objects with @{name}::
|
||||
3. For the wrapper: `use @{source_pkg_info.name}::@{source_pkg_info.module};` this way you can access the module functions and objects with @{source_pkg_info.module}::
|
||||
|
||||
4. The generic_wrapper.rs file will be hardcoded into the package, you can use code from there.
|
||||
|
||||
@@ -95,7 +95,5 @@ IMPORTANT NOTES:
|
||||
@{engine}
|
||||
|
||||
MOST IMPORTANT:
|
||||
import package being wrapped as `use sal::<n>`
|
||||
import package being wrapped as `use @{source_pkg_info.name}::@{source_pkg_info.module}`
|
||||
your engine create function is called `create_rhai_engine`
|
||||
|
||||
```
|
||||
|
||||
@@ -7,4 +7,4 @@ edition = "2021"
|
||||
rhai = "1.21.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sal = { path = "../../../" }
|
||||
@{source_pkg_info.name} = { path = "@{source_pkg_info.path}" }
|
||||
@@ -30,8 +30,9 @@ pub fn rhai_wrapper_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
// Extract the module name from the directory path (last component)
|
||||
name := rust.extract_module_name_from_path(source_path)
|
||||
|
||||
source_pkg_info := rust.detect_source_package(source_path)!
|
||||
|
||||
result := logic.rhai_wrapper_generation_prompt(name, source_code)!
|
||||
result := logic.rhai_wrapper_generation_prompt(name, source_code, source_pkg_info)!
|
||||
return [mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
|
||||
21
lib/ai/mcp/rust/command.v
Normal file
21
lib/ai/mcp/rust/command.v
Normal file
@@ -0,0 +1,21 @@
|
||||
module rust
|
||||
|
||||
import cli
|
||||
|
||||
pub const command := cli.Command{
|
||||
sort_flags: true
|
||||
name: 'rust'
|
||||
description: 'Rust language tools command'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'start'
|
||||
execute: cmd_start
|
||||
description: 'start the Rust MCP server'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fn cmd_start(cmd cli.Command) ! {
|
||||
mut server := new_mcp_server()!
|
||||
server.start()!
|
||||
}
|
||||
54
lib/ai/mcp/rust/generics.v
Normal file
54
lib/ai/mcp/rust/generics.v
Normal file
@@ -0,0 +1,54 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp {ToolContent}
|
||||
|
||||
pub fn result_to_mcp_tool_contents[T](result T) []ToolContent {
|
||||
return [result_to_mcp_tool_content[T](result)]
|
||||
}
|
||||
|
||||
pub fn result_to_mcp_tool_content[T](result T) ToolContent {
|
||||
$if T is string {
|
||||
return ToolContent{
|
||||
typ: 'text'
|
||||
text: result.str()
|
||||
}
|
||||
} $else $if T is int {
|
||||
return ToolContent{
|
||||
typ: 'number'
|
||||
number: result.int()
|
||||
}
|
||||
} $else $if T is bool {
|
||||
return ToolContent{
|
||||
typ: 'boolean'
|
||||
boolean: result.bool()
|
||||
}
|
||||
} $else $if result is $array {
|
||||
mut items := []ToolContent{}
|
||||
for item in result {
|
||||
items << result_to_mcp_tool_content(item)
|
||||
}
|
||||
return ToolContent{
|
||||
typ: 'array'
|
||||
items: items
|
||||
}
|
||||
} $else $if T is $struct {
|
||||
mut properties := map[string]ToolContent{}
|
||||
$for field in T.fields {
|
||||
properties[field.name] = result_to_mcp_tool_content(result.$(field.name))
|
||||
}
|
||||
return ToolContent{
|
||||
typ: 'object'
|
||||
properties: properties
|
||||
}
|
||||
} $else {
|
||||
panic('Unsupported type: ${typeof(result)}')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn array_to_mcp_tool_contents[U](array []U) []ToolContent {
|
||||
mut contents := []ToolContent{}
|
||||
for item in array {
|
||||
contents << result_to_mcp_tool_content(item)
|
||||
}
|
||||
return contents
|
||||
}
|
||||
52
lib/ai/mcp/rust/mcp.v
Normal file
52
lib/ai/mcp/rust/mcp.v
Normal file
@@ -0,0 +1,52 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import log
|
||||
|
||||
pub fn new_mcp_server() !&mcp.Server {
|
||||
log.info('Creating new Rust MCP server')
|
||||
|
||||
// Initialize the server with tools and prompts
|
||||
mut server := mcp.new_server(mcp.MemoryBackend{
|
||||
tools: {
|
||||
'list_functions_in_file': list_functions_in_file_spec
|
||||
'list_structs_in_file': list_structs_in_file_spec
|
||||
'list_modules_in_dir': list_modules_in_dir_spec
|
||||
'get_import_statement': get_import_statement_spec
|
||||
// 'get_module_dependency': get_module_dependency_spec
|
||||
}
|
||||
tool_handlers: {
|
||||
'list_functions_in_file': list_functions_in_file_handler
|
||||
'list_structs_in_file': list_structs_in_file_handler
|
||||
'list_modules_in_dir': list_modules_in_dir_handler
|
||||
'get_import_statement': get_import_statement_handler
|
||||
// 'get_module_dependency': get_module_dependency_handler
|
||||
}
|
||||
prompts: {
|
||||
'rust_functions': rust_functions_prompt_spec
|
||||
'rust_structs': rust_structs_prompt_spec
|
||||
'rust_modules': rust_modules_prompt_spec
|
||||
'rust_imports': rust_imports_prompt_spec
|
||||
'rust_dependencies': rust_dependencies_prompt_spec
|
||||
'rust_tools_guide': rust_tools_guide_prompt_spec
|
||||
}
|
||||
prompt_handlers: {
|
||||
'rust_functions': rust_functions_prompt_handler
|
||||
'rust_structs': rust_structs_prompt_handler
|
||||
'rust_modules': rust_modules_prompt_handler
|
||||
'rust_imports': rust_imports_prompt_handler
|
||||
'rust_dependencies': rust_dependencies_prompt_handler
|
||||
'rust_tools_guide': rust_tools_guide_prompt_handler
|
||||
}
|
||||
}, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
server_info: mcp.ServerInfo{
|
||||
name: 'rust'
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
})!
|
||||
|
||||
return server
|
||||
}
|
||||
144
lib/ai/mcp/rust/prompts.v
Normal file
144
lib/ai/mcp/rust/prompts.v
Normal file
@@ -0,0 +1,144 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp
|
||||
import os
|
||||
import x.json2 as json { Any }
|
||||
|
||||
// Prompt specification for Rust functions
|
||||
const rust_functions_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_functions'
|
||||
description: 'Provides guidance on working with Rust functions and using the list_functions_in_file tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_functions prompt
|
||||
pub fn rust_functions_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/functions.md')!
|
||||
|
||||
return [mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust structs
|
||||
const rust_structs_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_structs'
|
||||
description: 'Provides guidance on working with Rust structs and using the list_structs_in_file tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_structs prompt
|
||||
pub fn rust_structs_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/structs.md')!
|
||||
|
||||
return [mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust modules
|
||||
const rust_modules_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_modules'
|
||||
description: 'Provides guidance on working with Rust modules and using the list_modules_in_dir tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_modules prompt
|
||||
pub fn rust_modules_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/modules.md')!
|
||||
|
||||
return [mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust imports
|
||||
const rust_imports_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_imports'
|
||||
description: 'Provides guidance on working with Rust imports and using the get_import_statement tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_imports prompt
|
||||
pub fn rust_imports_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/imports.md')!
|
||||
|
||||
return [mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
// Prompt specification for Rust dependencies
|
||||
const rust_dependencies_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_dependencies'
|
||||
description: 'Provides guidance on working with Rust dependencies and using the get_module_dependency tool'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_dependencies prompt
|
||||
pub fn rust_dependencies_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
content := os.read_file('${os.dir(@FILE)}/prompts/dependencies.md')!
|
||||
|
||||
return [mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
// Prompt specification for general Rust tools guide
|
||||
const rust_tools_guide_prompt_spec = mcp.Prompt{
|
||||
name: 'rust_tools_guide'
|
||||
description: 'Provides a comprehensive guide on all available Rust tools and how to use them'
|
||||
arguments: []
|
||||
}
|
||||
|
||||
// Handler for rust_tools_guide prompt
|
||||
pub fn rust_tools_guide_prompt_handler(arguments []string) ![]mcp.PromptMessage {
|
||||
// Combine all prompt files into one comprehensive guide
|
||||
functions_content := os.read_file('${os.dir(@FILE)}/prompts/functions.md')!
|
||||
structs_content := os.read_file('${os.dir(@FILE)}/prompts/structs.md')!
|
||||
modules_content := os.read_file('${os.dir(@FILE)}/prompts/modules.md')!
|
||||
imports_content := os.read_file('${os.dir(@FILE)}/prompts/imports.md')!
|
||||
dependencies_content := os.read_file('${os.dir(@FILE)}/prompts/dependencies.md')!
|
||||
|
||||
combined_content := '# Rust Language Tools Guide\n\n' +
|
||||
'This guide provides comprehensive information on working with Rust code using the available tools.\n\n' +
|
||||
'## Table of Contents\n\n' +
|
||||
'1. [Functions](#functions)\n' +
|
||||
'2. [Structs](#structs)\n' +
|
||||
'3. [Modules](#modules)\n' +
|
||||
'4. [Imports](#imports)\n' +
|
||||
'5. [Dependencies](#dependencies)\n\n' +
|
||||
'<a name="functions"></a>\n' + functions_content + '\n\n' +
|
||||
'<a name="structs"></a>\n' + structs_content + '\n\n' +
|
||||
'<a name="modules"></a>\n' + modules_content + '\n\n' +
|
||||
'<a name="imports"></a>\n' + imports_content + '\n\n' +
|
||||
'<a name="dependencies"></a>\n' + dependencies_content
|
||||
|
||||
return [mcp.PromptMessage{
|
||||
role: 'assistant'
|
||||
content: mcp.PromptContent{
|
||||
typ: 'text'
|
||||
text: combined_content
|
||||
}
|
||||
}]
|
||||
}
|
||||
305
lib/ai/mcp/rust/tools.v
Normal file
305
lib/ai/mcp/rust/tools.v
Normal file
@@ -0,0 +1,305 @@
|
||||
module rust
|
||||
|
||||
import freeflowuniverse.herolib.ai.mcp {ToolContent}
|
||||
import freeflowuniverse.herolib.lang.rust
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import x.json2 as json { Any }
|
||||
|
||||
// Tool specification for listing functions in a Rust file
|
||||
const list_functions_in_file_spec = mcp.Tool{
|
||||
name: 'list_functions_in_file'
|
||||
description: 'Lists all function definitions in a Rust file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file'
|
||||
})
|
||||
}
|
||||
required: ['file_path']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for list_functions_in_file
|
||||
pub fn list_functions_in_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
result := rust.list_functions_in_file(file_path) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.array_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for listing structs in a Rust file
|
||||
const list_structs_in_file_spec = mcp.Tool{
|
||||
name: 'list_structs_in_file'
|
||||
description: 'Lists all struct definitions in a Rust file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file'
|
||||
})
|
||||
}
|
||||
required: ['file_path']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for list_structs_in_file
|
||||
pub fn list_structs_in_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
result := rust.list_structs_in_file(file_path) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.array_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for listing modules in a directory
|
||||
const list_modules_in_dir_spec = mcp.Tool{
|
||||
name: 'list_modules_in_dir'
|
||||
description: 'Lists all Rust modules in a directory'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'dir_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the directory'
|
||||
})
|
||||
}
|
||||
required: ['dir_path']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for list_modules_in_dir
|
||||
pub fn list_modules_in_dir_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
dir_path := arguments['dir_path'].str()
|
||||
result := rust.list_modules_in_directory(dir_path) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.array_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for getting an import statement
|
||||
const get_import_statement_spec = mcp.Tool{
|
||||
name: 'get_import_statement'
|
||||
description: 'Generates appropriate Rust import statement for a module based on file paths'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'current_file': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the file where the import will be added'
|
||||
}),
|
||||
'target_module': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the target module to be imported'
|
||||
})
|
||||
}
|
||||
required: ['current_file', 'target_module']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_import_statement
|
||||
pub fn get_import_statement_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
current_file := arguments['current_file'].str()
|
||||
target_module := arguments['target_module'].str()
|
||||
result := rust.generate_import_statement(current_file, target_module) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// Tool specification for getting module dependency information
|
||||
const get_module_dependency_spec = mcp.Tool{
|
||||
name: 'get_module_dependency'
|
||||
description: 'Gets dependency information for adding a Rust module to a project'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'importer_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the file that will import the module'
|
||||
}),
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the module that will be imported'
|
||||
})
|
||||
}
|
||||
required: ['importer_path', 'module_path']
|
||||
}
|
||||
}
|
||||
|
||||
struct Tester {
|
||||
import_statement string
|
||||
module_path string
|
||||
}
|
||||
|
||||
// Handler for get_module_dependency
|
||||
pub fn get_module_dependency_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
importer_path := arguments['importer_path'].str()
|
||||
module_path := arguments['module_path'].str()
|
||||
dependency := rust.get_module_dependency(importer_path, module_path) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: result_to_mcp_tool_contents[Tester](Tester{
|
||||
import_statement: dependency.import_statement
|
||||
module_path: dependency.module_path
|
||||
}) // Return JSON string
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Function from File Tool ---
|
||||
|
||||
// Specification for get_function_from_file tool
|
||||
const get_function_from_file_spec = mcp.Tool{
|
||||
name: 'get_function_from_file'
|
||||
description: 'Get the declaration of a Rust function from a specified file path.'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file.'
|
||||
}),
|
||||
'function_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Name of the function to retrieve (e.g., \'my_function\' or \'MyStruct::my_method\').'
|
||||
})
|
||||
}
|
||||
required: ['file_path', 'function_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_function_from_file
|
||||
pub fn get_function_from_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
function_name := arguments['function_name'].str()
|
||||
result := rust.get_function_from_file(file_path, function_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Function from Module Tool ---
|
||||
|
||||
// Specification for get_function_from_module tool
|
||||
const get_function_from_module_spec = mcp.Tool{
|
||||
name: 'get_function_from_module'
|
||||
description: 'Get the declaration of a Rust function from a specified module path (directory or file).'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust module directory or file.'
|
||||
}),
|
||||
'function_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Name of the function to retrieve (e.g., \'my_function\' or \'MyStruct::my_method\').'
|
||||
})
|
||||
}
|
||||
required: ['module_path', 'function_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_function_from_module
|
||||
pub fn get_function_from_module_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
module_path := arguments['module_path'].str()
|
||||
function_name := arguments['function_name'].str()
|
||||
result := rust.get_function_from_module(module_path, function_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Struct from File Tool ---
|
||||
|
||||
// Specification for get_struct_from_file tool
|
||||
const get_struct_from_file_spec = mcp.Tool{
|
||||
name: 'get_struct_from_file'
|
||||
description: 'Get the declaration of a Rust struct from a specified file path.'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'file_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust file.'
|
||||
}),
|
||||
'struct_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Name of the struct to retrieve (e.g., \'MyStruct\').'
|
||||
})
|
||||
}
|
||||
required: ['file_path', 'struct_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_struct_from_file
|
||||
pub fn get_struct_from_file_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
file_path := arguments['file_path'].str()
|
||||
struct_name := arguments['struct_name'].str()
|
||||
result := rust.get_struct_from_file(file_path, struct_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Get Struct from Module Tool ---
|
||||
|
||||
// Specification for get_struct_from_module tool
|
||||
const get_struct_from_module_spec = mcp.Tool{
|
||||
name: 'get_struct_from_module'
|
||||
description: 'Get the declaration of a Rust struct from a specified module path (directory or file).'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'module_path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the Rust module directory or file.'
|
||||
}),
|
||||
'struct_name': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Name of the struct to retrieve (e.g., \'MyStruct\').'
|
||||
})
|
||||
}
|
||||
required: ['module_path', 'struct_name']
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for get_struct_from_module
|
||||
pub fn get_struct_from_module_handler(arguments map[string]Any) !mcp.ToolCallResult {
|
||||
module_path := arguments['module_path'].str()
|
||||
struct_name := arguments['struct_name'].str()
|
||||
result := rust.get_struct_from_module(module_path, struct_name) or {
|
||||
return mcp.error_tool_call_result(err)
|
||||
}
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: mcp.result_to_mcp_tool_contents[string](result)
|
||||
}
|
||||
}
|
||||
534
lib/lang/rhai/prompts/generate_rhai_function_wrapper.md
Normal file
534
lib/lang/rhai/prompts/generate_rhai_function_wrapper.md
Normal file
@@ -0,0 +1,534 @@
|
||||
# Generate Single Rhai Wrapper Function
|
||||
|
||||
You are tasked with creating a **single** Rhai wrapper function for the provided Rust function or method signature.
|
||||
|
||||
## Input Rust Function
|
||||
|
||||
```rust
|
||||
@{gen.function}
|
||||
```
|
||||
|
||||
## Input Rust Types
|
||||
|
||||
Below are the struct declarations for types used in the function
|
||||
|
||||
@for structure in gen.structs
|
||||
```rust
|
||||
@{structure}
|
||||
```
|
||||
@end
|
||||
|
||||
## Instructions
|
||||
|
||||
1. **Analyze the Signature:**
|
||||
* Identify the function/method name.
|
||||
* Identify the input parameters and their types.
|
||||
* Identify the return type.
|
||||
* Determine if it's a method on a struct (e.g., `&self`, `&mut self`).
|
||||
|
||||
2. **Define the Wrapper Signature:**
|
||||
* The wrapper function name should generally match the original Rust function name (use snake_case).
|
||||
* Input parameters should correspond to the Rust function's parameters. You might need to adjust types for Rhai compatibility (e.g., `&str` becomes `&str`, `String` becomes `String`, `Vec<T>` might become `rhai::Array`, `HashMap<K, V>` might become `rhai::Map`).
|
||||
* If the original function is a method on a struct (e.g., `fn method(&self, ...) ` or `fn method_mut(&mut self, ...)`), the first parameter of the wrapper must be the receiver type (e.g., `mut? receiver: StructType`). Ensure the mutability matches.
|
||||
* The return type **must** be `Result<T, Box<EvalAltResult>>`, where `T` is the Rhai-compatible equivalent of the original Rust function's return type. If the original function returns `Result<U, E>`, `T` should be the Rhai-compatible version of `U`, and the error `E` should be mapped into `Box<EvalAltResult>`. If the original returns `()`, use `Result<(), Box<EvalAltResult>>`. If it returns a simple type `U`, use `Result<U, Box<EvalAltResult>>`.
|
||||
|
||||
3. **Implement the Wrapper Body:**
|
||||
* **Call the Original Function/Method:** Call the Rust function or method using the input parameters. Perform necessary type conversions if Rhai types (like `Array`, `Map`) were used in the wrapper signature.
|
||||
* **Handle Struct Methods:** If it's a method, call it on the `receiver` parameter (e.g., `receiver.method(...)`).
|
||||
* **Error Handling:**
|
||||
* Use `.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error description: {}", e).into(), rhai::Position::NONE)))` to convert any potential errors from the original function call into a `Box<EvalAltResult>`. Provide a descriptive error message.
|
||||
* If the original function doesn't return `Result`, wrap the successful result using `Ok(...)`.
|
||||
* **Return Value Conversion:** If the original function's success type needs conversion for Rhai (e.g., a complex struct to a `Map`, a `PathBuf` to `String`), perform the conversion before returning `Ok(...)`. Convert `PathBuf` or path references using `.to_string_lossy().to_string()` or `.to_string()` as appropriate.
|
||||
|
||||
4. **Best Practices:**
|
||||
* Use `rhai::{Engine, EvalAltResult, Dynamic, Map, Array}` imports as needed.
|
||||
* **Prefer strongly typed return values** (`Result<String, ...>`, `Result<bool, ...>`, `Result<Vec<String>, ...>`) over `Result<Dynamic, ...>`. Only use `Dynamic` if the return type is truly variable or complex and cannot be easily represented otherwise.
|
||||
* **Do NOT use the `#[rhai_fn]` attribute.** The function will be registered manually.
|
||||
* Handle string type consistency (e.g., `String::from()` for literals if mixed with `format!`).
|
||||
|
||||
### Error Handling
|
||||
|
||||
Assume that the following function is available to use in the rhai wrapper body:
|
||||
|
||||
```rust
|
||||
// Helper functions for error conversion with improved context
|
||||
fn [modulename]_error_to_rhai_error<T>(result: Result<T, [modulename]Error>) -> Result<T, Box<EvalAltResult>> {}
|
||||
```
|
||||
|
||||
And feel free to use it like:
|
||||
```rust
|
||||
/// Create a new Container
|
||||
pub fn container_new(name: &str) -> Result<Container, Box<EvalAltResult>> {
|
||||
nerdctl_error_to_rhai_error(Container::new(name))
|
||||
}
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Provide **only** the generated Rust code for the wrapper function, enclosed in triple backticks.
|
||||
|
||||
```rust
|
||||
// Your generated wrapper function here
|
||||
pub fn wrapper_function_name(...) -> Result<..., Box<EvalAltResult>> {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Below is a bunch of input and outputted wrapped functions.
|
||||
|
||||
Example Input Types:
|
||||
```rust
|
||||
pub struct Container {
|
||||
/// Name of the container
|
||||
pub name: String,
|
||||
/// Container ID
|
||||
pub container_id: Option<String>,
|
||||
/// Base image (if created from an image)
|
||||
pub image: Option<String>,
|
||||
/// Configuration options
|
||||
pub config: HashMap<String, String>,
|
||||
/// Port mappings
|
||||
pub ports: Vec<String>,
|
||||
/// Volume mounts
|
||||
pub volumes: Vec<String>,
|
||||
/// Environment variables
|
||||
pub env_vars: HashMap<String, String>,
|
||||
/// Network to connect to
|
||||
pub network: Option<String>,
|
||||
/// Network aliases
|
||||
pub network_aliases: Vec<String>,
|
||||
/// CPU limit
|
||||
pub cpu_limit: Option<String>,
|
||||
/// Memory limit
|
||||
pub memory_limit: Option<String>,
|
||||
/// Memory swap limit
|
||||
pub memory_swap_limit: Option<String>,
|
||||
/// CPU shares
|
||||
pub cpu_shares: Option<String>,
|
||||
/// Restart policy
|
||||
pub restart_policy: Option<String>,
|
||||
/// Health check
|
||||
pub health_check: Option<HealthCheck>,
|
||||
/// Whether to run in detached mode
|
||||
pub detach: bool,
|
||||
/// Snapshotter to use
|
||||
pub snapshotter: Option<String>,
|
||||
}
|
||||
|
||||
/// Health check configuration for a container
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HealthCheck {
|
||||
/// Command to run for health check
|
||||
pub cmd: String,
|
||||
/// Time between running the check (default: 30s)
|
||||
pub interval: Option<String>,
|
||||
/// Maximum time to wait for a check to complete (default: 30s)
|
||||
pub timeout: Option<String>,
|
||||
/// Number of consecutive failures needed to consider unhealthy (default: 3)
|
||||
pub retries: Option<u32>,
|
||||
/// Start period for the container to initialize before counting retries (default: 0s)
|
||||
pub start_period: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
Example Input Functions:
|
||||
```rust
|
||||
/// Set memory swap limit for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `memory_swap` - Memory swap limit (e.g., "1g" for 1GB)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_memory_swap_limit(mut self, memory_swap: &str) -> Self {
|
||||
self.memory_swap_limit = Some(memory_swap.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set CPU shares for the container (relative weight)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `shares` - CPU shares (e.g., "1024" for default, "512" for half)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_cpu_shares(mut self, shares: &str) -> Self {
|
||||
self.cpu_shares = Some(shares.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set restart policy for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `policy` - Restart policy (e.g., "no", "always", "on-failure", "unless-stopped")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_restart_policy(mut self, policy: &str) -> Self {
|
||||
self.restart_policy = Some(policy.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a simple health check for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cmd` - Command to run for health check (e.g., "curl -f http://localhost/ || exit 1")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_health_check(mut self, cmd: &str) -> Self {
|
||||
// Use the health check script module to prepare the command
|
||||
let prepared_cmd = prepare_health_check_command(cmd, &self.name);
|
||||
|
||||
self.health_check = Some(HealthCheck {
|
||||
cmd: prepared_cmd,
|
||||
interval: None,
|
||||
timeout: None,
|
||||
retries: None,
|
||||
start_period: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a health check with custom options for the container
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cmd` - Command to run for health check
|
||||
/// * `interval` - Optional time between running the check (e.g., "30s", "1m")
|
||||
/// * `timeout` - Optional maximum time to wait for a check to complete (e.g., "30s", "1m")
|
||||
/// * `retries` - Optional number of consecutive failures needed to consider unhealthy
|
||||
/// * `start_period` - Optional start period for the container to initialize before counting retries (e.g., "30s", "1m")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_health_check_options(
|
||||
mut self,
|
||||
cmd: &str,
|
||||
interval: Option<&str>,
|
||||
timeout: Option<&str>,
|
||||
retries: Option<u32>,
|
||||
start_period: Option<&str>,
|
||||
) -> Self {
|
||||
// Use the health check script module to prepare the command
|
||||
let prepared_cmd = prepare_health_check_command(cmd, &self.name);
|
||||
|
||||
let mut health_check = HealthCheck {
|
||||
cmd: prepared_cmd,
|
||||
interval: None,
|
||||
timeout: None,
|
||||
retries: None,
|
||||
start_period: None,
|
||||
};
|
||||
|
||||
if let Some(interval_value) = interval {
|
||||
health_check.interval = Some(interval_value.to_string());
|
||||
}
|
||||
|
||||
if let Some(timeout_value) = timeout {
|
||||
health_check.timeout = Some(timeout_value.to_string());
|
||||
}
|
||||
|
||||
if let Some(retries_value) = retries {
|
||||
health_check.retries = Some(retries_value);
|
||||
}
|
||||
|
||||
if let Some(start_period_value) = start_period {
|
||||
health_check.start_period = Some(start_period_value.to_string());
|
||||
}
|
||||
|
||||
self.health_check = Some(health_check);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the snapshotter
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `snapshotter` - Snapshotter to use
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_snapshotter(mut self, snapshotter: &str) -> Self {
|
||||
self.snapshotter = Some(snapshotter.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to run in detached mode
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `detach` - Whether to run in detached mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Self` - The container instance for method chaining
|
||||
pub fn with_detach(mut self, detach: bool) -> Self {
|
||||
self.detach = detach;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the container
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Self, NerdctlError>` - Container instance or error
|
||||
pub fn build(self) -> Result<Self, NerdctlError> {
|
||||
// If container already exists, return it
|
||||
if self.container_id.is_some() {
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
// If no image is specified, return an error
|
||||
let image = match &self.image {
|
||||
Some(img) => img,
|
||||
None => return Err(NerdctlError::Other("No image specified for container creation".to_string())),
|
||||
};
|
||||
|
||||
// Build the command arguments as strings
|
||||
let mut args_strings = Vec::new();
|
||||
args_strings.push("run".to_string());
|
||||
|
||||
if self.detach {
|
||||
args_strings.push("-d".to_string());
|
||||
}
|
||||
|
||||
args_strings.push("--name".to_string());
|
||||
args_strings.push(self.name.clone());
|
||||
|
||||
// Add port mappings
|
||||
for port in &self.ports {
|
||||
args_strings.push("-p".to_string());
|
||||
args_strings.push(port.clone());
|
||||
}
|
||||
|
||||
// Add volume mounts
|
||||
for volume in &self.volumes {
|
||||
args_strings.push("-v".to_string());
|
||||
args_strings.push(volume.clone());
|
||||
}
|
||||
|
||||
// Add environment variables
|
||||
for (key, value) in &self.env_vars {
|
||||
args_strings.push("-e".to_string());
|
||||
args_strings.push(format!("{}={}", key, value));
|
||||
}
|
||||
|
||||
// Add network configuration
|
||||
if let Some(network) = &self.network {
|
||||
args_strings.push("--network".to_string());
|
||||
args_strings.push(network.clone());
|
||||
}
|
||||
|
||||
// Add network aliases
|
||||
for alias in &self.network_aliases {
|
||||
args_strings.push("--network-alias".to_string());
|
||||
args_strings.push(alias.clone());
|
||||
}
|
||||
|
||||
// Add resource limits
|
||||
if let Some(cpu_limit) = &self.cpu_limit {
|
||||
args_strings.push("--cpus".to_string());
|
||||
args_strings.push(cpu_limit.clone());
|
||||
}
|
||||
|
||||
if let Some(memory_limit) = &self.memory_limit {
|
||||
args_strings.push("--memory".to_string());
|
||||
args_strings.push(memory_limit.clone());
|
||||
}
|
||||
|
||||
if let Some(memory_swap_limit) = &self.memory_swap_limit {
|
||||
args_strings.push("--memory-swap".to_string());
|
||||
args_strings.push(memory_swap_limit.clone());
|
||||
}
|
||||
|
||||
if let Some(cpu_shares) = &self.cpu_shares {
|
||||
args_strings.push("--cpu-shares".to_string());
|
||||
args_strings.push(cpu_shares.clone());
|
||||
}
|
||||
|
||||
// Add restart policy
|
||||
if let Some(restart_policy) = &self.restart_policy {
|
||||
args_strings.push("--restart".to_string());
|
||||
args_strings.push(restart_policy.clone());
|
||||
}
|
||||
|
||||
// Add health check
|
||||
if let Some(health_check) = &self.health_check {
|
||||
args_strings.push("--health-cmd".to_string());
|
||||
args_strings.push(health_check.cmd.clone());
|
||||
|
||||
if let Some(interval) = &health_check.interval {
|
||||
args_strings.push("--health-interval".to_string());
|
||||
args_strings.push(interval.clone());
|
||||
}
|
||||
|
||||
if let Some(timeout) = &health_check.timeout {
|
||||
args_strings.push("--health-timeout".to_string());
|
||||
args_strings.push(timeout.clone());
|
||||
}
|
||||
|
||||
if let Some(retries) = &health_check.retries {
|
||||
args_strings.push("--health-retries".to_string());
|
||||
args_strings.push(retries.to_string());
|
||||
}
|
||||
|
||||
if let Some(start_period) = &health_check.start_period {
|
||||
args_strings.push("--health-start-period".to_string());
|
||||
args_strings.push(start_period.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(snapshotter_value) = &self.snapshotter {
|
||||
args_strings.push("--snapshotter".to_string());
|
||||
args_strings.push(snapshotter_value.clone());
|
||||
}
|
||||
|
||||
// Add flags to avoid BPF issues
|
||||
args_strings.push("--cgroup-manager=cgroupfs".to_string());
|
||||
|
||||
args_strings.push(image.clone());
|
||||
|
||||
// Convert to string slices for the command
|
||||
let args: Vec<&str> = args_strings.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
// Execute the command
|
||||
let result = execute_nerdctl_command(&args)?;
|
||||
|
||||
// Get the container ID from the output
|
||||
let container_id = result.stdout.trim().to_string();
|
||||
|
||||
Ok(Self {
|
||||
name: self.name,
|
||||
container_id: Some(container_id),
|
||||
image: self.image,
|
||||
config: self.config,
|
||||
ports: self.ports,
|
||||
volumes: self.volumes,
|
||||
env_vars: self.env_vars,
|
||||
network: self.network,
|
||||
network_aliases: self.network_aliases,
|
||||
cpu_limit: self.cpu_limit,
|
||||
memory_limit: self.memory_limit,
|
||||
memory_swap_limit: self.memory_swap_limit,
|
||||
cpu_shares: self.cpu_shares,
|
||||
restart_policy: self.restart_policy,
|
||||
health_check: self.health_check,
|
||||
detach: self.detach,
|
||||
snapshotter: self.snapshotter,
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Example output functions:
|
||||
```rust
|
||||
|
||||
/// Set memory swap limit for a Container
|
||||
pub fn container_with_memory_swap_limit(container: Container, memory_swap: &str) -> Container {
|
||||
container.with_memory_swap_limit(memory_swap)
|
||||
}
|
||||
|
||||
/// Set CPU shares for a Container
|
||||
pub fn container_with_cpu_shares(container: Container, shares: &str) -> Container {
|
||||
container.with_cpu_shares(shares)
|
||||
}
|
||||
|
||||
/// Set health check with options for a Container
|
||||
pub fn container_with_health_check_options(
|
||||
container: Container,
|
||||
cmd: &str,
|
||||
interval: Option<&str>,
|
||||
timeout: Option<&str>,
|
||||
retries: Option<i64>,
|
||||
start_period: Option<&str>
|
||||
) -> Container {
|
||||
// Convert i64 to u32 for retries
|
||||
let retries_u32 = retries.map(|r| r as u32);
|
||||
container.with_health_check_options(cmd, interval, timeout, retries_u32, start_period)
|
||||
}
|
||||
|
||||
/// Set snapshotter for a Container
|
||||
pub fn container_with_snapshotter(container: Container, snapshotter: &str) -> Container {
|
||||
container.with_snapshotter(snapshotter)
|
||||
}
|
||||
|
||||
/// Set detach mode for a Container
|
||||
pub fn container_with_detach(container: Container, detach: bool) -> Container {
|
||||
container.with_detach(detach)
|
||||
}
|
||||
|
||||
/// Build and run the Container
|
||||
///
|
||||
/// This function builds and runs the container using the configured options.
|
||||
/// It provides detailed error information if the build fails.
|
||||
pub fn container_build(container: Container) -> Result<Container, Box<EvalAltResult>> {
|
||||
// Get container details for better error reporting
|
||||
let container_name = container.name.clone();
|
||||
let image = container.image.clone().unwrap_or_else(|| "none".to_string());
|
||||
let ports = container.ports.clone();
|
||||
let volumes = container.volumes.clone();
|
||||
let env_vars = container.env_vars.clone();
|
||||
|
||||
// Try to build the container
|
||||
let build_result = container.build();
|
||||
|
||||
// Handle the result with improved error context
|
||||
match build_result {
|
||||
Ok(built_container) => {
|
||||
// Container built successfully
|
||||
Ok(built_container)
|
||||
},
|
||||
Err(err) => {
|
||||
// Add more context to the error
|
||||
let enhanced_error = match err {
|
||||
NerdctlError::CommandFailed(msg) => {
|
||||
// Provide more detailed error information
|
||||
let mut enhanced_msg = format!("Failed to build container '{}' from image '{}': {}",
|
||||
container_name, image, msg);
|
||||
|
||||
// Add information about configured options that might be relevant
|
||||
if !ports.is_empty() {
|
||||
enhanced_msg.push_str(&format!("\nConfigured ports: {:?}", ports));
|
||||
}
|
||||
|
||||
if !volumes.is_empty() {
|
||||
enhanced_msg.push_str(&format!("\nConfigured volumes: {:?}", volumes));
|
||||
}
|
||||
|
||||
if !env_vars.is_empty() {
|
||||
enhanced_msg.push_str(&format!("\nConfigured environment variables: {:?}", env_vars));
|
||||
}
|
||||
|
||||
// Add suggestions for common issues
|
||||
if msg.contains("not found") || msg.contains("no such image") {
|
||||
enhanced_msg.push_str("\nSuggestion: The specified image may not exist or may not be pulled yet. Try pulling the image first with nerdctl_image_pull().");
|
||||
} else if msg.contains("port is already allocated") {
|
||||
enhanced_msg.push_str("\nSuggestion: One of the specified ports is already in use. Try using a different port or stopping the container using that port.");
|
||||
} else if msg.contains("permission denied") {
|
||||
enhanced_msg.push_str("\nSuggestion: Permission issues detected. Check if you have the necessary permissions to create containers or access the specified volumes.");
|
||||
}
|
||||
|
||||
NerdctlError::CommandFailed(enhanced_msg)
|
||||
},
|
||||
_ => err
|
||||
};
|
||||
|
||||
nerdctl_error_to_rhai_error(Err(enhanced_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
58
lib/lang/rhai/rhai.v
Normal file
58
lib/lang/rhai/rhai.v
Normal file
@@ -0,0 +1,58 @@
|
||||
module rhai
|
||||
|
||||
import freeflowuniverse.herolib.ai.escalayer
|
||||
|
||||
pub struct WrapperGenerator {
|
||||
pub:
|
||||
function string
|
||||
structs []string
|
||||
}
|
||||
|
||||
// generate_rhai_function_wrapper generates a Rhai wrapper function for a given Rust function.
|
||||
//
|
||||
// Args:
|
||||
// rust_function (string): The Rust function signature string.
|
||||
// struct_declarations ([]string): Optional struct declarations used by the function.
|
||||
//
|
||||
// Returns:
|
||||
// !string: The generated Rhai wrapper function code or an error.
|
||||
pub fn generate_rhai_function_wrapper(rust_function string, struct_declarations []string) !string {
|
||||
mut task := escalayer.new_task(
|
||||
name: 'generate_rhai_function_wrapper'
|
||||
description: 'Create a single Rhai wrapper for a Rust function'
|
||||
)
|
||||
|
||||
mut gen := WrapperGenerator {
|
||||
function: rust_function
|
||||
structs: struct_declarations
|
||||
}
|
||||
|
||||
// Define a single unit task that handles everything
|
||||
task.new_unit_task(
|
||||
name: 'generate_rhai_function_wrapper'
|
||||
prompt_function: gen.generate_rhai_function_wrapper_prompt
|
||||
callback_function: gen.generate_rhai_function_wrapper_callback
|
||||
base_model: escalayer.claude_3_sonnet // Use actual model identifier
|
||||
retry_model: escalayer.gpt4 // Use actual model identifier
|
||||
retry_count: 1
|
||||
)
|
||||
|
||||
return task.initiate('')
|
||||
}
|
||||
|
||||
pub fn (gen WrapperGenerator) generate_rhai_function_wrapper_prompt(input string) string {
|
||||
return $tmpl('./prompts/generate_rhai_function_wrapper.md')
|
||||
}
|
||||
|
||||
// generate_rhai_function_wrapper_callback validates the generated Rhai wrapper.
|
||||
//
|
||||
// Args:
|
||||
// rhai_wrapper (string): The generated wrapper code.
|
||||
//
|
||||
// Returns:
|
||||
// !string: The validated wrapper code or an error.
|
||||
pub fn (gen WrapperGenerator) generate_rhai_function_wrapper_callback(rhai_wrapper string) !string {
|
||||
// TODO: Implement actual validation logic for the Rhai wrapper code.
|
||||
// This could involve trying to parse it or other checks.
|
||||
return rhai_wrapper // Return the input for now
|
||||
}
|
||||
89
lib/lang/rhai/rhai_test.v
Normal file
89
lib/lang/rhai/rhai_test.v
Normal file
@@ -0,0 +1,89 @@
|
||||
module rhai
|
||||
|
||||
import freeflowuniverse.herolib.lang.rhai
|
||||
// import os // Unused, remove later if not needed
|
||||
|
||||
fn testsuite_begin() {
|
||||
// Optional: Setup code before tests run
|
||||
}
|
||||
|
||||
fn testsuite_end() {
|
||||
// Optional: Teardown code after tests run
|
||||
}
|
||||
|
||||
fn test_generate_wrapper_simple_function() {
|
||||
rust_fn := 'pub fn add(a: i32, b: i32) -> i32'
|
||||
expected_wrapper := 'pub fn add(a: i64, b: i64) -> Result<i64, Box<EvalAltResult>> {\n // Assuming the function exists in the scope where the wrapper is defined\n Ok(add(a, b))\n}'
|
||||
|
||||
actual_wrapper := rhai.generate_rhai_function_wrapper(rust_function: rust_fn, struct_declarations: []string{}) or {
|
||||
assert false, 'Function returned error: ${err}'
|
||||
return // Needed for compiler
|
||||
}
|
||||
// Normalize whitespace for comparison
|
||||
assert actual_wrapper.trim_space() == expected_wrapper.trim_space()
|
||||
}
|
||||
|
||||
fn test_generate_wrapper_immutable_method() {
|
||||
rust_fn := 'pub fn get_name(&self) -> String'
|
||||
// receiver := 'MyStruct' // No longer passed directly
|
||||
struct_decls := ['struct MyStruct { name: String }'] // Example declaration
|
||||
expected_wrapper := 'pub fn get_name(receiver: &MyStruct) -> Result<String, Box<EvalAltResult>> {\n Ok(receiver.get_name())\n}'
|
||||
|
||||
actual_wrapper := rhai.generate_rhai_function_wrapper(rust_function: rust_fn, struct_declarations: struct_decls) or {
|
||||
assert false, 'Function returned error: ${err}'
|
||||
return
|
||||
}
|
||||
assert actual_wrapper.trim_space() == expected_wrapper.trim_space()
|
||||
}
|
||||
|
||||
fn test_generate_wrapper_mutable_method() {
|
||||
rust_fn := 'pub fn set_name(&mut self, new_name: String)' // Implicit () return
|
||||
// receiver := 'MyStruct' // No longer passed directly
|
||||
struct_decls := ['struct MyStruct { name: String }'] // Example declaration
|
||||
expected_wrapper := 'pub fn set_name(receiver: &mut MyStruct, new_name: String) -> Result<(), Box<EvalAltResult>> {\n Ok(receiver.set_name(new_name))\n}'
|
||||
|
||||
actual_wrapper := rhai.generate_rhai_function_wrapper(rust_function: rust_fn, struct_declarations: struct_decls) or {
|
||||
assert false, 'Function returned error: ${err}'
|
||||
return
|
||||
}
|
||||
assert actual_wrapper.trim_space() == expected_wrapper.trim_space()
|
||||
}
|
||||
|
||||
fn test_generate_wrapper_function_returning_result() {
|
||||
rust_fn := 'pub fn load_config(path: &str) -> Result<Config, io::Error>'
|
||||
// receiver := '' // No longer relevant
|
||||
struct_decls := ['struct Config { ... }'] // Example placeholder declaration
|
||||
expected_wrapper := 'pub fn load_config(path: &str) -> Result<Config, Box<EvalAltResult>> {\n load_config(path)\n .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error in load_config: {}", e).into(), rhai::Position::NONE)))\n}'
|
||||
|
||||
actual_wrapper := rhai.generate_rhai_function_wrapper(rust_function: rust_fn, struct_declarations: struct_decls) or {
|
||||
assert false, 'Function returned error: ${err}'
|
||||
return
|
||||
}
|
||||
assert actual_wrapper.trim_space() == expected_wrapper.trim_space()
|
||||
}
|
||||
|
||||
fn test_generate_wrapper_function_returning_pathbuf() {
|
||||
rust_fn := 'pub fn get_home_dir() -> PathBuf'
|
||||
// receiver := '' // No longer relevant
|
||||
struct_decls := []string{} // No relevant structs
|
||||
// Expecting conversion to String for Rhai
|
||||
expected_wrapper := 'pub fn get_home_dir() -> Result<String, Box<EvalAltResult>> {\n Ok(get_home_dir().to_string_lossy().to_string())\n}'
|
||||
actual_wrapper := rhai.generate_rhai_function_wrapper(rust_function: rust_fn, struct_declarations: struct_decls) or {
|
||||
assert false, 'Function returned error: ${err}'
|
||||
return
|
||||
}
|
||||
assert actual_wrapper.trim_space() == expected_wrapper.trim_space()
|
||||
}
|
||||
|
||||
fn test_generate_wrapper_function_with_vec() {
|
||||
rust_fn := 'pub fn list_files(dir: &str) -> Vec<String>'
|
||||
// receiver := '' // No longer relevant
|
||||
struct_decls := []string{} // No relevant structs
|
||||
// Expecting Vec<String> to map directly
|
||||
expected_wrapper := 'pub fn list_files(dir: &str) -> Result<Vec<String>, Box<EvalAltResult>> {\n Ok(list_files(dir))\n}'
|
||||
actual_wrapper := rhai.generate_rhai_function_wrapper(rust_function: rust_fn, struct_declarations: struct_decls) or {
|
||||
assert false, 'Function returned error: ${err}'
|
||||
return
|
||||
}
|
||||
assert actual_wrapper.trim_space() == expected_wrapper.trim_space()
|
||||
}
|
||||
1004
lib/lang/rust/rust.v
1004
lib/lang/rust/rust.v
File diff suppressed because it is too large
Load Diff
416
lib/lang/rust/rust_test.v
Normal file
416
lib/lang/rust/rust_test.v
Normal file
@@ -0,0 +1,416 @@
|
||||
module rust_test
|
||||
|
||||
import freeflowuniverse.herolib.lang.rust
|
||||
import os
|
||||
|
||||
fn test_extract_functions_from_content() {
|
||||
content := '
|
||||
// This is a comment
|
||||
/* This is a block comment */
|
||||
|
||||
pub fn public_function() {
|
||||
println("Hello, world!")
|
||||
}
|
||||
|
||||
fn private_function() {
|
||||
println("Private function")
|
||||
}
|
||||
|
||||
// Another comment
|
||||
pub fn another_function() -> i32 {
|
||||
return 42
|
||||
}
|
||||
'
|
||||
functions := rust.extract_functions_from_content(content)
|
||||
|
||||
assert functions.len == 3
|
||||
assert functions[0] == 'public_function'
|
||||
assert functions[1] == 'private_function'
|
||||
assert functions[2] == 'another_function'
|
||||
}
|
||||
|
||||
fn test_extract_structs_from_content() {
|
||||
content := '
|
||||
// This is a comment
|
||||
/* This is a block comment */
|
||||
|
||||
pub struct PublicStruct {
|
||||
field: i32
|
||||
}
|
||||
|
||||
struct PrivateStruct {
|
||||
field: String
|
||||
}
|
||||
|
||||
pub struct GenericStruct<T> {
|
||||
field: T
|
||||
}
|
||||
'
|
||||
structs := rust.extract_structs_from_content(content)
|
||||
|
||||
assert structs.len == 3
|
||||
assert structs[0] == 'PublicStruct'
|
||||
assert structs[1] == 'PrivateStruct'
|
||||
assert structs[2] == 'GenericStruct'
|
||||
}
|
||||
|
||||
fn test_extract_imports_from_content() {
|
||||
content := '
|
||||
// This is a comment
|
||||
/* This is a block comment */
|
||||
|
||||
use std::io;
|
||||
use std::fs::File;
|
||||
use crate::module::function;
|
||||
|
||||
// Some code here
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
'
|
||||
imports := rust.extract_imports_from_content(content)
|
||||
|
||||
assert imports.len == 3
|
||||
assert imports[0] == 'std::io'
|
||||
assert imports[1] == 'std::fs::File'
|
||||
assert imports[2] == 'crate::module::function'
|
||||
}
|
||||
|
||||
fn test_get_module_name() {
|
||||
// Test regular file
|
||||
assert rust.get_module_name('/path/to/file.rs') == 'file'
|
||||
|
||||
// Test mod.rs file
|
||||
assert rust.get_module_name('/path/to/module/mod.rs') == 'module'
|
||||
}
|
||||
|
||||
// Helper function to create temporary test files
|
||||
fn setup_test_files() !string {
|
||||
// Create temporary directory
|
||||
tmp_dir := os.join_path(os.temp_dir(), 'rust_test_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or {
|
||||
return error('Failed to create temporary directory: ${err}')
|
||||
}
|
||||
|
||||
// Create test file
|
||||
test_file_content := '
|
||||
// This is a test file
|
||||
use std::io;
|
||||
use std::fs::File;
|
||||
|
||||
pub struct TestStruct {
|
||||
field: i32
|
||||
}
|
||||
|
||||
pub fn test_function() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
fn private_function() {
|
||||
println!("Private function");
|
||||
}
|
||||
'
|
||||
|
||||
test_file_path := os.join_path(tmp_dir, 'test_file.rs')
|
||||
os.write_file(test_file_path, test_file_content) or {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
return error('Failed to write test file: ${err}')
|
||||
}
|
||||
|
||||
// Create mod.rs file
|
||||
mod_file_content := '
|
||||
// This is a mod file
|
||||
pub mod test_file;
|
||||
|
||||
pub fn mod_function() {
|
||||
println!("Mod function");
|
||||
}
|
||||
'
|
||||
|
||||
mod_file_path := os.join_path(tmp_dir, 'mod.rs')
|
||||
os.write_file(mod_file_path, mod_file_content) or {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
return error('Failed to write mod file: ${err}')
|
||||
}
|
||||
|
||||
// Create submodule directory with mod.rs
|
||||
submod_dir := os.join_path(tmp_dir, 'submodule')
|
||||
os.mkdir_all(submod_dir) or {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
return error('Failed to create submodule directory: ${err}')
|
||||
}
|
||||
|
||||
submod_file_content := '
|
||||
// This is a submodule mod file
|
||||
pub fn submod_function() {
|
||||
println!("Submodule function");
|
||||
}
|
||||
'
|
||||
|
||||
submod_file_path := os.join_path(submod_dir, 'mod.rs')
|
||||
os.write_file(submod_file_path, submod_file_content) or {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
return error('Failed to write submodule mod file: ${err}')
|
||||
}
|
||||
|
||||
// Create Cargo.toml
|
||||
cargo_content := '
|
||||
[package]
|
||||
name = "test_package"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0"
|
||||
tokio = { version = "1.25", features = ["full"] }
|
||||
'
|
||||
|
||||
cargo_path := os.join_path(tmp_dir, 'Cargo.toml')
|
||||
os.write_file(cargo_path, cargo_content) or {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
return error('Failed to write Cargo.toml: ${err}')
|
||||
}
|
||||
|
||||
return tmp_dir
|
||||
}
|
||||
|
||||
fn teardown_test_files(tmp_dir string) {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
|
||||
fn test_list_functions_in_file() ! {
|
||||
tmp_dir := setup_test_files()!
|
||||
defer { teardown_test_files(tmp_dir) }
|
||||
|
||||
test_file_path := os.join_path(tmp_dir, 'test_file.rs')
|
||||
functions := rust.list_functions_in_file(test_file_path)!
|
||||
|
||||
assert functions.len == 2
|
||||
assert functions.contains('test_function')
|
||||
assert functions.contains('private_function')
|
||||
}
|
||||
|
||||
fn test_list_structs_in_file() ! {
|
||||
tmp_dir := setup_test_files()!
|
||||
defer { teardown_test_files(tmp_dir) }
|
||||
|
||||
test_file_path := os.join_path(tmp_dir, 'test_file.rs')
|
||||
structs := rust.list_structs_in_file(test_file_path)!
|
||||
|
||||
assert structs.len == 1
|
||||
assert structs[0] == 'TestStruct'
|
||||
}
|
||||
|
||||
fn test_extract_imports() ! {
|
||||
tmp_dir := setup_test_files()!
|
||||
defer { teardown_test_files(tmp_dir) }
|
||||
|
||||
test_file_path := os.join_path(tmp_dir, 'test_file.rs')
|
||||
imports := rust.extract_imports(test_file_path)!
|
||||
|
||||
assert imports.len == 2
|
||||
assert imports[0] == 'std::io'
|
||||
assert imports[1] == 'std::fs::File'
|
||||
}
|
||||
|
||||
fn test_list_modules_in_directory() ! {
|
||||
tmp_dir := setup_test_files()!
|
||||
defer { teardown_test_files(tmp_dir) }
|
||||
|
||||
modules := rust.list_modules_in_directory(tmp_dir)!
|
||||
|
||||
// Should contain the module itself (mod.rs), test_file.rs and submodule directory
|
||||
assert modules.len == 3
|
||||
assert modules.contains(os.base(tmp_dir)) // Directory name (mod.rs)
|
||||
assert modules.contains('test_file')
|
||||
assert modules.contains('submodule')
|
||||
}
|
||||
|
||||
fn test_extract_dependencies() ! {
|
||||
tmp_dir := setup_test_files()!
|
||||
defer { teardown_test_files(tmp_dir) }
|
||||
|
||||
cargo_path := os.join_path(tmp_dir, 'Cargo.toml')
|
||||
dependencies := rust.extract_dependencies(cargo_path)!
|
||||
|
||||
assert dependencies.len == 2
|
||||
assert dependencies['serde'] == '1.0'
|
||||
assert dependencies['tokio'] == '{ version = "1.25", features = ["full"] }'
|
||||
}
|
||||
|
||||
fn test_extract_impl_methods() {
|
||||
test_impl_content := os.read_file('${os.dir(@FILE)}/test_impl.rs') or {
|
||||
assert false, 'Failed to read test_impl.rs: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
functions := rust.extract_functions_from_content(test_impl_content)
|
||||
|
||||
assert functions.len == 3
|
||||
assert functions[0] == 'Currency::new'
|
||||
assert functions[1] == 'Currency::to_usd'
|
||||
assert functions[2] == 'Currency::to_currency'
|
||||
|
||||
println('Extracted functions:')
|
||||
for f in functions {
|
||||
println(' "${f}"')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_get_function_from_content() {
|
||||
mut content_lines := []string{}
|
||||
content_lines << '// Some comment'
|
||||
content_lines << ''
|
||||
content_lines << 'fn standalone_function() -> i32 {'
|
||||
content_lines << ' 42'
|
||||
content_lines << '}'
|
||||
content_lines << ''
|
||||
content_lines << 'pub struct MyData {'
|
||||
content_lines << ' value: String,'
|
||||
content_lines << '}'
|
||||
content_lines << ''
|
||||
content_lines << 'impl MyData {'
|
||||
content_lines << ' pub fn new(value: String) -> Self {'
|
||||
content_lines << ' Self { value }'
|
||||
content_lines << ' }'
|
||||
content_lines << ''
|
||||
content_lines << ' fn internal_method(&self) {'
|
||||
content_lines << ' println!("Internal");'
|
||||
content_lines << ' }'
|
||||
content_lines << '}'
|
||||
content_lines << ''
|
||||
content_lines << '// Another comment'
|
||||
content := content_lines.join('\n')
|
||||
|
||||
// Test standalone function
|
||||
decl1 := rust.get_function_from_content(content, 'standalone_function') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected1 := 'fn standalone_function() -> i32 {\n 42\n}'
|
||||
assert decl1.trim_space() == expected1
|
||||
|
||||
// Test struct method
|
||||
decl2 := rust.get_function_from_content(content, 'MyData::new') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected2 := 'pub fn new(value: String) -> Self {\n Self { value }\n }'
|
||||
assert decl2.trim_space() == expected2
|
||||
|
||||
// Test private struct method
|
||||
decl3 := rust.get_function_from_content(content, 'MyData::internal_method') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected3 := 'fn internal_method(&self) {\n println!("Internal");\n }'
|
||||
assert decl3.trim_space() == expected3
|
||||
|
||||
// Test function not found
|
||||
_ := rust.get_function_from_content(content, 'non_existent_function') or {
|
||||
assert err.msg() == 'Function non_existent_function not found in content'
|
||||
return
|
||||
}
|
||||
assert false, 'Expected error for non-existent function'
|
||||
}
|
||||
|
||||
fn test_get_struct_from_content() {
|
||||
mut content_lines := []string{}
|
||||
content_lines << '// Comment'
|
||||
content_lines << 'pub struct SimpleStruct {'
|
||||
content_lines << ' field1: i32,'
|
||||
content_lines << '}'
|
||||
content_lines << ''
|
||||
content_lines << 'struct GenericStruct<T> {'
|
||||
content_lines << ' data: T,'
|
||||
content_lines << '}'
|
||||
content_lines << ''
|
||||
content_lines << '// Another struct'
|
||||
content_lines << 'pub struct ComplexStruct<A, B> where A: Clone {'
|
||||
content_lines << ' a: A,'
|
||||
content_lines << ' b: B,'
|
||||
content_lines << ' c: Vec<String>,'
|
||||
content_lines << '}'
|
||||
content_lines << ''
|
||||
content_lines << 'struct EmptyStruct;'
|
||||
content_lines << ''
|
||||
content_lines << 'struct StructWithImpl {'
|
||||
content_lines << ' val: bool,'
|
||||
content_lines << '}'
|
||||
content_lines << ''
|
||||
content_lines << 'impl StructWithImpl {'
|
||||
content_lines << ' fn method() {}'
|
||||
content_lines << '}'
|
||||
content := content_lines.join('\n')
|
||||
|
||||
// Test simple struct
|
||||
decl1 := rust.get_struct_from_content(content, 'SimpleStruct') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected1 := 'pub struct SimpleStruct {\n field1: i32,\n}'
|
||||
assert decl1.trim_space() == expected1
|
||||
|
||||
// Test generic struct
|
||||
decl2 := rust.get_struct_from_content(content, 'GenericStruct') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected2 := 'struct GenericStruct<T> {\n data: T,\n}'
|
||||
assert decl2.trim_space() == expected2
|
||||
|
||||
// Test complex struct
|
||||
decl3 := rust.get_struct_from_content(content, 'ComplexStruct') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected3 := 'pub struct ComplexStruct<A, B> where A: Clone {\n a: A,\n b: B,\n c: Vec<String>,\n}'
|
||||
assert decl3.trim_space() == expected3
|
||||
|
||||
// Test empty struct
|
||||
decl4 := rust.get_struct_from_content(content, 'EmptyStruct') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected4 := 'struct EmptyStruct;'
|
||||
assert decl4.trim_space() == expected4
|
||||
|
||||
// Test struct with impl
|
||||
decl5 := rust.get_struct_from_content(content, 'StructWithImpl') or {
|
||||
assert false, 'Failed: ${err}'
|
||||
return
|
||||
}
|
||||
expected5 := 'struct StructWithImpl {\n val: bool,\n}'
|
||||
assert decl5.trim_space() == expected5
|
||||
|
||||
// Test struct not found
|
||||
_ := rust.get_struct_from_content(content, 'non_existent_struct') or {
|
||||
assert err.msg() == 'Struct non_existent_struct not found in content'
|
||||
return
|
||||
}
|
||||
assert false, 'Expected error for non-existent struct'
|
||||
}
|
||||
|
||||
fn test_get_struct_from_file() ! {
|
||||
tmp_dir := setup_test_files()!
|
||||
defer { teardown_test_files(tmp_dir) }
|
||||
|
||||
test_file_path := os.join_path(tmp_dir, 'test_file.rs')
|
||||
structs := rust.list_structs_in_file(test_file_path)!
|
||||
|
||||
assert structs.len == 1
|
||||
assert structs[0] == 'TestStruct'
|
||||
}
|
||||
|
||||
fn test_get_struct_from_module() ! {
|
||||
tmp_dir := setup_test_files()!
|
||||
defer { teardown_test_files(tmp_dir) }
|
||||
|
||||
modules := rust.list_modules_in_directory(tmp_dir)!
|
||||
|
||||
// Should contain the module itself (mod.rs), test_file.rs and submodule directory
|
||||
assert modules.len == 3
|
||||
assert modules.contains(os.base(tmp_dir)) // Directory name (mod.rs)
|
||||
assert modules.contains('test_file')
|
||||
assert modules.contains('submodule')
|
||||
}
|
||||
29
lib/lang/rust/test_impl.rs
Normal file
29
lib/lang/rust/test_impl.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
impl Currency {
|
||||
/// Create a new currency with amount and code
|
||||
pub fn new(amount: f64, currency_code: String) -> Self {
|
||||
Self {
|
||||
amount,
|
||||
currency_code,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the currency to USD
|
||||
pub fn to_usd(&self) -> Option<Currency> {
|
||||
if self.currency_code == "USD" {
|
||||
return Some(self.clone());
|
||||
}
|
||||
|
||||
EXCHANGE_RATE_SERVICE.convert(self.amount, &self.currency_code, "USD")
|
||||
.map(|amount| Currency::new(amount, "USD".to_string()))
|
||||
}
|
||||
|
||||
/// Convert the currency to another currency
|
||||
pub fn to_currency(&self, target_currency: &str) -> Option<Currency> {
|
||||
if self.currency_code == target_currency {
|
||||
return Some(self.clone());
|
||||
}
|
||||
|
||||
EXCHANGE_RATE_SERVICE.convert(self.amount, &self.currency_code, target_currency)
|
||||
.map(|amount| Currency::new(amount, target_currency.to_string()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user