Skip to main content

xtask/
provider_manifest.rs

1use anyhow::{anyhow, bail, Context, Result};
2use jsonschema::Validator;
3use serde_json::Value;
4use std::{fs, path::Path};
5
6const EXPECTED_INVALID_CODES: &[(&str, &str)] = &[
7    ("duplicate-tool.invalid.json", "duplicate_tool_name"),
8    ("duplicate-rest-path.invalid.json", "duplicate_rest_route"),
9    ("duplicate-cli-alias.invalid.json", "duplicate_cli_command"),
10    ("reserved-cli-command.invalid.json", "reserved_cli_command"),
11    ("missing-env.invalid.json", "invalid_env_declaration"),
12    ("denied-capability.invalid.json", "empty_capability_scope"),
13    (
14        "duplicate-mcp-primitive.invalid.json",
15        "duplicate_mcp_primitive",
16    ),
17    (
18        "generated-doc-prompt-injection.invalid.json",
19        "generated_doc_prompt_injection",
20    ),
21];
22
23pub fn check() -> Result<()> {
24    let root = std::env::current_dir().context("failed to read cwd")?;
25    let schema_path = root.join("docs/contracts/provider-manifest.schema.json");
26    let schema = load_json(&schema_path)?;
27    let compiled = jsonschema::options()
28        .build(&schema)
29        .map_err(|error| anyhow!("failed to compile {}: {error}", schema_path.display()))?;
30    let fixtures = root.join("docs/contracts/examples/provider-manifests");
31
32    let valid = [
33        "static-rust.valid.json",
34        "mcp.valid.json",
35        "mcp-rest-opt-in.valid.json",
36        "openapi.valid.json",
37        "wasm.valid.json",
38        "ai-sdk.valid.json",
39    ];
40    for name in valid {
41        let path = fixtures.join(name);
42        let payload = load_json(&path)?;
43        validate_schema(&compiled, &payload, &path)?;
44        soma_domain::provider_validation::validate_provider_manifest_value(&payload)
45            .with_context(|| format!("semantic validation failed for {}", path.display()))?;
46    }
47
48    for (name, code) in EXPECTED_INVALID_CODES {
49        let path = fixtures.join(name);
50        let payload = load_json(&path)?;
51        validate_schema(&compiled, &payload, &path)?;
52        let error = soma_domain::provider_validation::validate_provider_manifest_value(&payload)
53            .expect_err("invalid fixture should fail semantic validation");
54        if error.code() != *code {
55            bail!(
56                "{} failed with {}, expected {}",
57                path.display(),
58                error.code(),
59                code
60            );
61        }
62    }
63
64    println!("provider manifest contract fixtures are valid");
65    Ok(())
66}
67
68fn load_json(path: &Path) -> Result<Value> {
69    let raw = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?;
70    serde_json::from_slice(&raw).with_context(|| format!("failed to parse {}", path.display()))
71}
72
73fn validate_schema(compiled: &Validator, payload: &Value, path: &Path) -> Result<()> {
74    let errors = compiled
75        .iter_errors(payload)
76        .map(|error| format!("{}: {}", error.instance_path(), error))
77        .collect::<Vec<_>>();
78    if !errors.is_empty() {
79        let details = errors.join("; ");
80        bail!(
81            "{} failed JSON Schema validation: {}",
82            path.display(),
83            details
84        );
85    }
86    Ok(())
87}