use anyhow::Result;
use rand::RngExt;
use serde_json::{Value, json};
use std::collections::HashMap;
use crate::executors::types::{Skill, SkillParameter};
#[derive(Debug)]
pub struct RandomNumberSkill;
#[async_trait::async_trait]
impl Skill for RandomNumberSkill {
fn name(&self) -> &str {
"random_number"
}
fn description(&self) -> &str {
"Generate a random number within a specified range"
}
fn usage_hint(&self) -> &str {
"Use this skill when you need to generate random integers"
}
fn parameters(&self) -> Vec<SkillParameter> {
vec![
SkillParameter {
name: "min".to_string(),
param_type: "integer".to_string(),
description: "Minimum value (inclusive)".to_string(),
required: false,
default: Some(Value::Number(0.into())),
example: Some(Value::Number(1.into())),
enum_values: None,
},
SkillParameter {
name: "max".to_string(),
param_type: "integer".to_string(),
description: "Maximum value (inclusive)".to_string(),
required: false,
default: Some(Value::Number(100.into())),
example: Some(Value::Number(100.into())),
enum_values: None,
},
]
}
fn example_call(&self) -> Value {
json!({
"action": "random_number",
"parameters": {
"min": 1,
"max": 100
}
})
}
fn example_output(&self) -> String {
"Random number: 42".to_string()
}
fn category(&self) -> &str {
"random"
}
async fn execute(&self, parameters: &HashMap<String, Value>) -> Result<String> {
let min = parameters.get("min").and_then(|v| v.as_i64()).unwrap_or(0);
let max = parameters
.get("max")
.and_then(|v| v.as_i64())
.unwrap_or(100);
if min > max {
anyhow::bail!("min ({}) cannot be greater than max ({})", min, max);
}
let mut rng = rand::rng();
let number = rng.random_range(min..=max);
Ok(format!("Random number: {}", number))
}
fn validate(&self, parameters: &HashMap<String, Value>) -> Result<()> {
if let (Some(min), Some(max)) = (parameters.get("min"), parameters.get("max")) {
if min.as_i64().unwrap_or(0) > max.as_i64().unwrap_or(0) {
anyhow::bail!("min cannot be greater than max");
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct RandomStringSkill;
#[async_trait::async_trait]
impl Skill for RandomStringSkill {
fn name(&self) -> &str {
"random_string"
}
fn description(&self) -> &str {
"Generate a random string of specified length"
}
fn usage_hint(&self) -> &str {
"Use this skill to generate random strings for IDs, tokens, or test data"
}
fn parameters(&self) -> Vec<SkillParameter> {
vec![
SkillParameter {
name: "length".to_string(),
param_type: "integer".to_string(),
description: "Length of the random string".to_string(),
required: false,
default: Some(Value::Number(10.into())),
example: Some(Value::Number(16.into())),
enum_values: None,
},
SkillParameter {
name: "charset".to_string(),
param_type: "string".to_string(),
description: "Character set to use (alphanumeric, alpha, numeric, hex)".to_string(),
required: false,
default: Some(Value::String("alphanumeric".to_string())),
example: Some(Value::String("alphanumeric".to_string())),
enum_values: Some(vec![
"alphanumeric".to_string(),
"alpha".to_string(),
"numeric".to_string(),
"hex".to_string(),
]),
},
]
}
fn example_call(&self) -> Value {
json!({
"action": "random_string",
"parameters": {
"length": 16,
"charset": "alphanumeric"
}
})
}
fn example_output(&self) -> String {
"Random string: aB3dE9fG2hJ1kL4m".to_string()
}
fn category(&self) -> &str {
"random"
}
async fn execute(&self, parameters: &HashMap<String, Value>) -> Result<String> {
let length = parameters
.get("length")
.and_then(|v| v.as_u64())
.unwrap_or(10) as usize;
let charset = parameters
.get("charset")
.and_then(|v| v.as_str())
.unwrap_or("alphanumeric");
let chars: Vec<char> = match charset {
"alpha" => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.chars()
.collect(),
"numeric" => "0123456789".chars().collect(),
"hex" => "0123456789abcdef".chars().collect(),
_ => "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.chars()
.collect(),
};
let mut rng = rand::rng();
let result: String = (0..length)
.map(|_| {
let idx = rng.random_range(0..chars.len());
chars[idx]
})
.collect();
Ok(format!("Random string: {}", result))
}
fn validate(&self, parameters: &HashMap<String, Value>) -> Result<()> {
if let Some(length) = parameters.get("length").and_then(|v| v.as_u64()) {
if length == 0 {
anyhow::bail!("length must be greater than 0");
}
if length > 1024 {
anyhow::bail!("length cannot exceed 1024");
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct RandomUuidSkill;
#[async_trait::async_trait]
impl Skill for RandomUuidSkill {
fn name(&self) -> &str {
"random_uuid"
}
fn description(&self) -> &str {
"Generate a random UUID (v4)"
}
fn usage_hint(&self) -> &str {
"Use this skill when you need to generate a unique identifier"
}
fn parameters(&self) -> Vec<SkillParameter> {
vec![]
}
fn example_call(&self) -> Value {
json!({
"action": "random_uuid",
"parameters": {}
})
}
fn example_output(&self) -> String {
"UUID: 550e8400-e29b-41d4-a716-446655440000".to_string()
}
fn category(&self) -> &str {
"random"
}
async fn execute(&self, _parameters: &HashMap<String, Value>) -> Result<String> {
let uuid = uuid::Uuid::new_v4();
Ok(format!("UUID: {}", uuid))
}
}
#[derive(Debug)]
pub struct RandomPasswordSkill;
#[async_trait::async_trait]
impl Skill for RandomPasswordSkill {
fn name(&self) -> &str {
"random_password"
}
fn description(&self) -> &str {
"Generate a secure random password with configurable complexity"
}
fn usage_hint(&self) -> &str {
"Use this skill to generate strong passwords for accounts or services"
}
fn parameters(&self) -> Vec<SkillParameter> {
vec![
SkillParameter {
name: "length".to_string(),
param_type: "integer".to_string(),
description: "Password length".to_string(),
required: false,
default: Some(Value::Number(16.into())),
example: Some(Value::Number(20.into())),
enum_values: None,
},
SkillParameter {
name: "use_uppercase".to_string(),
param_type: "boolean".to_string(),
description: "Include uppercase letters".to_string(),
required: false,
default: Some(Value::Bool(true)),
example: Some(Value::Bool(true)),
enum_values: None,
},
SkillParameter {
name: "use_lowercase".to_string(),
param_type: "boolean".to_string(),
description: "Include lowercase letters".to_string(),
required: false,
default: Some(Value::Bool(true)),
example: Some(Value::Bool(true)),
enum_values: None,
},
SkillParameter {
name: "use_numbers".to_string(),
param_type: "boolean".to_string(),
description: "Include numbers".to_string(),
required: false,
default: Some(Value::Bool(true)),
example: Some(Value::Bool(true)),
enum_values: None,
},
SkillParameter {
name: "use_symbols".to_string(),
param_type: "boolean".to_string(),
description: "Include special symbols".to_string(),
required: false,
default: Some(Value::Bool(true)),
example: Some(Value::Bool(true)),
enum_values: None,
},
]
}
fn example_call(&self) -> Value {
json!({
"action": "random_password",
"parameters": {
"length": 16
}
})
}
fn example_output(&self) -> String {
"Password: aB3#dE9$fG2hJ1kL".to_string()
}
fn category(&self) -> &str {
"random"
}
async fn execute(&self, parameters: &HashMap<String, Value>) -> Result<String> {
let length = parameters
.get("length")
.and_then(|v| v.as_u64())
.unwrap_or(16) as usize;
let use_uppercase = parameters
.get("use_uppercase")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let use_lowercase = parameters
.get("use_lowercase")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let use_numbers = parameters
.get("use_numbers")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let use_symbols = parameters
.get("use_symbols")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let mut char_pool = Vec::new();
if use_uppercase {
char_pool.extend_from_slice(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
if use_lowercase {
char_pool.extend_from_slice(b"abcdefghijklmnopqrstuvwxyz");
}
if use_numbers {
char_pool.extend_from_slice(b"0123456789");
}
if use_symbols {
char_pool.extend_from_slice(b"!@#$%^&*()_+-=[]{}|;:,.<>?");
}
if char_pool.is_empty() {
anyhow::bail!("At least one character type must be enabled");
}
let mut rng = rand::rng();
let password: String = (0..length)
.map(|_| {
let idx = rng.random_range(0..char_pool.len());
char_pool[idx] as char
})
.collect();
Ok(format!("Password: {}", password))
}
fn validate(&self, parameters: &HashMap<String, Value>) -> Result<()> {
if let Some(length) = parameters.get("length").and_then(|v| v.as_u64()) {
if length == 0 {
anyhow::bail!("length must be greater than 0");
}
if length > 128 {
anyhow::bail!("length cannot exceed 128");
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[tokio::test]
async fn test_random_number_skill() {
let skill = RandomNumberSkill;
let params = HashMap::new();
let result = skill.execute(¶ms).await.unwrap();
assert!(result.starts_with("Random number: "));
let num = result
.trim_start_matches("Random number: ")
.parse::<i64>()
.unwrap();
assert!(num >= 0 && num <= 100);
let mut params = HashMap::new();
params.insert("min".to_string(), json!(10));
params.insert("max".to_string(), json!(20));
let result = skill.execute(¶ms).await.unwrap();
let num = result
.trim_start_matches("Random number: ")
.parse::<i64>()
.unwrap();
assert!(num >= 10 && num <= 20);
let mut params = HashMap::new();
params.insert("min".to_string(), json!(100));
params.insert("max".to_string(), json!(1));
assert!(skill.execute(¶ms).await.is_err());
}
#[tokio::test]
async fn test_random_string_skill() {
let skill = RandomStringSkill;
let params = HashMap::new();
let result = skill.execute(¶ms).await.unwrap();
let s = result.trim_start_matches("Random string: ");
assert_eq!(s.len(), 10);
let mut params = HashMap::new();
params.insert("length".to_string(), json!(8));
params.insert("charset".to_string(), json!("numeric"));
let result = skill.execute(¶ms).await.unwrap();
let s = result.trim_start_matches("Random string: ");
assert_eq!(s.len(), 8);
assert!(s.chars().all(|c| c.is_ascii_digit()));
let mut params = HashMap::new();
params.insert("length".to_string(), json!(6));
params.insert("charset".to_string(), json!("hex"));
let result = skill.execute(¶ms).await.unwrap();
let s = result.trim_start_matches("Random string: ");
assert_eq!(s.len(), 6);
assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
let mut params = HashMap::new();
params.insert("length".to_string(), json!(0));
assert!(skill.validate(¶ms).is_err());
}
#[tokio::test]
async fn test_random_password_skill() {
let skill = RandomPasswordSkill;
let params = HashMap::new();
let result = skill.execute(¶ms).await.unwrap();
let password = result.trim_start_matches("Password: ");
assert_eq!(password.len(), 16);
let mut params = HashMap::new();
params.insert("length".to_string(), json!(12));
params.insert("use_symbols".to_string(), json!(false));
let result = skill.execute(¶ms).await.unwrap();
let password = result.trim_start_matches("Password: ");
assert_eq!(password.len(), 12);
assert!(password.chars().all(|c| c.is_ascii_alphanumeric()));
let mut params = HashMap::new();
params.insert("length".to_string(), json!(0));
assert!(skill.validate(¶ms).is_err());
let mut params = HashMap::new();
params.insert("length".to_string(), json!(200));
assert!(skill.validate(¶ms).is_err());
}
}