1use crate::Rule;
2use indexmap::IndexMap;
3use log::trace;
4use pest::iterators::{Pair, Pairs};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::fmt::{Display, Formatter};
9use std::ops::{Add, Deref, Sub};
10
11#[derive(Debug, Clone)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[cfg_attr(feature = "serde", serde(transparent))]
15pub struct DateTimeValue {
16 value: chrono::DateTime<chrono::FixedOffset>,
17 #[cfg_attr(feature = "serde", serde(skip))]
18 timezone: Option<TimezoneValue>,
19 #[cfg_attr(feature = "serde", serde(skip))]
20 zone_name: Option<String>,
21}
22
23impl DateTimeValue {
24 pub fn fixed(value: chrono::DateTime<chrono::FixedOffset>) -> Self {
25 Self { value, timezone: None, zone_name: None }
26 }
27
28 pub fn zoned(
29 value: chrono::DateTime<chrono_tz::Tz>,
30 timezone: impl Into<TimezoneValue>,
31 ) -> Self {
32 Self {
33 value: value.fixed_offset(),
34 timezone: Some(timezone.into()),
35 zone_name: None,
36 }
37 }
38
39 pub(crate) fn with_timezone(&self, timezone: TimezoneValue) -> Self {
40 Self::zoned(self.value.with_timezone(&timezone.timezone()), timezone)
41 }
42
43 pub(crate) fn timezone(&self) -> Option<TimezoneValue> {
44 self.timezone
45 }
46
47 pub(crate) fn with_zone_name(mut self, zone_name: String) -> Self {
48 self.zone_name = Some(zone_name);
49 self
50 }
51
52 pub fn checked_add_signed(mut self, duration: chrono::Duration) -> Option<Self> {
53 self.value = self.value.checked_add_signed(duration)?;
54 self.refresh_timezone();
55 Some(self)
56 }
57
58 pub fn checked_sub_signed(mut self, duration: chrono::Duration) -> Option<Self> {
59 self.value = self.value.checked_sub_signed(duration)?;
60 self.refresh_timezone();
61 Some(self)
62 }
63
64 fn refresh_timezone(&mut self) {
65 if let Some(timezone) = self.timezone {
66 self.value = self.value.with_timezone(&timezone.timezone()).fixed_offset();
67 }
68 }
69
70 pub(crate) fn zone_name(&self) -> String {
71 if let Some(zone_name) = &self.zone_name {
72 return zone_name.clone();
73 }
74 if let Some(timezone) = self.timezone {
75 self.value
76 .with_timezone(&timezone.timezone())
77 .format("%Z")
78 .to_string()
79 } else if self.value.offset().local_minus_utc() == 0 {
80 "UTC".to_string()
81 } else {
82 self.value.format("%z").to_string()
83 }
84 }
85}
86
87impl From<chrono::DateTime<chrono::FixedOffset>> for DateTimeValue {
88 fn from(value: chrono::DateTime<chrono::FixedOffset>) -> Self {
89 Self::fixed(value)
90 }
91}
92
93impl Deref for DateTimeValue {
94 type Target = chrono::DateTime<chrono::FixedOffset>;
95
96 fn deref(&self) -> &Self::Target {
97 &self.value
98 }
99}
100
101impl PartialEq for DateTimeValue {
102 fn eq(&self, other: &Self) -> bool {
103 self.value == other.value
104 }
105}
106
107impl PartialOrd for DateTimeValue {
108 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
109 self.value.partial_cmp(&other.value)
110 }
111}
112
113impl Add<chrono::Duration> for DateTimeValue {
114 type Output = Self;
115
116 fn add(mut self, duration: chrono::Duration) -> Self::Output {
117 self.value += duration;
118 self.refresh_timezone();
119 self
120 }
121}
122
123impl Sub<chrono::Duration> for DateTimeValue {
124 type Output = Self;
125
126 fn sub(mut self, duration: chrono::Duration) -> Self::Output {
127 self.value -= duration;
128 self.refresh_timezone();
129 self
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[cfg_attr(feature = "serde", serde(transparent))]
137pub struct TimezoneValue {
138 timezone: chrono_tz::Tz,
139 #[cfg_attr(feature = "serde", serde(skip))]
140 local: bool,
141}
142
143impl TimezoneValue {
144 pub fn named(timezone: chrono_tz::Tz) -> Self {
146 Self { timezone, local: false }
147 }
148
149 pub fn local() -> Result<Self, String> {
151 let timezone = match std::env::var_os("TZ") {
152 Some(timezone) => timezone_from_env(&timezone),
153 None => {
154 iana_time_zone::get_timezone()
155 .map_err(|error| error.to_string())?
156 .parse::<chrono_tz::Tz>()
157 .map_err(|error| error.to_string())
158 }?,
159 };
160 Ok(Self { timezone, local: true })
161 }
162
163 #[cfg(test)]
164 pub(crate) fn local_with_timezone(timezone: chrono_tz::Tz) -> Self {
165 Self { timezone, local: true }
166 }
167
168 pub fn timezone(self) -> chrono_tz::Tz {
170 self.timezone
171 }
172
173 pub fn name(self) -> &'static str {
175 if self.local {
176 "Local"
177 } else {
178 self.timezone.name()
179 }
180 }
181
182 pub fn is_local(self) -> bool {
184 self.local
185 }
186}
187
188fn timezone_from_env(value: &std::ffi::OsStr) -> chrono_tz::Tz {
189 value
190 .to_str()
191 .and_then(|timezone| {
192 timezone
193 .strip_prefix(':')
194 .unwrap_or(timezone)
195 .parse::<chrono_tz::Tz>()
196 .ok()
197 })
198 .unwrap_or(chrono_tz::UTC)
199}
200
201impl From<chrono_tz::Tz> for TimezoneValue {
202 fn from(timezone: chrono_tz::Tz) -> Self {
203 Self::named(timezone)
204 }
205}
206
207impl Display for TimezoneValue {
208 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
209 f.write_str(self.name())
210 }
211}
212
213#[cfg(test)]
214mod timezone_tests {
215 use super::*;
216
217 #[test]
218 fn empty_and_invalid_tz_environment_values_use_utc() {
219 assert_eq!(timezone_from_env(std::ffi::OsStr::new("")), chrono_tz::UTC);
220 assert_eq!(
221 timezone_from_env(std::ffi::OsStr::new("not-a-timezone")),
222 chrono_tz::UTC
223 );
224 }
225
226 #[test]
227 fn tz_environment_value_accepts_go_colon_prefix() {
228 assert_eq!(
229 timezone_from_env(std::ffi::OsStr::new(":America/New_York")),
230 chrono_tz::America::New_York
231 );
232 }
233}
234
235impl Sub for DateTimeValue {
236 type Output = chrono::Duration;
237
238 fn sub(self, other: Self) -> Self::Output {
239 self.value - other.value
240 }
241}
242
243#[derive(Debug, Default, Clone, PartialEq)]
245#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
246#[cfg_attr(feature = "serde", serde(untagged))]
247pub enum Value {
248 Integer(i64),
249 Bool(bool),
250 Float(f64),
251 #[default]
252 Nil,
253 String(String),
254 DateTime(DateTimeValue),
255 Duration(i64),
256 Timezone(TimezoneValue),
257 Month(u32),
258 Weekday(u32),
259 Array(Vec<Value>),
260 Bytes(Vec<u8>),
262 Map(IndexMap<String, Value>),
263 KeyedMap(Vec<(Value, Value)>),
265}
266
267impl Value {
268 pub(crate) fn parse_integer(value: &str) -> std::result::Result<i64, std::num::ParseIntError> {
269 let value = value.replace('_', "");
270 let (digits, radix) = match value.as_bytes() {
271 [b'0', b'x' | b'X', ..] => (&value[2..], 16),
272 [b'0', b'o' | b'O', ..] => (&value[2..], 8),
273 [b'0', b'b' | b'B', ..] => (&value[2..], 2),
274 _ => (value.as_str(), 10),
275 };
276 i64::from_str_radix(digits, radix)
277 }
278
279 pub(crate) fn parse_float(value: &str) -> std::result::Result<f64, std::num::ParseFloatError> {
280 value.replace('_', "").parse()
281 }
282
283 pub fn as_bool(&self) -> Option<bool> {
284 match self {
285 Value::Bool(b) => Some(*b),
286 _ => None,
287 }
288 }
289
290 pub fn as_integer(&self) -> Option<i64> {
291 match self {
292 Value::Integer(n) => Some(*n),
293 _ => None,
294 }
295 }
296
297 pub fn as_float(&self) -> Option<f64> {
298 match self {
299 Value::Float(f) => Some(*f),
300 _ => None,
301 }
302 }
303
304 pub fn as_string(&self) -> Option<&str> {
305 match self {
306 Value::String(s) => Some(s),
307 _ => None,
308 }
309 }
310
311 pub fn as_bytes(&self) -> Option<&[u8]> {
312 match self {
313 Value::Bytes(bytes) => Some(bytes),
314 _ => None,
315 }
316 }
317
318 pub fn as_datetime(&self) -> Option<&chrono::DateTime<chrono::FixedOffset>> {
319 match self {
320 Value::DateTime(value) => Some(&value.value),
321 _ => None,
322 }
323 }
324
325 pub fn as_duration(&self) -> Option<i64> {
326 match self {
327 Value::Duration(value) => Some(*value),
328 _ => None,
329 }
330 }
331
332 pub fn as_array(&self) -> Option<&[Value]> {
333 match self {
334 Value::Array(a) => Some(a),
335 _ => None,
336 }
337 }
338
339 pub fn as_map(&self) -> Option<&IndexMap<String, Value>> {
340 match self {
341 Value::Map(m) => Some(m),
342 _ => None,
343 }
344 }
345
346 pub fn as_keyed_map(&self) -> Option<&[(Value, Value)]> {
347 match self {
348 Value::KeyedMap(m) => Some(m),
349 _ => None,
350 }
351 }
352
353 pub fn is_nil(&self) -> bool {
354 matches!(self, Value::Nil)
355 }
356}
357
358impl<K, V> FromIterator<(K, V)> for Value
359where
360 K: Into<String>,
361 V: Into<Value>,
362{
363 fn from_iter<I>(iter: I) -> Self
364 where I: IntoIterator<Item = (K, V)> {
365 Value::Map(iter.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
366 }
367}
368
369impl AsRef<Value> for Value {
370 fn as_ref(&self) -> &Value {
371 self
372 }
373}
374
375impl From<i64> for Value {
376 fn from(n: i64) -> Self {
377 Value::Integer(n)
378 }
379}
380
381impl From<i32> for Value {
382 fn from(n: i32) -> Self {
383 Value::Integer(n as i64)
384 }
385}
386
387impl From<usize> for Value {
388 fn from(n: usize) -> Self {
389 Value::Integer(n as i64)
390 }
391}
392
393impl From<f64> for Value {
394 fn from(f: f64) -> Self {
395 Value::Float(f)
396 }
397}
398
399impl From<bool> for Value {
400 fn from(b: bool) -> Self {
401 Value::Bool(b)
402 }
403}
404
405impl From<String> for Value {
406 fn from(s: String) -> Self {
407 Value::String(s)
408 }
409}
410
411impl From<&String> for Value {
412 fn from(s: &String) -> Self {
413 s.to_string().into()
414 }
415}
416
417impl From<&str> for Value {
418 fn from(s: &str) -> Self {
419 s.to_string().into()
420 }
421}
422
423impl<V: Into<Value>> From<Vec<V>> for Value {
424 fn from(a: Vec<V>) -> Self {
425 Value::Array(a.into_iter().map(|v| v.into()).collect())
426 }
427}
428
429impl From<IndexMap<String, Value>> for Value {
430 fn from(m: IndexMap<String, Value>) -> Self {
431 Value::Map(m)
432 }
433}
434
435impl Display for Value {
436 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
437 match self {
438 Value::Integer(n) => write!(f, "{n}"),
439 Value::Float(n) => write!(f, "{n}"),
440 Value::Bool(b) => write!(f, "{b}"),
441 Value::Nil => write!(f, "nil"),
442 Value::String(s) => write!(
443 f,
444 r#""{}""#,
445 s.replace("\\", "\\\\")
446 .replace("\n", "\\n")
447 .replace("\r", "\\r")
448 .replace("\t", "\\t")
449 .replace("\"", "\\\"")
450 ),
451 Value::Bytes(bytes) => write!(
452 f,
453 "[{}]",
454 bytes
455 .iter()
456 .map(u8::to_string)
457 .collect::<Vec<String>>()
458 .join(" ")
459 ),
460 Value::DateTime(value) => write!(f, "{}", value.to_rfc3339()),
461 Value::Duration(value) => write!(f, "{value}ns"),
462 Value::Timezone(value) => write!(f, "{value}"),
463 Value::Month(value) | Value::Weekday(value) => write!(f, "{value}"),
464 Value::Array(a) => write!(
465 f,
466 "[{}]",
467 a.iter()
468 .map(|v| v.to_string())
469 .collect::<Vec<String>>()
470 .join(", ")
471 ),
472 Value::Map(m) => write!(
473 f,
474 "{{{}}}",
475 m.iter()
476 .map(|(k, v)| format!("{}: {}", k, v))
477 .collect::<Vec<String>>()
478 .join(", ")
479 ),
480 Value::KeyedMap(m) => write!(
481 f,
482 "{{{}}}",
483 m.iter()
484 .map(|(k, v)| format!("{}: {}", k, v))
485 .collect::<Vec<String>>()
486 .join(", ")
487 ),
488 }
489 }
490}
491
492impl From<Pairs<'_, Rule>> for Value {
493 fn from(mut pairs: Pairs<Rule>) -> Self {
494 pairs.next().unwrap().into()
495 }
496}
497
498impl From<Pair<'_, Rule>> for Value {
499 fn from(pair: Pair<Rule>) -> Self {
500 trace!("{:?} = {}", pair.as_rule(), pair.as_str());
501 match pair.as_rule() {
502 Rule::value => pair.into_inner().into(),
503 Rule::nil => Value::Nil,
504 Rule::bool => Value::Bool(pair.as_str().parse().unwrap()),
505 Rule::int => {
506 Value::Integer(Value::parse_integer(pair.as_str()).expect("literal validated"))
507 }
508 Rule::decimal => {
509 Value::Float(Value::parse_float(pair.as_str()).expect("literal validated"))
510 }
511 Rule::bytes => Value::Bytes(parse_bytes_literal(pair.as_str())),
512 Rule::string_multiline => pair.into_inner().as_str().into(),
513 Rule::string => pair
514 .into_inner()
515 .as_str()
516 .replace("\\\\", "\\")
517 .replace("\\n", "\n")
518 .replace("\\r", "\r")
519 .replace("\\t", "\t")
520 .replace("\\\"", "\"")
521 .into(),
522 rule => unreachable!("Unexpected rule: {rule:?} {}", pair.as_str()),
534 }
535 }
536}
537
538fn parse_bytes_literal(literal: &str) -> Vec<u8> {
539 let mut chars = literal[2..literal.len() - 1].chars();
540 let mut bytes = Vec::new();
541 while let Some(character) = chars.next() {
542 if character != '\\' {
543 let mut encoded = [0; 4];
544 bytes.extend_from_slice(character.encode_utf8(&mut encoded).as_bytes());
545 continue;
546 }
547
548 let escape = chars.next().expect("byte escape validated by grammar");
549 match escape {
550 'a' => bytes.push(7),
551 'b' => bytes.push(8),
552 'f' => bytes.push(12),
553 'n' => bytes.push(b'\n'),
554 'r' => bytes.push(b'\r'),
555 't' => bytes.push(b'\t'),
556 'v' => bytes.push(11),
557 '\\' | '\'' | '"' => bytes.push(escape as u8),
558 'x' => {
559 let digits = [
560 chars.next().expect("hex escape validated by grammar"),
561 chars.next().expect("hex escape validated by grammar"),
562 ];
563 bytes.push(
564 u8::from_str_radix(&digits.iter().collect::<String>(), 16)
565 .expect("hex escape validated by grammar"),
566 );
567 }
568 digit @ '0'..='7' => {
569 let digits = [
570 digit,
571 chars.next().expect("octal escape validated by grammar"),
572 chars.next().expect("octal escape validated by grammar"),
573 ];
574 bytes.push(
575 u8::from_str_radix(&digits.iter().collect::<String>(), 8)
576 .expect("octal escape validated by grammar"),
577 );
578 }
579 _ => unreachable!("byte escape validated by grammar"),
580 }
581 }
582 bytes
583}