use std::sync::Arc;
use serde::ser::SerializeStruct;
use serde::Serialize;
use crate::{events::AssignmentEvent, Str};
use crate::ufc::VariationType;
use super::ValueWire;
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Assignment {
pub value: AssignmentValue,
pub event: Option<AssignmentEvent>,
}
#[derive(Debug, Clone)]
pub enum AssignmentValue {
String(Str),
Integer(i64),
Numeric(f64),
Boolean(bool),
Json {
raw: Str,
parsed: Arc<serde_json::Value>,
},
}
impl Serialize for AssignmentValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("AssignmentValue", 2)?;
match self {
AssignmentValue::String(s) => {
state.serialize_field("type", "STRING")?;
state.serialize_field("value", s)?;
}
AssignmentValue::Integer(i) => {
state.serialize_field("type", "INTEGER")?;
state.serialize_field("value", i)?;
}
AssignmentValue::Numeric(n) => {
state.serialize_field("type", "NUMERIC")?;
state.serialize_field("value", n)?;
}
AssignmentValue::Boolean(b) => {
state.serialize_field("type", "BOOLEAN")?;
state.serialize_field("value", b)?;
}
AssignmentValue::Json { raw: _, parsed } => {
state.serialize_field("type", "JSON")?;
state.serialize_field("value", parsed)?;
}
}
state.end()
}
}
impl PartialEq for AssignmentValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(AssignmentValue::String(v1), AssignmentValue::String(v2)) => v1 == v2,
(AssignmentValue::Integer(v1), AssignmentValue::Integer(v2)) => v1 == v2,
(AssignmentValue::Numeric(v1), AssignmentValue::Numeric(v2)) => v1 == v2,
(AssignmentValue::Boolean(v1), AssignmentValue::Boolean(v2)) => v1 == v2,
(
AssignmentValue::Json { parsed: v1, .. },
AssignmentValue::Json { parsed: v2, .. },
) => v1 == v2,
_ => false,
}
}
}
impl AssignmentValue {
pub fn from_json(value: serde_json::Value) -> Result<AssignmentValue, serde_json::Error> {
let raw = serde_json::to_string(&value)?;
Ok(AssignmentValue::Json {
raw: raw.into(),
parsed: Arc::new(value),
})
}
pub fn is_string(&self) -> bool {
self.as_str().is_some()
}
pub fn as_str(&self) -> Option<&str> {
match self {
AssignmentValue::String(s) => Some(s),
_ => None,
}
}
pub fn to_string(self) -> Option<Str> {
match self {
AssignmentValue::String(s) => Some(s),
_ => None,
}
}
pub fn is_integer(&self) -> bool {
self.as_integer().is_some()
}
pub fn as_integer(&self) -> Option<i64> {
match self {
AssignmentValue::Integer(i) => Some(*i),
_ => None,
}
}
pub fn is_numeric(&self) -> bool {
self.as_numeric().is_some()
}
pub fn as_numeric(&self) -> Option<f64> {
match self {
Self::Numeric(n) => Some(*n),
_ => None,
}
}
pub fn is_boolean(&self) -> bool {
self.as_boolean().is_some()
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
AssignmentValue::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn is_json(&self) -> bool {
self.as_json().is_some()
}
pub fn as_json(&self) -> Option<&serde_json::Value> {
match self {
Self::Json { raw: _, parsed } => Some(parsed),
_ => None,
}
}
pub fn to_json(self) -> Option<Arc<serde_json::Value>> {
match self {
Self::Json { raw: _, parsed } => Some(parsed),
_ => None,
}
}
pub fn variation_type(&self) -> VariationType {
match self {
AssignmentValue::String(_) => VariationType::String,
AssignmentValue::Integer(_) => VariationType::Integer,
AssignmentValue::Numeric(_) => VariationType::Numeric,
AssignmentValue::Boolean(_) => VariationType::Boolean,
AssignmentValue::Json { .. } => VariationType::Json,
}
}
pub(crate) fn variation_value(&self) -> ValueWire {
match self {
AssignmentValue::String(s) => ValueWire::String(s.clone()),
AssignmentValue::Integer(i) => ValueWire::Number(*i as f64),
AssignmentValue::Numeric(n) => ValueWire::Number(*n),
AssignmentValue::Boolean(b) => ValueWire::Boolean(*b),
AssignmentValue::Json { raw, parsed: _ } => ValueWire::String(raw.clone()),
}
}
}
#[cfg(feature = "pyo3")]
mod pyo3_impl {
use pyo3::prelude::*;
use super::*;
impl<'py> IntoPyObject<'py> for &AssignmentValue {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
AssignmentValue::String(s) => {
let Ok(obj) = s.into_pyobject(py);
Ok(obj.into_any())
}
AssignmentValue::Integer(i) => {
let Ok(obj) = i.into_pyobject(py);
Ok(obj.into_any())
}
AssignmentValue::Numeric(n) => {
let Ok(obj) = n.into_pyobject(py);
Ok(obj.into_any())
}
AssignmentValue::Boolean(b) => {
let Ok(obj) = b.into_pyobject(py);
Ok(obj.as_any().clone())
}
AssignmentValue::Json { raw: _, parsed } => {
crate::pyo3::serde_to_pyobject(parsed, py)
}
}
}
}
impl<'py> IntoPyObject<'py> for AssignmentValue {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
}
#[cfg(feature = "magnus")]
mod magnus_impl {
use magnus::prelude::*;
use magnus::{IntoValue, Ruby};
use super::*;
impl IntoValue for AssignmentValue {
fn into_value_with(self, handle: &Ruby) -> magnus::Value {
match self {
AssignmentValue::String(s) => s.into_value_with(handle),
AssignmentValue::Integer(i) => i.into_value_with(handle),
AssignmentValue::Numeric(n) => n.into_value_with(handle),
AssignmentValue::Boolean(b) => b.into_value_with(handle),
AssignmentValue::Json { raw: _, parsed } => {
serde_magnus::serialize(handle, &parsed)
.expect("JSON value should always be serializable to Ruby")
}
}
}
}
impl IntoValue for Assignment {
fn into_value_with(self, handle: &Ruby) -> magnus::Value {
let hash = handle.hash_new();
let _ = hash.aset(handle.sym_new("value"), self.value);
let _ = hash.aset(
handle.sym_new("event"),
serde_magnus::serialize::<_, magnus::Value>(handle, &self.event)
.expect("AssignmentEvent should always be serializable to Ruby"),
);
hash.as_value()
}
}
}