surrealdb-sql 1.1.0

Full type definitions for the SurrealQL query language
Documentation
use super::super::{ending::duration as ending, error::expected, IResult, ParseError};
use crate::{
	duration::{
		SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE, SECONDS_PER_WEEK, SECONDS_PER_YEAR,
	},
	syn::v1::common::take_u64,
	Duration,
};
use nom::{branch::alt, bytes::complete::tag, multi::many1};
use std::time;

pub fn duration(i: &str) -> IResult<&str, Duration> {
	expected("a duration", |i| {
		let (i, v) = many1(duration_raw)(i)?;
		let (i, _) = ending(i)?;
		Ok((i, v.iter().sum::<Duration>()))
	})(i)
}

fn duration_raw(i: &str) -> IResult<&str, Duration> {
	let (i, v) = part(i)?;
	let (i, u) = unit(i)?;

	let std_duration = match u {
		"ns" => Some(time::Duration::from_nanos(v)),
		"µs" => Some(time::Duration::from_micros(v)),
		"us" => Some(time::Duration::from_micros(v)),
		"ms" => Some(time::Duration::from_millis(v)),
		"s" => Some(time::Duration::from_secs(v)),
		"m" => v.checked_mul(SECONDS_PER_MINUTE).map(time::Duration::from_secs),
		"h" => v.checked_mul(SECONDS_PER_HOUR).map(time::Duration::from_secs),
		"d" => v.checked_mul(SECONDS_PER_DAY).map(time::Duration::from_secs),
		"w" => v.checked_mul(SECONDS_PER_WEEK).map(time::Duration::from_secs),
		"y" => v.checked_mul(SECONDS_PER_YEAR).map(time::Duration::from_secs),
		_ => unreachable!("shouldn't have parsed {u} as duration unit"),
	};

	std_duration.map(|d| (i, Duration(d))).ok_or(nom::Err::Error(ParseError::Base(i)))
}

fn part(i: &str) -> IResult<&str, u64> {
	take_u64(i)
}

fn unit(i: &str) -> IResult<&str, &str> {
	alt((
		tag("ns"),
		tag("µs"),
		tag("us"),
		tag("ms"),
		tag("s"),
		tag("m"),
		tag("h"),
		tag("d"),
		tag("w"),
		tag("y"),
	))(i)
}

#[cfg(test)]
mod tests {

	use super::*;
	use std::time::Duration;

	#[test]
	fn duration_nil() {
		let sql = "0ns";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("0ns", format!("{}", out));
		assert_eq!(out.0, Duration::new(0, 0));
	}

	#[test]
	fn duration_basic() {
		let sql = "1s";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("1s", format!("{}", out));
		assert_eq!(out.0, Duration::new(1, 0));
	}

	#[test]
	fn duration_simple() {
		let sql = "1000ms";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("1s", format!("{}", out));
		assert_eq!(out.0, Duration::new(1, 0));
	}

	#[test]
	fn duration_complex() {
		let sql = "86400s";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("1d", format!("{}", out));
		assert_eq!(out.0, Duration::new(86_400, 0));
	}

	#[test]
	fn duration_days() {
		let sql = "5d";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("5d", format!("{}", out));
		assert_eq!(out.0, Duration::new(432_000, 0));
	}

	#[test]
	fn duration_weeks() {
		let sql = "4w";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("4w", format!("{}", out));
		assert_eq!(out.0, Duration::new(2_419_200, 0));
	}

	#[test]
	fn duration_split() {
		let sql = "129600s";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("1d12h", format!("{}", out));
		assert_eq!(out.0, Duration::new(129_600, 0));
	}

	#[test]
	fn duration_multi() {
		let sql = "1d12h30m";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("1d12h30m", format!("{}", out));
		assert_eq!(out.0, Duration::new(131_400, 0));
	}

	#[test]
	fn duration_milliseconds() {
		let sql = "500ms";
		let res = duration(sql);
		let out = res.unwrap().1;
		assert_eq!("500ms", format!("{}", out));
		assert_eq!(out.0, Duration::new(0, 500000000));
	}

	#[test]
	fn duration_overflow() {
		let sql = "10000000000000000d";
		let res = duration(sql);
		res.unwrap_err();
	}
}