1use crate::computation::rational::{
14 checked_div, checked_mul, decimal_to_display_str, NumericFailure,
15};
16use crate::literals::rational_from_parsed_decimal;
17use crate::planning::semantics::{
18 format_decimal_for_api, range_element_type_specification, ratio_element_type_for_api,
19 semantic_calendar_unit_from_measure_type, LemmaType, LiteralUnitMapFailure, LiteralValue,
20 SemanticDateTime, SemanticTime, SpecInstance, SpecValue, TypeSpecification, TypedLiteral,
21 UnitFactorSource, ValueKind,
22};
23use crate::planning::unit_family::{declared_bare_names_only, FamilyUnitCatalog, FamilyUnitEntry};
24use indexmap::IndexMap;
25use rust_decimal::Decimal;
26use serde::{Deserialize, Serialize};
27use std::borrow::Cow;
28use std::collections::BTreeMap;
29use std::fmt;
30use std::sync::Arc;
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct CalendarResult {
35 pub value: Decimal,
36 pub unit: String,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub struct RangeResult {
47 pub from: RuleResultValue,
48 pub to: RuleResultValue,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
61pub struct RuleResultValue {
62 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub result: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub measure: Option<BTreeMap<String, Decimal>>,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub ratio: Option<BTreeMap<String, Decimal>>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub number: Option<Decimal>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub boolean: Option<bool>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub text: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub date: Option<SemanticDateTime>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub time: Option<SemanticTime>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub calendar: Option<CalendarResult>,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub unit: Option<String>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub range: Option<Box<RangeResult>>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub spec: Option<Box<SpecResult>>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub row: Option<RowReference>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub list: Option<Vec<RuleResultValue>>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub fields: Option<IndexMap<String, RuleResultValue>>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub reference: Option<String>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108pub struct SpecResult {
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub repository: Option<String>,
111 pub spec: String,
112 pub effective: String,
114 pub instance: String,
116 pub path: Vec<crate::api::PathSegment>,
118 pub data: IndexMap<String, SpecRuleResult>,
122 pub rules: IndexMap<String, SpecRuleResult>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
128pub struct RowReference {
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub repository: Option<String>,
131 pub spec: String,
132 pub effective: String,
134 pub instance: String,
136 pub index: usize,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141pub struct RowResult {
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub repository: Option<String>,
144 pub spec: String,
145 pub effective: String,
147 pub data: IndexMap<String, SpecRuleResult>,
150 pub rules: IndexMap<String, SpecRuleResult>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158pub struct SpecRuleResult {
159 pub vetoed: bool,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub veto_reason: Option<String>,
162 pub rule_type: String,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub result: Option<RuleResultValue>,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum RuleResultValueFailure {
170 DecimalLimit,
171 NumericOverflow,
172 OutOfMemory,
173}
174
175pub(crate) enum UnitExpansion<'a> {
177 Declared,
179 Family(&'a FamilyUnitEntry),
181}
182
183pub fn rule_result_value_failure_message(failure: RuleResultValueFailure) -> &'static str {
185 match failure {
186 RuleResultValueFailure::DecimalLimit => {
187 "The calculated result exceeds the decimal value limit."
188 }
189 RuleResultValueFailure::NumericOverflow => "The numeric result overflowed.",
190 RuleResultValueFailure::OutOfMemory => "The calculation ran out of memory.",
191 }
192}
193
194fn map_numeric_to_rule_result_value_failure(failure: NumericFailure) -> RuleResultValueFailure {
195 match failure {
196 NumericFailure::Overflow => RuleResultValueFailure::DecimalLimit,
197 NumericFailure::OutOfMemory => RuleResultValueFailure::OutOfMemory,
198 NumericFailure::DivisionByZero => {
199 panic!(
200 "BUG: decimal commit encountered division by zero while building RuleResultValue"
201 )
202 }
203 NumericFailure::Irrational => {
204 panic!(
205 "BUG: decimal commit encountered irrational result while building RuleResultValue"
206 )
207 }
208 NumericFailure::MagnitudeLimit => {
209 panic!("BUG: decimal commit does not apply the arithmetic magnitude limit")
210 }
211 }
212}
213
214fn map_unit_conversion_failure(failure: NumericFailure) -> RuleResultValueFailure {
215 match failure {
216 NumericFailure::Overflow => RuleResultValueFailure::NumericOverflow,
217 NumericFailure::OutOfMemory => RuleResultValueFailure::OutOfMemory,
218 NumericFailure::DivisionByZero => {
219 panic!("BUG: unit conversion division by zero while building RuleResultValue")
220 }
221 NumericFailure::Irrational => {
222 panic!("BUG: unit conversion irrational while building RuleResultValue")
223 }
224 NumericFailure::MagnitudeLimit => {
225 panic!("BUG: unit conversion does not apply the arithmetic magnitude limit")
226 }
227 }
228}
229
230fn map_literal_unit_map_failure(failure: LiteralUnitMapFailure) -> RuleResultValueFailure {
231 match failure {
232 LiteralUnitMapFailure::Commit(failure) => map_numeric_to_rule_result_value_failure(failure),
233 LiteralUnitMapFailure::UnitConversion(failure) => map_unit_conversion_failure(failure),
234 }
235}
236
237fn expansion_for_type<'a>(
238 rule_type: &LemmaType,
239 catalog: Option<&'a FamilyUnitCatalog>,
240) -> UnitExpansion<'a> {
241 let Some(catalog) = catalog else {
242 return UnitExpansion::Declared;
243 };
244 match catalog.entry_for_type(rule_type) {
245 Some(entry) => UnitExpansion::Family(entry),
246 None => UnitExpansion::Declared,
247 }
248}
249
250fn unit_names_for_expansion(
251 result_type: &LemmaType,
252 expansion: &UnitExpansion<'_>,
253 catalog: Option<&FamilyUnitCatalog>,
254) -> Arc<[String]> {
255 match expansion {
256 UnitExpansion::Declared => Arc::from(declared_bare_names_only(result_type)),
257 UnitExpansion::Family(_) => catalog
258 .expect("BUG: family expansion requires FamilyUnitCatalog")
259 .ordered_bare_names_for_type(result_type),
260 }
261}
262
263fn measure_factor_source<'a>(
264 result_type: &'a LemmaType,
265 expansion: &'a UnitExpansion<'a>,
266) -> UnitFactorSource<'a> {
267 match expansion {
268 UnitExpansion::Declared => UnitFactorSource::DeclaredOn(result_type),
269 UnitExpansion::Family(entry) => UnitFactorSource::Merged {
270 measure: entry.merged_measure_units.as_ref(),
271 ratio: entry.merged_ratio_units.as_ref(),
272 },
273 }
274}
275
276fn ratio_factor_source<'a>(
277 result_type: &'a LemmaType,
278 expansion: &'a UnitExpansion<'a>,
279) -> UnitFactorSource<'a> {
280 measure_factor_source(result_type, expansion)
281}
282
283enum NodeType<'a> {
286 Borrowed(&'a LemmaType),
287 Shared(Arc<LemmaType>),
288}
289
290impl NodeType<'_> {
291 fn get(&self) -> &LemmaType {
292 match self {
293 Self::Borrowed(lemma_type) => lemma_type,
294 Self::Shared(lemma_type) => lemma_type.as_ref(),
295 }
296 }
297}
298
299enum ResultValueTask<'a> {
300 Visit {
301 literal: Cow<'a, LiteralValue>,
302 lemma_type: NodeType<'a>,
303 },
304 AssembleList {
305 display: String,
306 rows: usize,
307 },
308}
309
310fn result_value_from_literal(
311 literal: &LiteralValue,
312 lemma_type: &LemmaType,
313 expansion: &UnitExpansion<'_>,
314 catalog: Option<&FamilyUnitCatalog>,
315) -> Result<RuleResultValue, RuleResultValueFailure> {
316 let mut tasks = vec![ResultValueTask::Visit {
317 literal: Cow::Borrowed(literal),
318 lemma_type: NodeType::Borrowed(lemma_type),
319 }];
320 let mut converted: Vec<RuleResultValue> = Vec::new();
321 while let Some(task) = tasks.pop() {
322 match task {
323 ResultValueTask::Visit {
324 literal,
325 lemma_type,
326 } => {
327 let lemma_type = lemma_type.get();
328 match &literal.value {
329 ValueKind::Range(from, to) => {
330 let from_value = result_value_from_range_endpoint(
331 &from.to_literal(),
332 from.lemma_type.as_ref(),
333 expansion,
334 catalog,
335 )?;
336 let to_value = result_value_from_range_endpoint(
337 &to.to_literal(),
338 to.lemma_type.as_ref(),
339 expansion,
340 catalog,
341 )?;
342 converted.push(RuleResultValue {
343 result: Some(literal.display_value_with_type(lemma_type)),
344 range: Some(Box::new(RangeResult {
345 from: from_value,
346 to: to_value,
347 })),
348 ..RuleResultValue::default()
349 });
350 }
351 ValueKind::List(rows) => {
352 tasks.push(ResultValueTask::AssembleList {
353 display: literal.display_value_with_type(lemma_type),
354 rows: rows.len(),
355 });
356 for row in rows.iter().rev() {
357 tasks.push(ResultValueTask::Visit {
358 literal: Cow::Owned(row.to_literal()),
359 lemma_type: NodeType::Shared(Arc::clone(&row.lemma_type)),
360 });
361 }
362 }
363 _ => converted.push(result_value_from_non_range_literal(
364 &literal, lemma_type, expansion, catalog,
365 )?),
366 }
367 }
368 ResultValueTask::AssembleList { display, rows } => {
369 let list = converted.split_off(converted.len() - rows);
370 converted.push(RuleResultValue {
371 result: Some(display),
372 list: Some(list),
373 ..RuleResultValue::default()
374 });
375 }
376 }
377 }
378 Ok(converted
379 .pop()
380 .expect("BUG: literal conversion yields exactly one RuleResultValue"))
381}
382
383pub(crate) fn rule_result_value_from_literal(
387 literal: &LiteralValue,
388 rule_type: &LemmaType,
389 catalog: &FamilyUnitCatalog,
390) -> Result<RuleResultValue, RuleResultValueFailure> {
391 let expansion = expansion_for_type(rule_type, Some(catalog));
392 result_value_from_literal(literal, rule_type, &expansion, Some(catalog))
393}
394
395pub fn type_scoped_result_value_from_literal(
397 literal: &LiteralValue,
398 lemma_type: &LemmaType,
399) -> Result<RuleResultValue, RuleResultValueFailure> {
400 let expansion = UnitExpansion::Declared;
401 result_value_from_literal(literal, lemma_type, &expansion, None)
402}
403
404fn result_value_from_range_endpoint(
405 endpoint: &LiteralValue,
406 endpoint_type: &LemmaType,
407 expansion: &UnitExpansion<'_>,
408 catalog: Option<&FamilyUnitCatalog>,
409) -> Result<RuleResultValue, RuleResultValueFailure> {
410 if matches!(&endpoint.value, ValueKind::Range(_, _)) {
411 panic!("BUG: range endpoint must not itself be a range");
412 }
413 result_value_from_non_range_literal(endpoint, endpoint_type, expansion, catalog)
414}
415
416fn result_value_from_non_range_literal(
417 literal: &LiteralValue,
418 result_type: &LemmaType,
419 expansion: &UnitExpansion<'_>,
420 catalog: Option<&FamilyUnitCatalog>,
421) -> Result<RuleResultValue, RuleResultValueFailure> {
422 match &literal.value {
423 ValueKind::Measure(rational) if result_type.is_calendar_like() => {
424 let unit = semantic_calendar_unit_from_measure_type(result_type);
425 let value = result_type
426 .try_rational_as_decimal(rational)
427 .map_err(map_numeric_to_rule_result_value_failure)?;
428 let result = Some(literal.display_value_with_type(result_type));
429 Ok(RuleResultValue {
430 result,
431 calendar: Some(CalendarResult {
432 value,
433 unit: unit.to_string(),
434 }),
435 ..RuleResultValue::default()
436 })
437 }
438 ValueKind::Measure(_) => {
439 let result = Some(literal.display_value_with_type(result_type));
440 let unit_names = unit_names_for_expansion(result_type, expansion, catalog);
441 let unit_name_refs: Vec<&str> = unit_names.iter().map(String::as_str).collect();
442 Ok(RuleResultValue {
443 result,
444 measure: Some(
445 result_type
446 .measure_literal_unit_map(
447 literal,
448 &unit_name_refs,
449 measure_factor_source(result_type, expansion),
450 )
451 .map_err(map_literal_unit_map_failure)?,
452 ),
453 unit: result_type.unit().map(str::to_string),
454 ..RuleResultValue::default()
455 })
456 }
457 ValueKind::Ratio(_) => {
458 let result = Some(literal.display_value_with_type(result_type));
459 let unit_names = unit_names_for_expansion(result_type, expansion, catalog);
460 let unit_name_refs: Vec<&str> = unit_names.iter().map(String::as_str).collect();
461 Ok(RuleResultValue {
462 result,
463 ratio: Some(
464 result_type
465 .ratio_literal_unit_map(
466 literal,
467 &unit_name_refs,
468 ratio_factor_source(result_type, expansion),
469 )
470 .map_err(map_literal_unit_map_failure)?,
471 ),
472 unit: result_type.unit().map(str::to_string),
473 ..RuleResultValue::default()
474 })
475 }
476 other => scalar_result_value(result_type, other),
477 }
478}
479
480fn scalar_result_value(
481 result_type: &LemmaType,
482 value: &ValueKind,
483) -> Result<RuleResultValue, RuleResultValueFailure> {
484 match value {
485 ValueKind::Number(rational) => {
486 let decimal = rational
487 .try_to_decimal()
488 .map_err(map_numeric_to_rule_result_value_failure)?;
489 let api_decimal = format_decimal_for_api(decimal, result_type.decimal_places());
490 let display_string = decimal_to_display_str(&decimal);
491 Ok(RuleResultValue {
492 result: Some(display_string),
493 number: Some(api_decimal),
494 ..RuleResultValue::default()
495 })
496 }
497 ValueKind::Boolean(b) => {
498 let result = Some(value.to_string());
499 Ok(RuleResultValue {
500 result,
501 boolean: Some(*b),
502 ..RuleResultValue::default()
503 })
504 }
505 ValueKind::Text(_) | ValueKind::Date(_) | ValueKind::Time(_) => {
506 let result = Some(value.to_string());
507 Ok(scalar_result_value_non_numeric(result, value))
508 }
509 ValueKind::Measure(_) | ValueKind::Ratio(_) => {
510 unreachable!("BUG: measure and ratio must be handled by caller")
511 }
512 ValueKind::Range(_, _) => {
513 unreachable!("BUG: range must be handled by result_value_from_literal")
514 }
515 ValueKind::List(_) => {
516 unreachable!("BUG: list must be handled by result_value_from_literal")
517 }
518 ValueKind::Spec(SpecValue::Fixed(instance)) => {
519 let identity = result_type.spec_identity().unwrap_or_else(|| {
520 panic!(
521 "BUG: spec value whose type is not a spec ({})",
522 result_type.name()
523 )
524 });
525 Ok(RuleResultValue {
526 result: Some(instance.to_string()),
527 spec: Some(Box::new(SpecResult {
528 repository: identity.repository.clone(),
529 spec: identity.spec.clone(),
530 effective: identity.effective.to_string(),
531 instance: instance.to_string(),
532 path: instance
533 .prefix
534 .iter()
535 .map(crate::api::PathSegment::from)
536 .collect(),
537 data: IndexMap::new(),
538 rules: IndexMap::new(),
539 })),
540 ..RuleResultValue::default()
541 })
542 }
543 ValueKind::Spec(SpecValue::Row(row)) => Ok(RuleResultValue {
544 result: Some(row.origin.to_string()),
545 ..RuleResultValue::default()
546 }),
547 }
548}
549
550fn scalar_result_value_non_numeric(result: Option<String>, value: &ValueKind) -> RuleResultValue {
551 match value {
552 ValueKind::Text(s) => RuleResultValue {
553 result,
554 text: Some(s.clone()),
555 ..RuleResultValue::default()
556 },
557 ValueKind::Date(d) => RuleResultValue {
558 result,
559 date: Some(d.clone()),
560 ..RuleResultValue::default()
561 },
562 ValueKind::Time(t) => RuleResultValue {
563 result,
564 time: Some(t.clone()),
565 ..RuleResultValue::default()
566 },
567 _ => unreachable!("BUG: scalar_result_value_non_numeric called with non-scalar type"),
568 }
569}
570
571fn literal_from_measure_map(
572 measure: &BTreeMap<String, Decimal>,
573 rule_type: &LemmaType,
574) -> LiteralValue {
575 let unit_names = rule_type
576 .measure_unit_names()
577 .expect("BUG: measure rule result must have declared units");
578 let unit_name = rule_type
579 .unit()
580 .map(str::to_string)
581 .or_else(|| unit_names.first().map(|name| (*name).to_string()))
582 .expect("BUG: measure rule result type must declare at least one unit");
583 let magnitude = measure
584 .get(&unit_name)
585 .unwrap_or_else(|| panic!("BUG: measure map missing unit '{unit_name}'"));
586 let rational = rational_from_parsed_decimal(*magnitude)
587 .expect("BUG: measure rule result value must lift to rational");
588 let factor = rule_type.measure_unit_factor(&unit_name);
589 let canonical = checked_mul(&rational, factor).unwrap_or_else(|failure| {
590 panic!("BUG: measure canonicalization from RuleResultValue fields failed: {failure}")
591 });
592 LiteralValue::measure(canonical)
593}
594
595fn literal_from_ratio_map(
596 ratio: &BTreeMap<String, Decimal>,
597 rule_type: &LemmaType,
598) -> LiteralValue {
599 let ratio_type = ratio_element_type_for_api(rule_type);
600 let units = match &ratio_type.specifications {
601 TypeSpecification::Ratio { units, .. } => units,
602 _ => panic!(
603 "BUG: ratio rule result type must be Ratio, got {}",
604 rule_type.name()
605 ),
606 };
607 let unit = rule_type
608 .unit()
609 .and_then(|name| units.get(name).ok())
610 .or_else(|| units.iter().next())
611 .expect("BUG: ratio rule result type must declare at least one unit");
612 let magnitude = ratio
613 .get(&unit.name)
614 .unwrap_or_else(|| panic!("BUG: ratio map missing unit '{}'", unit.name));
615 let display_rational = rational_from_parsed_decimal(*magnitude)
616 .expect("BUG: ratio rule result value must lift to rational");
617 let canonical = checked_div(&display_rational, &unit.value).unwrap_or_else(|failure| {
618 panic!("BUG: ratio canonicalization from RuleResultValue fields failed: {failure}")
619 });
620 LiteralValue::ratio(canonical)
621}
622
623impl RuleResultValue {
624 pub fn to_literal(&self, rule_type: &LemmaType) -> LiteralValue {
631 if let Some(row) = &self.row {
632 panic!(
633 "BUG: list row '{}' cannot be rebuilt from its API value",
634 row.instance
635 );
636 }
637 if let Some(spec) = &self.spec {
638 assert!(
639 !spec.path.is_empty(),
640 "BUG: spec result '{}' has no uses hop",
641 spec.instance
642 );
643 return LiteralValue::new(ValueKind::Spec(SpecValue::Fixed(SpecInstance {
644 prefix: spec
645 .path
646 .iter()
647 .map(|segment| crate::planning::semantics::PathSegment {
648 uses: segment.uses.clone(),
649 repository: segment.repository.clone(),
650 spec: segment.spec.clone(),
651 })
652 .collect(),
653 })));
654 }
655 if let Some(rows) = &self.list {
656 let TypeSpecification::List { element, .. } = &rule_type.specifications else {
657 panic!(
658 "BUG: list result whose rule type is not a list ({})",
659 rule_type.name()
660 );
661 };
662 return LiteralValue::new(ValueKind::List(
663 rows.iter()
664 .map(|row| {
665 let row_type =
666 endpoint_type_with_written_unit(element, row.unit.as_deref());
667 TypedLiteral {
668 value: row.to_literal(&row_type).value,
669 lemma_type: Arc::new(row_type),
670 }
671 })
672 .collect(),
673 ));
674 }
675 if let Some(range) = &self.range {
676 if range.from.range.is_some() || range.to.range.is_some() {
677 panic!("BUG: range endpoint must not itself be a range");
678 }
679 let endpoint_schema =
680 element_type_from_range_rule(rule_type).unwrap_or_else(|| rule_type.clone());
681 let left_type =
682 endpoint_type_with_written_unit(&endpoint_schema, range.from.unit.as_deref());
683 let right_type =
684 endpoint_type_with_written_unit(&endpoint_schema, range.to.unit.as_deref());
685 let left = range.from.to_literal(&left_type);
686 let right = range.to.to_literal(&right_type);
687 return LiteralValue::range(
688 crate::planning::semantics::TypedLiteral {
689 value: left.value,
690 lemma_type: Arc::new(left_type),
691 },
692 crate::planning::semantics::TypedLiteral {
693 value: right.value,
694 lemma_type: Arc::new(right_type),
695 },
696 );
697 }
698
699 if let Some(b) = self.boolean {
700 return LiteralValue::from_bool(b);
701 }
702 let owned_rule_type = match self.unit.as_deref() {
703 Some(unit)
704 if matches!(
705 &rule_type.specifications,
706 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
707 ) && rule_type.unit() != Some(unit) =>
708 {
709 Arc::new(rule_type.clone().with_unit(unit))
710 }
711 _ => Arc::new(rule_type.clone()),
712 };
713 if let Some(number) = self.number {
714 return LiteralValue::number_from_decimal(number);
715 }
716 if let Some(calendar) = &self.calendar {
717 let rational = rational_from_parsed_decimal(calendar.value)
718 .expect("BUG: calendar rule result value must lift to rational");
719 return LiteralValue::measure(rational);
720 }
721 if let Some(measure) = &self.measure {
722 return literal_from_measure_map(measure, owned_rule_type.as_ref());
723 }
724 if let Some(ratio) = &self.ratio {
725 return literal_from_ratio_map(ratio, owned_rule_type.as_ref());
726 }
727 if let Some(date) = &self.date {
728 return LiteralValue::date(date.clone());
729 }
730 if let Some(time) = &self.time {
731 return LiteralValue::time(time.clone());
732 }
733 if let Some(text) = &self.text {
734 return LiteralValue::text(text.clone());
735 }
736 panic!("BUG: rule result value fields cannot reconstruct literal");
737 }
738}
739
740fn endpoint_type_with_written_unit(endpoint_schema: &LemmaType, unit: Option<&str>) -> LemmaType {
741 match unit {
742 Some(unit)
743 if matches!(
744 &endpoint_schema.specifications,
745 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
746 ) && endpoint_schema.unit() != Some(unit) =>
747 {
748 endpoint_schema.clone().with_unit(unit)
749 }
750 _ => endpoint_schema.clone(),
751 }
752}
753
754fn element_type_from_range_rule(rule_type: &LemmaType) -> Option<LemmaType> {
755 range_element_type_specification(&rule_type.specifications).map(LemmaType::primitive)
756}
757
758fn format_unit_map(map: &BTreeMap<String, Decimal>) -> String {
759 map.iter()
760 .map(|(unit, value)| format!("{value} {unit}"))
761 .collect::<Vec<_>>()
762 .join(", ")
763}
764
765impl fmt::Display for RuleResultValue {
766 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767 if let Some(range) = &self.range {
768 return write!(f, "{}...{}", range.from, range.to);
769 }
770 if let Some(measure) = &self.measure {
771 return write!(f, "{}", format_unit_map(measure));
772 }
773 if let Some(ratio) = &self.ratio {
774 return write!(f, "{}", format_unit_map(ratio));
775 }
776 if let Some(number) = &self.number {
777 return write!(f, "{number}");
778 }
779 if let Some(b) = self.boolean {
780 return write!(f, "{b}");
781 }
782 if let Some(text) = &self.text {
783 return write!(f, "{text}");
784 }
785 if let Some(date) = &self.date {
786 return write!(f, "{date}");
787 }
788 if let Some(time) = &self.time {
789 return write!(f, "{time}");
790 }
791 if let Some(calendar) = &self.calendar {
792 return write!(f, "{} {}", calendar.value, calendar.unit);
793 }
794 if let Some(display) = &self.result {
795 return write!(f, "{display}");
796 }
797 if let Some(reference) = &self.reference {
798 return write!(f, "{reference}");
799 }
800 write!(f, "")
801 }
802}