Lifei/UI parameter input (#5222)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::recipe::read_recipe_file_content::{read_parameter_file_content, RecipeFile};
|
||||
use crate::recipe::read_recipe_file_content::read_parameter_file_content;
|
||||
use crate::recipe::template_recipe::render_recipe_content_with_params;
|
||||
use crate::recipe::validate_recipe::validate_recipe_template_from_content;
|
||||
use crate::recipe::{
|
||||
@@ -19,33 +19,26 @@ pub enum RecipeError {
|
||||
RecipeParsing { source: anyhow::Error },
|
||||
}
|
||||
|
||||
pub fn render_recipe_template<F>(
|
||||
recipe_file: RecipeFile,
|
||||
fn render_recipe_template<F>(
|
||||
recipe_content: String,
|
||||
recipe_dir: &Path,
|
||||
params: Vec<(String, String)>,
|
||||
user_prompt_fn: Option<F>,
|
||||
) -> Result<(String, Vec<String>)>
|
||||
where
|
||||
F: Fn(&str, &str) -> Result<String, anyhow::Error>,
|
||||
{
|
||||
let RecipeFile {
|
||||
content: recipe_file_content,
|
||||
parent_dir: recipe_parent_dir,
|
||||
..
|
||||
} = recipe_file;
|
||||
let recipe_dir_str = recipe_parent_dir
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Error getting recipe directory"))?;
|
||||
let recipe_parameters = validate_recipe_template_from_content(
|
||||
&recipe_file_content,
|
||||
Some(recipe_dir_str.to_string()),
|
||||
)?
|
||||
.parameters;
|
||||
let recipe_dir_str = recipe_dir.display().to_string();
|
||||
|
||||
let recipe_parameters =
|
||||
validate_recipe_template_from_content(&recipe_content, Some(recipe_dir_str.clone()))?
|
||||
.parameters;
|
||||
|
||||
let (params_for_template, missing_params) =
|
||||
apply_values_to_parameters(¶ms, recipe_parameters, recipe_dir_str, user_prompt_fn)?;
|
||||
apply_values_to_parameters(¶ms, recipe_parameters, &recipe_dir_str, user_prompt_fn)?;
|
||||
|
||||
let rendered_content = if missing_params.is_empty() {
|
||||
render_recipe_content_with_params(&recipe_file_content, ¶ms_for_template)?
|
||||
render_recipe_content_with_params(&recipe_content, ¶ms_for_template)?
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
@@ -54,16 +47,16 @@ where
|
||||
}
|
||||
|
||||
pub fn build_recipe_from_template<F>(
|
||||
recipe_file: RecipeFile,
|
||||
recipe_content: String,
|
||||
recipe_dir: &Path,
|
||||
params: Vec<(String, String)>,
|
||||
user_prompt_fn: Option<F>,
|
||||
) -> Result<Recipe, RecipeError>
|
||||
where
|
||||
F: Fn(&str, &str) -> Result<String, anyhow::Error>,
|
||||
{
|
||||
let recipe_parent_dir = recipe_file.parent_dir.clone();
|
||||
let (rendered_content, missing_params) =
|
||||
render_recipe_template(recipe_file, params.clone(), user_prompt_fn)
|
||||
render_recipe_template(recipe_content, recipe_dir, params.clone(), user_prompt_fn)
|
||||
.map_err(|source| RecipeError::TemplateRendering { source })?;
|
||||
|
||||
if !missing_params.is_empty() {
|
||||
@@ -77,8 +70,7 @@ where
|
||||
|
||||
if let Some(ref mut sub_recipes) = recipe.sub_recipes {
|
||||
for sub_recipe in sub_recipes {
|
||||
if let Ok(resolved_path) = resolve_sub_recipe_path(&sub_recipe.path, &recipe_parent_dir)
|
||||
{
|
||||
if let Ok(resolved_path) = resolve_sub_recipe_path(&sub_recipe.path, recipe_dir) {
|
||||
sub_recipe.path = resolved_path;
|
||||
}
|
||||
}
|
||||
@@ -90,7 +82,7 @@ where
|
||||
pub fn apply_values_to_parameters<F>(
|
||||
user_params: &[(String, String)],
|
||||
recipe_parameters: Option<Vec<RecipeParameter>>,
|
||||
recipe_parent_dir: &str,
|
||||
recipe_dir: &str,
|
||||
user_prompt_fn: Option<F>,
|
||||
) -> Result<(HashMap<String, String>, Vec<String>)>
|
||||
where
|
||||
@@ -99,7 +91,7 @@ where
|
||||
let mut param_map: HashMap<String, String> = user_params.iter().cloned().collect();
|
||||
param_map.insert(
|
||||
BUILT_IN_RECIPE_DIR_PARAM.to_string(),
|
||||
recipe_parent_dir.to_string(),
|
||||
recipe_dir.to_string(),
|
||||
);
|
||||
let mut missing_params: Vec<String> = Vec::new();
|
||||
for param in recipe_parameters.unwrap_or_default() {
|
||||
|
||||
@@ -3,12 +3,13 @@ use crate::recipe::build_recipe::{
|
||||
};
|
||||
use crate::recipe::read_recipe_file_content::RecipeFile;
|
||||
use crate::recipe::{RecipeParameterInputType, RecipeParameterRequirement};
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
const NO_USER_PROMPT: Option<fn(&str, &str) -> Result<String, anyhow::Error>> = None;
|
||||
|
||||
fn setup_recipe_file(instructions_and_parameters: &str) -> (TempDir, RecipeFile) {
|
||||
fn setup_recipe_file(instructions_and_parameters: &str) -> (TempDir, String, PathBuf) {
|
||||
let recipe_content = format!(
|
||||
r#"{{
|
||||
"version": "1.0.0",
|
||||
@@ -22,14 +23,10 @@ fn setup_recipe_file(instructions_and_parameters: &str) -> (TempDir, RecipeFile)
|
||||
let recipe_path = temp_dir.path().join("test_recipe.json");
|
||||
|
||||
std::fs::write(&recipe_path, recipe_content).unwrap();
|
||||
let recipe_dir = temp_dir.path().to_path_buf();
|
||||
let recipe_content = std::fs::read_to_string(&recipe_path).unwrap();
|
||||
|
||||
let recipe_file = RecipeFile {
|
||||
content: std::fs::read_to_string(&recipe_path).unwrap(),
|
||||
parent_dir: temp_dir.path().to_path_buf(),
|
||||
file_path: recipe_path,
|
||||
};
|
||||
|
||||
(temp_dir, recipe_file)
|
||||
(temp_dir, recipe_content, recipe_dir)
|
||||
}
|
||||
|
||||
fn setup_test_file(temp_dir: &TempDir, filename: &str, content: &str) -> std::path::PathBuf {
|
||||
@@ -101,10 +98,11 @@ fn test_build_recipe_from_template_success() {
|
||||
}
|
||||
]"#;
|
||||
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let params = vec![("my_name".to_string(), "value".to_string())];
|
||||
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
|
||||
let recipe =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, params, NO_USER_PROMPT).unwrap();
|
||||
|
||||
assert_eq!(recipe.title, "Test Recipe");
|
||||
assert_eq!(recipe.description, "A test recipe");
|
||||
@@ -134,10 +132,11 @@ fn test_build_recipe_from_template_success_variable_in_prompt() {
|
||||
}
|
||||
]"#;
|
||||
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let params = vec![("my_name".to_string(), "value".to_string())];
|
||||
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
|
||||
let recipe =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, params, NO_USER_PROMPT).unwrap();
|
||||
|
||||
assert_eq!(recipe.title, "Test Recipe");
|
||||
assert_eq!(recipe.description, "A test recipe");
|
||||
@@ -165,9 +164,10 @@ fn test_build_recipe_from_template_wrong_parameters_in_recipe_file() {
|
||||
"description": "A test parameter"
|
||||
}
|
||||
]"#;
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let build_recipe_result = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT);
|
||||
let build_recipe_result =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, Vec::new(), NO_USER_PROMPT);
|
||||
assert!(build_recipe_result.is_err());
|
||||
let err = build_recipe_result.unwrap_err();
|
||||
println!("{}", err);
|
||||
@@ -203,10 +203,11 @@ fn test_build_recipe_from_template_with_default_values_in_recipe_file() {
|
||||
"description": "A test parameter"
|
||||
}
|
||||
]"#;
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
let params = vec![("param_without_default".to_string(), "value1".to_string())];
|
||||
|
||||
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
|
||||
let recipe =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, params, NO_USER_PROMPT).unwrap();
|
||||
|
||||
assert_eq!(recipe.title, "Test Recipe");
|
||||
assert_eq!(recipe.description, "A test recipe");
|
||||
@@ -229,9 +230,11 @@ fn test_build_recipe_from_template_optional_parameters_with_empty_default_values
|
||||
"default": ""
|
||||
}
|
||||
]"#;
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
|
||||
let recipe =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, Vec::new(), NO_USER_PROMPT)
|
||||
.unwrap();
|
||||
assert_eq!(recipe.title, "Test Recipe");
|
||||
assert_eq!(recipe.description, "A test recipe");
|
||||
assert_eq!(recipe.instructions.unwrap(), "Test instructions with ");
|
||||
@@ -249,9 +252,10 @@ fn test_build_recipe_from_template_optional_parameters_without_default_values_in
|
||||
"description": "A test parameter"
|
||||
}
|
||||
]"#;
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let build_recipe_result = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT);
|
||||
let build_recipe_result =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, Vec::new(), NO_USER_PROMPT);
|
||||
assert!(build_recipe_result.is_err());
|
||||
let err = build_recipe_result.unwrap_err();
|
||||
println!("{}", err);
|
||||
@@ -276,9 +280,10 @@ fn test_build_recipe_from_template_wrong_input_type_in_recipe_file() {
|
||||
}
|
||||
]"#;
|
||||
let params = vec![("param".to_string(), "value".to_string())];
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let build_recipe_result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
|
||||
let build_recipe_result =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, params, NO_USER_PROMPT);
|
||||
assert!(build_recipe_result.is_err());
|
||||
let err = build_recipe_result.unwrap_err();
|
||||
match err {
|
||||
@@ -296,9 +301,11 @@ fn test_build_recipe_from_template_success_without_parameters() {
|
||||
let instructions_and_parameters = r#"
|
||||
"instructions": "Test instructions"
|
||||
"#;
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
|
||||
let recipe =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, Vec::new(), NO_USER_PROMPT)
|
||||
.unwrap();
|
||||
assert_eq!(recipe.instructions.unwrap(), "Test instructions");
|
||||
assert!(recipe.parameters.is_none());
|
||||
}
|
||||
@@ -306,9 +313,10 @@ fn test_build_recipe_from_template_success_without_parameters() {
|
||||
#[test]
|
||||
fn test_build_recipe_from_template_missing_prompt_and_instructions() {
|
||||
let instructions_and_parameters = "";
|
||||
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
|
||||
let (_temp_dir, recipe_content, recipe_dir) = setup_recipe_file(instructions_and_parameters);
|
||||
|
||||
let build_recipe_result = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT);
|
||||
let build_recipe_result =
|
||||
build_recipe_from_template(recipe_content, &recipe_dir, Vec::new(), NO_USER_PROMPT);
|
||||
assert!(build_recipe_result.is_err());
|
||||
let err = build_recipe_result.unwrap_err();
|
||||
println!("{}", err);
|
||||
@@ -366,8 +374,13 @@ fn test_template_inheritance() {
|
||||
("is_enabled".to_string(), "true".to_string()),
|
||||
];
|
||||
|
||||
let parent_recipe =
|
||||
build_recipe_from_template(parent_recipe_file, params.clone(), NO_USER_PROMPT).unwrap();
|
||||
let parent_recipe = build_recipe_from_template(
|
||||
parent_recipe_file.content,
|
||||
&parent_recipe_file.parent_dir,
|
||||
params.clone(),
|
||||
NO_USER_PROMPT,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(parent_recipe.description, "Parent recipe");
|
||||
assert_eq!(
|
||||
parent_recipe.prompt.unwrap(),
|
||||
@@ -380,8 +393,13 @@ fn test_template_inheritance() {
|
||||
"is_enabled"
|
||||
);
|
||||
|
||||
let child_recipe =
|
||||
build_recipe_from_template(child_recipe_file, params, NO_USER_PROMPT).unwrap();
|
||||
let child_recipe = build_recipe_from_template(
|
||||
child_recipe_file.content,
|
||||
&child_recipe_file.parent_dir,
|
||||
params,
|
||||
NO_USER_PROMPT,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(child_recipe.title, "Parent");
|
||||
assert_eq!(child_recipe.description, "Parent recipe");
|
||||
assert_eq!(
|
||||
@@ -467,7 +485,13 @@ instructions: Child instructions
|
||||
file_path: main_recipe_path,
|
||||
};
|
||||
|
||||
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
|
||||
let recipe = build_recipe_from_template(
|
||||
recipe_file.content,
|
||||
&recipe_file.parent_dir,
|
||||
Vec::new(),
|
||||
NO_USER_PROMPT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(recipe.title, "Main Recipe");
|
||||
assert!(recipe.sub_recipes.is_some());
|
||||
@@ -505,7 +529,12 @@ parameters:
|
||||
"FILE_PARAM".to_string(),
|
||||
test_file_path.to_string_lossy().to_string(),
|
||||
)];
|
||||
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
|
||||
let result = build_recipe_from_template(
|
||||
recipe_file.content,
|
||||
&recipe_file.parent_dir,
|
||||
params,
|
||||
NO_USER_PROMPT,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let recipe = result.unwrap();
|
||||
@@ -530,7 +559,12 @@ parameters:
|
||||
"FILE_PARAM".to_string(),
|
||||
"/nonexistent/path/file.txt".to_string(),
|
||||
)];
|
||||
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
|
||||
let result = build_recipe_from_template(
|
||||
recipe_file.content,
|
||||
&recipe_file.parent_dir,
|
||||
params,
|
||||
NO_USER_PROMPT,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(RecipeError::TemplateRendering { source }) = result {
|
||||
@@ -553,7 +587,12 @@ parameters:
|
||||
let (_temp_dir, recipe_file) = setup_yaml_recipe_file(instructions_and_parameters);
|
||||
|
||||
let params = vec![];
|
||||
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
|
||||
let result = build_recipe_from_template(
|
||||
recipe_file.content,
|
||||
&recipe_file.parent_dir,
|
||||
params,
|
||||
NO_USER_PROMPT,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(RecipeError::TemplateRendering { source }) = result {
|
||||
|
||||
@@ -150,6 +150,11 @@ fn get_env_with_template_variables(
|
||||
Ok((env, template_variables))
|
||||
}
|
||||
|
||||
fn uses_template_inheritance(content: &str) -> bool {
|
||||
let re = Regex::new(r"\{%-?\s*(extends|include)").unwrap();
|
||||
re.is_match(content)
|
||||
}
|
||||
|
||||
pub fn parse_recipe_content(
|
||||
content: &str,
|
||||
recipe_dir: Option<String>,
|
||||
@@ -163,46 +168,23 @@ pub fn parse_recipe_content(
|
||||
UndefinedBehavior::Lenient,
|
||||
)?;
|
||||
let template = env.get_template(CURRENT_TEMPLATE_NAME).unwrap();
|
||||
let rendered_content = template
|
||||
.render(())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse the recipe {}", e))?;
|
||||
let recipe = Recipe::from_content(&rendered_content)?;
|
||||
|
||||
// Detect if template uses inheritance or includes
|
||||
let recipe_content = if uses_template_inheritance(&preprocessed_content) {
|
||||
// Must render to resolve inheritance
|
||||
template
|
||||
.render(())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse the recipe {}", e))?
|
||||
} else {
|
||||
// Preserve conditionals and variables as-is
|
||||
preprocessed_content
|
||||
};
|
||||
|
||||
let recipe = Recipe::from_content(&recipe_content)?;
|
||||
// return recipe (without loading any variables) and the variable names that are in the recipe
|
||||
Ok((recipe, template_variables))
|
||||
}
|
||||
|
||||
// render the recipe for validation, deeplink and explain, etc.
|
||||
pub fn render_recipe_for_preview(
|
||||
content: &str,
|
||||
recipe_dir: Option<String>,
|
||||
params: &HashMap<String, String>,
|
||||
) -> Result<Recipe> {
|
||||
// Pre-process template variables to handle invalid variable names
|
||||
let preprocessed_content = preprocess_template_variables(content)?;
|
||||
|
||||
let (env, template_variables) = get_env_with_template_variables(
|
||||
&preprocessed_content,
|
||||
recipe_dir,
|
||||
UndefinedBehavior::Lenient,
|
||||
)?;
|
||||
let template = env.get_template(CURRENT_TEMPLATE_NAME).unwrap();
|
||||
// if the variables are not provided, the template will be rendered with the variables, otherwise it will keep the variables as is
|
||||
let mut ctx = preserve_vars(&template_variables).clone();
|
||||
ctx.extend(params.clone());
|
||||
let rendered_content = template
|
||||
.render(ctx)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse the recipe {}", e))?;
|
||||
Recipe::from_content(&rendered_content)
|
||||
}
|
||||
|
||||
fn preserve_vars(variables: &HashSet<String>) -> HashMap<String, String> {
|
||||
let mut context = HashMap::<String, String>::new();
|
||||
for template_var in variables {
|
||||
context.insert(template_var.clone(), format!("{{{{ {} }}}}", template_var));
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod render_content_with_params_tests {
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
use crate::recipe::read_recipe_file_content::RecipeFile;
|
||||
use crate::recipe::template_recipe::{parse_recipe_content, render_recipe_for_preview};
|
||||
use crate::recipe::template_recipe::parse_recipe_content;
|
||||
use crate::recipe::{
|
||||
Recipe, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement,
|
||||
BUILT_IN_RECIPE_DIR_PARAM,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn validate_recipe_parameters(
|
||||
pub fn parse_and_validate_parameters(
|
||||
recipe_file_content: &str,
|
||||
recipe_dir_str: Option<String>,
|
||||
) -> Result<Option<Vec<RecipeParameter>>> {
|
||||
) -> Result<Recipe> {
|
||||
let (recipe_template, template_variables) =
|
||||
parse_recipe_content(recipe_file_content, recipe_dir_str)?;
|
||||
let recipe_parameters = recipe_template.parameters;
|
||||
validate_optional_parameters(&recipe_parameters)?;
|
||||
validate_parameters_in_template(&recipe_parameters, &template_variables)?;
|
||||
Ok(recipe_parameters)
|
||||
let recipe_parameters = &recipe_template.parameters;
|
||||
validate_optional_parameters(recipe_parameters)?;
|
||||
validate_parameters_in_template(recipe_parameters, &template_variables)?;
|
||||
Ok(recipe_template)
|
||||
}
|
||||
|
||||
fn validate_json_schema(schema: &serde_json::Value) -> Result<()> {
|
||||
@@ -40,8 +40,8 @@ pub fn validate_recipe_template_from_content(
|
||||
recipe_content: &str,
|
||||
recipe_dir: Option<String>,
|
||||
) -> Result<Recipe> {
|
||||
validate_recipe_parameters(recipe_content, recipe_dir.clone())?;
|
||||
let recipe = render_recipe_for_preview(recipe_content, recipe_dir, &HashMap::new())?;
|
||||
parse_and_validate_parameters(recipe_content, recipe_dir.clone())?;
|
||||
let (recipe, _) = parse_recipe_content(recipe_content, recipe_dir)?;
|
||||
|
||||
validate_prompt_or_instructions(&recipe)?;
|
||||
if let Some(response) = &recipe.response {
|
||||
@@ -154,3 +154,47 @@ fn validate_optional_parameters(parameters: &Option<Vec<RecipeParameter>>) -> Re
|
||||
Err(anyhow::anyhow!("Optional parameters missing default values in the recipe: {}. Please provide defaults.", optional_params_without_default_values.join(", ")))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_validate_recipe_template_from_content_success() {
|
||||
let recipe_content = r#"
|
||||
version: 1.0.0
|
||||
title: Test Recipe
|
||||
description: A test recipe for validation
|
||||
instructions: Test instructions with {{ user_role }}
|
||||
prompt: |
|
||||
{% if user_role in ["Director, Account Management", "Senior Director, Account Management"] %}
|
||||
- Focus on strategic planning and organizational performance
|
||||
{% else %}
|
||||
- Provide foundational account management guidance
|
||||
{% endif %}
|
||||
parameters:
|
||||
- key: user_role
|
||||
input_type: string
|
||||
requirement: required
|
||||
description: A test parameter
|
||||
"#;
|
||||
|
||||
let result = validate_recipe_template_from_content(recipe_content, None);
|
||||
if let Err(e) = &result {
|
||||
eprintln!("Validation error: {}", e);
|
||||
eprintln!("Error chain:");
|
||||
let mut source = e.source();
|
||||
while let Some(err) = source {
|
||||
eprintln!(" Caused by: {}", err);
|
||||
source = err.source();
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok(), "Validation failed: {:?}", result.err());
|
||||
|
||||
let recipe = result.unwrap();
|
||||
assert_eq!(recipe.title, "Test Recipe");
|
||||
assert_eq!(recipe.description, "A test recipe for validation");
|
||||
assert!(recipe.instructions.is_some());
|
||||
println!("Recipe: {:?}", recipe.prompt);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user