use std::rc::Rc;
use bstr::BString;
use thiserror::Error;
use crate::types;
use crate::types::{Array, TypeValue};
pub struct Variable(TypeValue);
#[derive(Error, Debug, Eq, PartialEq)]
pub enum VariableError {
#[error("variable `{0}` not defined")]
Undefined(String),
#[error("variable `{0}` already exists")]
AlreadyExists(String),
#[error("invalid identifier `{0}`")]
InvalidIdentifier(String),
#[error("null values are not accepted")]
UnexpectedNull,
#[error(
"arrays can't be empty and all items must be non-null and the same type"
)]
InvalidArray,
#[error("integer value is out of range")]
IntegerOutOfRange,
#[error(
"invalid type for `{variable}`, expecting `{expected_type}`, got `{actual_type}"
)]
InvalidType {
variable: String,
expected_type: String,
actual_type: String,
},
}
impl TryFrom<bool> for Variable {
type Error = VariableError;
fn try_from(value: bool) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_bool_from(value)))
}
}
impl TryFrom<i64> for Variable {
type Error = VariableError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_integer_from(value)))
}
}
impl TryFrom<i32> for Variable {
type Error = VariableError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_integer_from(value)))
}
}
impl TryFrom<i16> for Variable {
type Error = VariableError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_integer_from(value)))
}
}
impl TryFrom<i8> for Variable {
type Error = VariableError;
fn try_from(value: i8) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_integer_from(value)))
}
}
impl TryFrom<u32> for Variable {
type Error = VariableError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_integer_from(value)))
}
}
impl TryFrom<u16> for Variable {
type Error = VariableError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_integer_from(value)))
}
}
impl TryFrom<u8> for Variable {
type Error = VariableError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_integer_from(value)))
}
}
impl TryFrom<f64> for Variable {
type Error = VariableError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_float_from(value)))
}
}
impl TryFrom<f32> for Variable {
type Error = VariableError;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_float_from(value)))
}
}
impl TryFrom<&str> for Variable {
type Error = VariableError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_string_from(value)))
}
}
impl TryFrom<&[u8]> for Variable {
type Error = VariableError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_string_from(value)))
}
}
impl TryFrom<String> for Variable {
type Error = VariableError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(Variable(TypeValue::var_string_from(value)))
}
}
impl TryFrom<serde_json::Value> for Variable {
type Error = VariableError;
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
Variable::try_from(&value)
}
}
impl TryFrom<&serde_json::Value> for Variable {
type Error = VariableError;
fn try_from(value: &serde_json::Value) -> Result<Self, Self::Error> {
match value {
serde_json::Value::Null => Err(VariableError::UnexpectedNull),
serde_json::Value::Bool(b) => {
Ok(Variable(TypeValue::var_bool_from(*b)))
}
serde_json::Value::Number(n) => {
if let Some(n) = n.as_u64() {
let n: i64 = n
.try_into()
.map_err(|_| VariableError::IntegerOutOfRange)?;
Ok(Variable(TypeValue::var_integer_from(n)))
} else if let Some(n) = n.as_i64() {
Ok(Variable(TypeValue::var_integer_from(n)))
} else if let Some(n) = n.as_f64() {
Ok(Variable(TypeValue::var_float_from(n)))
} else {
unreachable!()
}
}
serde_json::Value::String(s) => {
Ok(Variable(TypeValue::var_string_from(s)))
}
serde_json::Value::Array(values) => {
let mut array = None;
for v in values {
if v.is_boolean() {
array = Some(Array::Bools(Vec::new()));
break;
} else if v.is_i64() {
array = Some(Array::Integers(Vec::new()));
break;
} else if v.is_f64() {
array = Some(Array::Floats(Vec::new()));
break;
} else if v.is_string() {
array = Some(Array::Strings(Vec::new()));
break;
} else if v.is_object() {
array = Some(Array::Structs(Vec::new()));
break;
} else if v.is_array() {
return Err(VariableError::InvalidArray);
}
}
if array.is_none() {
return Err(VariableError::InvalidArray);
}
let mut array = array.unwrap();
match array {
Array::Integers(ref mut integers) => {
for v in values {
match v.as_i64() {
Some(v) => {
integers.push(v);
}
None => {
return Err(VariableError::InvalidArray);
}
};
}
}
Array::Floats(ref mut floats) => {
for v in values {
match v.as_f64() {
Some(v) => {
floats.push(v);
}
None => {
return Err(VariableError::InvalidArray);
}
};
}
}
Array::Bools(ref mut bools) => {
for v in values {
match v.as_bool() {
Some(v) => {
bools.push(v);
}
None => {
return Err(VariableError::InvalidArray);
}
};
}
}
Array::Strings(ref mut strings) => {
for v in values {
match v.as_str() {
Some(v) => {
strings.push(BString::from(v).into());
}
None => {
return Err(VariableError::InvalidArray);
}
};
}
}
Array::Structs(ref mut structs) => {
for v in values {
match v.as_object() {
Some(v) => {
let mut s = types::Struct::new();
for (key, value) in v {
s.add_field(
key,
TypeValue::from(
Variable::try_from(value)?,
),
);
}
structs.push(Rc::new(s));
}
None => {
return Err(VariableError::InvalidArray);
}
};
}
}
}
Ok(Variable(TypeValue::Array(Rc::new(array))))
}
serde_json::Value::Object(obj) => {
let mut s = types::Struct::new();
for (key, value) in obj {
if !is_valid_identifier(key) {
return Err(VariableError::InvalidIdentifier(
key.to_string(),
));
}
s.add_field(
key,
TypeValue::from(Variable::try_from(value)?),
);
}
Ok(Variable(TypeValue::Struct(Rc::new(s))))
}
}
}
}
impl From<Variable> for TypeValue {
fn from(value: Variable) -> Self {
value.0
}
}
pub(crate) fn is_valid_identifier(ident: &str) -> bool {
let mut chars = ident.chars();
if let Some(first) = chars.next() {
if !first.is_alphabetic() && first != '_' {
return false;
}
} else {
return false;
}
chars.all(|c| c.is_alphanumeric() || c == '_')
}
#[cfg(test)]
mod test {
#[test]
fn is_valid_identifier() {
assert!(super::is_valid_identifier("a"));
assert!(super::is_valid_identifier("_"));
assert!(super::is_valid_identifier("foo"));
assert!(super::is_valid_identifier("_foo"));
assert!(!super::is_valid_identifier("123"));
assert!(!super::is_valid_identifier("1foo"));
assert!(!super::is_valid_identifier("foo|"));
assert!(!super::is_valid_identifier("foo.bar"));
}
}