Skip to main content

actr_web_protoc_codegen/
config.rs

1//! Configuration types.
2
3use std::path::PathBuf;
4
5/// Configuration for web code generation.
6#[derive(Debug, Clone)]
7pub struct WebCodegenConfig {
8    /// List of proto file paths.
9    pub proto_files: Vec<PathBuf>,
10
11    /// Rust output directory for the WASM side.
12    pub rust_output_dir: PathBuf,
13
14    /// TypeScript output directory for the web SDK side.
15    pub ts_output_dir: PathBuf,
16
17    /// Whether to generate React Hooks.
18    pub generate_react_hooks: bool,
19
20    /// Proto include paths used to resolve imports.
21    pub includes: Vec<PathBuf>,
22
23    /// Whether to format generated code.
24    pub format_code: bool,
25
26    /// Optional custom template directory.
27    pub custom_templates_dir: Option<PathBuf>,
28}
29
30impl WebCodegenConfig {
31    /// Create a new configuration builder.
32    pub fn builder() -> WebCodegenConfigBuilder {
33        WebCodegenConfigBuilder::default()
34    }
35
36    /// Validate the configuration.
37    pub fn validate(&self) -> crate::Result<()> {
38        use crate::error::CodegenError;
39
40        // At least one proto file is required.
41        if self.proto_files.is_empty() {
42            return Err(CodegenError::config("at least one proto file is required"));
43        }
44
45        // Every proto file must exist.
46        for proto in &self.proto_files {
47            if !proto.exists() {
48                return Err(CodegenError::FileNotFound(proto.clone()));
49            }
50        }
51
52        // Every include directory must exist.
53        for include in &self.includes {
54            if !include.exists() {
55                return Err(CodegenError::FileNotFound(include.clone()));
56            }
57        }
58
59        Ok(())
60    }
61}
62
63/// Configuration builder.
64#[derive(Default)]
65pub struct WebCodegenConfigBuilder {
66    proto_files: Vec<PathBuf>,
67    rust_output_dir: Option<PathBuf>,
68    ts_output_dir: Option<PathBuf>,
69    generate_react_hooks: bool,
70    includes: Vec<PathBuf>,
71    format_code: bool,
72    custom_templates_dir: Option<PathBuf>,
73}
74
75impl WebCodegenConfigBuilder {
76    /// Add one proto file.
77    pub fn proto_file<P: Into<PathBuf>>(mut self, path: P) -> Self {
78        self.proto_files.push(path.into());
79        self
80    }
81
82    /// Add multiple proto files.
83    pub fn proto_files<I, P>(mut self, paths: I) -> Self
84    where
85        I: IntoIterator<Item = P>,
86        P: Into<PathBuf>,
87    {
88        self.proto_files.extend(paths.into_iter().map(Into::into));
89        self
90    }
91
92    /// Set the Rust output directory.
93    pub fn rust_output<P: Into<PathBuf>>(mut self, dir: P) -> Self {
94        self.rust_output_dir = Some(dir.into());
95        self
96    }
97
98    /// Set the TypeScript output directory.
99    pub fn ts_output<P: Into<PathBuf>>(mut self, dir: P) -> Self {
100        self.ts_output_dir = Some(dir.into());
101        self
102    }
103
104    /// Enable React Hooks generation.
105    pub fn with_react_hooks(mut self, enabled: bool) -> Self {
106        self.generate_react_hooks = enabled;
107        self
108    }
109
110    /// Add one include path.
111    pub fn include<P: Into<PathBuf>>(mut self, path: P) -> Self {
112        self.includes.push(path.into());
113        self
114    }
115
116    /// Add multiple include paths.
117    pub fn includes<I, P>(mut self, paths: I) -> Self
118    where
119        I: IntoIterator<Item = P>,
120        P: Into<PathBuf>,
121    {
122        self.includes.extend(paths.into_iter().map(Into::into));
123        self
124    }
125
126    /// Enable code formatting.
127    pub fn with_formatting(mut self, enabled: bool) -> Self {
128        self.format_code = enabled;
129        self
130    }
131
132    /// Set a custom template directory.
133    pub fn custom_templates<P: Into<PathBuf>>(mut self, dir: P) -> Self {
134        self.custom_templates_dir = Some(dir.into());
135        self
136    }
137
138    /// Build the configuration.
139    pub fn build(self) -> crate::Result<WebCodegenConfig> {
140        use crate::error::CodegenError;
141
142        let rust_output_dir = self
143            .rust_output_dir
144            .ok_or_else(|| CodegenError::config("missing rust_output_dir configuration"))?;
145
146        let ts_output_dir = self
147            .ts_output_dir
148            .ok_or_else(|| CodegenError::config("missing ts_output_dir configuration"))?;
149
150        let config = WebCodegenConfig {
151            proto_files: self.proto_files,
152            rust_output_dir,
153            ts_output_dir,
154            generate_react_hooks: self.generate_react_hooks,
155            includes: self.includes,
156            format_code: self.format_code,
157            custom_templates_dir: self.custom_templates_dir,
158        };
159
160        config.validate()?;
161
162        Ok(config)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_builder() {
172        let config = WebCodegenConfig::builder()
173            .proto_file("test.proto")
174            .rust_output("src/generated")
175            .ts_output("src/types")
176            .with_react_hooks(true)
177            .include("proto")
178            .with_formatting(true);
179
180        // `build()` is not used here because the files do not exist in the test.
181        assert!(config.generate_react_hooks);
182        assert!(config.format_code);
183    }
184}