systemprompt-models 0.1.22

Shared data models and types for systemprompt.io OS
Documentation
use anyhow::Result;
use systemprompt_traits::validation_report::{
    ValidationError, ValidationReport, ValidationWarning,
};

use crate::profile::Profile;

pub fn validate_profile_paths(profile: &Profile, profile_path: &str) -> ValidationReport {
    let mut report = ValidationReport::new("paths");

    validate_required_path(&mut report, "system", &profile.paths.system);
    validate_required_path(&mut report, "services", &profile.paths.services);
    validate_required_path(&mut report, "bin", &profile.paths.bin);

    validate_required_path(&mut report, "skills", &profile.paths.skills());
    validate_required_path(&mut report, "config", &profile.paths.config());
    validate_required_path(&mut report, "web_path", &profile.paths.web_path_resolved());
    validate_required_path(&mut report, "web_config", &profile.paths.web_config());
    validate_required_path(&mut report, "web_metadata", &profile.paths.web_metadata());
    validate_required_path(
        &mut report,
        "content_config",
        &profile.paths.content_config(),
    );

    validate_optional_path(
        &mut report,
        "geoip_database",
        profile.paths.geoip_database.as_ref(),
    );
    validate_optional_path(&mut report, "storage", profile.paths.storage.as_ref());

    let _ = profile_path;
    report
}

pub fn validate_required_path(report: &mut ValidationReport, field: &str, path: &str) {
    if path.is_empty() {
        report.add_error(
            ValidationError::new(format!("paths.{}", field), "Required path not configured")
                .with_suggestion(format!(
                    "Add paths.{} to your profile or run 'systemprompt cloud config'",
                    field
                )),
        );
        return;
    }

    let path_buf = std::path::Path::new(path);
    if !path_buf.exists() {
        report.add_error(
            ValidationError::new(format!("paths.{}", field), "Path does not exist")
                .with_path(path)
                .with_suggestion("Create the directory/file or update the path in your profile"),
        );
    }
}

pub fn validate_required_optional_path(
    report: &mut ValidationReport,
    field: &str,
    path: Option<&String>,
) {
    match path {
        None => {
            report.add_error(
                ValidationError::new(format!("paths.{}", field), "Required path not configured")
                    .with_suggestion(format!(
                        "Add paths.{} to your profile or run 'systemprompt cloud config'",
                        field
                    )),
            );
        },
        Some(p) if p.is_empty() => {
            report.add_error(
                ValidationError::new(format!("paths.{}", field), "Path is empty").with_suggestion(
                    format!("Set a valid path for paths.{} in your profile", field),
                ),
            );
        },
        Some(p) => {
            let path_buf = std::path::Path::new(p);
            if !path_buf.exists() {
                report.add_error(
                    ValidationError::new(format!("paths.{}", field), "Path does not exist")
                        .with_path(p)
                        .with_suggestion("Create the directory/file or update the path"),
                );
            }
        },
    }
}

pub fn validate_optional_path(report: &mut ValidationReport, field: &str, path: Option<&String>) {
    if let Some(p) = path {
        if !p.is_empty() {
            let path_buf = std::path::Path::new(p);
            if !path_buf.exists() {
                report.add_warning(
                    ValidationWarning::new(
                        format!("paths.{}", field),
                        format!("Path does not exist: {}", p),
                    )
                    .with_suggestion("Create the path or remove the config entry"),
                );
            }
        }
    }
}

pub fn format_path_errors(report: &ValidationReport, profile_path: &str) -> String {
    let mut output = String::new();
    output.push_str("Profile Path Validation Failed\n\n");
    output.push_str(&format!("Profile: {}\n\n", profile_path));
    output.push_str("ERRORS:\n\n");

    for error in &report.errors {
        output.push_str(&format!("[{}] {}\n", report.domain, error.field));
        output.push_str(&format!("  {}\n", error.message));
        if let Some(ref path) = error.path {
            output.push_str(&format!("  Path: {}\n", path.display()));
        }
        if let Some(ref suggestion) = error.suggestion {
            output.push_str(&format!("  To fix: {}\n", suggestion));
        }
        output.push('\n');
    }

    if !report.warnings.is_empty() {
        output.push_str("WARNINGS:\n\n");
        for warning in &report.warnings {
            output.push_str(&format!("[{}] {}\n", report.domain, warning.field));
            output.push_str(&format!("  {}\n", warning.message));
            if let Some(ref suggestion) = warning.suggestion {
                output.push_str(&format!("  To enable: {}\n", suggestion));
            }
            output.push('\n');
        }
    }

    output
}

pub fn validate_postgres_url(url: &str) -> Result<()> {
    if !url.starts_with("postgres://") && !url.starts_with("postgresql://") {
        return Err(anyhow::anyhow!(
            "DATABASE_URL must be a PostgreSQL connection string (postgres:// or postgresql://)"
        ));
    }
    Ok(())
}