Skip to main content

foundry_mcp/core/ops/
validate_content.rs

1//! Core op for content validation (tool-agnostic)
2
3use anyhow::{Context, Result};
4
5use crate::core::validation::{parse_content_type, validate_content};
6use crate::types::responses::{FoundryResponse, ValidateContentResponse, ValidationStatus};
7
8#[derive(Debug, Clone)]
9pub struct Input {
10    pub content_type: String,
11    pub content: String,
12}
13
14pub async fn run(input: Input) -> Result<FoundryResponse<ValidateContentResponse>> {
15    validate_input_args(&input.content_type, &input.content)
16        .with_context(|| "Input validation failed")?;
17
18    let content_type = parse_content_type(&input.content_type).with_context(|| {
19        format!(
20            "Invalid content type '{}'. Supported types are: vision, tech-stack, summary, spec, notes, tasks",
21            input.content_type
22        )
23    })?;
24
25    let validation_result = validate_content(content_type, &input.content);
26
27    let response_data = ValidateContentResponse {
28        content_type: input.content_type.clone(),
29        is_valid: validation_result.is_valid,
30        validation_errors: validation_result.errors.clone(),
31        suggestions: validation_result.suggestions.clone(),
32    };
33
34    let validation_status = if validation_result.is_valid {
35        ValidationStatus::Complete
36    } else {
37        ValidationStatus::Error
38    };
39
40    let next_steps = if validation_result.is_valid {
41        let mut steps =
42            vec!["Content validation passed - ready to use in project creation".to_string()];
43        if !validation_result.suggestions.is_empty() {
44            steps.push(format!(
45                "Consider incorporating {} suggestions to improve content quality",
46                validation_result.suggestions.len()
47            ));
48        }
49        steps.push("Use this content via MCP: {\"name\": \"create_project\", \"arguments\": {\"project_name\": \"<name>\", \"vision\": \"...\", \"tech_stack\": \"...\", \"summary\": \"...\"}} or {\"name\": \"analyze_project\", \"arguments\": {\"project_name\": \"<name>\", \"vision\": \"...\", \"tech_stack\": \"...\", \"summary\": \"...\"}}".to_string());
50        steps
51    } else {
52        let error_count = validation_result.errors.len();
53        let suggestion_count = validation_result.suggestions.len();
54        let mut steps = vec![format!(
55            "Fix {} validation error(s) before using this content",
56            error_count
57        )];
58        if suggestion_count > 0 {
59            steps.push(format!(
60                "Review {} suggestion(s) for improvement guidance",
61                suggestion_count
62            ));
63        }
64        steps.push("Re-run validation after making changes".to_string());
65        steps
66    };
67
68    let mut workflow_hints = vec![
69        "Use this command to pre-validate content before project operations".to_string(),
70        "Validation helps ensure content meets Foundry's structural requirements".to_string(),
71    ];
72
73    match input.content_type.as_str() {
74        "vision" => workflow_hints.push(
75            "Vision should describe the problem, target users, and value proposition".to_string(),
76        ),
77        "tech-stack" => workflow_hints.push(
78            "Tech stack should include languages, frameworks, and deployment decisions".to_string(),
79        ),
80        "summary" => workflow_hints
81            .push("Summary should be concise but capture key project insights".to_string()),
82        "spec" => workflow_hints.push(
83            "Spec should include clear requirements and functionality descriptions".to_string(),
84        ),
85        "notes" => workflow_hints
86            .push("Notes provide additional context and implementation considerations".to_string()),
87        "tasks" => workflow_hints
88            .push("Tasks should be actionable items with clear completion criteria".to_string()),
89        _ => workflow_hints
90            .push("Follow the content guidelines for your specific content type".to_string()),
91    }
92
93    workflow_hints
94        .push("Content validation is performed client-side for immediate feedback".to_string());
95
96    Ok(FoundryResponse {
97        data: response_data,
98        next_steps,
99        validation_status,
100        workflow_hints,
101    })
102}
103
104fn validate_input_args(content_type: &str, content: &str) -> Result<()> {
105    if content_type.trim().is_empty() {
106        return Err(anyhow::anyhow!(
107            "Content type cannot be empty. Supported types: vision, tech-stack, summary, spec, notes, tasks"
108        ));
109    }
110    const MAX_VALIDATION_SIZE: usize = 100_000;
111    if content.len() > MAX_VALIDATION_SIZE {
112        return Err(anyhow::anyhow!(
113            "Content too large for validation ({} characters). Maximum size for validation is {} characters.",
114            content.len(),
115            MAX_VALIDATION_SIZE
116        ));
117    }
118    if content.contains('\0') {
119        return Err(anyhow::anyhow!(
120            "Content appears to contain binary data. Only text content can be validated."
121        ));
122    }
123    Ok(())
124}