soma_domain/
provider_validation.rs1use serde_json::Value;
9
10use soma_provider_core::{ProviderKind, ProviderManifest};
11
12pub use soma_provider_core::ProviderValidationError;
13
14const RESERVED_CLI_COMMANDS: &[&str] = &[
15 "serve",
16 "mcp",
17 "doctor",
18 "watch",
19 "self-update",
20 "setup",
21 "package",
22 "tools",
23 "providers",
24 "openapi",
25 "help",
26];
27
28pub fn validate_provider_manifest_value(
31 value: &Value,
32) -> Result<ProviderManifest, ProviderValidationError> {
33 validate_manifest_schema(value)?;
34 let manifest: ProviderManifest = serde_json::from_value(value.clone()).map_err(|error| {
35 ProviderValidationError::new("manifest_deserialize_failed", error.to_string())
36 })?;
37 validate_provider_manifest(&manifest)?;
38 Ok(manifest)
39}
40
41pub fn validate_manifest_schema(value: &Value) -> Result<(), ProviderValidationError> {
43 soma_provider_core::validate_manifest_schema(value)
44}
45
46pub fn validate_provider_manifest(
49 manifest: &ProviderManifest,
50) -> Result<(), ProviderValidationError> {
51 soma_provider_core::validate_provider_manifest(manifest)?;
52 validate_soma_cli_policy(manifest)?;
53 for env in manifest
54 .env
55 .iter()
56 .chain(manifest.tools.iter().flat_map(|tool| tool.env.iter()))
57 {
58 if env.name.starts_with("SOMA_") || env.name.starts_with("LAB_") {
59 return Err(ProviderValidationError::new(
60 "invalid_env_declaration",
61 format!(
62 "env declaration `{}` must be logical and unprefixed",
63 env.name
64 ),
65 ));
66 }
67 }
68 Ok(())
69}
70
71fn validate_soma_cli_policy(manifest: &ProviderManifest) -> Result<(), ProviderValidationError> {
72 for tool in &manifest.tools {
73 let Some(cli) = &tool.cli else {
74 continue;
75 };
76 if !cli.enabled {
77 continue;
78 }
79 let command = cli.command.as_deref().unwrap_or(&tool.name);
80 for candidate in std::iter::once(command).chain(cli.aliases.iter().map(String::as_str)) {
81 if manifest.provider.kind == ProviderKind::StaticRust
82 && tool.name == "help"
83 && candidate == "help"
84 {
85 continue;
86 }
87 if RESERVED_CLI_COMMANDS.contains(&candidate) {
88 return Err(ProviderValidationError::new(
89 "reserved_cli_command",
90 format!("provider command `{candidate}` is reserved"),
91 ));
92 }
93 }
94 }
95 Ok(())
96}
97
98#[cfg(test)]
99#[path = "provider_validation_tests.rs"]
100mod tests;