use crate::context::FeelContext;
use crate::errors::*;
use crate::names::Name;
use crate::value_null;
use crate::values::Value;
use dsntk_common::{DsntkError, Result};
use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
pub const FEEL_TYPE_NAME_ANY: &str = "Any";
pub const FEEL_TYPE_NAME_BOOLEAN: &str = "boolean";
pub const FEEL_TYPE_NAME_DATE: &str = "date";
pub const FEEL_TYPE_NAME_DATE_AND_TIME: &str = "date and time";
pub const FEEL_TYPE_NAME_DAYS_AND_TIME_DURATION: &str = "days and time duration";
pub const FEEL_TYPE_NAME_NULL: &str = "Null";
pub const FEEL_TYPE_NAME_NUMBER: &str = "number";
pub const FEEL_TYPE_NAME_STRING: &str = "string";
pub const FEEL_TYPE_NAME_TIME: &str = "time";
pub const FEEL_TYPE_NAME_YEARS_AND_MONTHS_DURATION: &str = "years and months duration";
#[derive(Debug, Clone, PartialEq)]
#[must_use]
pub enum FeelType {
Any,
Boolean,
Context(
BTreeMap<Name, FeelType>,
),
Date,
DateTime,
DaysAndTimeDuration,
Function(
Vec<FeelType>,
Box<FeelType>,
),
List(Box<FeelType>),
Null,
Number,
Range(Box<FeelType>),
String,
Time,
YearsAndMonthsDuration,
}
impl fmt::Display for FeelType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FeelType::Any => write!(f, "{FEEL_TYPE_NAME_ANY}"),
FeelType::Boolean => write!(f, "{FEEL_TYPE_NAME_BOOLEAN}"),
FeelType::Context(entries) => {
let entries_str = entries
.iter()
.map(|(entry_name, entry_type)| format!("{entry_name}: {entry_type}"))
.collect::<Vec<String>>()
.join(", ");
write!(f, "context<{entries_str}>",)
}
FeelType::Date => write!(f, "{FEEL_TYPE_NAME_DATE}"),
FeelType::DateTime => write!(f, "{FEEL_TYPE_NAME_DATE_AND_TIME}"),
FeelType::DaysAndTimeDuration => write!(f, "{FEEL_TYPE_NAME_DAYS_AND_TIME_DURATION}"),
FeelType::Function(parameter_types, result_type) => {
let parameter_types_str = parameter_types.iter().map(|parameter_type| format!("{parameter_type}")).collect::<Vec<String>>().join(", ");
let result_type_str = result_type.to_string();
write!(f, "function<{parameter_types_str}>->{result_type_str}")
}
FeelType::List(item_type) => {
write!(f, "list<{item_type}>")
}
FeelType::Null => write!(f, "{FEEL_TYPE_NAME_NULL}"),
FeelType::Number => write!(f, "{FEEL_TYPE_NAME_NUMBER}"),
FeelType::Range(range_type) => {
write!(f, "range<{range_type}>")
}
FeelType::String => write!(f, "{FEEL_TYPE_NAME_STRING}"),
FeelType::Time => write!(f, "{FEEL_TYPE_NAME_TIME}"),
FeelType::YearsAndMonthsDuration => write!(f, "{FEEL_TYPE_NAME_YEARS_AND_MONTHS_DURATION}"),
}
}
}
impl FromStr for FeelType {
type Err = DsntkError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
FEEL_TYPE_NAME_ANY => Ok(Self::Any),
FEEL_TYPE_NAME_BOOLEAN => Ok(Self::Boolean),
FEEL_TYPE_NAME_DATE => Ok(Self::Date),
FEEL_TYPE_NAME_DATE_AND_TIME => Ok(Self::DateTime),
FEEL_TYPE_NAME_DAYS_AND_TIME_DURATION => Ok(Self::DaysAndTimeDuration),
FEEL_TYPE_NAME_NULL => Ok(Self::Null),
FEEL_TYPE_NAME_NUMBER => Ok(Self::Number),
FEEL_TYPE_NAME_STRING => Ok(Self::String),
FEEL_TYPE_NAME_TIME => Ok(Self::Time),
FEEL_TYPE_NAME_YEARS_AND_MONTHS_DURATION => Ok(Self::YearsAndMonthsDuration),
_ => Err(err_invalid_feel_type_name(s)),
}
}
}
impl From<&Name> for FeelType {
fn from(name: &Name) -> Self {
if let Ok(feel_type) = Self::from_str(name.to_string().as_str()) {
feel_type
} else {
FeelType::Any
}
}
}
pub fn is_built_in_type_name(name: &str) -> bool {
matches!(
name,
FEEL_TYPE_NAME_ANY
| FEEL_TYPE_NAME_BOOLEAN
| FEEL_TYPE_NAME_DATE
| FEEL_TYPE_NAME_DATE_AND_TIME
| FEEL_TYPE_NAME_DAYS_AND_TIME_DURATION
| FEEL_TYPE_NAME_NULL
| FEEL_TYPE_NAME_NUMBER
| FEEL_TYPE_NAME_STRING
| FEEL_TYPE_NAME_TIME
| FEEL_TYPE_NAME_YEARS_AND_MONTHS_DURATION
)
}
impl FeelType {
pub fn get_conformant_value(&self, actual_value: &Value) -> Value {
if let FeelType::Null = self {
return value_null!();
}
if actual_value.is_conformant(self) {
self.get_value_checked(actual_value).unwrap()
} else {
value_null!("type '{}' is not conformant with value '{}'", self.to_string(), actual_value)
}
}
pub fn get_value_checked(&self, value: &Value) -> Result<Value> {
if let Value::Null(_) = value {
return Ok(value_null!());
}
match self {
FeelType::Any => {
return Ok(value.clone());
}
FeelType::Boolean => {
if let Value::Boolean(_) = value {
return Ok(value.clone());
}
}
FeelType::Context(entries) => {
if let Value::Context(context) = value {
let mut result = FeelContext::default();
for (name, entry_type) in entries {
if let Some(entry_value) = context.get_entry(name) {
result.set_entry(name, entry_type.get_value_checked(entry_value)?);
}
}
return Ok(Value::Context(result));
}
}
FeelType::Date => {
if let Value::Date(_) = value {
return Ok(value.clone());
}
}
FeelType::DateTime => {
if let Value::DateTime(_) = value {
return Ok(value.clone());
}
}
FeelType::DaysAndTimeDuration => {
if let Value::DaysAndTimeDuration(_) = value {
return Ok(value.clone());
}
}
FeelType::Function(_, _) => {
if let Value::FunctionDefinition { .. } = value {
return Ok(value.clone());
}
}
FeelType::List(items_type) => {
if let Value::List(items) = value {
let mut result = vec![];
for item in items {
result.push(items_type.get_value_checked(item)?);
}
return Ok(Value::List(result));
}
}
FeelType::Number => {
if let Value::Number(_) = value {
return Ok(value.clone());
}
}
FeelType::Range(_) => {
if let Value::Range(_, _, _, _) = value {
return Ok(value.clone());
}
}
FeelType::String => {
if let Value::String(_) = value {
return Ok(value.clone());
}
}
FeelType::Time => {
if let Value::Time(_) = value {
return Ok(value.clone());
}
}
FeelType::YearsAndMonthsDuration => {
if let Value::YearsAndMonthsDuration(_) = value {
return Ok(value.clone());
}
}
_ => {}
}
Err(err_invalid_value_for_retrieving_using_feel_type(&self.to_string(), &value.to_string()))
}
pub fn is_simple_built_in_type(&self) -> bool {
matches!(
self,
Self::Any | Self::Boolean | Self::Date | Self::DateTime | Self::DaysAndTimeDuration | Self::Null | Self::Number | Self::String | Self::Time | Self::YearsAndMonthsDuration
)
}
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn list(items_type: &FeelType) -> FeelType {
FeelType::List(Box::new(items_type.clone()))
}
pub fn range(elements_type: &FeelType) -> FeelType {
FeelType::Range(Box::new(elements_type.clone()))
}
pub fn context(entries_types: &[(&Name, &FeelType)]) -> FeelType {
FeelType::Context(entries_types.iter().map(|(name, typ)| ((*name).clone(), (*typ).clone())).collect())
}
pub fn function(parameter_types: &[FeelType], result_type: &FeelType) -> FeelType {
FeelType::Function(parameter_types.iter().map(|typ| (*typ).clone()).collect(), Box::new((*result_type).clone()))
}
pub fn is_conformant(&self, target_type: &FeelType) -> bool {
if matches!(self, FeelType::Null) {
return true;
}
match target_type {
FeelType::Any => return true,
FeelType::Null => return matches!(self, FeelType::Null),
FeelType::Boolean => return matches!(self, FeelType::Boolean),
FeelType::Number => return matches!(self, FeelType::Number),
FeelType::String => return matches!(self, FeelType::String),
FeelType::Date => return matches!(self, FeelType::Date),
FeelType::Time => return matches!(self, FeelType::Time),
FeelType::DateTime => return matches!(self, FeelType::DateTime),
FeelType::DaysAndTimeDuration => return matches!(self, FeelType::DaysAndTimeDuration),
FeelType::YearsAndMonthsDuration => return matches!(self, FeelType::YearsAndMonthsDuration),
FeelType::Range(type_other) => {
if let FeelType::Range(type_self) = self {
return type_self.is_conformant(type_other);
}
}
FeelType::List(type_other) => {
if let FeelType::List(type_self) = self {
return type_self.is_conformant(type_other);
}
}
FeelType::Context(target_entries) => {
if let FeelType::Context(self_entries) = self {
for (name, type_target) in target_entries {
if let Some(type_self) = self_entries.get(name) {
if !type_self.is_conformant(type_target) {
return false;
}
} else {
return false;
}
}
return true;
}
return false;
}
FeelType::Function(parameters_other, result_other) => {
if let FeelType::Function(parameters_self, result_self) = self {
if parameters_self.len() == parameters_other.len() {
for (i, parameter_other) in parameters_other.iter().enumerate() {
if !parameter_other.is_conformant(¶meters_self[i]) {
return false;
}
if !result_self.is_conformant(result_other) {
return false;
}
}
return true;
}
}
return false;
}
}
false
}
pub fn instance_of(&self, other: &FeelType) -> bool {
if let FeelType::List(self_inner_type) = self {
if let FeelType::List(other_inner_type) = other {
return self_inner_type == other_inner_type || **other_inner_type == FeelType::Any;
}
}
if let FeelType::Context(self_inner_type) = self {
if let FeelType::Context(other_inner_type) = other {
if self_inner_type == other_inner_type {
return true;
}
for (name, other_inner_feel_type) in other_inner_type {
if let Some(self_inner_feel_type) = self_inner_type.get(name) {
if !self_inner_feel_type.is_null() && self_inner_feel_type != other_inner_feel_type {
return false;
}
} else {
return false;
}
}
return true;
}
}
false
}
}