1use crate::error::SynthError;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct GenerationContext {
13 pub seed: u64,
15 pub fiscal_year: u32,
17 pub company_code: String,
19 pub industry: String,
21 #[serde(default)]
23 pub extra: HashMap<String, String>,
24}
25
26impl GenerationContext {
27 pub fn new(seed: u64, fiscal_year: u32, company_code: impl Into<String>) -> Self {
29 Self {
30 seed,
31 fiscal_year,
32 company_code: company_code.into(),
33 industry: String::new(),
34 extra: HashMap::new(),
35 }
36 }
37
38 pub fn with_industry(mut self, industry: impl Into<String>) -> Self {
40 self.industry = industry.into();
41 self
42 }
43
44 pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
46 self.extra.insert(key.into(), value.into());
47 self
48 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct GeneratedRecord {
54 pub record_type: String,
56 pub fields: HashMap<String, serde_json::Value>,
58}
59
60impl GeneratedRecord {
61 pub fn new(record_type: impl Into<String>) -> Self {
63 Self {
64 record_type: record_type.into(),
65 fields: HashMap::new(),
66 }
67 }
68
69 pub fn with_field(
71 mut self,
72 key: impl Into<String>,
73 value: impl Into<serde_json::Value>,
74 ) -> Self {
75 self.fields.insert(key.into(), value.into());
76 self
77 }
78
79 pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
81 self.fields.get(key)
82 }
83
84 pub fn get_str(&self, key: &str) -> Option<&str> {
86 self.fields.get(key).and_then(|v| v.as_str())
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct SinkSummary {
93 pub records_written: usize,
95 pub bytes_written: Option<u64>,
97 pub file_paths: Vec<String>,
99 #[serde(default)]
101 pub metadata: HashMap<String, String>,
102}
103
104impl SinkSummary {
105 pub fn new(records_written: usize) -> Self {
107 Self {
108 records_written,
109 bytes_written: None,
110 file_paths: Vec::new(),
111 metadata: HashMap::new(),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct PluginInfo {
119 pub name: String,
121 pub version: String,
123 pub description: String,
125 pub plugin_type: PluginType,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
131#[serde(rename_all = "snake_case")]
132pub enum PluginType {
133 Generator,
135 Sink,
137 Transform,
139}
140
141pub trait GeneratorPlugin: Send + Sync {
168 fn name(&self) -> &str;
170 fn version(&self) -> &str;
172 fn description(&self) -> &str;
174 fn config_schema(&self) -> Option<serde_json::Value>;
176 fn generate(
178 &self,
179 config: &serde_json::Value,
180 context: &GenerationContext,
181 ) -> Result<Vec<GeneratedRecord>, SynthError>;
182}
183
184pub trait SinkPlugin: Send + Sync {
194 fn name(&self) -> &str;
196 fn version(&self) -> &str {
198 "0.1.0"
199 }
200 fn description(&self) -> &str {
202 ""
203 }
204 fn initialize(&mut self, config: &serde_json::Value) -> Result<(), SynthError>;
206 fn write_records(&mut self, records: &[GeneratedRecord]) -> Result<usize, SynthError>;
208 fn finalize(&mut self) -> Result<SinkSummary, SynthError>;
210}
211
212pub trait TransformPlugin: Send + Sync {
216 fn name(&self) -> &str;
218 fn version(&self) -> &str {
220 "0.1.0"
221 }
222 fn description(&self) -> &str {
224 ""
225 }
226 fn transform(&self, records: Vec<GeneratedRecord>) -> Result<Vec<GeneratedRecord>, SynthError>;
228}
229
230#[cfg(test)]
231#[allow(clippy::unwrap_used)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_generation_context_creation() {
237 let ctx = GenerationContext::new(42, 2024, "C001")
238 .with_industry("manufacturing")
239 .with_extra("region", "EU");
240 assert_eq!(ctx.seed, 42);
241 assert_eq!(ctx.fiscal_year, 2024);
242 assert_eq!(ctx.company_code, "C001");
243 assert_eq!(ctx.industry, "manufacturing");
244 assert_eq!(ctx.extra.get("region").map(|s| s.as_str()), Some("EU"));
245 }
246
247 #[test]
248 fn test_generated_record_creation() {
249 let record = GeneratedRecord::new("test_record")
250 .with_field("name", serde_json::Value::String("Test".to_string()))
251 .with_field("amount", serde_json::json!(100.0));
252 assert_eq!(record.record_type, "test_record");
253 assert_eq!(record.get_str("name"), Some("Test"));
254 assert!(record.get("amount").is_some());
255 }
256
257 #[test]
258 fn test_generated_record_serialization() {
259 let record = GeneratedRecord::new("vendor")
260 .with_field("id", serde_json::json!("V001"))
261 .with_field("name", serde_json::json!("Acme Corp"));
262 let json = serde_json::to_string(&record).expect("should serialize");
263 let deser: GeneratedRecord = serde_json::from_str(&json).expect("should deserialize");
264 assert_eq!(deser.record_type, "vendor");
265 assert_eq!(deser.get_str("id"), Some("V001"));
266 }
267
268 #[test]
269 fn test_sink_summary_creation() {
270 let summary = SinkSummary::new(100);
271 assert_eq!(summary.records_written, 100);
272 assert!(summary.bytes_written.is_none());
273 assert!(summary.file_paths.is_empty());
274 }
275
276 #[test]
277 fn test_plugin_info_serialization() {
278 let info = PluginInfo {
279 name: "test_plugin".to_string(),
280 version: "1.0.0".to_string(),
281 description: "A test plugin".to_string(),
282 plugin_type: PluginType::Generator,
283 };
284 let json = serde_json::to_string(&info).expect("should serialize");
285 assert!(json.contains("test_plugin"));
286 assert!(json.contains("generator"));
287 }
288}