1use crate::parsing::ast::Span;
2use crate::planning::ExecutionPlan;
3use crate::semantic::{
4 BooleanValue, FactPath, LemmaFact, LemmaType, LiteralValue, Value as LemmaValue,
5};
6use crate::LemmaError;
7use crate::Source;
8use rust_decimal::Decimal;
9use serde::{Deserialize, Deserializer, Serializer};
10use serde_json::Value;
11use std::collections::{HashMap, HashSet};
12use std::sync::Arc;
13
14pub fn from_json(
33 json: &[u8],
34 plan: &ExecutionPlan,
35) -> Result<HashMap<String, LiteralValue>, LemmaError> {
36 let map: HashMap<String, Value> = serde_json::from_slice(json).map_err(|e| {
37 LemmaError::engine(
38 format!("JSON parse error: {}", e),
39 Span {
40 start: 0,
41 end: 0,
42 line: 1,
43 col: 0,
44 },
45 "<unknown>",
46 Arc::from(""),
47 "<unknown>",
48 1,
49 None::<String>,
50 )
51 })?;
52
53 let mut result = HashMap::new();
54
55 for (fact_name, json_value) in map {
56 if json_value.is_null() {
57 continue;
58 }
59
60 let fact_path = plan.get_fact_path_by_str(&fact_name).ok_or_else(|| {
61 let available: Vec<String> = plan.fact_schema.keys().map(|p| p.to_string()).collect();
62 LemmaError::engine(
63 format!(
64 "Fact '{}' not found in document. Available facts: {}",
65 fact_name,
66 available.join(", ")
67 ),
68 Span {
69 start: 0,
70 end: 0,
71 line: 1,
72 col: 0,
73 },
74 "<unknown>",
75 Arc::from(""),
76 "<unknown>",
77 1,
78 None::<String>,
79 )
80 })?;
81
82 let expected_type = plan.fact_schema.get(fact_path).cloned().unwrap_or_else(|| {
83 unreachable!(
84 "BUG: get_fact_path_by_str returned a fact path that is not in fact_schema (fact={})",
85 fact_name
86 )
87 });
88 let literal_value = convert_json_value(&fact_name, &json_value, &expected_type)?;
89
90 result.insert(fact_name, literal_value);
91 }
92
93 Ok(result)
94}
95
96fn convert_json_value(
99 fact_name: &str,
100 json_value: &Value,
101 expected_type: &crate::semantic::LemmaType,
102) -> Result<LiteralValue, LemmaError> {
103 match &expected_type.specifications {
104 crate::semantic::TypeSpecification::Text { .. } => {
105 convert_to_text(fact_name, json_value, expected_type)
106 }
107 crate::semantic::TypeSpecification::Scale { .. } => {
108 convert_to_scale(fact_name, json_value, expected_type)
109 }
110 crate::semantic::TypeSpecification::Number { .. } => {
111 convert_to_number(fact_name, json_value, expected_type)
112 }
113 crate::semantic::TypeSpecification::Boolean { .. } => {
114 convert_to_boolean(fact_name, json_value, expected_type)
115 }
116 crate::semantic::TypeSpecification::Ratio { .. } => {
117 convert_to_ratio(fact_name, json_value, expected_type)
118 }
119 crate::semantic::TypeSpecification::Date { .. } => {
120 convert_to_date(fact_name, json_value, expected_type)
121 }
122 crate::semantic::TypeSpecification::Duration { .. } => {
123 convert_to_duration(fact_name, json_value, expected_type)
124 }
125 crate::semantic::TypeSpecification::Time { .. } => {
126 convert_to_time(fact_name, json_value, expected_type)
127 }
128 crate::semantic::TypeSpecification::Veto { .. } => Err(LemmaError::engine(
129 "Veto type is not a user-declarable type and cannot be converted from JSON",
130 Span {
131 start: 0,
132 end: 0,
133 line: 1,
134 col: 0,
135 },
136 "<unknown>",
137 Arc::from(""),
138 "<unknown>",
139 1,
140 None::<String>,
141 )),
142 }
143}
144
145fn convert_to_text(
146 fact_name: &str,
147 json_value: &Value,
148 expected_type: &crate::semantic::LemmaType,
149) -> Result<LiteralValue, LemmaError> {
150 let text = match json_value {
151 Value::String(s) => s.clone(),
152 Value::Number(n) => n.to_string(),
153 Value::Bool(b) => b.to_string(),
154 Value::Array(_) | Value::Object(_) => {
155 serde_json::to_string(json_value).unwrap_or_else(|_| json_value.to_string())
156 }
157 Value::Null => return Err(type_error(fact_name, "text", "null")),
158 };
159 Ok(LiteralValue::text_with_type(text, expected_type.clone()))
160}
161
162fn convert_to_number(
163 fact_name: &str,
164 json_value: &Value,
165 expected_type: &crate::semantic::LemmaType,
166) -> Result<LiteralValue, LemmaError> {
167 match json_value {
168 Value::Number(n) => {
169 let decimal = json_number_to_decimal(fact_name, n)?;
170 Ok(LiteralValue::number_with_type(
171 decimal,
172 expected_type.clone(),
173 ))
174 }
175 Value::String(s) => {
176 let clean = s.trim().replace(['_', ','], "");
177 let decimal = Decimal::from_str_exact(&clean).map_err(|_| {
178 LemmaError::engine(
179 format!(
180 "Invalid number string for fact '{}': '{}' is not a valid decimal",
181 fact_name, s
182 ),
183 Span {
184 start: 0,
185 end: 0,
186 line: 1,
187 col: 0,
188 },
189 "<unknown>",
190 Arc::from(s.as_str()),
191 "<unknown>",
192 1,
193 None::<String>,
194 )
195 })?;
196 Ok(LiteralValue::number_with_type(
197 decimal,
198 expected_type.clone(),
199 ))
200 }
201 Value::Null => Err(type_error(fact_name, "number", "null")),
202 Value::Bool(_) => Err(type_error(fact_name, "number", "boolean")),
203 Value::Array(_) => Err(type_error(fact_name, "number", "array")),
204 Value::Object(_) => Err(type_error(fact_name, "number", "object")),
205 }
206}
207
208fn convert_to_scale(
209 fact_name: &str,
210 json_value: &Value,
211 expected_type: &crate::semantic::LemmaType,
212) -> Result<LiteralValue, LemmaError> {
213 match json_value {
214 Value::Number(n) => {
215 let decimal = json_number_to_decimal(fact_name, n)?;
217 Ok(LiteralValue::scale_with_type(
218 decimal,
219 None,
220 expected_type.clone(),
221 ))
222 }
223 Value::String(s) => {
224 let trimmed = s.trim();
225
226 let mut number_end = 0;
232 let chars: Vec<char> = trimmed.chars().collect();
233 let mut has_decimal = false;
234
235 let start = if chars.first().is_some_and(|c| *c == '+' || *c == '-') {
237 1
238 } else {
239 0
240 };
241
242 for (i, &ch) in chars.iter().enumerate().skip(start) {
243 match ch {
244 '0'..='9' => number_end = i + 1,
245 '.' if !has_decimal => {
246 has_decimal = true;
247 number_end = i + 1;
248 }
249 '_' | ',' => {
250 number_end = i + 1;
252 }
253 _ => {
254 break;
256 }
257 }
258 }
259
260 let number_part = trimmed[..number_end].trim();
262 let unit_part = trimmed[number_end..].trim();
263
264 let clean_number = number_part.replace(['_', ','], "");
266 let decimal = Decimal::from_str_exact(&clean_number).map_err(|_| {
267 LemmaError::engine(
268 format!(
269 "Invalid scale string for fact '{}': '{}' is not a valid number",
270 fact_name, s
271 ),
272 Span {
273 start: 0,
274 end: 0,
275 line: 1,
276 col: 0,
277 },
278 "<unknown>",
279 Arc::from(s.as_str()),
280 "<unknown>",
281 1,
282 None::<String>,
283 )
284 })?;
285
286 let allowed_units = match &expected_type.specifications {
288 crate::semantic::TypeSpecification::Scale { units, .. } => units,
289 _ => {
290 return Err(LemmaError::engine(
291 format!(
292 "Internal error: expected a scale type for fact '{}' but got {}",
293 fact_name,
294 expected_type.name()
295 ),
296 Span {
297 start: 0,
298 end: 0,
299 line: 1,
300 col: 0,
301 },
302 "<unknown>",
303 Arc::from(""),
304 "<unknown>",
305 1,
306 None::<String>,
307 ));
308 }
309 };
310
311 let unit = if unit_part.is_empty() {
312 if !allowed_units.is_empty() {
313 let valid: Vec<String> = allowed_units.iter().map(|u| u.name.clone()).collect();
314 return Err(LemmaError::engine(
315 format!(
316 "Missing unit for fact '{}'. Valid units: {}",
317 fact_name,
318 valid.join(", ")
319 ),
320 Span {
321 start: 0,
322 end: 0,
323 line: 1,
324 col: 0,
325 },
326 "<unknown>",
327 Arc::from(s.as_str()),
328 "<unknown>",
329 1,
330 None::<String>,
331 ));
332 }
333 None
334 } else {
335 let matched = allowed_units
336 .iter()
337 .find(|u| u.name.eq_ignore_ascii_case(unit_part));
338 match matched {
339 Some(unit_def) => Some(unit_def.name.clone()),
340 None => {
341 let valid: Vec<String> =
342 allowed_units.iter().map(|u| u.name.clone()).collect();
343 let valid_str = if valid.is_empty() {
344 "none".to_string()
345 } else {
346 valid.join(", ")
347 };
348 return Err(LemmaError::engine(
349 format!(
350 "Invalid unit '{}' for fact '{}'. Valid units: {}",
351 unit_part, fact_name, valid_str
352 ),
353 Span {
354 start: 0,
355 end: 0,
356 line: 1,
357 col: 0,
358 },
359 "<unknown>",
360 Arc::from(s.as_str()),
361 "<unknown>",
362 1,
363 None::<String>,
364 ));
365 }
366 }
367 };
368
369 Ok(LiteralValue::scale_with_type(
370 decimal,
371 unit,
372 expected_type.clone(),
373 ))
374 }
375 Value::Null => Err(type_error(fact_name, "scale", "null")),
376 Value::Bool(_) => Err(type_error(fact_name, "scale", "boolean")),
377 Value::Array(_) => Err(type_error(fact_name, "scale", "array")),
378 Value::Object(_) => Err(type_error(fact_name, "scale", "object")),
379 }
380}
381
382fn convert_to_boolean(
383 fact_name: &str,
384 json_value: &Value,
385 expected_type: &crate::semantic::LemmaType,
386) -> Result<LiteralValue, LemmaError> {
387 match json_value {
388 Value::Bool(b) => {
389 let boolean_value = if *b {
390 BooleanValue::True
391 } else {
392 BooleanValue::False
393 };
394 Ok(LiteralValue::boolean_with_type(
395 boolean_value,
396 expected_type.clone(),
397 ))
398 }
399 Value::String(s) => {
400 let boolean_value: BooleanValue = s.parse().map_err(|_| {
401 LemmaError::engine(
402 format!("Invalid boolean string for fact '{}': '{}'. Expected one of: true, false, yes, no, accept, reject", fact_name, s),
403 Span { start: 0, end: 0, line: 1, col: 0 },
404 "<unknown>",
405 Arc::from(s.as_str()),
406 "<unknown>",
407 1,
408 None::<String>,
409 )
410 })?;
411 Ok(LiteralValue::boolean_with_type(
412 boolean_value,
413 expected_type.clone(),
414 ))
415 }
416 Value::Null => Err(type_error(fact_name, "boolean", "null")),
417 Value::Number(_) => Err(type_error(fact_name, "boolean", "number")),
418 Value::Array(_) => Err(type_error(fact_name, "boolean", "array")),
419 Value::Object(_) => Err(type_error(fact_name, "boolean", "object")),
420 }
421}
422
423fn convert_to_ratio(
424 fact_name: &str,
425 json_value: &Value,
426 expected_type: &crate::semantic::LemmaType,
427) -> Result<LiteralValue, LemmaError> {
428 match json_value {
429 Value::Number(n) => {
430 let decimal = json_number_to_decimal(fact_name, n)?;
432 Ok(LiteralValue::ratio_with_type(
433 decimal,
434 None,
435 expected_type.clone(),
436 ))
437 }
438 Value::String(s) => {
439 let trimmed = s.trim();
440 let trimmed_lower = trimmed.to_lowercase();
441
442 let (number_part, unit) = if let Some(stripped) = trimmed.strip_suffix("%%") {
444 (stripped.trim(), Some("permille".to_string()))
446 } else if let Some(stripped) = trimmed.strip_suffix('%') {
447 (stripped.trim(), Some("percent".to_string()))
449 } else if trimmed_lower.ends_with("permille") {
450 (
452 trimmed[..trimmed.len() - 8].trim(),
453 Some("permille".to_string()),
454 )
455 } else if trimmed_lower.ends_with("percent") {
456 (
458 trimmed[..trimmed.len() - 7].trim(),
459 Some("percent".to_string()),
460 )
461 } else {
462 (trimmed, None)
464 };
465
466 let clean_number = number_part.replace(['_', ','], "");
467 let decimal = Decimal::from_str_exact(&clean_number).map_err(|_| {
468 LemmaError::engine(
469 format!(
470 "Invalid ratio string for fact '{}': '{}' is not a valid number",
471 fact_name, s
472 ),
473 Span {
474 start: 0,
475 end: 0,
476 line: 1,
477 col: 0,
478 },
479 "<unknown>",
480 Arc::from(s.as_str()),
481 "<unknown>",
482 1,
483 None::<String>,
484 )
485 })?;
486
487 let ratio_value = if let Some(ref unit_name) = unit {
489 if unit_name == "percent" {
490 decimal / Decimal::from(100)
491 } else if unit_name == "permille" {
492 decimal / Decimal::from(1000)
493 } else {
494 decimal
495 }
496 } else {
497 decimal
498 };
499
500 Ok(LiteralValue::ratio_with_type(
501 ratio_value,
502 unit,
503 expected_type.clone(),
504 ))
505 }
506 Value::Null => Err(type_error(fact_name, "ratio", "null")),
507 Value::Bool(_) => Err(type_error(fact_name, "ratio", "boolean")),
508 Value::Array(_) => Err(type_error(fact_name, "ratio", "array")),
509 Value::Object(_) => Err(type_error(fact_name, "ratio", "object")),
510 }
511}
512
513fn convert_to_date(
514 fact_name: &str,
515 json_value: &Value,
516 expected_type: &crate::semantic::LemmaType,
517) -> Result<LiteralValue, LemmaError> {
518 match json_value {
519 Value::String(s) => expected_type.parse_value(s).map_err(|e| {
520 LemmaError::engine(
521 format!("Invalid date for fact '{}': {}", fact_name, e),
522 Span {
523 start: 0,
524 end: 0,
525 line: 1,
526 col: 0,
527 },
528 "<unknown>",
529 Arc::from(s.as_str()),
530 "<unknown>",
531 1,
532 None::<String>,
533 )
534 }),
535 Value::Null => Err(type_error(fact_name, "date", "null")),
536 Value::Bool(_) => Err(type_error(fact_name, "date", "boolean")),
537 Value::Number(_) => Err(type_error(fact_name, "date", "number")),
538 Value::Array(_) => Err(type_error(fact_name, "date", "array")),
539 Value::Object(_) => Err(type_error(fact_name, "date", "object")),
540 }
541}
542
543fn convert_to_duration(
544 fact_name: &str,
545 json_value: &Value,
546 expected_type: &crate::semantic::LemmaType,
547) -> Result<LiteralValue, LemmaError> {
548 match json_value {
549 Value::String(s) => expected_type.parse_value(s).map_err(|e| {
550 LemmaError::engine(
551 format!("Invalid duration value for fact '{}': {}", fact_name, e),
552 Span { start: 0, end: 0, line: 1, col: 0 },
553 "<unknown>",
554 Arc::from(s.as_str()),
555 "<unknown>",
556 1,
557 None::<String>,
558 )
559 }),
560 Value::Null => Err(type_error(fact_name, "duration", "null")),
561 Value::Bool(_) => Err(type_error(fact_name, "duration", "boolean")),
562 Value::Number(_) => Err(LemmaError::engine(
563 format!("Invalid JSON type for fact '{}': expected duration (as string like '5 days'), got number. Duration values must include the unit name.", fact_name),
564 Span { start: 0, end: 0, line: 1, col: 0 },
565 "<unknown>",
566 Arc::from(""),
567 "<unknown>",
568 1,
569 None::<String>,
570 )),
571 Value::Array(_) => Err(type_error(fact_name, "duration", "array")),
572 Value::Object(_) => Err(type_error(fact_name, "duration", "object")),
573 }
574}
575
576fn convert_to_time(
577 fact_name: &str,
578 json_value: &Value,
579 expected_type: &crate::semantic::LemmaType,
580) -> Result<LiteralValue, LemmaError> {
581 match json_value {
582 Value::String(s) => expected_type.parse_value(s).map_err(|e| {
583 LemmaError::engine(
584 format!("Invalid time value for fact '{}': {}", fact_name, e),
585 Span {
586 start: 0,
587 end: 0,
588 line: 1,
589 col: 0,
590 },
591 "<unknown>",
592 Arc::from(s.as_str()),
593 "<unknown>",
594 1,
595 None::<String>,
596 )
597 }),
598 Value::Null => Err(type_error(fact_name, "time", "null")),
599 Value::Bool(_) => Err(type_error(fact_name, "time", "boolean")),
600 Value::Number(_) => Err(type_error(fact_name, "time", "number")),
601 Value::Array(_) => Err(type_error(fact_name, "time", "array")),
602 Value::Object(_) => Err(type_error(fact_name, "time", "object")),
603 }
604}
605
606fn json_number_to_decimal(fact_name: &str, n: &serde_json::Number) -> Result<Decimal, LemmaError> {
607 if let Some(i) = n.as_i64() {
608 Ok(Decimal::from(i))
609 } else if let Some(u) = n.as_u64() {
610 Ok(Decimal::from(u))
611 } else if let Some(f) = n.as_f64() {
612 Decimal::try_from(f).map_err(|_| {
613 LemmaError::engine(
614 format!(
615 "Invalid number for fact '{}': {} cannot be represented as a decimal",
616 fact_name, n
617 ),
618 Span {
619 start: 0,
620 end: 0,
621 line: 1,
622 col: 0,
623 },
624 "<unknown>",
625 Arc::from(""),
626 "<unknown>",
627 1,
628 None::<String>,
629 )
630 })
631 } else {
632 Err(LemmaError::engine(
633 format!(
634 "Invalid number for fact '{}': {} is not a valid number",
635 fact_name, n
636 ),
637 Span {
638 start: 0,
639 end: 0,
640 line: 1,
641 col: 0,
642 },
643 "<unknown>",
644 Arc::from(""),
645 "<unknown>",
646 1,
647 None::<String>,
648 ))
649 }
650}
651
652fn type_error(fact_name: &str, expected: &str, got: &str) -> LemmaError {
653 LemmaError::engine(
654 format!(
655 "Invalid JSON type for fact '{}': expected {}, got {}",
656 fact_name, expected, got
657 ),
658 Span {
659 start: 0,
660 end: 0,
661 line: 1,
662 col: 0,
663 },
664 "<unknown>",
665 Arc::from(""),
666 "<unknown>",
667 1,
668 None::<String>,
669 )
670}
671
672pub fn serialize_literal_value<S>(value: &LiteralValue, serializer: S) -> Result<S::Ok, S::Error>
676where
677 S: serde::ser::Serializer,
678{
679 use serde::ser::SerializeMap;
680 use serde_json::Number;
681 use std::str::FromStr;
682
683 let mut map = serializer.serialize_map(Some(2))?;
684
685 match &value.value {
686 LemmaValue::Number(n) => {
687 map.serialize_entry("type", "number")?;
688 let num = Number::from_str(&n.to_string())
689 .map_err(|_| serde::ser::Error::custom("Failed to convert Decimal to Number"))?;
690 map.serialize_entry("value", &num)?;
691 }
692 LemmaValue::Scale(n, unit_opt) => {
693 map.serialize_entry("type", "scale")?;
694 let num = Number::from_str(&n.to_string())
695 .map_err(|_| serde::ser::Error::custom("Failed to convert Decimal to Number"))?;
696 map.serialize_entry("value", &num)?;
697 if let Some(unit) = unit_opt {
698 map.serialize_entry("unit", unit)?;
699 }
700 }
701 LemmaValue::Ratio(r, _) => {
702 map.serialize_entry("type", "ratio")?;
703 let num = Number::from_str(&r.to_string())
704 .map_err(|_| serde::ser::Error::custom("Failed to convert Decimal to Number"))?;
705 map.serialize_entry("value", &num)?;
706 }
707 LemmaValue::Boolean(b) => {
708 map.serialize_entry("type", "boolean")?;
709 map.serialize_entry("value", &bool::from(b.clone()))?;
710 }
711 LemmaValue::Text(s) => {
712 map.serialize_entry("type", "text")?;
713 map.serialize_entry("value", s)?;
714 }
715 LemmaValue::Date(dt) => {
716 map.serialize_entry("type", "date")?;
717 map.serialize_entry("value", &dt.to_string())?;
718 }
719 LemmaValue::Time(time) => {
720 map.serialize_entry("type", "time")?;
721 map.serialize_entry("value", &time.to_string())?;
722 }
723 LemmaValue::Duration(value, unit) => {
724 map.serialize_entry("type", "duration")?;
725 map.serialize_entry("value", &format!("{} {}", value, unit))?;
726 }
727 }
728
729 map.end()
730}
731
732pub fn serialize_operation_result<S>(
734 result: &crate::evaluation::operations::OperationResult,
735 serializer: S,
736) -> Result<S::Ok, S::Error>
737where
738 S: serde::ser::Serializer,
739{
740 use crate::evaluation::operations::OperationResult;
741 use serde::ser::SerializeMap;
742
743 match result {
744 OperationResult::Value(lit_val) => {
745 serialize_literal_value(lit_val, serializer)
747 }
748 OperationResult::Veto(msg) => {
749 let mut map = serializer.serialize_map(Some(if msg.is_some() { 2 } else { 1 }))?;
750 map.serialize_entry("type", "veto")?;
751 if let Some(m) = msg {
752 map.serialize_entry("message", m)?;
753 }
754 map.end()
755 }
756 }
757}
758
759pub fn serialize_fact_path_map<S>(
764 map: &HashMap<FactPath, LemmaFact>,
765 serializer: S,
766) -> Result<S::Ok, S::Error>
767where
768 S: Serializer,
769{
770 use serde::ser::SerializeMap;
771 let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
772 for (key, value) in map {
773 map_serializer.serialize_entry(&key.to_string(), value)?;
774 }
775 map_serializer.end()
776}
777
778pub fn deserialize_fact_path_map<'de, D>(
782 deserializer: D,
783) -> Result<HashMap<FactPath, LemmaFact>, D::Error>
784where
785 D: Deserializer<'de>,
786{
787 let map: HashMap<String, LemmaFact> = HashMap::deserialize(deserializer)?;
788 let mut result = HashMap::new();
789 for (key_str, value) in map {
790 let path_parts: Vec<String> = key_str.split('.').map(|s| s.to_string()).collect();
791 let fact_path = FactPath::from_path(path_parts);
792 result.insert(fact_path, value);
793 }
794 Ok(result)
795}
796
797pub fn serialize_fact_type_map<S>(
802 map: &HashMap<FactPath, LemmaType>,
803 serializer: S,
804) -> Result<S::Ok, S::Error>
805where
806 S: Serializer,
807{
808 use serde::ser::SerializeMap;
809 let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
810 for (key, value) in map {
811 map_serializer.serialize_entry(&key.to_string(), value)?;
812 }
813 map_serializer.end()
814}
815
816pub fn deserialize_fact_type_map<'de, D>(
820 deserializer: D,
821) -> Result<HashMap<FactPath, LemmaType>, D::Error>
822where
823 D: Deserializer<'de>,
824{
825 let map: HashMap<String, LemmaType> = HashMap::deserialize(deserializer)?;
826 let mut result = HashMap::new();
827 for (key_str, value) in map {
828 let path_parts: Vec<String> = key_str.split('.').map(|s| s.to_string()).collect();
829 let fact_path = FactPath::from_path(path_parts);
830 result.insert(fact_path, value);
831 }
832 Ok(result)
833}
834
835pub fn serialize_fact_value_map<S>(
840 map: &HashMap<FactPath, LiteralValue>,
841 serializer: S,
842) -> Result<S::Ok, S::Error>
843where
844 S: Serializer,
845{
846 use serde::ser::SerializeMap;
847 let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
848 for (key, value) in map {
849 map_serializer.serialize_entry(&key.to_string(), value)?;
850 }
851 map_serializer.end()
852}
853
854pub fn deserialize_fact_value_map<'de, D>(
858 deserializer: D,
859) -> Result<HashMap<FactPath, LiteralValue>, D::Error>
860where
861 D: Deserializer<'de>,
862{
863 let map: HashMap<String, LiteralValue> = HashMap::deserialize(deserializer)?;
864 let mut result = HashMap::new();
865 for (key_str, value) in map {
866 let path_parts: Vec<String> = key_str.split('.').map(|s| s.to_string()).collect();
867 let fact_path = FactPath::from_path(path_parts);
868 result.insert(fact_path, value);
869 }
870 Ok(result)
871}
872
873pub fn serialize_fact_doc_ref_map<S>(
875 map: &HashMap<FactPath, String>,
876 serializer: S,
877) -> Result<S::Ok, S::Error>
878where
879 S: Serializer,
880{
881 use serde::ser::SerializeMap;
882 let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
883 for (key, value) in map {
884 map_serializer.serialize_entry(&key.to_string(), value)?;
885 }
886 map_serializer.end()
887}
888
889pub fn deserialize_fact_doc_ref_map<'de, D>(
891 deserializer: D,
892) -> Result<HashMap<FactPath, String>, D::Error>
893where
894 D: Deserializer<'de>,
895{
896 let map: HashMap<String, String> = HashMap::deserialize(deserializer)?;
897 let mut result = HashMap::new();
898 for (key_str, value) in map {
899 let path_parts: Vec<String> = key_str.split('.').map(|s| s.to_string()).collect();
900 let fact_path = FactPath::from_path(path_parts);
901 result.insert(fact_path, value);
902 }
903 Ok(result)
904}
905
906pub fn serialize_fact_source_map<S>(
908 map: &HashMap<FactPath, Source>,
909 serializer: S,
910) -> Result<S::Ok, S::Error>
911where
912 S: Serializer,
913{
914 use serde::ser::SerializeMap;
915 let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
916 for (key, value) in map {
917 map_serializer.serialize_entry(&key.to_string(), value)?;
918 }
919 map_serializer.end()
920}
921
922pub fn deserialize_fact_source_map<'de, D>(
924 deserializer: D,
925) -> Result<HashMap<FactPath, Source>, D::Error>
926where
927 D: Deserializer<'de>,
928{
929 let map: HashMap<String, Source> = HashMap::deserialize(deserializer)?;
930 let mut result = HashMap::new();
931 for (key_str, value) in map {
932 let path_parts: Vec<String> = key_str.split('.').map(|s| s.to_string()).collect();
933 let fact_path = FactPath::from_path(path_parts);
934 result.insert(fact_path, value);
935 }
936 Ok(result)
937}
938
939pub fn serialize_fact_path_set<S>(set: &HashSet<FactPath>, serializer: S) -> Result<S::Ok, S::Error>
943where
944 S: Serializer,
945{
946 use serde::ser::SerializeSeq;
947 let mut seq = serializer.serialize_seq(Some(set.len()))?;
948 for item in set {
949 seq.serialize_element(&item.to_string())?;
950 }
951 seq.end()
952}
953
954pub fn deserialize_fact_path_set<'de, D>(deserializer: D) -> Result<HashSet<FactPath>, D::Error>
958where
959 D: Deserializer<'de>,
960{
961 let vec: Vec<String> = Vec::deserialize(deserializer)?;
962 let mut result = HashSet::new();
963 for key_str in vec {
964 let path_parts: Vec<String> = key_str.split('.').map(|s| s.to_string()).collect();
965 let fact_path = FactPath::from_path(path_parts);
966 result.insert(fact_path);
967 }
968 Ok(result)
969}
970
971#[cfg(test)]
972mod tests {
973 use super::*;
974 use crate::semantic::{
975 standard_boolean, standard_date, standard_duration, standard_number, standard_ratio,
976 standard_text, standard_time, FactPath, LemmaType, LiteralValue,
977 };
978 use rust_decimal::Decimal;
979
980 fn create_test_plan(facts: Vec<(&str, LemmaType)>) -> ExecutionPlan {
981 let mut fact_schema = HashMap::new();
982 for (name, lemma_type) in facts {
983 let fact_path = FactPath {
984 segments: vec![],
985 fact: name.to_string(),
986 };
987 fact_schema.insert(fact_path, lemma_type);
988 }
989 ExecutionPlan {
990 doc_name: "test".to_string(),
991 fact_schema,
992 fact_values: HashMap::new(),
993 doc_refs: HashMap::new(),
994 fact_sources: HashMap::new(),
995 rules: vec![],
996 sources: HashMap::new(),
997 }
998 }
999
1000 fn create_text_literal(s: String) -> LiteralValue {
1001 LiteralValue::text(s)
1002 }
1003
1004 fn create_number_literal(n: Decimal) -> LiteralValue {
1005 LiteralValue::number(n)
1006 }
1007
1008 fn create_percentage_literal(p: Decimal) -> LiteralValue {
1009 LiteralValue::ratio(p / Decimal::from(100), Some("percent".to_string()))
1013 }
1014
1015 #[test]
1016 fn test_text_from_string() {
1017 let plan = create_test_plan(vec![("name", standard_text().clone())]);
1018 let json = br#"{"name": "Alice"}"#;
1019 let result = from_json(json, &plan).unwrap();
1020 assert_eq!(
1021 result.get("name"),
1022 Some(&create_text_literal("Alice".to_string()))
1023 );
1024 }
1025
1026 #[test]
1027 fn test_text_from_number() {
1028 let plan = create_test_plan(vec![("name", standard_text().clone())]);
1029 let json = br#"{"name": 42}"#;
1030 let result = from_json(json, &plan).unwrap();
1031 assert_eq!(
1032 result.get("name"),
1033 Some(&create_text_literal("42".to_string()))
1034 );
1035 }
1036
1037 #[test]
1038 fn test_text_from_boolean() {
1039 let plan = create_test_plan(vec![("name", standard_text().clone())]);
1040 let json = br#"{"name": true}"#;
1041 let result = from_json(json, &plan).unwrap();
1042 assert_eq!(
1043 result.get("name"),
1044 Some(&create_text_literal("true".to_string()))
1045 );
1046 }
1047
1048 #[test]
1049 fn test_text_from_array() {
1050 let plan = create_test_plan(vec![("data", standard_text().clone())]);
1051 let json = br#"{"data": [1, 2, 3]}"#;
1052 let result = from_json(json, &plan).unwrap();
1053 assert_eq!(
1054 result.get("data"),
1055 Some(&create_text_literal("[1,2,3]".to_string()))
1056 );
1057 }
1058
1059 #[test]
1060 fn test_text_from_object() {
1061 let plan = create_test_plan(vec![("config", standard_text().clone())]);
1062 let json = br#"{"config": {"key": "value"}}"#;
1063 let result = from_json(json, &plan).unwrap();
1064 assert_eq!(
1065 result.get("config"),
1066 Some(&create_text_literal("{\"key\":\"value\"}".to_string()))
1067 );
1068 }
1069
1070 #[test]
1071 fn test_number_from_integer() {
1072 let plan = create_test_plan(vec![("count", standard_number().clone())]);
1073 let json = br#"{"count": 42}"#;
1074 let result = from_json(json, &plan).unwrap();
1075 assert_eq!(
1076 result.get("count"),
1077 Some(&create_number_literal(Decimal::from(42)))
1078 );
1079 }
1080
1081 #[test]
1082 fn test_number_from_decimal() {
1083 let plan = create_test_plan(vec![("price", standard_number().clone())]);
1084 let json = br#"{"price": 99.95}"#;
1085 let result = from_json(json, &plan).unwrap();
1086 match result.get("price") {
1087 Some(lit) => {
1088 if let LemmaValue::Number(n) = &lit.value {
1089 let expected = Decimal::try_from(99.95).unwrap();
1090 let tolerance = Decimal::try_from(0.001).unwrap();
1091 assert!((*n - expected).abs() < tolerance);
1092 } else {
1093 panic!("Expected Number, got {:?}", lit);
1094 }
1095 }
1096 other => panic!("Expected Number, got {:?}", other),
1097 }
1098 }
1099
1100 #[test]
1101 fn test_number_from_string() {
1102 let plan = create_test_plan(vec![("count", standard_number().clone())]);
1103 let json = br#"{"count": "42"}"#;
1104 let result = from_json(json, &plan).unwrap();
1105 assert_eq!(
1106 result.get("count"),
1107 Some(&create_number_literal(Decimal::from(42)))
1108 );
1109 }
1110
1111 #[test]
1112 fn test_number_from_string_with_formatting() {
1113 let plan = create_test_plan(vec![("price", standard_number().clone())]);
1114 let json = br#"{"price": "1,234.56"}"#;
1115 let result = from_json(json, &plan).unwrap();
1116 match result.get("price") {
1117 Some(lit) => {
1118 if let LemmaValue::Number(n) = &lit.value {
1119 let expected = Decimal::try_from(1234.56).unwrap();
1120 let tolerance = Decimal::try_from(0.001).unwrap();
1121 assert!((*n - expected).abs() < tolerance);
1122 } else {
1123 panic!("Expected Number, got {:?}", lit);
1124 }
1125 }
1126 other => panic!("Expected Number, got {:?}", other),
1127 }
1128 }
1129
1130 #[test]
1131 fn test_number_from_invalid_string() {
1132 let plan = create_test_plan(vec![("count", standard_number().clone())]);
1133 let json = br#"{"count": "hello"}"#;
1134 let result = from_json(json, &plan);
1135 assert!(result.is_err());
1136 let error_message = result.unwrap_err().to_string();
1137 assert!(error_message.contains("Invalid number string"));
1138 }
1139
1140 #[test]
1141 fn test_number_rejects_boolean() {
1142 let plan = create_test_plan(vec![("count", standard_number().clone())]);
1143 let json = br#"{"count": true}"#;
1144 let result = from_json(json, &plan);
1145 assert!(result.is_err());
1146 let error_message = result.unwrap_err().to_string();
1147 assert!(error_message.contains("expected number"));
1148 assert!(error_message.contains("got boolean"));
1149 }
1150
1151 #[test]
1152 fn test_boolean_from_true() {
1153 let plan = create_test_plan(vec![("active", standard_boolean().clone())]);
1154 let json = br#"{"active": true}"#;
1155 let result = from_json(json, &plan).unwrap();
1156 match result.get("active") {
1157 Some(lit) => {
1158 if let LemmaValue::Boolean(b) = &lit.value {
1159 assert!(bool::from(b));
1160 } else {
1161 panic!("Expected Boolean, got {:?}", lit);
1162 }
1163 }
1164 other => panic!("Expected Boolean, got {:?}", other),
1165 }
1166 }
1167
1168 #[test]
1169 fn test_boolean_from_false() {
1170 let plan = create_test_plan(vec![("active", standard_boolean().clone())]);
1171 let json = br#"{"active": false}"#;
1172 let result = from_json(json, &plan).unwrap();
1173 match result.get("active") {
1174 Some(lit) => {
1175 if let LemmaValue::Boolean(b) = &lit.value {
1176 assert!(!bool::from(b));
1177 } else {
1178 panic!("Expected Boolean, got {:?}", lit);
1179 }
1180 }
1181 other => panic!("Expected Boolean, got {:?}", other),
1182 }
1183 }
1184
1185 #[test]
1186 fn test_boolean_from_string_yes() {
1187 let plan = create_test_plan(vec![("active", standard_boolean().clone())]);
1188 let json = br#"{"active": "yes"}"#;
1189 let result = from_json(json, &plan).unwrap();
1190 match result.get("active") {
1191 Some(lit) => {
1192 if let LemmaValue::Boolean(b) = &lit.value {
1193 assert!(bool::from(b));
1194 } else {
1195 panic!("Expected Boolean, got {:?}", lit);
1196 }
1197 }
1198 other => panic!("Expected Boolean, got {:?}", other),
1199 }
1200 }
1201
1202 #[test]
1203 fn test_boolean_from_string_no() {
1204 let plan = create_test_plan(vec![("active", standard_boolean().clone())]);
1205 let json = br#"{"active": "no"}"#;
1206 let result = from_json(json, &plan).unwrap();
1207 match result.get("active") {
1208 Some(lit) => {
1209 if let LemmaValue::Boolean(b) = &lit.value {
1210 assert!(!bool::from(b));
1211 } else {
1212 panic!("Expected Boolean, got {:?}", lit);
1213 }
1214 }
1215 other => panic!("Expected Boolean, got {:?}", other),
1216 }
1217 }
1218
1219 #[test]
1220 fn test_boolean_rejects_number() {
1221 let plan = create_test_plan(vec![("active", standard_boolean().clone())]);
1222 let json = br#"{"active": 1}"#;
1223 let result = from_json(json, &plan);
1224 assert!(result.is_err());
1225 let error_message = result.unwrap_err().to_string();
1226 assert!(error_message.contains("expected boolean"));
1227 assert!(error_message.contains("got number"));
1228 }
1229
1230 #[test]
1231 fn test_boolean_rejects_invalid_string() {
1232 let plan = create_test_plan(vec![("active", standard_boolean().clone())]);
1233 let json = br#"{"active": "maybe"}"#;
1234 let result = from_json(json, &plan);
1235 assert!(result.is_err());
1236 let error_message = result.unwrap_err().to_string();
1237 assert!(error_message.contains("Invalid boolean string"));
1238 }
1239
1240 #[test]
1241 fn test_percentage_from_number() {
1242 let plan = create_test_plan(vec![("discount", standard_ratio().clone())]);
1244 let json = br#"{"discount": 21}"#;
1245 let result = from_json(json, &plan).unwrap();
1246 assert_eq!(
1247 result.get("discount"),
1248 Some(&LiteralValue::ratio(Decimal::from(21), None))
1249 );
1250 }
1251
1252 #[test]
1253 fn test_percentage_from_string_with_percent_sign() {
1254 let plan = create_test_plan(vec![("discount", standard_ratio().clone())]);
1255 let json = br#"{"discount": "21%"}"#;
1256 let result = from_json(json, &plan).unwrap();
1257 assert_eq!(
1258 result.get("discount"),
1259 Some(&create_percentage_literal(Decimal::from(21)))
1260 );
1261 }
1262
1263 #[test]
1264 fn test_percentage_from_string_with_percent_word() {
1265 let plan = create_test_plan(vec![("discount", standard_ratio().clone())]);
1266 let json = br#"{"discount": "21 percent"}"#;
1267 let result = from_json(json, &plan).unwrap();
1268 assert_eq!(
1269 result.get("discount"),
1270 Some(&create_percentage_literal(Decimal::from(21)))
1271 );
1272 }
1273
1274 #[test]
1275 fn test_percentage_from_bare_string() {
1276 let plan = create_test_plan(vec![("discount", standard_ratio().clone())]);
1278 let json = br#"{"discount": "21"}"#;
1279 let result = from_json(json, &plan).unwrap();
1280 assert_eq!(
1281 result.get("discount"),
1282 Some(&LiteralValue::ratio(Decimal::from(21), None))
1283 );
1284 }
1285
1286 #[test]
1287 fn test_percentage_from_invalid_string() {
1288 let plan = create_test_plan(vec![("discount", standard_ratio().clone())]);
1289 let json = br#"{"discount": "hello"}"#;
1290 let result = from_json(json, &plan);
1291 assert!(result.is_err());
1292 let error_message = result.unwrap_err().to_string();
1293 assert!(error_message.contains("Invalid ratio string"));
1294 }
1295
1296 #[test]
1297 fn test_percentage_rejects_boolean() {
1298 let plan = create_test_plan(vec![("discount", standard_ratio().clone())]);
1299 let json = br#"{"discount": false}"#;
1300 let result = from_json(json, &plan);
1301 assert!(result.is_err());
1302 let error_message = result.unwrap_err().to_string();
1303 assert!(error_message.contains("expected ratio"));
1304 assert!(error_message.contains("got boolean"));
1305 }
1306
1307 #[test]
1308 fn test_date_from_string() {
1309 let plan = create_test_plan(vec![("start_date", standard_date().clone())]);
1310 let json = br#"{"start_date": "2024-01-15"}"#;
1311 let result = from_json(json, &plan).unwrap();
1312 match result.get("start_date") {
1313 Some(lit) => {
1314 if let LemmaValue::Date(dt) = &lit.value {
1315 assert_eq!(dt.year, 2024);
1316 assert_eq!(dt.month, 1);
1317 assert_eq!(dt.day, 15);
1318 } else {
1319 panic!("Expected Date, got {:?}", lit);
1320 }
1321 }
1322 other => panic!("Expected Date, got {:?}", other),
1323 }
1324 }
1325
1326 #[test]
1327 fn test_date_rejects_number() {
1328 let plan = create_test_plan(vec![("start_date", standard_date().clone())]);
1329 let json = br#"{"start_date": 20240115}"#;
1330 let result = from_json(json, &plan);
1331 assert!(result.is_err());
1332 let error_message = result.unwrap_err().to_string();
1333 assert!(error_message.contains("expected date"));
1334 assert!(error_message.contains("got number"));
1335 }
1336
1337 #[test]
1338 fn test_duration_from_string() {
1339 let plan = create_test_plan(vec![("duration", standard_duration().clone())]);
1340 let json = br#"{"duration": "5 days"}"#;
1341 let result = from_json(json, &plan).unwrap();
1342 match result.get("duration") {
1343 Some(lit) => {
1344 if let LemmaValue::Duration(value, unit) = &lit.value {
1345 assert_eq!(*value, Decimal::from(5));
1346 assert_eq!(*unit, crate::DurationUnit::Day);
1347 } else {
1348 panic!("Expected Duration, got {:?}", lit);
1349 }
1350 }
1351 other => panic!("Expected Duration, got {:?}", other),
1352 }
1353 }
1354
1355 #[test]
1356 fn test_duration_rejects_number() {
1357 let plan = create_test_plan(vec![("duration", standard_duration().clone())]);
1358 let json = br#"{"duration": 100}"#;
1359 let result = from_json(json, &plan);
1360 assert!(result.is_err());
1361 let error_message = result.unwrap_err().to_string();
1362 assert!(error_message.contains("Duration values must include the unit name"));
1363 }
1364
1365 #[test]
1366 fn test_time_from_string_hhmm() {
1367 let plan = create_test_plan(vec![("start_time", standard_time().clone())]);
1368 let json = br#"{"start_time": "14:30"}"#;
1369 let result = from_json(json, &plan).unwrap();
1370 match result.get("start_time") {
1371 Some(lit) => {
1372 if let LemmaValue::Time(t) = &lit.value {
1373 assert_eq!(t.hour, 14);
1374 assert_eq!(t.minute, 30);
1375 assert_eq!(t.second, 0);
1376 assert_eq!(t.timezone, None);
1377 } else {
1378 panic!("Expected Time, got {:?}", lit);
1379 }
1380 }
1381 other => panic!("Expected Time, got {:?}", other),
1382 }
1383 }
1384
1385 #[test]
1386 fn test_time_from_string_hhmmss() {
1387 let plan = create_test_plan(vec![("start_time", standard_time().clone())]);
1388 let json = br#"{"start_time": "14:30:45"}"#;
1389 let result = from_json(json, &plan).unwrap();
1390 match result.get("start_time") {
1391 Some(lit) => {
1392 if let LemmaValue::Time(t) = &lit.value {
1393 assert_eq!(t.hour, 14);
1394 assert_eq!(t.minute, 30);
1395 assert_eq!(t.second, 45);
1396 assert_eq!(t.timezone, None);
1397 } else {
1398 panic!("Expected Time, got {:?}", lit);
1399 }
1400 }
1401 other => panic!("Expected Time, got {:?}", other),
1402 }
1403 }
1404
1405 #[test]
1406 fn test_time_from_string_with_timezone() {
1407 let plan = create_test_plan(vec![("start_time", standard_time().clone())]);
1408 let json = br#"{"start_time": "14:30:00Z"}"#;
1410 let result = from_json(json, &plan);
1411 match result {
1415 Ok(values) => {
1416 if let Some(lit) = values.get("start_time") {
1418 if let LemmaValue::Time(t) = &lit.value {
1419 assert!(t.hour < 24 && t.minute < 60 && t.second < 60);
1420 }
1421 }
1422 }
1423 Err(_) => {
1424 }
1427 }
1428 }
1429
1430 #[test]
1431 fn test_time_rejects_number() {
1432 let plan = create_test_plan(vec![("start_time", standard_time().clone())]);
1433 let json = br#"{"start_time": 1430}"#;
1434 let result = from_json(json, &plan);
1435 assert!(result.is_err());
1436 let error_message = result.unwrap_err().to_string();
1437 assert!(error_message.contains("expected time"));
1438 assert!(error_message.contains("got number"));
1439 }
1440
1441 #[test]
1442 fn test_time_rejects_invalid_format() {
1443 let plan = create_test_plan(vec![("start_time", standard_time().clone())]);
1444 let json = br#"{"start_time": "25:00"}"#;
1445 let result = from_json(json, &plan);
1446 assert!(result.is_err());
1447 let error_message = result.unwrap_err().to_string();
1448 assert!(error_message.contains("Invalid time"));
1449 }
1450
1451 #[test]
1452 fn test_unknown_fact_error() {
1453 let plan = create_test_plan(vec![("known", standard_text().clone())]);
1454 let json = br#"{"unknown": "value"}"#;
1455 let result = from_json(json, &plan);
1456 assert!(result.is_err());
1457 let error_message = result.unwrap_err().to_string();
1458 assert!(error_message.contains("Fact 'unknown' not found"));
1459 assert!(error_message.contains("Available facts"));
1460 }
1461
1462 #[test]
1463 fn test_null_value_skipped() {
1464 let plan = create_test_plan(vec![
1465 ("name", standard_text().clone()),
1466 ("age", standard_number().clone()),
1467 ]);
1468 let json = br#"{"name": null, "age": 30}"#;
1469 let result = from_json(json, &plan).unwrap();
1470 assert_eq!(result.len(), 1);
1471 assert!(!result.contains_key("name"));
1472 assert_eq!(
1473 result.get("age"),
1474 Some(&create_number_literal(Decimal::from(30)))
1475 );
1476 }
1477
1478 #[test]
1479 fn test_all_null_values() {
1480 let plan = create_test_plan(vec![("name", standard_text().clone())]);
1481 let json = br#"{"name": null}"#;
1482 let result = from_json(json, &plan).unwrap();
1483 assert!(result.is_empty());
1484 }
1485
1486 #[test]
1487 fn test_array_value_for_non_text() {
1488 let plan = create_test_plan(vec![("items", standard_number().clone())]);
1489 let json = br#"{"items": [1, 2, 3]}"#;
1490 let result = from_json(json, &plan);
1491 assert!(result.is_err());
1492 let error_message = result.unwrap_err().to_string();
1493 assert!(error_message.contains("got array"));
1494 }
1495
1496 #[test]
1497 fn test_object_value_for_non_text() {
1498 let plan = create_test_plan(vec![("config", standard_number().clone())]);
1499 let json = br#"{"config": {"key": "value"}}"#;
1500 let result = from_json(json, &plan);
1501 assert!(result.is_err());
1502 let error_message = result.unwrap_err().to_string();
1503 assert!(error_message.contains("got object"));
1504 }
1505
1506 #[test]
1507 fn test_mixed_valid_types() {
1508 let plan = create_test_plan(vec![
1509 ("name", standard_text().clone()),
1510 ("count", standard_number().clone()),
1511 ("active", standard_boolean().clone()),
1512 ("discount", standard_ratio().clone()),
1513 ]);
1514 let json = br#"{"name": "Test", "count": 5, "active": true, "discount": 21}"#;
1515 let result = from_json(json, &plan).unwrap();
1516 assert_eq!(result.len(), 4);
1517 assert_eq!(
1518 result.get("name"),
1519 Some(&create_text_literal("Test".to_string()))
1520 );
1521 assert_eq!(
1522 result.get("count"),
1523 Some(&create_number_literal(Decimal::from(5)))
1524 );
1525 assert_eq!(
1527 result.get("discount"),
1528 Some(&LiteralValue::ratio(Decimal::from(21), None))
1529 );
1530 }
1531
1532 #[test]
1533 fn test_invalid_json_syntax() {
1534 let plan = create_test_plan(vec![("name", standard_text().clone())]);
1535 let json = br#"{"name": }"#;
1536 let result = from_json(json, &plan);
1537 assert!(result.is_err());
1538 let error_message = result.unwrap_err().to_string();
1539 assert!(error_message.contains("JSON parse error"));
1540 }
1541}