1use 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#[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#[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#[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
54pub fn init_project(request: InitRequest) -> Result<InitResponse> {
56 InitEngine::execute(request)
57}
58
59pub fn execute_codegen(request: CodegenRequest) -> Result<CodegenOutcome> {
61 CodegenEngine::execute_validated(request)
62}
63
64pub fn execute_codegen_unvalidated(request: CodegenRequest) -> Result<CodegenOutcome> {
66 CodegenEngine::execute(request)
67}
68
69pub 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
89pub 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#[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}