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