1use chrono::{Datelike, Timelike};
5use rust_decimal::Decimal;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::collections::BTreeMap;
8use std::fmt;
9
10use crate::computation::rational::{self, RationalInteger};
11
12pub type BaseQuantityVector = BTreeMap<String, i32>;
21
22pub fn rational_to_serialized_str(rational: &RationalInteger) -> Result<String, String> {
27 rational::rational_to_wire_str(rational).map_err(|failure| failure.to_string())
28}
29
30pub fn rational_from_parsed_decimal(decimal: Decimal) -> Result<RationalInteger, String> {
31 rational::decimal_to_rational(decimal).map_err(|failure| failure.to_string())
32}
33
34pub mod stored_rational_serde {
36 use super::{rational_from_parsed_decimal, rational_to_serialized_str, RationalInteger};
37 use rust_decimal::Decimal;
38 use serde::{Deserialize, Deserializer, Serializer};
39
40 pub fn serialize<S: Serializer>(
41 value: &RationalInteger,
42 serializer: S,
43 ) -> Result<S::Ok, S::Error> {
44 serializer
45 .serialize_str(&rational_to_serialized_str(value).map_err(serde::ser::Error::custom)?)
46 }
47
48 pub mod option {
49 use super::*;
50
51 pub fn serialize<S: Serializer>(
52 value: &Option<RationalInteger>,
53 serializer: S,
54 ) -> Result<S::Ok, S::Error> {
55 match value {
56 Some(rational) => super::serialize(rational, serializer),
57 None => serializer.serialize_none(),
58 }
59 }
60
61 pub fn deserialize<'de, D: Deserializer<'de>>(
62 deserializer: D,
63 ) -> Result<Option<RationalInteger>, D::Error> {
64 Option::<Decimal>::deserialize(deserializer)?
65 .map(rational_from_parsed_decimal)
66 .transpose()
67 .map_err(serde::de::Error::custom)
68 }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Hash)]
80pub struct QuantityUnit {
81 pub name: String,
82 pub factor: RationalInteger,
84 pub derived_quantity_factors: Vec<(String, i32)>,
85 pub decomposition: BaseQuantityVector,
86 pub minimum: Option<RationalInteger>,
88 pub maximum: Option<RationalInteger>,
90 pub default_magnitude: Option<RationalInteger>,
92}
93
94impl Serialize for QuantityUnit {
95 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
96 use quantity_unit_factor_serialization::FactorSerializer;
97 use serde::ser::SerializeStruct;
98 let mut state = serializer.serialize_struct("QuantityUnit", 7)?;
99 state.serialize_field("name", &self.name)?;
100 state.serialize_field("factor", &FactorSerializer::from_ratio(&self.factor))?;
101 state.serialize_field("derived_quantity_factors", &self.derived_quantity_factors)?;
102 state.serialize_field("decomposition", &self.decomposition)?;
103 if let Some(minimum) = &self.minimum {
104 state.serialize_field(
105 "minimum",
106 &rational_to_serialized_str(minimum).map_err(serde::ser::Error::custom)?,
107 )?;
108 }
109 if let Some(maximum) = &self.maximum {
110 state.serialize_field(
111 "maximum",
112 &rational_to_serialized_str(maximum).map_err(serde::ser::Error::custom)?,
113 )?;
114 }
115 if let Some(default_magnitude) = &self.default_magnitude {
116 state.serialize_field(
117 "default",
118 &rational_to_serialized_str(default_magnitude)
119 .map_err(serde::ser::Error::custom)?,
120 )?;
121 }
122 state.end()
123 }
124}
125
126impl<'de> Deserialize<'de> for QuantityUnit {
127 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
128 #[derive(Deserialize)]
129 struct QuantityUnitData {
130 name: String,
131 #[serde(with = "quantity_unit_factor_serialization")]
132 factor: RationalInteger,
133 #[serde(default)]
134 derived_quantity_factors: Vec<(String, i32)>,
135 #[serde(default)]
136 decomposition: BaseQuantityVector,
137 #[serde(default)]
138 minimum: Option<Decimal>,
139 #[serde(default)]
140 maximum: Option<Decimal>,
141 #[serde(default, rename = "default")]
142 default_magnitude: Option<Decimal>,
143 }
144 let data = QuantityUnitData::deserialize(deserializer)?;
145 Ok(Self {
146 name: data.name,
147 factor: data.factor,
148 derived_quantity_factors: data.derived_quantity_factors,
149 decomposition: data.decomposition,
150 minimum: data
151 .minimum
152 .map(rational_from_parsed_decimal)
153 .transpose()
154 .map_err(serde::de::Error::custom)?,
155 maximum: data
156 .maximum
157 .map(rational_from_parsed_decimal)
158 .transpose()
159 .map_err(serde::de::Error::custom)?,
160 default_magnitude: data
161 .default_magnitude
162 .map(rational_from_parsed_decimal)
163 .transpose()
164 .map_err(serde::de::Error::custom)?,
165 })
166 }
167}
168
169impl QuantityUnit {
170 pub fn from_decimal_factor(
171 name: String,
172 decimal_factor: Decimal,
173 derived_quantity_factors: Vec<(String, i32)>,
174 ) -> Result<Self, String> {
175 let factor =
176 rational::decimal_to_rational(decimal_factor).map_err(|failure| failure.to_string())?;
177 Ok(QuantityUnit {
178 name,
179 factor,
180 derived_quantity_factors,
181 decomposition: BaseQuantityVector::new(),
182 minimum: None,
183 maximum: None,
184 default_magnitude: None,
185 })
186 }
187
188 pub fn clear_constraint_magnitudes(&mut self) {
189 self.minimum = None;
190 self.maximum = None;
191 self.default_magnitude = None;
192 }
193
194 pub fn is_canonical_factor(&self) -> bool {
195 self.factor == rational::rational_one()
196 }
197
198 pub fn is_positive_factor(&self) -> bool {
199 let numerator = *self.factor.numer();
200 let denominator = *self.factor.denom();
201 numerator != 0 && (numerator > 0) == (denominator > 0)
202 }
203}
204
205mod quantity_unit_factor_serialization {
206 use super::RationalInteger;
207 use serde::{Deserialize, Serialize};
208
209 #[derive(Serialize, Deserialize)]
210 pub struct FactorSerializer {
211 numer: String,
212 denom: String,
213 }
214
215 impl FactorSerializer {
216 pub fn from_ratio(value: &RationalInteger) -> Self {
217 let reduced = value.reduced();
218 FactorSerializer {
219 numer: reduced.numer().to_string(),
220 denom: reduced.denom().to_string(),
221 }
222 }
223
224 pub fn into_ratio(self) -> Result<RationalInteger, String> {
225 let numer: i128 = self
226 .numer
227 .parse()
228 .map_err(|error: std::num::ParseIntError| error.to_string())?;
229 let denom: i128 = self
230 .denom
231 .parse()
232 .map_err(|error: std::num::ParseIntError| error.to_string())?;
233 if denom == 0 {
234 return Err("QuantityUnit conversion factor denominator cannot be zero".to_string());
235 }
236 Ok(RationalInteger::new(numer, denom).reduced())
237 }
238 }
239
240 pub fn deserialize<'de, D: serde::Deserializer<'de>>(
241 deserializer: D,
242 ) -> Result<RationalInteger, D::Error> {
243 FactorSerializer::deserialize(deserializer)?
244 .into_ratio()
245 .map_err(serde::de::Error::custom)
246 }
247}
248
249#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
250#[serde(transparent)]
251pub struct QuantityUnits(pub Vec<QuantityUnit>);
252
253impl QuantityUnits {
254 pub fn new() -> Self {
255 QuantityUnits(Vec::new())
256 }
257 pub fn get(&self, name: &str) -> Result<&QuantityUnit, String> {
258 self.0.iter().find(|u| u.name == name).ok_or_else(|| {
259 let valid: Vec<&str> = self.0.iter().map(|u| u.name.as_str()).collect();
260 format!(
261 "Unknown unit '{}' for this quantity type. Valid units: {}",
262 name,
263 valid.join(", ")
264 )
265 })
266 }
267
268 pub fn iter(&self) -> std::slice::Iter<'_, QuantityUnit> {
269 self.0.iter()
270 }
271 pub fn push(&mut self, u: QuantityUnit) {
272 self.0.push(u);
273 }
274 pub fn is_empty(&self) -> bool {
275 self.0.is_empty()
276 }
277 pub fn len(&self) -> usize {
278 self.0.len()
279 }
280}
281
282impl Default for QuantityUnits {
283 fn default() -> Self {
284 QuantityUnits::new()
285 }
286}
287
288impl From<Vec<QuantityUnit>> for QuantityUnits {
289 fn from(v: Vec<QuantityUnit>) -> Self {
290 QuantityUnits(v)
291 }
292}
293
294impl<'a> IntoIterator for &'a QuantityUnits {
295 type Item = &'a QuantityUnit;
296 type IntoIter = std::slice::Iter<'a, QuantityUnit>;
297 fn into_iter(self) -> Self::IntoIter {
298 self.0.iter()
299 }
300}
301
302#[derive(Clone, Debug, PartialEq, Eq, Hash)]
303pub struct RatioUnit {
304 pub name: String,
305 pub value: RationalInteger,
306 pub minimum: Option<RationalInteger>,
307 pub maximum: Option<RationalInteger>,
308 pub default_magnitude: Option<RationalInteger>,
309}
310
311impl Serialize for RatioUnit {
312 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
313 use quantity_unit_factor_serialization::FactorSerializer;
314 use serde::ser::SerializeStruct;
315 let mut state = serializer.serialize_struct("RatioUnit", 5)?;
316 state.serialize_field("name", &self.name)?;
317 state.serialize_field("value", &FactorSerializer::from_ratio(&self.value))?;
318 if let Some(minimum) = &self.minimum {
319 state.serialize_field(
320 "minimum",
321 &rational_to_serialized_str(minimum).map_err(serde::ser::Error::custom)?,
322 )?;
323 }
324 if let Some(maximum) = &self.maximum {
325 state.serialize_field(
326 "maximum",
327 &rational_to_serialized_str(maximum).map_err(serde::ser::Error::custom)?,
328 )?;
329 }
330 if let Some(default_magnitude) = &self.default_magnitude {
331 state.serialize_field(
332 "default",
333 &rational_to_serialized_str(default_magnitude)
334 .map_err(serde::ser::Error::custom)?,
335 )?;
336 }
337 state.end()
338 }
339}
340
341impl<'de> Deserialize<'de> for RatioUnit {
342 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
343 #[derive(Deserialize)]
344 struct RatioUnitData {
345 name: String,
346 #[serde(with = "quantity_unit_factor_serialization")]
347 value: RationalInteger,
348 #[serde(default)]
349 minimum: Option<Decimal>,
350 #[serde(default)]
351 maximum: Option<Decimal>,
352 #[serde(default, rename = "default")]
353 default_magnitude: Option<Decimal>,
354 }
355 let data = RatioUnitData::deserialize(deserializer)?;
356 Ok(Self {
357 name: data.name,
358 value: data.value,
359 minimum: data
360 .minimum
361 .map(rational_from_parsed_decimal)
362 .transpose()
363 .map_err(serde::de::Error::custom)?,
364 maximum: data
365 .maximum
366 .map(rational_from_parsed_decimal)
367 .transpose()
368 .map_err(serde::de::Error::custom)?,
369 default_magnitude: data
370 .default_magnitude
371 .map(rational_from_parsed_decimal)
372 .transpose()
373 .map_err(serde::de::Error::custom)?,
374 })
375 }
376}
377
378impl RatioUnit {
379 pub fn clear_constraint_magnitudes(&mut self) {
380 self.minimum = None;
381 self.maximum = None;
382 self.default_magnitude = None;
383 }
384}
385
386#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
387#[serde(transparent)]
388pub struct RatioUnits(pub Vec<RatioUnit>);
389
390impl RatioUnits {
391 pub fn new() -> Self {
392 RatioUnits(Vec::new())
393 }
394 pub fn get(&self, name: &str) -> Result<&RatioUnit, String> {
395 self.0.iter().find(|u| u.name == name).ok_or_else(|| {
396 let valid: Vec<&str> = self.0.iter().map(|u| u.name.as_str()).collect();
397 format!(
398 "Unknown unit '{}' for this ratio type. Valid units: {}",
399 name,
400 valid.join(", ")
401 )
402 })
403 }
404
405 pub fn iter(&self) -> std::slice::Iter<'_, RatioUnit> {
406 self.0.iter()
407 }
408 pub fn push(&mut self, u: RatioUnit) {
409 self.0.push(u);
410 }
411 pub fn is_empty(&self) -> bool {
412 self.0.is_empty()
413 }
414 pub fn len(&self) -> usize {
415 self.0.len()
416 }
417}
418
419impl Default for RatioUnits {
420 fn default() -> Self {
421 RatioUnits::new()
422 }
423}
424
425impl From<Vec<RatioUnit>> for RatioUnits {
426 fn from(v: Vec<RatioUnit>) -> Self {
427 RatioUnits(v)
428 }
429}
430
431impl<'a> IntoIterator for &'a RatioUnits {
432 type Item = &'a RatioUnit;
433 type IntoIter = std::slice::Iter<'a, RatioUnit>;
434 fn into_iter(self) -> Self::IntoIter {
435 self.0.iter()
436 }
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
444#[serde(rename_all = "lowercase")]
445pub enum BooleanValue {
446 True,
447 False,
448 Yes,
449 No,
450 Accept,
451 Reject,
452}
453
454impl From<BooleanValue> for bool {
455 fn from(value: BooleanValue) -> bool {
456 matches!(
457 value,
458 BooleanValue::True | BooleanValue::Yes | BooleanValue::Accept
459 )
460 }
461}
462
463impl From<&BooleanValue> for bool {
464 fn from(value: &BooleanValue) -> bool {
465 (*value).into() }
467}
468
469impl From<bool> for BooleanValue {
470 fn from(value: bool) -> BooleanValue {
471 if value {
472 BooleanValue::True
473 } else {
474 BooleanValue::False
475 }
476 }
477}
478
479impl std::ops::Not for BooleanValue {
480 type Output = BooleanValue;
481
482 fn not(self) -> Self::Output {
483 if self.into() {
484 BooleanValue::False
485 } else {
486 BooleanValue::True
487 }
488 }
489}
490
491impl std::ops::Not for &BooleanValue {
492 type Output = BooleanValue;
493
494 fn not(self) -> Self::Output {
495 if (*self).into() {
496 BooleanValue::False
497 } else {
498 BooleanValue::True
499 }
500 }
501}
502
503impl std::str::FromStr for BooleanValue {
504 type Err = String;
505
506 fn from_str(s: &str) -> Result<Self, Self::Err> {
507 match s.trim().to_lowercase().as_str() {
508 "true" => Ok(BooleanValue::True),
509 "false" => Ok(BooleanValue::False),
510 "yes" => Ok(BooleanValue::Yes),
511 "no" => Ok(BooleanValue::No),
512 "accept" => Ok(BooleanValue::Accept),
513 "reject" => Ok(BooleanValue::Reject),
514 _ => Err(format!("Invalid boolean: '{}'", s)),
515 }
516 }
517}
518
519impl BooleanValue {
520 #[must_use]
521 pub fn as_str(&self) -> &'static str {
522 match self {
523 BooleanValue::True => "true",
524 BooleanValue::False => "false",
525 BooleanValue::Yes => "yes",
526 BooleanValue::No => "no",
527 BooleanValue::Accept => "accept",
528 BooleanValue::Reject => "reject",
529 }
530 }
531}
532
533impl fmt::Display for BooleanValue {
534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 write!(f, "{}", self.as_str())
536 }
537}
538
539#[derive(Debug, Clone, PartialEq, Eq, Hash)]
540pub enum CalendarUnit {
541 Month,
542 Year,
543}
544
545impl CalendarUnit {
546 #[must_use]
547 pub fn from_keyword(s: &str) -> Option<Self> {
548 match s.trim().to_lowercase().as_str() {
549 "month" | "months" => Some(Self::Month),
550 "year" | "years" => Some(Self::Year),
551 _ => None,
552 }
553 }
554
555 #[must_use]
556 pub fn canonical_factor(&self) -> RationalInteger {
557 match self {
558 Self::Month => rational::rational_one(),
559 Self::Year => RationalInteger::new(12, 1),
560 }
561 }
562}
563
564impl Serialize for CalendarUnit {
565 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
566 where
567 S: serde::Serializer,
568 {
569 serializer.serialize_str(&self.to_string())
570 }
571}
572
573impl<'de> Deserialize<'de> for CalendarUnit {
574 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
575 where
576 D: serde::Deserializer<'de>,
577 {
578 let s = String::deserialize(deserializer)?;
579 s.parse().map_err(serde::de::Error::custom)
580 }
581}
582
583impl fmt::Display for CalendarUnit {
584 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585 let s = match self {
586 CalendarUnit::Month => "months",
587 CalendarUnit::Year => "years",
588 };
589 write!(f, "{}", s)
590 }
591}
592
593impl std::str::FromStr for CalendarUnit {
594 type Err = String;
595
596 fn from_str(s: &str) -> Result<Self, Self::Err> {
597 Self::from_keyword(s).ok_or_else(|| format!("Unknown calendar unit: '{}'", s))
598 }
599}
600
601#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
602pub struct TimezoneValue {
603 pub offset_hours: i8,
604 pub offset_minutes: u8,
605}
606
607impl fmt::Display for TimezoneValue {
608 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609 if self.offset_hours == 0 && self.offset_minutes == 0 {
610 write!(f, "Z")
611 } else {
612 let sign = if self.offset_hours >= 0 { "+" } else { "-" };
613 let hours = self.offset_hours.abs();
614 write!(f, "{}{:02}:{:02}", sign, hours, self.offset_minutes)
615 }
616 }
617}
618
619#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
620pub struct TimeValue {
621 pub hour: u8,
622 pub minute: u8,
623 pub second: u8,
624 #[serde(default)]
625 pub microsecond: u32,
626 pub timezone: Option<TimezoneValue>,
627}
628
629impl fmt::Display for TimeValue {
630 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
631 write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
632 if self.microsecond != 0 {
633 write!(f, ".{:06}", self.microsecond)?;
634 }
635 if let Some(timezone) = &self.timezone {
636 write!(f, "{}", timezone)?;
637 }
638 Ok(())
639 }
640}
641
642#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
643pub struct DateTimeValue {
644 pub year: i32,
645 pub month: u32,
646 pub day: u32,
647 pub hour: u32,
648 pub minute: u32,
649 pub second: u32,
650 #[serde(default)]
651 pub microsecond: u32,
652 pub timezone: Option<TimezoneValue>,
653}
654
655impl DateTimeValue {
656 pub fn now() -> Self {
657 let now = chrono::Local::now();
658 let offset_secs = now.offset().local_minus_utc();
659 Self {
660 year: now.year(),
661 month: now.month(),
662 day: now.day(),
663 hour: now.time().hour(),
664 minute: now.time().minute(),
665 second: now.time().second(),
666 microsecond: now.time().nanosecond() / 1000 % 1_000_000,
667 timezone: Some(TimezoneValue {
668 offset_hours: (offset_secs / 3600) as i8,
669 offset_minutes: ((offset_secs.abs() % 3600) / 60) as u8,
670 }),
671 }
672 }
673
674 fn parse_iso_week(s: &str) -> Option<Self> {
675 let parts: Vec<&str> = s.split("-W").collect();
676 if parts.len() != 2 {
677 return None;
678 }
679 let year: i32 = parts[0].parse().ok()?;
680 let week: u32 = parts[1].parse().ok()?;
681 if week == 0 || week > 53 {
682 return None;
683 }
684 let date = chrono::NaiveDate::from_isoywd_opt(year, week, chrono::Weekday::Mon)?;
685 Some(Self {
686 year: date.year(),
687 month: date.month(),
688 day: date.day(),
689 hour: 0,
690 minute: 0,
691 second: 0,
692 microsecond: 0,
693 timezone: None,
694 })
695 }
696}
697
698impl fmt::Display for DateTimeValue {
699 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
700 let has_time = self.hour != 0
701 || self.minute != 0
702 || self.second != 0
703 || self.microsecond != 0
704 || self.timezone.is_some();
705 if !has_time {
706 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
707 } else {
708 write!(
709 f,
710 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
711 self.year, self.month, self.day, self.hour, self.minute, self.second
712 )?;
713 if self.microsecond != 0 {
714 write!(f, ".{:06}", self.microsecond)?;
715 }
716 if let Some(tz) = &self.timezone {
717 write!(f, "{}", tz)?;
718 }
719 Ok(())
720 }
721 }
722}
723
724#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
729#[serde(rename_all = "snake_case")]
730pub enum Value {
731 Number(Decimal),
732 NumberWithUnit(Decimal, String),
733 Text(String),
734 Date(DateTimeValue),
735 Time(TimeValue),
736 Boolean(BooleanValue),
737 Calendar(Decimal, CalendarUnit),
738 Range(Box<Value>, Box<Value>),
739}
740
741impl fmt::Display for Value {
742 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743 match self {
744 Value::Number(n) => write!(f, "{}", n),
745 Value::Text(s) => write!(f, "{}", s),
746 Value::Date(dt) => write!(f, "{}", dt),
747 Value::Boolean(b) => write!(f, "{}", b),
748 Value::Time(time) => write!(f, "{}", time),
749 Value::NumberWithUnit(n, u) => match u.as_str() {
750 "percent" => {
751 let norm = n.normalize();
752 let s = if norm.fract().is_zero() {
753 norm.trunc().to_string()
754 } else {
755 norm.to_string()
756 };
757 write!(f, "{}%", s)
758 }
759 "permille" => {
760 let norm = n.normalize();
761 let s = if norm.fract().is_zero() {
762 norm.trunc().to_string()
763 } else {
764 norm.to_string()
765 };
766 write!(f, "{}%%", s)
767 }
768 unit => {
769 let norm = n.normalize();
770 let s = if norm.fract().is_zero() {
771 norm.trunc().to_string()
772 } else {
773 norm.to_string()
774 };
775 write!(f, "{} {}", s, unit)
776 }
777 },
778 Value::Calendar(n, u) => write!(f, "{} {}", n, u),
779 Value::Range(left, right) => write!(f, "{}...{}", left, right),
780 }
781 }
782}
783
784impl std::str::FromStr for DateTimeValue {
789 type Err = String;
790
791 fn from_str(s: &str) -> Result<Self, Self::Err> {
792 if let Ok(dt) = s.parse::<chrono::DateTime<chrono::FixedOffset>>() {
793 let offset = dt.offset().local_minus_utc();
794 let microsecond = dt.nanosecond() / 1000 % 1_000_000;
795 return Ok(DateTimeValue {
796 year: dt.year(),
797 month: dt.month(),
798 day: dt.day(),
799 hour: dt.hour(),
800 minute: dt.minute(),
801 second: dt.second(),
802 microsecond,
803 timezone: Some(TimezoneValue {
804 offset_hours: (offset / 3600) as i8,
805 offset_minutes: ((offset.abs() % 3600) / 60) as u8,
806 }),
807 });
808 }
809 if let Ok(dt) = s.parse::<chrono::NaiveDateTime>() {
810 let microsecond = dt.nanosecond() / 1000 % 1_000_000;
811 return Ok(DateTimeValue {
812 year: dt.year(),
813 month: dt.month(),
814 day: dt.day(),
815 hour: dt.hour(),
816 minute: dt.minute(),
817 second: dt.second(),
818 microsecond,
819 timezone: None,
820 });
821 }
822 if let Ok(d) = s.parse::<chrono::NaiveDate>() {
823 return Ok(DateTimeValue {
824 year: d.year(),
825 month: d.month(),
826 day: d.day(),
827 hour: 0,
828 minute: 0,
829 second: 0,
830 microsecond: 0,
831 timezone: None,
832 });
833 }
834 if let Some(week_val) = Self::parse_iso_week(s) {
835 return Ok(week_val);
836 }
837 if let Ok(ym) = chrono::NaiveDate::parse_from_str(&format!("{}-01", s), "%Y-%m-%d") {
838 return Ok(Self {
839 year: ym.year(),
840 month: ym.month(),
841 day: 1,
842 hour: 0,
843 minute: 0,
844 second: 0,
845 microsecond: 0,
846 timezone: None,
847 });
848 }
849 if let Ok(year) = s.parse::<i32>() {
850 if (1..=9999).contains(&year) {
851 return Ok(Self {
852 year,
853 month: 1,
854 day: 1,
855 hour: 0,
856 minute: 0,
857 second: 0,
858 microsecond: 0,
859 timezone: None,
860 });
861 }
862 }
863 Err(format!("Invalid date format: '{}'", s))
864 }
865}
866
867impl std::str::FromStr for TimeValue {
868 type Err = String;
869
870 fn from_str(s: &str) -> Result<Self, Self::Err> {
871 let trimmed = s.trim();
872
873 let (time_text, timezone) = if trimmed.ends_with('Z') || trimmed.ends_with('z') {
874 (
875 &trimmed[..trimmed.len() - 1],
876 Some(TimezoneValue {
877 offset_hours: 0,
878 offset_minutes: 0,
879 }),
880 )
881 } else if trimmed.len() > 1 {
882 if let Some(sign_index) = trimmed[1..].rfind(['+', '-']).map(|index| index + 1) {
883 let timezone_text = &trimmed[sign_index..];
884 if timezone_text.len() == 6
885 && (timezone_text.starts_with('+') || timezone_text.starts_with('-'))
886 && timezone_text.as_bytes()[3] == b':'
887 {
888 let offset_hours: i8 = timezone_text[1..3]
889 .parse()
890 .map_err(|_| format!("Invalid time format: '{}'", s))?;
891 let offset_minutes: u8 = timezone_text[4..6]
892 .parse()
893 .map_err(|_| format!("Invalid time format: '{}'", s))?;
894 let signed_hours = if timezone_text.starts_with('-') {
895 -offset_hours
896 } else {
897 offset_hours
898 };
899 (
900 &trimmed[..sign_index],
901 Some(TimezoneValue {
902 offset_hours: signed_hours,
903 offset_minutes,
904 }),
905 )
906 } else {
907 (trimmed, None)
908 }
909 } else {
910 (trimmed, None)
911 }
912 } else {
913 (trimmed, None)
914 };
915
916 if let Ok(t) = chrono::NaiveTime::parse_from_str(time_text, "%H:%M:%S%.f") {
917 return Ok(TimeValue {
918 hour: t.hour() as u8,
919 minute: t.minute() as u8,
920 second: t.second() as u8,
921 microsecond: t.nanosecond() / 1000 % 1_000_000,
922 timezone,
923 });
924 }
925 if let Ok(t) = chrono::NaiveTime::parse_from_str(time_text, "%H:%M:%S") {
926 return Ok(TimeValue {
927 hour: t.hour() as u8,
928 minute: t.minute() as u8,
929 second: t.second() as u8,
930 microsecond: 0,
931 timezone,
932 });
933 }
934 if let Ok(t) = chrono::NaiveTime::parse_from_str(time_text, "%H:%M") {
935 return Ok(TimeValue {
936 hour: t.hour() as u8,
937 minute: t.minute() as u8,
938 second: 0,
939 microsecond: 0,
940 timezone,
941 });
942 }
943 Err(format!("Invalid time format: '{}'", s))
944 }
945}
946
947pub(crate) struct NumberLiteral(pub Decimal);
949
950impl std::str::FromStr for NumberLiteral {
951 type Err = String;
952
953 fn from_str(s: &str) -> Result<Self, Self::Err> {
954 let clean = s.trim().replace(['_', ','], "");
955 let digit_count = clean.chars().filter(|c| c.is_ascii_digit()).count();
956 if digit_count > crate::limits::MAX_NUMBER_DIGITS {
957 return Err(format!(
958 "Number has too many digits (max {})",
959 crate::limits::MAX_NUMBER_DIGITS
960 ));
961 }
962 Decimal::from_str(&clean)
963 .map_err(|_| format!("Invalid number: '{}'", s))
964 .map(NumberLiteral)
965 }
966}
967
968pub(crate) struct TextLiteral(pub String);
970
971impl std::str::FromStr for TextLiteral {
972 type Err = String;
973
974 fn from_str(s: &str) -> Result<Self, Self::Err> {
975 if s.len() > crate::limits::MAX_TEXT_VALUE_LENGTH {
976 return Err(format!(
977 "Text value exceeds maximum length (max {} characters)",
978 crate::limits::MAX_TEXT_VALUE_LENGTH
979 ));
980 }
981 Ok(TextLiteral(s.to_string()))
982 }
983}
984
985pub(crate) struct CalendarLiteral(pub Decimal, pub CalendarUnit);
987
988impl std::str::FromStr for CalendarLiteral {
989 type Err = String;
990
991 fn from_str(s: &str) -> Result<Self, Self::Err> {
992 let trimmed = s.trim();
993 let mut parts: Vec<&str> = trimmed.split_whitespace().collect();
994 if parts.len() < 2 {
995 return Err(format!(
996 "Invalid calendar value: '{}'. Expected format: <number> <unit> (e.g. 10 months, 2 years)",
997 s
998 ));
999 }
1000 let unit_str = parts.pop().unwrap();
1001 let number_str = parts.join(" ");
1002 let n = number_str
1003 .parse::<NumberLiteral>()
1004 .map_err(|_| format!("Invalid calendar number: '{}'", number_str))?
1005 .0;
1006 let unit = unit_str.parse()?;
1007 Ok(CalendarLiteral(n, unit))
1008 }
1009}
1010
1011pub(crate) struct NumberWithUnit(pub Decimal, pub String);
1013
1014impl std::str::FromStr for NumberWithUnit {
1015 type Err = String;
1016
1017 fn from_str(s: &str) -> Result<Self, Self::Err> {
1018 let trimmed = s.trim();
1019 if trimmed.is_empty() {
1020 return Err(
1021 "Quantity value cannot be empty. Use a number followed by a unit (e.g. '10 eur')."
1022 .to_string(),
1023 );
1024 }
1025
1026 let mut parts = trimmed.split_whitespace();
1027 let number_part = parts
1028 .next()
1029 .expect("split_whitespace yields >=1 token after non-empty guard");
1030 let unit_part = parts.next().ok_or_else(|| {
1031 format!(
1032 "Quantity value must include a unit (e.g. '{} eur').",
1033 number_part
1034 )
1035 })?;
1036 if parts.next().is_some() {
1037 return Err(format!(
1038 "Invalid quantity value: '{}'. Expected exactly '<number> <unit>', got extra tokens.",
1039 s
1040 ));
1041 }
1042 let n = number_part
1043 .parse::<NumberLiteral>()
1044 .map_err(|_| format!("Invalid quantity: '{}'", s))?
1045 .0;
1046 Ok(NumberWithUnit(n, unit_part.to_string()))
1047 }
1048}
1049
1050#[derive(Debug, Clone, PartialEq, Eq)]
1075pub(crate) enum RatioLiteral {
1076 Bare(Decimal),
1077 Percent(Decimal),
1078 Permille(Decimal),
1079 Named { value: Decimal, unit: String },
1080}
1081
1082impl std::str::FromStr for RatioLiteral {
1083 type Err = String;
1084
1085 fn from_str(s: &str) -> Result<Self, Self::Err> {
1086 let trimmed = s.trim();
1087 if trimmed.is_empty() {
1088 return Err(
1089 "Ratio value cannot be empty. Use a number, optionally followed by '%', '%%', or a unit name (e.g. '0.5', '50%', '25%%', '50 percent')."
1090 .to_string(),
1091 );
1092 }
1093
1094 let mut parts = trimmed.split_whitespace();
1095 let first = parts
1096 .next()
1097 .expect("split_whitespace yields >=1 token after non-empty guard");
1098 let second = parts.next();
1099 if parts.next().is_some() {
1100 return Err(format!(
1101 "Invalid ratio value: '{}'. Expected '<number>', '<number>%', '<number>%%', or '<number> <unit>'.",
1102 s
1103 ));
1104 }
1105
1106 match second {
1107 None => {
1109 if let Some(rest) = first.strip_suffix("%%") {
1110 if rest.is_empty() {
1111 return Err(format!(
1112 "Invalid ratio value: '{}'. '%%' must follow a number (e.g. '25%%').",
1113 s
1114 ));
1115 }
1116 let n = rest
1117 .parse::<NumberLiteral>()
1118 .map_err(|_| {
1119 format!(
1120 "Invalid ratio value: '{}'. '{}' is not a valid number before '%%'.",
1121 s, rest
1122 )
1123 })?
1124 .0;
1125 return Ok(RatioLiteral::Permille(n));
1126 }
1127 if let Some(rest) = first.strip_suffix('%') {
1128 if rest.is_empty() {
1129 return Err(format!(
1130 "Invalid ratio value: '{}'. '%' must follow a number (e.g. '50%').",
1131 s
1132 ));
1133 }
1134 let n = rest
1135 .parse::<NumberLiteral>()
1136 .map_err(|_| {
1137 format!(
1138 "Invalid ratio value: '{}'. '{}' is not a valid number before '%'.",
1139 s, rest
1140 )
1141 })?
1142 .0;
1143 return Ok(RatioLiteral::Percent(n));
1144 }
1145 let n = first.parse::<NumberLiteral>().map_err(|_| {
1146 format!(
1147 "Invalid ratio value: '{}'. Must be a number, '<n>%', '<n>%%', '<n> percent', '<n> permille', or '<n> <unit>'.",
1148 s
1149 )
1150 })?.0;
1151 Ok(RatioLiteral::Bare(n))
1152 }
1153 Some(unit) => {
1155 if unit == "%" || unit == "%%" {
1156 return Err(format!(
1157 "Invalid ratio value: '{}'. '{}' must be glued to the number (e.g. '{}{}'), not separated by whitespace.",
1158 s, unit, first, unit
1159 ));
1160 }
1161 let n = first
1162 .parse::<NumberLiteral>()
1163 .map_err(|_| {
1164 format!(
1165 "Invalid ratio value: '{}'. '{}' is not a valid number.",
1166 s, first
1167 )
1168 })?
1169 .0;
1170 Ok(RatioLiteral::Named {
1171 value: n,
1172 unit: unit.to_string(),
1173 })
1174 }
1175 }
1176 }
1177}