1use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5use std::collections::HashMap;
6
7pub type Time = u64;
11
12pub type Attr = String;
14
15pub type ModelName = String;
17
18pub type SimId = String;
20
21pub type EntityId = String;
23
24pub type FullId = String;
26
27pub type InputData = HashMap<EntityId, HashMap<Attr, Map<FullId, Value>>>;
29
30pub type OutputRequest = HashMap<EntityId, Vec<Attr>>;
33
34pub const API_VERSION: &str = "3.0";
36
37#[derive(Debug, Serialize, Deserialize)]
39pub struct OutputData {
40 #[serde(flatten)]
41 pub requests: HashMap<EntityId, HashMap<Attr, Value>>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub time: Option<Time>,
44}
45
46#[derive(Debug, Serialize, PartialEq, Clone, Default)]
62pub struct ModelDescription {
63 pub public: bool,
65 pub params: &'static [&'static str],
67 pub attrs: &'static [&'static str],
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub any_inputs: Option<bool>,
72 #[serde(skip_serializing_if = "Option::is_none")]
76 pub trigger: Option<&'static [&'static str]>,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub persistent: Option<&'static [&'static str]>,
80}
81
82impl ModelDescription {
83 #[must_use]
85 pub fn new(
86 public: bool,
87 params: &'static [&'static str],
88 attrs: &'static [&'static str],
89 ) -> Self {
90 Self {
91 public,
92 params,
93 attrs,
94 any_inputs: None,
95 trigger: None,
96 persistent: None,
97 }
98 }
99}
100
101#[derive(Debug, Serialize, PartialEq, Clone)]
103pub struct Meta {
104 api_version: &'static str,
106 #[serde(rename = "type")]
108 pub simulator_type: SimulatorType,
109 pub models: HashMap<ModelName, ModelDescription>,
111 #[serde(skip_serializing_if = "Option::is_none")]
117 pub extra_methods: Option<Vec<String>>,
118}
119
120impl Meta {
121 #[must_use]
122 pub fn new(
123 simulator_type: SimulatorType,
124 models: HashMap<ModelName, ModelDescription>,
125 extra_methods: Option<Vec<String>>,
126 ) -> Self {
127 Self {
128 api_version: API_VERSION,
129 simulator_type,
130 models,
131 extra_methods,
132 }
133 }
134
135 #[must_use]
136 pub fn version(&self) -> &str {
137 self.api_version
138 }
139}
140
141impl Default for Meta {
142 #[must_use]
143 fn default() -> Self {
144 Self {
145 api_version: API_VERSION,
146 simulator_type: SimulatorType::default(),
147 models: HashMap::new(),
148 extra_methods: None,
149 }
150 }
151}
152
153#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]
158#[serde(rename_all = "kebab-case")]
159pub enum SimulatorType {
160 TimeBased,
161 EventBased,
162 #[default]
163 Hybrid,
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
168pub struct CreateResult {
169 pub eid: EntityId,
171 #[serde(rename = "type")]
173 pub model_type: ModelName,
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub rel: Option<Vec<EntityId>>,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub children: Option<Vec<CreateResult>>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub extra_info: Option<HashMap<String, String>>,
183}
184
185impl CreateResult {
186 #[must_use]
187 pub fn new(eid: EntityId, model_type: ModelName) -> Self {
188 Self {
189 eid,
190 model_type,
191 rel: None,
192 children: None,
193 extra_info: None,
194 }
195 }
196}
197
198#[cfg(test)]
212#[allow(clippy::unwrap_used)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_output_data() {
218 let json_data = r#"{
220 "eid_1": {"attr_1": "val_1"},
221 "time": 64
222 }
223 "#
224 .replace(['\n', ' '], "");
225
226 let data: OutputData = serde_json::from_str(&json_data).unwrap();
228 assert_ne!(data.requests, HashMap::new());
229 assert_eq!(data.time, Some(64));
230
231 let serialized_json = serde_json::to_string(&data).unwrap();
233 assert!(!serialized_json.contains("requests"));
234 assert!(serialized_json.contains("time"));
235
236 assert_eq!(serialized_json, json_data);
237 }
238
239 #[test]
240 fn test_model_description_without_optionals() {
241 let mut model = ModelDescription::default();
242
243 assert!(!model.public);
244 assert_eq!(model.params.len(), 0);
245 assert_eq!(model.attrs.len(), 0);
246 assert_eq!(model.any_inputs, None);
247 assert_eq!(model.trigger, None);
248 assert_eq!(model.persistent, None);
249
250 let model_json = serde_json::to_string(&model).unwrap();
251 assert_eq!(r#"{"public":false,"params":[],"attrs":[]}"#, model_json);
252
253 model.public = true;
254 model.params = &["init_reading"];
255 model.attrs = &["trades", "total"];
256
257 assert!(model.public);
258 assert_eq!(model.params.len(), 1);
259 assert_eq!(model.attrs.len(), 2);
260
261 let model_json = serde_json::to_string(&model).unwrap();
262 assert_eq!(
263 r#"{"public":true,"params":["init_reading"],"attrs":["trades","total"]}"#,
264 model_json
265 );
266 }
267
268 #[test]
269 fn test_model_description_with_optionals() {
270 let mut model = ModelDescription::new(true, &["init_reading"], &["p_mw_pv", "p_mw_load"]);
271 model.any_inputs = Some(true);
272 model.trigger = Some(&["trigger1"]);
273 model.persistent = Some(&["trades"]);
274
275 let model_json = serde_json::to_string(&model).unwrap();
276 assert_eq!(
277 r#"{"public":true,"params":["init_reading"],"attrs":["p_mw_pv","p_mw_load"],"any_inputs":true,"trigger":["trigger1"],"persistent":["trades"]}"#,
278 model_json
279 );
280
281 model.trigger = Some(&["trigger1"]);
282 model.any_inputs = None;
283 model.persistent = None;
284
285 let model_json = serde_json::to_string(&model).unwrap();
286 assert_eq!(
287 r#"{"public":true,"params":["init_reading"],"attrs":["p_mw_pv","p_mw_load"],"trigger":["trigger1"]}"#,
288 model_json
289 );
290 }
291
292 #[test]
293 fn test_meta_empty() {
294 let meta = Meta::new(SimulatorType::default(), HashMap::new(), None);
295 assert_eq!(
296 meta.api_version, API_VERSION,
297 "API version should match the global variable."
298 );
299 assert_eq!(
300 meta.version(),
301 API_VERSION,
302 "version should return the API version."
303 );
304 assert_eq!(
305 meta.simulator_type,
306 SimulatorType::Hybrid,
307 "Default type should be Hybrid"
308 );
309
310 let empty_meta_json = serde_json::to_string(&meta).unwrap();
311 assert_eq!(
312 r#"{"api_version":"3.0","type":"hybrid","models":{}}"#, empty_meta_json,
313 "Empty meta should not have extra_methods and empty models."
314 );
315 assert!(meta.models.is_empty());
316 }
317
318 #[test]
319 fn test_meta_with_models() {
320 let model1 = ModelDescription::new(true, &["init_reading"], &["trades", "total"]);
321 let meta = Meta::new(
322 SimulatorType::default(),
323 HashMap::from([("MarktplatzModel".to_string(), model1)]),
324 None,
325 );
326 assert_eq!(meta.models.len(), 1, "Should have one model");
327
328 assert!(meta.extra_methods.is_none());
329 let meta_json = serde_json::to_string(&meta).unwrap();
330 assert_eq!(
331 r#"{"api_version":"3.0","type":"hybrid","models":{"MarktplatzModel":{"public":true,"params":["init_reading"],"attrs":["trades","total"]}}}"#,
332 meta_json,
333 "Meta should have one model and no extra methods."
334 );
335 }
336
337 #[test]
338 fn test_meta_optionals() {
339 let meta = Meta::new(
340 SimulatorType::default(),
341 HashMap::new(),
342 Some(vec!["foo".to_string(), "bar".to_string()]),
343 );
344
345 assert_eq!(
346 meta.extra_methods.as_ref().unwrap().len(),
347 2,
348 "Should have 2 extra methods."
349 );
350
351 let meta_json = serde_json::to_string(&meta).unwrap();
352 assert_eq!(
353 r#"{"api_version":"3.0","type":"hybrid","models":{},"extra_methods":["foo","bar"]}"#,
354 meta_json,
355 "JSON String should contain 'foo' and 'bar' as extra methods."
356 );
357 }
358
359 #[test]
360 fn test_create_result_new() {
361 let create_result = CreateResult::new(String::from("eid_1"), String::from("model_name"));
362 assert_eq!(create_result.eid, "eid_1");
363 assert_eq!(create_result.model_type, "model_name");
364 assert!(create_result.rel.is_none());
365 assert!(create_result.children.is_none());
366 assert!(create_result.extra_info.is_none());
367
368 let create_result_json = serde_json::to_string(&create_result).unwrap();
369 assert_eq!(
370 r#"{"eid":"eid_1","type":"model_name"}"#, create_result_json,
371 "New CreateResult should not contain any optional fields"
372 );
373 }
374
375 #[test]
376 fn test_create_results_filled() {
377 let mut create_result = CreateResult::new("eid_1".to_string(), "model_name".to_string());
378
379 create_result.rel = Some(vec!["eid_2".to_string()]);
380 create_result.children = Some(vec![CreateResult::new(
381 "child_1".to_string(),
382 "child".to_string(),
383 )]);
384
385 assert_eq!(create_result.eid, "eid_1");
386 assert_eq!(create_result.model_type, "model_name");
387 assert_eq!(create_result.rel, Some(vec!["eid_2".to_string()]));
388 assert!(create_result.children.is_some());
389 if let Some(children) = &create_result.children {
390 assert_eq!(children.len(), 1);
391 assert_eq!(children[0].eid, "child_1");
392 }
393
394 let create_result_json = serde_json::to_string(&create_result).unwrap();
395 assert_eq!(
396 r#"{"eid":"eid_1","type":"model_name","rel":["eid_2"],"children":[{"eid":"child_1","type":"child"}]}"#,
397 create_result_json,
398 "Filled create result should contain optional fields without extra_info"
399 );
400 }
401}