use crate::scope::Scope;
use crate::smol_str::SmolStr;
use ordered_float::OrderedFloat;
use relon_parser::Node;
use serde::de::{self, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ValueDict {
pub map: BTreeMap<SmolStr, Value>,
pub brand: Option<String>,
pub variant_of: Option<String>,
}
impl ValueDict {
pub fn new<K, I>(map: I) -> Self
where
K: Into<SmolStr>,
I: IntoIterator<Item = (K, Value)>,
{
Self {
map: map.into_iter().map(|(k, v)| (k.into(), v)).collect(),
brand: None,
variant_of: None,
}
}
pub fn with_brand<K, I>(map: I, brand: Option<String>) -> Self
where
K: Into<SmolStr>,
I: IntoIterator<Item = (K, Value)>,
{
Self {
map: map.into_iter().map(|(k, v)| (k.into(), v)).collect(),
brand,
variant_of: None,
}
}
}
impl Serialize for ValueDict {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.map.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ValueDict {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let map = BTreeMap::deserialize(deserializer)?;
Ok(ValueDict {
map,
brand: None,
variant_of: None,
})
}
}
impl PartialEq for ValueDict {
fn eq(&self, other: &Self) -> bool {
self.map == other.map && self.brand == other.brand && self.variant_of == other.variant_of
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SchemaField {
pub type_hint: relon_parser::TypeNode,
pub predicates: Vec<Value>,
pub custom_error: Option<String>,
pub default_value: Option<Value>,
}
#[derive(Debug, Clone)]
pub struct ClosureData {
pub params: Vec<String>,
pub body: Arc<Node>,
pub captured_env: Arc<Scope>,
}
#[derive(Debug, Clone)]
pub struct SchemaData {
pub generics: Vec<String>,
pub fields: std::collections::HashMap<String, SchemaField>,
pub tuple_elements: Option<Vec<relon_parser::TypeNode>>,
}
#[derive(Debug, Clone)]
pub struct EnumSchemaData {
pub name: String,
pub generics: Vec<String>,
pub variants: std::collections::HashMap<String, std::collections::HashMap<String, SchemaField>>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum Value {
Bool(bool),
Int(i64),
Float(OrderedFloat<f64>),
String(SmolStr),
List(Arc<Vec<Value>>),
Tuple(Arc<Vec<Value>>),
Dict(Arc<ValueDict>),
#[serde(skip)]
Closure(Box<ClosureData>),
#[serde(skip)]
Schema(Arc<SchemaData>),
#[serde(skip)]
EnumSchema(Arc<EnumSchemaData>),
#[serde(skip)]
Type(Box<relon_parser::TypeNode>),
Wildcard,
}
impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(ValueVisitor)
}
}
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a Relon value; JSON null is only valid with an Option<T> target")
}
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
Ok(Value::Bool(value))
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
Ok(Value::Int(value))
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
let value =
i64::try_from(value).map_err(|_| E::custom("integer is out of range for Int"))?;
Ok(Value::Int(value))
}
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> {
Ok(Value::Float(OrderedFloat(value)))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
Ok(Value::String(SmolStr::from(value)))
}
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E> {
Ok(Value::String(SmolStr::from(value)))
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
Ok(Value::String(SmolStr::from(value)))
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(E::custom(
"JSON null is not a Relon value; use an Option<T> target type so it decodes as None",
))
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Err(E::custom(
"JSON null is not a Relon value; use an Option<T> target type so it decodes as None",
))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Vec::new();
while let Some(value) = seq.next_element::<Value>()? {
values.push(value);
}
Ok(Value::list(values))
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut values = BTreeMap::new();
while let Some((key, value)) = map.next_entry::<String, Value>()? {
values.insert(SmolStr::from(key), value);
}
Ok(Value::Dict(Arc::new(ValueDict {
map: values,
brand: None,
variant_of: None,
})))
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Bool(l), Self::Bool(r)) => l == r,
(Self::Int(l), Self::Int(r)) => l == r,
(Self::Float(l), Self::Float(r)) => l == r,
(Self::String(l), Self::String(r)) => l == r,
(Self::List(l), Self::List(r)) => l == r,
(Self::Tuple(l), Self::Tuple(r)) => l == r,
(Self::Dict(l), Self::Dict(r)) => l == r,
(Self::Schema(_), Self::Schema(_)) => false,
(Self::EnumSchema(_), Self::EnumSchema(_)) => false,
(Self::Type(l), Self::Type(r)) => l == r,
(Self::Wildcard, Self::Wildcard) => true,
(Self::Closure(a), Self::Closure(b)) => {
a.params == b.params
&& a.body == b.body
&& Arc::ptr_eq(&a.captured_env, &b.captured_env)
}
_ => false,
}
}
}
impl Value {
pub fn list(items: Vec<Value>) -> Self {
Self::List(Arc::new(items))
}
pub fn tuple(items: Vec<Value>) -> Self {
Self::Tuple(Arc::new(items))
}
pub fn option_some(value: Value) -> Self {
let mut map = BTreeMap::new();
map.insert(SmolStr::from("value"), value);
Self::variant_dict(map, "Some".to_string(), "Option".to_string())
}
pub fn option_none() -> Self {
Self::variant_dict(
BTreeMap::<SmolStr, Value>::new(),
"None".to_string(),
"Option".to_string(),
)
}
pub fn result_ok(value: Value) -> Self {
let mut map = BTreeMap::new();
map.insert(SmolStr::from("value"), value);
Self::variant_dict(map, "Ok".to_string(), "Result".to_string())
}
pub fn result_err(error: Value) -> Self {
let mut map = BTreeMap::new();
map.insert(SmolStr::from("error"), error);
Self::variant_dict(map, "Err".to_string(), "Result".to_string())
}
pub fn is_option_none(&self) -> bool {
matches!(
self,
Value::Dict(d)
if d.variant_of.as_deref() == Some("Option")
&& d.brand.as_deref() == Some("None")
)
}
pub fn option_some_value(&self) -> Option<&Value> {
match self {
Value::Dict(d)
if d.variant_of.as_deref() == Some("Option")
&& d.brand.as_deref() == Some("Some") =>
{
d.map.get("value")
}
_ => None,
}
}
pub fn dict<K, I>(map: I) -> Self
where
K: Into<SmolStr>,
I: IntoIterator<Item = (K, Value)>,
{
Self::Dict(Arc::new(ValueDict {
map: map.into_iter().map(|(k, v)| (k.into(), v)).collect(),
brand: None,
variant_of: None,
}))
}
pub fn branded_dict<K, I>(map: I, brand: Option<String>) -> Self
where
K: Into<SmolStr>,
I: IntoIterator<Item = (K, Value)>,
{
Self::Dict(Arc::new(ValueDict {
map: map.into_iter().map(|(k, v)| (k.into(), v)).collect(),
brand,
variant_of: None,
}))
}
pub fn variant_dict<K, I>(map: I, variant: String, enum_name: String) -> Self
where
K: Into<SmolStr>,
I: IntoIterator<Item = (K, Value)>,
{
Self::Dict(Arc::new(ValueDict {
map: map.into_iter().map(|(k, v)| (k.into(), v)).collect(),
brand: Some(variant),
variant_of: Some(enum_name),
}))
}
pub fn list_mut(&mut self) -> Option<&mut Vec<Value>> {
match self {
Value::List(arc) => Some(Arc::make_mut(arc)),
_ => None,
}
}
pub fn dict_mut(&mut self) -> Option<&mut ValueDict> {
match self {
Value::Dict(arc) => Some(Arc::make_mut(arc)),
_ => None,
}
}
pub fn is_truthy(&self) -> bool {
match self {
Value::Bool(b) => *b,
Value::Int(i) => *i != 0,
Value::Float(f) => f.into_inner() != 0.0,
Value::String(s) => !s.is_empty(),
Value::List(l) | Value::Tuple(l) => !l.is_empty(),
Value::Dict(d) => !d.map.is_empty(),
Value::Closure(_) => true,
Value::Schema(_) => true,
Value::EnumSchema(_) => true,
Value::Type(_) => true,
Value::Wildcard => true,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Bool(_) => "Bool",
Value::Int(_) => "Int",
Value::Float(_) => "Float",
Value::String(_) => "String",
Value::List(_) => "List",
Value::Tuple(_) => "Tuple",
Value::Dict(_) => "Dict",
Value::Closure(_) => "Closure",
Value::Schema(_) => "Schema",
Value::EnumSchema(_) => "EnumSchema",
Value::Type(_) => "Type",
Value::Wildcard => "Wildcard",
}
}
pub fn deep_merge(&mut self, patch: &Value) {
match (self, patch) {
(Value::Dict(base), Value::Dict(patch)) => {
let base = Arc::make_mut(base);
for (k, v) in &patch.map {
if let Some(base_val) = base.map.get_mut(k) {
base_val.deep_merge(v);
} else {
base.map.insert(k.clone(), v.clone());
}
}
}
(Value::Schema(base), Value::Schema(patch)) => {
let base = Arc::make_mut(base);
let base_fields = &mut base.fields;
let patch_fields = &patch.fields;
for (k, v) in patch_fields {
if let Some(base_field) = base_fields.get_mut(k) {
base_field.type_hint = v.type_hint.clone();
for pred in &v.predicates {
if !matches!(pred, Value::Wildcard) {
base_field.predicates.push(pred.clone());
}
}
if v.custom_error.is_some() {
base_field.custom_error = v.custom_error.clone();
}
if v.default_value.is_some() {
base_field.default_value = v.default_value.clone();
}
} else {
base_fields.insert(k.clone(), v.clone());
}
}
}
(Value::Schema(base), Value::Dict(patch_data)) => {
let base = Arc::make_mut(base);
let base_fields = &mut base.fields;
for (k, v) in &patch_data.map {
if let Some(base_field) = base_fields.get_mut(k.as_str()) {
base_field.default_value = Some(v.clone());
}
}
}
(b, p) => *b = p.clone(),
}
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Bool(b) => write!(f, "{}", b),
Value::Int(i) => write!(f, "{}", i),
Value::Float(fl) => write!(f, "{}", fl),
Value::String(s) => write!(f, "{}", s),
Value::List(l) => write!(f, "{:?}", l),
Value::Tuple(l) => {
write!(f, "(")?;
for (i, item) in l.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
if l.len() == 1 {
write!(f, ",")?;
}
write!(f, ")")
}
Value::Dict(d) => write!(f, "{:?}", d.map),
Value::Closure(_) => write!(f, "<closure>"),
Value::Schema(_) => write!(f, "<schema>"),
Value::EnumSchema(enum_data) => write!(f, "<enum {}>", enum_data.name),
Value::Type(t) => write!(f, "Type<{}>", relon_analyzer::format_type(t)),
Value::Wildcard => write!(f, "*"),
}
}
}
#[cfg(test)]
mod deserialize_tests {
use super::Value;
#[test]
fn rejects_json_null_at_root() {
let err = serde_json::from_value::<Value>(serde_json::json!(null)).unwrap_err();
assert!(
err.to_string().contains("JSON null is not a Relon value"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_json_null_inside_list() {
let err = serde_json::from_value::<Value>(serde_json::json!([1, null])).unwrap_err();
assert!(
err.to_string().contains("JSON null is not a Relon value"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_json_null_inside_dict() {
let err = serde_json::from_value::<Value>(serde_json::json!({ "k": null })).unwrap_err();
assert!(
err.to_string().contains("JSON null is not a Relon value"),
"unexpected error: {err}"
);
}
}
#[cfg(test)]
mod size_guard {
use super::Value;
#[test]
fn value_enum_is_compact() {
let size = std::mem::size_of::<Value>();
eprintln!("Value enum size: {} bytes", size);
assert!(size <= 48, "Value enum grew: {} bytes", size);
}
}