Skip to main content

cc_plugin_validator/manifest/
mod.rs

1use once_cell::sync::Lazy;
2use regex::Regex;
3use serde_json::Value;
4
5use crate::ValidationResult;
6
7mod channels_settings;
8mod commands;
9mod common;
10mod dependencies;
11mod hooks;
12mod lsp_servers;
13mod mcp_servers;
14mod metadata;
15mod path_fields;
16mod user_config;
17
18static PLUGIN_NAME_RE: Lazy<Regex> =
19    Lazy::new(|| Regex::new(r"^[a-z0-9][-a-z0-9._]*$").expect("valid plugin name regex"));
20static DEP_STR_RE: Lazy<Regex> = Lazy::new(|| {
21    Regex::new(r"^[a-z0-9][-a-z0-9._]*(?:@[a-z0-9][-a-z0-9._]*)?(?:@\^[^@]*)?$")
22        .expect("valid dependency regex")
23});
24static DEP_NAME_RE: Lazy<Regex> =
25    Lazy::new(|| Regex::new(r"^[a-z0-9][-a-z0-9._]*$").expect("valid dep name regex"));
26static USER_CONFIG_KEY_RE: Lazy<Regex> =
27    Lazy::new(|| Regex::new(r"^[A-Za-z_]\w*$").expect("valid user config key regex"));
28
29pub fn validate_plugin_manifest(input: Value) -> ValidationResult {
30    let mut issues = Vec::new();
31
32    let Some(root) = input.as_object() else {
33        common::push_issue(
34            &mut issues,
35            "",
36            "invalid_type",
37            "Manifest root must be a JSON object",
38        );
39        return ValidationResult::Failure { issues };
40    };
41
42    metadata::validate_required_name(root, &mut issues, &PLUGIN_NAME_RE);
43    metadata::validate_optional_metadata(root, &mut issues);
44    dependencies::validate_dependencies(root, &mut issues, &DEP_STR_RE, &DEP_NAME_RE);
45    hooks::validate_hooks_field(root, &mut issues);
46    commands::validate_commands_field(root, &mut issues);
47    path_fields::validate_agents_skills_output_styles(root, &mut issues);
48    mcp_servers::validate_mcp_servers_field(root, &mut issues);
49    lsp_servers::validate_lsp_servers_field(root, &mut issues);
50    user_config::validate_user_config(root, &mut issues, &USER_CONFIG_KEY_RE);
51    channels_settings::validate_channels(root, &mut issues);
52    channels_settings::validate_settings(root, &mut issues);
53
54    if issues.is_empty() {
55        ValidationResult::Success { data: input }
56    } else {
57        ValidationResult::Failure { issues }
58    }
59}