use std::borrow;
use std::cmp::Ordering;
use std::fmt;
use chrono;
pub type Date = chrono::DateTime<chrono::FixedOffset>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScalarCow<'s>(ScalarCowEnum<'s>);
pub type Scalar = ScalarCow<'static>;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum ScalarCowEnum<'s> {
Integer(i32),
Float(f64),
Bool(bool),
#[serde(with = "friendly_date")]
Date(Date),
Str(borrow::Cow<'s, str>),
}
impl<'s> ScalarCow<'s> {
pub fn new<T: Into<Self>>(value: T) -> Self {
value.into()
}
pub fn source(&self) -> ScalarSource {
ScalarSource(&self.0)
}
pub fn render(&self) -> ScalarRendered {
ScalarRendered(&self.0)
}
pub fn into_owned(self) -> Self {
match self.0 {
ScalarCowEnum::Str(x) => Scalar::new(x.into_owned()),
_ => self,
}
}
pub fn as_ref<'r: 's>(&'r self) -> ScalarCow<'r> {
match self.0 {
ScalarCowEnum::Integer(x) => Scalar::new(x),
ScalarCowEnum::Float(x) => Scalar::new(x),
ScalarCowEnum::Bool(x) => Scalar::new(x),
ScalarCowEnum::Date(x) => Scalar::new(x),
ScalarCowEnum::Str(ref x) => Scalar::new(x.as_ref()),
}
}
pub fn to_str(&self) -> borrow::Cow<str> {
match self.0 {
ScalarCowEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()),
ScalarCowEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()),
ScalarCowEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()),
ScalarCowEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()),
ScalarCowEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_ref()),
}
}
pub fn into_string(self) -> String {
match self.0 {
ScalarCowEnum::Integer(x) => x.to_string(),
ScalarCowEnum::Float(x) => x.to_string(),
ScalarCowEnum::Bool(x) => x.to_string(),
ScalarCowEnum::Date(x) => x.to_string(),
ScalarCowEnum::Str(x) => x.into_owned(),
}
}
pub fn to_integer(&self) -> Option<i32> {
match self.0 {
ScalarCowEnum::Integer(ref x) => Some(*x),
ScalarCowEnum::Str(ref x) => x.parse::<i32>().ok(),
_ => None,
}
}
pub fn to_float(&self) -> Option<f64> {
match self.0 {
ScalarCowEnum::Integer(ref x) => Some(f64::from(*x)),
ScalarCowEnum::Float(ref x) => Some(*x),
ScalarCowEnum::Str(ref x) => x.parse::<f64>().ok(),
_ => None,
}
}
pub fn to_bool(&self) -> Option<bool> {
match self.0 {
ScalarCowEnum::Bool(ref x) => Some(*x),
_ => None,
}
}
pub fn to_date(&self) -> Option<Date> {
match self.0 {
ScalarCowEnum::Date(ref x) => Some(*x),
ScalarCowEnum::Str(ref x) => parse_date(x.as_ref()),
_ => None,
}
}
pub fn is_truthy(&self) -> bool {
match self.0 {
ScalarCowEnum::Bool(ref x) => *x,
_ => true,
}
}
pub fn is_default(&self) -> bool {
match self.0 {
ScalarCowEnum::Bool(ref x) => !*x,
ScalarCowEnum::Str(ref x) => x.is_empty(),
_ => false,
}
}
pub fn type_name(&self) -> &'static str {
match self.0 {
ScalarCowEnum::Integer(_) => "whole number",
ScalarCowEnum::Float(_) => "fractional number",
ScalarCowEnum::Bool(_) => "boolean",
ScalarCowEnum::Date(_) => "date",
ScalarCowEnum::Str(_) => "string",
}
}
}
impl<'s> From<i32> for ScalarCow<'s> {
fn from(s: i32) -> Self {
ScalarCow {
0: ScalarCowEnum::Integer(s),
}
}
}
impl<'s> From<f64> for ScalarCow<'s> {
fn from(s: f64) -> Self {
ScalarCow {
0: ScalarCowEnum::Float(s),
}
}
}
impl<'s> From<bool> for ScalarCow<'s> {
fn from(s: bool) -> Self {
ScalarCow {
0: ScalarCowEnum::Bool(s),
}
}
}
impl<'s> From<Date> for ScalarCow<'s> {
fn from(s: Date) -> Self {
ScalarCow {
0: ScalarCowEnum::Date(s),
}
}
}
impl<'s> From<String> for ScalarCow<'s> {
fn from(s: String) -> Self {
ScalarCow {
0: ScalarCowEnum::Str(s.into()),
}
}
}
impl<'s> From<&'s String> for ScalarCow<'s> {
fn from(s: &'s String) -> ScalarCow<'s> {
ScalarCow {
0: ScalarCowEnum::Str(s.as_str().into()),
}
}
}
impl<'s> From<&'s str> for ScalarCow<'s> {
fn from(s: &'s str) -> Self {
ScalarCow {
0: ScalarCowEnum::Str(s.into()),
}
}
}
impl<'s> From<borrow::Cow<'s, str>> for ScalarCow<'s> {
fn from(s: borrow::Cow<'s, str>) -> Self {
ScalarCow {
0: ScalarCowEnum::Str(s),
}
}
}
impl<'s> PartialEq<ScalarCow<'s>> for ScalarCow<'s> {
fn eq(&self, other: &Self) -> bool {
scalar_eq(self, other)
}
}
impl<'s> PartialEq<i32> for ScalarCow<'s> {
fn eq(&self, other: &i32) -> bool {
let other = (*other).into();
scalar_eq(self, &other)
}
}
impl<'s> PartialEq<f64> for ScalarCow<'s> {
fn eq(&self, other: &f64) -> bool {
let other = (*other).into();
scalar_eq(self, &other)
}
}
impl<'s> PartialEq<bool> for ScalarCow<'s> {
fn eq(&self, other: &bool) -> bool {
let other = (*other).into();
scalar_eq(self, &other)
}
}
impl<'s> PartialEq<Date> for ScalarCow<'s> {
fn eq(&self, other: &Date) -> bool {
let other = (*other).into();
scalar_eq(self, &other)
}
}
impl<'s> PartialEq<str> for ScalarCow<'s> {
fn eq(&self, other: &str) -> bool {
let other = other.into();
scalar_eq(self, &other)
}
}
impl<'s> Eq for ScalarCow<'s> {}
impl<'s> PartialOrd<ScalarCow<'s>> for ScalarCow<'s> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
scalar_cmp(self, other)
}
}
impl<'s> PartialOrd<i32> for ScalarCow<'s> {
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
let other = (*other).into();
scalar_cmp(self, &other)
}
}
impl<'s> PartialOrd<f64> for ScalarCow<'s> {
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
let other = (*other).into();
scalar_cmp(self, &other)
}
}
impl<'s> PartialOrd<bool> for ScalarCow<'s> {
fn partial_cmp(&self, other: &bool) -> Option<Ordering> {
let other = (*other).into();
scalar_cmp(self, &other)
}
}
impl<'s> PartialOrd<Date> for ScalarCow<'s> {
fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
let other = (*other).into();
scalar_cmp(self, &other)
}
}
impl<'s> PartialOrd<str> for ScalarCow<'s> {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
let other = other.into();
scalar_cmp(self, &other)
}
}
#[derive(Debug)]
pub struct ScalarSource<'s>(&'s ScalarCowEnum<'s>);
impl<'s> fmt::Display for ScalarSource<'s> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ScalarCowEnum::Integer(ref x) => write!(f, "{}", x),
ScalarCowEnum::Float(ref x) => write!(f, "{}", x),
ScalarCowEnum::Bool(ref x) => write!(f, "{}", x),
ScalarCowEnum::Date(ref x) => write!(f, "{}", x.format(DATE_FORMAT)),
ScalarCowEnum::Str(ref x) => write!(f, r#""{}""#, x),
}
}
}
#[derive(Debug)]
pub struct ScalarRendered<'s>(&'s ScalarCowEnum<'s>);
impl<'s> fmt::Display for ScalarRendered<'s> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ScalarCowEnum::Integer(ref x) => write!(f, "{}", x),
ScalarCowEnum::Float(ref x) => write!(f, "{}", x),
ScalarCowEnum::Bool(ref x) => write!(f, "{}", x),
ScalarCowEnum::Date(ref x) => write!(f, "{}", x.format(DATE_FORMAT)),
ScalarCowEnum::Str(ref x) => write!(f, "{}", x),
}
}
}
fn scalar_eq<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> bool {
match (&lhs.0, &rhs.0) {
(&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x == y,
(&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => (f64::from(x)) == y,
(&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x == (f64::from(y)),
(&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x == y,
(&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x == y,
(&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x == y,
(&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x == y,
(_, &ScalarCowEnum::Bool(b)) | (&ScalarCowEnum::Bool(b), _) => b,
_ => false,
}
}
fn scalar_cmp<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> Option<Ordering> {
match (&lhs.0, &rhs.0) {
(&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&y),
(&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => (f64::from(x)).partial_cmp(&y),
(&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&(f64::from(y))),
(&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x.partial_cmp(&y),
(&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x.partial_cmp(&y),
(&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x.partial_cmp(&y),
(&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x.partial_cmp(y),
_ => None,
}
}
const DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";
mod friendly_date {
use super::*;
use serde::{self, Deserialize, Deserializer, Serializer};
pub(crate) fn serialize<S>(date: &Date, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = date.format(DATE_FORMAT).to_string();
serializer.serialize_str(&s)
}
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Date::parse_from_str(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
}
}
fn parse_date(s: &str) -> Option<Date> {
match s {
"now" | "today" => {
let now = chrono::offset::Utc::now();
let now = now.naive_utc();
let now = chrono::DateTime::from_utc(now, chrono::offset::FixedOffset::east(0));
Some(now)
}
_ => {
let formats = ["%d %B %Y %H:%M:%S %z", "%Y-%m-%d %H:%M:%S %z"];
formats
.iter()
.filter_map(|f| Date::parse_from_str(s, f).ok())
.next()
}
}
}
#[cfg(test)]
mod test {
use super::*;
static TRUE: ScalarCow = ScalarCow(ScalarCowEnum::Bool(true));
static FALSE: ScalarCow = ScalarCow(ScalarCowEnum::Bool(false));
#[test]
fn test_to_str_bool() {
assert_eq!(TRUE.to_str(), "true");
}
#[test]
fn test_to_str_integer() {
let val: ScalarCow = 42i32.into();
assert_eq!(val.to_str(), "42");
}
#[test]
fn test_to_str_float() {
let val: ScalarCow = 42f64.into();
assert_eq!(val.to_str(), "42");
let val: ScalarCow = 42.34.into();
assert_eq!(val.to_str(), "42.34");
}
#[test]
fn test_to_str_str() {
let val: ScalarCow = "foobar".into();
assert_eq!(val.to_str(), "foobar");
}
#[test]
fn test_to_integer_bool() {
assert_eq!(TRUE.to_integer(), None);
}
#[test]
fn test_to_integer_integer() {
let val: ScalarCow = 42i32.into();
assert_eq!(val.to_integer(), Some(42i32));
}
#[test]
fn test_to_integer_float() {
let val: ScalarCow = 42f64.into();
assert_eq!(val.to_integer(), None);
let val: ScalarCow = 42.34.into();
assert_eq!(val.to_integer(), None);
}
#[test]
fn test_to_integer_str() {
let val: ScalarCow = "foobar".into();
assert_eq!(val.to_integer(), None);
let val: ScalarCow = "42.34".into();
assert_eq!(val.to_integer(), None);
let val: ScalarCow = "42".into();
assert_eq!(val.to_integer(), Some(42));
}
#[test]
fn test_to_float_bool() {
assert_eq!(TRUE.to_float(), None);
}
#[test]
fn test_to_float_integer() {
let val: ScalarCow = 42i32.into();
assert_eq!(val.to_float(), Some(42f64));
}
#[test]
fn test_to_float_float() {
let val: ScalarCow = 42f64.into();
assert_eq!(val.to_float(), Some(42f64));
let val: ScalarCow = 42.34.into();
assert_eq!(val.to_float(), Some(42.34));
}
#[test]
fn test_to_float_str() {
let val: ScalarCow = "foobar".into();
assert_eq!(val.to_float(), None);
let val: ScalarCow = "42.34".into();
assert_eq!(val.to_float(), Some(42.34));
let val: ScalarCow = "42".into();
assert_eq!(val.to_float(), Some(42f64));
}
#[test]
fn test_to_bool_bool() {
assert_eq!(TRUE.to_bool(), Some(true));
}
#[test]
fn test_to_bool_integer() {
let val: ScalarCow = 42i32.into();
assert_eq!(val.to_bool(), None);
}
#[test]
fn test_to_bool_float() {
let val: ScalarCow = 42f64.into();
assert_eq!(val.to_bool(), None);
let val: ScalarCow = 42.34.into();
assert_eq!(val.to_bool(), None);
}
#[test]
fn test_to_bool_str() {
let val: ScalarCow = "foobar".into();
assert_eq!(val.to_bool(), None);
let val: ScalarCow = "true".into();
assert_eq!(val.to_bool(), None);
let val: ScalarCow = "false".into();
assert_eq!(val.to_bool(), None);
}
#[test]
fn integer_equality() {
let val: ScalarCow = 42i32.into();
let zero: ScalarCow = 0i32.into();
assert_eq!(val, val);
assert_eq!(zero, zero);
assert!(val != zero);
assert!(zero != val);
}
#[test]
fn integers_have_ruby_truthiness() {
let val: ScalarCow = 42i32.into();
let zero: ScalarCow = 0i32.into();
assert_eq!(TRUE, val);
assert_eq!(val, TRUE);
assert!(val.is_truthy());
assert_eq!(TRUE, zero);
assert_eq!(zero, TRUE);
assert!(zero.is_truthy());
}
#[test]
fn float_equality() {
let val: ScalarCow = 42f64.into();
let zero: ScalarCow = 0f64.into();
assert_eq!(val, val);
assert_eq!(zero, zero);
assert!(val != zero);
assert!(zero != val);
}
#[test]
fn floats_have_ruby_truthiness() {
let val: ScalarCow = 42f64.into();
let zero: ScalarCow = 0f64.into();
assert_eq!(TRUE, val);
assert_eq!(val, TRUE);
assert!(val.is_truthy());
assert_eq!(TRUE, zero);
assert_eq!(zero, TRUE);
assert!(zero.is_truthy());
}
#[test]
fn boolean_equality() {
assert_eq!(TRUE, TRUE);
assert_eq!(FALSE, FALSE);
assert!(FALSE != TRUE);
assert!(TRUE != FALSE);
}
#[test]
fn booleans_have_ruby_truthiness() {
assert!(TRUE.is_truthy());
assert!(!FALSE.is_truthy());
}
#[test]
fn string_equality() {
let alpha: ScalarCow = "alpha".into();
let beta: ScalarCow = "beta".into();
let empty: ScalarCow = "".into();
assert_eq!(alpha, alpha);
assert_eq!(empty, empty);
assert!(alpha != beta);
assert!(beta != alpha);
}
#[test]
fn strings_have_ruby_truthiness() {
let alpha: ScalarCow = "alpha".into();
let empty: ScalarCow = "".into();
assert_eq!(TRUE, alpha);
assert_eq!(alpha, TRUE);
assert!(alpha.is_truthy());
assert_eq!(TRUE, empty);
assert_eq!(empty, TRUE);
assert!(empty.is_truthy());
}
#[test]
fn parse_date_empty_is_bad() {
assert!(parse_date("").is_none());
}
#[test]
fn parse_date_bad() {
assert!(parse_date("aaaaa").is_none());
}
#[test]
fn parse_date_now() {
assert!(parse_date("now").is_some());
}
#[test]
fn parse_date_today() {
assert!(parse_date("today").is_some());
}
}