Applied server side call to parse and save recipe (#5022)
This commit is contained in:
@@ -1019,7 +1019,7 @@ pub async fn cli() -> Result<()> {
|
||||
.and_then(|rf| {
|
||||
goose::recipe::template_recipe::parse_recipe_content(
|
||||
&rf.content,
|
||||
rf.parent_dir.to_string_lossy().to_string(),
|
||||
Some(rf.parent_dir.to_string_lossy().to_string()),
|
||||
)
|
||||
.ok()
|
||||
.map(|(r, _)| r.version)
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use console::style;
|
||||
use goose::recipe::validate_recipe::validate_recipe_template_from_file;
|
||||
|
||||
use crate::recipes::github_recipe::RecipeSource;
|
||||
use crate::recipes::recipe::load_recipe_for_validation;
|
||||
use crate::recipes::search_recipe::list_available_recipes;
|
||||
use crate::recipes::search_recipe::{list_available_recipes, load_recipe_file};
|
||||
use goose::recipe_deeplink;
|
||||
|
||||
/// Validates a recipe file
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `file_path` - Path to the recipe file to validate
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result indicating success or failure
|
||||
pub fn handle_validate(recipe_name: &str) -> Result<()> {
|
||||
// Load and validate the recipe file
|
||||
match load_recipe_for_validation(recipe_name) {
|
||||
Ok(_) => {
|
||||
println!("{} recipe file is valid", style("✓").green().bold());
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{} {}", style("✗").red().bold(), err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
let recipe_file = load_recipe_file(recipe_name)?;
|
||||
validate_recipe_template_from_file(&recipe_file).map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"{} recipe file is invalid: {}",
|
||||
style("✗").red().bold(),
|
||||
err
|
||||
)
|
||||
})?;
|
||||
println!("{} recipe file is valid", style("✓").green().bold());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates a deeplink for a recipe file
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `recipe_name` - Path to the recipe file
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result indicating success or failure
|
||||
pub fn handle_deeplink(recipe_name: &str) -> Result<String> {
|
||||
match generate_deeplink(recipe_name) {
|
||||
Ok((deeplink_url, recipe)) => {
|
||||
@@ -60,15 +42,6 @@ pub fn handle_deeplink(recipe_name: &str) -> Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens a recipe in Goose Desktop
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `recipe_name` - Path to the recipe file
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result indicating success or failure
|
||||
pub fn handle_open(recipe_name: &str) -> Result<()> {
|
||||
// Generate the deeplink using the helper function (no printing)
|
||||
// This reuses all the validation and encoding logic
|
||||
@@ -107,16 +80,6 @@ pub fn handle_open(recipe_name: &str) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists all available recipes from local paths and GitHub repositories
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `format` - Output format ("text" or "json")
|
||||
/// * `verbose` - Whether to show detailed information
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result indicating success or failure
|
||||
pub fn handle_list(format: &str, verbose: bool) -> Result<()> {
|
||||
let recipes = match list_available_recipes() {
|
||||
Ok(recipes) => recipes,
|
||||
@@ -168,18 +131,10 @@ pub fn handle_list(format: &str, verbose: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to generate a deeplink
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `recipe_name` - Path to the recipe file
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result containing the deeplink URL and recipe
|
||||
fn generate_deeplink(recipe_name: &str) -> Result<(String, goose::recipe::Recipe)> {
|
||||
let recipe_file = load_recipe_file(recipe_name)?;
|
||||
// Load the recipe file first to validate it
|
||||
let recipe = load_recipe_for_validation(recipe_name)?;
|
||||
let recipe = validate_recipe_template_from_file(&recipe_file)?;
|
||||
match recipe_deeplink::encode(&recipe) {
|
||||
Ok(encoded) => {
|
||||
let full_url = format!("goose://recipe?config={}", encoded);
|
||||
@@ -227,20 +182,6 @@ prompt: "Test prompt content {{ name }}"
|
||||
instructions: "Test instructions"
|
||||
"#;
|
||||
|
||||
const RECIPE_WITH_INVALID_JSON_SCHEMA: &str = r#"
|
||||
title: "Test Recipe with Invalid JSON Schema"
|
||||
description: "A test recipe with invalid JSON schema"
|
||||
prompt: "Test prompt content"
|
||||
instructions: "Test instructions"
|
||||
response:
|
||||
json_schema:
|
||||
type: invalid_type
|
||||
properties:
|
||||
result:
|
||||
type: unknown_type
|
||||
required: "should_be_array_not_string"
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn test_handle_deeplink_valid_recipe() {
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp directory");
|
||||
@@ -305,23 +246,6 @@ response:
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_validation_recipe_with_invalid_json_schema() {
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp directory");
|
||||
let recipe_path = create_test_recipe_file(
|
||||
&temp_dir,
|
||||
"test_recipe.yaml",
|
||||
RECIPE_WITH_INVALID_JSON_SCHEMA,
|
||||
);
|
||||
|
||||
let result = handle_validate(&recipe_path);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("JSON schema validation failed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_deeplink_valid_recipe() {
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp directory");
|
||||
|
||||
@@ -331,7 +331,7 @@ fn get_github_recipe_info(repo: &str, dir_name: &str, recipe_filename: &str) ->
|
||||
.map_err(|e| anyhow!("Failed to convert content to string: {}", e))?;
|
||||
|
||||
// Parse the recipe content
|
||||
let (recipe, _) = parse_recipe_content(&content, format!("{}/{}", repo, dir_name))?;
|
||||
let (recipe, _) = parse_recipe_content(&content, Some(format!("{}/{}", repo, dir_name)))?;
|
||||
|
||||
return Ok(RecipeInfo {
|
||||
name: dir_name.to_string(),
|
||||
|
||||
@@ -7,13 +7,13 @@ use crate::recipes::secret_discovery::{discover_recipe_secrets, SecretRequiremen
|
||||
use anyhow::Result;
|
||||
use goose::config::Config;
|
||||
use goose::recipe::build_recipe::{
|
||||
apply_values_to_parameters, build_recipe_from_template, validate_recipe_parameters, RecipeError,
|
||||
apply_values_to_parameters, build_recipe_from_template, RecipeError,
|
||||
};
|
||||
use goose::recipe::read_recipe_file_content::RecipeFile;
|
||||
use goose::recipe::template_recipe::render_recipe_for_preview;
|
||||
use goose::recipe::validate_recipe::validate_recipe_parameters;
|
||||
use goose::recipe::Recipe;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn create_user_prompt_callback() -> impl Fn(&str, &str) -> Result<String> {
|
||||
|key: &str, description: &str| -> Result<String> {
|
||||
@@ -131,29 +131,11 @@ pub fn render_recipe_as_yaml(recipe_name: &str, params: Vec<(String, String)>) -
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_recipe_for_validation(recipe_name: &str) -> Result<Recipe> {
|
||||
let (recipe_file, recipe_dir_str) = load_recipe_file_with_dir(recipe_name)?;
|
||||
let recipe_file_content = &recipe_file.content;
|
||||
validate_recipe_parameters(recipe_file_content, &recipe_dir_str)?;
|
||||
let recipe = render_recipe_for_preview(
|
||||
recipe_file_content,
|
||||
recipe_dir_str.to_string(),
|
||||
&HashMap::new(),
|
||||
)?;
|
||||
|
||||
if let Some(response) = &recipe.response {
|
||||
if let Some(json_schema) = &response.json_schema {
|
||||
validate_json_schema(json_schema)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(recipe)
|
||||
}
|
||||
|
||||
pub fn explain_recipe(recipe_name: &str, params: Vec<(String, String)>) -> Result<()> {
|
||||
let (recipe_file, recipe_dir_str) = load_recipe_file_with_dir(recipe_name)?;
|
||||
let recipe_file_content = &recipe_file.content;
|
||||
let recipe_parameters = validate_recipe_parameters(recipe_file_content, &recipe_dir_str)?;
|
||||
let recipe_parameters =
|
||||
validate_recipe_parameters(recipe_file_content, Some(recipe_dir_str.clone()))?;
|
||||
|
||||
let (params_for_template, missing_params) = apply_values_to_parameters(
|
||||
¶ms,
|
||||
@@ -163,7 +145,7 @@ pub fn explain_recipe(recipe_name: &str, params: Vec<(String, String)>) -> Resul
|
||||
)?;
|
||||
let recipe = render_recipe_for_preview(
|
||||
recipe_file_content,
|
||||
recipe_dir_str.to_string(),
|
||||
Some(recipe_dir_str.clone()),
|
||||
¶ms_for_template,
|
||||
)?;
|
||||
print_recipe_explanation(&recipe);
|
||||
@@ -172,13 +154,6 @@ pub fn explain_recipe(recipe_name: &str, params: Vec<(String, String)>) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_json_schema(schema: &serde_json::Value) -> Result<()> {
|
||||
match jsonschema::validator_for(schema) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(anyhow::anyhow!("JSON schema validation failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use goose::recipe::{RecipeParameterInputType, RecipeParameterRequirement};
|
||||
|
||||
Reference in New Issue
Block a user