Skip to main content

spikard_cli/
app.rs

1//! Shared application layer for CLI and MCP operations.
2
3use crate::codegen::{
4    CodegenEngine, CodegenOutcome, CodegenRequest, GeneratedAsset, PhpDtoGenerator, detect_primary_protocol,
5    parse_asyncapi_schema,
6};
7use crate::init::{InitEngine, InitRequest, InitResponse};
8use anyhow::{Context, Result};
9use serde::Serialize;
10use std::fs;
11use std::path::Path;
12
13/// Result of AsyncAPI validation exposed to both CLI and MCP surfaces.
14#[derive(Debug, Clone, Serialize)]
15#[cfg_attr(
16    feature = "mcp",
17    derive(rmcp::schemars::JsonSchema),
18    schemars(crate = "rmcp::schemars")
19)]
20pub struct AsyncApiValidationSummary {
21    pub spec_version: String,
22    pub title: String,
23    pub api_version: String,
24    pub primary_protocol: String,
25    pub channel_count: usize,
26}
27
28/// Human-readable framework capability summary for agents and CLI users.
29#[derive(Debug, Clone, Serialize)]
30#[cfg_attr(
31    feature = "mcp",
32    derive(rmcp::schemars::JsonSchema),
33    schemars(crate = "rmcp::schemars")
34)]
35pub struct FeatureSummary {
36    pub rust_core: bool,
37    pub language_bindings: Vec<LanguageBinding>,
38    pub documentation_url: String,
39}
40
41/// Install and usage hint for a supported binding.
42#[derive(Debug, Clone, Serialize)]
43#[cfg_attr(
44    feature = "mcp",
45    derive(rmcp::schemars::JsonSchema),
46    schemars(crate = "rmcp::schemars")
47)]
48pub struct LanguageBinding {
49    pub name: String,
50    pub install_hint: String,
51    pub usage_hint: String,
52}
53
54/// Execute a project initialization request.
55pub fn init_project(request: InitRequest) -> Result<InitResponse> {
56    InitEngine::execute(request)
57}
58
59/// Execute validated code generation.
60pub fn execute_codegen(request: CodegenRequest) -> Result<CodegenOutcome> {
61    CodegenEngine::execute_validated(request)
62}
63
64/// Execute code generation without validator passes.
65pub fn execute_codegen_unvalidated(request: CodegenRequest) -> Result<CodegenOutcome> {
66    CodegenEngine::execute(request)
67}
68
69/// Generate the PHP DTO helper classes.
70pub fn generate_php_dto(output: &Path) -> Result<Vec<GeneratedAsset>> {
71    let generator = PhpDtoGenerator::new();
72    let generated = generator.generate_all().context("Failed to generate PHP DTOs")?;
73
74    fs::create_dir_all(output).with_context(|| format!("Failed to create output directory: {}", output.display()))?;
75
76    let mut assets = Vec::with_capacity(generated.len());
77    for (filename, code) in generated {
78        let file_path = output.join(&filename);
79        fs::write(&file_path, code).with_context(|| format!("Failed to write DTO file: {}", file_path.display()))?;
80        assets.push(GeneratedAsset {
81            path: file_path,
82            description: format!("PHP DTO class {}", filename),
83        });
84    }
85
86    Ok(assets)
87}
88
89/// Validate an AsyncAPI schema and return the structured summary.
90pub fn validate_asyncapi_schema(schema: &Path) -> Result<AsyncApiValidationSummary> {
91    let spec = parse_asyncapi_schema(schema).context("Failed to parse AsyncAPI schema")?;
92    let protocol = detect_primary_protocol(&spec)?;
93
94    Ok(AsyncApiValidationSummary {
95        spec_version: "3.0.0".to_string(),
96        title: spec.info.title,
97        api_version: spec.info.version,
98        primary_protocol: format!("{protocol:?}"),
99        channel_count: spec.channels.len(),
100    })
101}
102
103/// Return the current feature summary shown by the CLI and exposed via MCP.
104#[must_use]
105pub fn feature_summary() -> FeatureSummary {
106    FeatureSummary {
107        rust_core: true,
108        language_bindings: vec![
109            LanguageBinding {
110                name: "Rust".to_string(),
111                install_hint: "cargo add spikard".to_string(),
112                usage_hint: "cargo run".to_string(),
113            },
114            LanguageBinding {
115                name: "Python".to_string(),
116                install_hint: "pip install spikard".to_string(),
117                usage_hint: "python server.py".to_string(),
118            },
119            LanguageBinding {
120                name: "TypeScript".to_string(),
121                install_hint: "npm install spikard".to_string(),
122                usage_hint: "node server.js".to_string(),
123            },
124            LanguageBinding {
125                name: "Ruby".to_string(),
126                install_hint: "gem install spikard".to_string(),
127                usage_hint: "ruby app.rb".to_string(),
128            },
129            LanguageBinding {
130                name: "PHP".to_string(),
131                install_hint: "composer require spikard/spikard".to_string(),
132                usage_hint: "php src/App.php".to_string(),
133            },
134            LanguageBinding {
135                name: "Elixir".to_string(),
136                install_hint: "mix deps.get".to_string(),
137                usage_hint: "iex -S mix".to_string(),
138            },
139        ],
140        documentation_url: "https://spikard.dev".to_string(),
141    }
142}