Skip to main content

actr_cli/commands/codegen/
metadata.rs

1use crate::commands::SupportedLanguage;
2use crate::commands::codegen::proto_model::{MethodModel, ProtoModel, ServiceModel};
3use crate::error::{ActrCliError, Result};
4use actr_protocol::ActrType;
5use serde::{Deserialize, Serialize};
6use std::path::{Path, PathBuf};
7
8pub const ACTR_GEN_META_FILE: &str = "actr-gen-meta.json";
9
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
11pub struct ActrGenMetadata {
12    pub plugin_version: String,
13    pub language: String,
14    #[serde(default)]
15    pub local_services: Vec<LocalServiceMetadata>,
16    #[serde(default)]
17    pub remote_services: Vec<RemoteServiceMetadata>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct LocalServiceMetadata {
22    pub name: String,
23    pub package: String,
24    pub proto_file: String,
25    pub handler_interface: String,
26    pub workload_type: String,
27    pub dispatcher_type: String,
28    pub methods: Vec<MethodMetadata>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct RemoteServiceMetadata {
33    pub name: String,
34    pub package: String,
35    pub proto_file: String,
36    pub actr_type: String,
37    pub client_type: String,
38    pub methods: Vec<MethodMetadata>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct MethodMetadata {
43    pub name: String,
44    pub snake_name: String,
45    pub input_type: String,
46    pub output_type: String,
47    pub route_key: String,
48}
49
50impl ActrGenMetadata {
51    pub fn from_proto_model(language: SupportedLanguage, proto_model: &ProtoModel) -> Self {
52        Self {
53            plugin_version: "actr-cli".to_string(),
54            language: language_key(language).to_string(),
55            local_services: proto_model
56                .local_services
57                .iter()
58                .map(build_local_service_metadata)
59                .collect(),
60            remote_services: proto_model
61                .remote_services
62                .iter()
63                .map(build_remote_service_metadata)
64                .collect(),
65        }
66    }
67}
68
69pub fn metadata_path(output_dir: &Path) -> PathBuf {
70    output_dir.join(ACTR_GEN_META_FILE)
71}
72
73pub fn load_metadata(output_dir: &Path) -> Result<Option<ActrGenMetadata>> {
74    let path = metadata_path(output_dir);
75    if !path.exists() {
76        return Ok(None);
77    }
78
79    let content = std::fs::read_to_string(&path).map_err(|e| {
80        ActrCliError::config_error(format!("Failed to read {}: {e}", path.display()))
81    })?;
82    let metadata = serde_json::from_str(&content).map_err(|e| {
83        ActrCliError::config_error(format!("Failed to parse {}: {e}", path.display()))
84    })?;
85    Ok(Some(metadata))
86}
87
88pub fn write_metadata(output_dir: &Path, metadata: &ActrGenMetadata) -> Result<PathBuf> {
89    std::fs::create_dir_all(output_dir).map_err(|e| {
90        ActrCliError::config_error(format!(
91            "Failed to create metadata output directory {}: {e}",
92            output_dir.display()
93        ))
94    })?;
95
96    let path = metadata_path(output_dir);
97    let content = serde_json::to_string_pretty(metadata)?;
98    std::fs::write(&path, content).map_err(|e| {
99        ActrCliError::config_error(format!("Failed to write {}: {e}", path.display()))
100    })?;
101
102    Ok(path)
103}
104
105fn language_key(language: SupportedLanguage) -> &'static str {
106    match language {
107        SupportedLanguage::Rust => "rust",
108        SupportedLanguage::Python => "python",
109        SupportedLanguage::Swift => "swift",
110        SupportedLanguage::Kotlin => "kotlin",
111        SupportedLanguage::TypeScript => "typescript",
112    }
113}
114
115fn build_local_service_metadata(service: &ServiceModel) -> LocalServiceMetadata {
116    LocalServiceMetadata {
117        name: service.name.clone(),
118        package: service.package.clone(),
119        proto_file: service.relative_path.to_string_lossy().to_string(),
120        handler_interface: format!("{}Handler", service.name),
121        workload_type: format!("{}Workload", service.name),
122        dispatcher_type: format!("{}Dispatcher", service.name),
123        methods: service.methods.iter().map(build_method_metadata).collect(),
124    }
125}
126
127fn build_remote_service_metadata(service: &ServiceModel) -> RemoteServiceMetadata {
128    RemoteServiceMetadata {
129        name: service.name.clone(),
130        package: service.package.clone(),
131        proto_file: service.relative_path.to_string_lossy().to_string(),
132        actr_type: service.actr_type.clone().unwrap_or_else(|| {
133            ActrType {
134                manufacturer: "acme".to_string(),
135                name: service.name.clone(),
136                version: "1.0.0".to_string(),
137            }
138            .to_string_repr()
139        }),
140        client_type: format!("{}Client", service.name),
141        methods: service.methods.iter().map(build_method_metadata).collect(),
142    }
143}
144
145fn build_method_metadata(method: &MethodModel) -> MethodMetadata {
146    MethodMetadata {
147        name: method.name.clone(),
148        snake_name: method.snake_name.clone(),
149        input_type: method.input_type.clone(),
150        output_type: method.output_type.clone(),
151        route_key: method.route_key.clone(),
152    }
153}