From 7b8b823d230bd345dd1126c8a42aa78e3818eb80 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Tue, 25 Oct 2022 19:41:41 +0000 Subject: [PATCH] refactor!: implement serde support directly Instead of relying on `serde_json::Value`. BREAKING CHANGE: There is no longer `impl Typed for serde_json::Value`, either parse directly into jrsonnet-evaluator, or use `serde_transcode` --- --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake --- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -13,9 +13,7 @@ # Experimental feature, which allows to preserve order of object fields exp-preserve-order = [ "jrsonnet-evaluator/exp-preserve-order", - "jrsonnet-evaluator/exp-serde-preserve-order", "jrsonnet-cli/exp-preserve-order", - "jrsonnet-cli/exp-serde-preserve-order", ] # Destructuring of locals exp-destruct = ["jrsonnet-evaluator/exp-destruct"] --- a/crates/jrsonnet-cli/Cargo.toml +++ b/crates/jrsonnet-cli/Cargo.toml @@ -11,10 +11,6 @@ "jrsonnet-evaluator/exp-preserve-order", "jrsonnet-stdlib/exp-preserve-order", ] -exp-serde-preserve-order = [ - "jrsonnet-evaluator/exp-serde-preserve-order", - "jrsonnet-stdlib/exp-serde-preserve-order", -] legacy-this-file = ["jrsonnet-stdlib/legacy-this-file"] [dependencies] --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -18,7 +18,6 @@ # Allows to preserve field order in objects exp-preserve-order = [] -exp-serde-preserve-order = ["serde_json/preserve_order"] # Implements field destructuring exp-destruct = ["jrsonnet-parser/exp-destruct"] # Provide Typed for conversions to/from serde_json::Value type @@ -40,8 +39,6 @@ thiserror = "1.0" serde = "1.0" -# Optional integration -serde_json = { version = "1.0.82", optional = true } anyhow = { version = "1.0", optional = true } # Friendly errors --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -1,78 +1,164 @@ -use jrsonnet_types::ComplexValType; -use serde_json::{Map, Number, Value}; +use std::borrow::Cow; + +use jrsonnet_gcmodule::Cc; +use serde::{de::Visitor, ser::Error, Deserialize, Serialize}; + +use crate::{error::Result, val::ArrValue, ObjValueBuilder, State, Val}; -use crate::{ - error::{Error::*, Result}, - throw, - typed::Typed, - ObjValueBuilder, State, Val, -}; +impl<'de> Deserialize<'de> for Val { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct ValVisitor; + + // macro_rules! visit_num { + // ($($method:ident => $ty:ty),* $(,)?) => {$( + // fn $method(self, v: $ty) -> Result + // where + // E: serde::de::Error, + // { + // Ok(Val::Num(f64::from(v))) + // } + // )*}; + // } -impl Typed for Value { - const TYPE: &'static ComplexValType = &ComplexValType::Any; + impl<'de> Visitor<'de> for ValVisitor { + type Value = Val; - fn into_untyped(value: Self, s: State) -> Result { - Ok(match value { - Self::Null => Val::Null, - Self::Bool(v) => Val::Bool(v), - Self::Number(n) => Val::Num(n.as_f64().ok_or_else(|| { - RuntimeError(format!("json number can't be represented as jsonnet: {n}").into()) - })?), - Self::String(s) => Val::Str((&s as &str).into()), - Self::Array(a) => { - let mut out: Vec = Vec::with_capacity(a.len()); - for v in a { - out.push(Self::into_untyped(v, s.clone())?); + fn visit_bool(self, v: bool) -> Result + where + E: serde::de::Error, + { + Ok(Val::Bool(v)) + } + fn visit_f64(self, v: f64) -> Result + where + E: serde::de::Error, + { + if !v.is_finite() { + return Err(E::custom("only finite numbers are supported")); } - Val::Arr(out.into()) + Ok(Val::Num(v)) + } + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(Val::Str(v.into())) + } + + // visit_num! { + // visit_i8 => i8, + // visit_i16 => i16, + // visit_i32 => i32, + // visit_u8 => u8, + // visit_u16 => u16, + // visit_u32 => u32, + // } + fn visit_i64(self, v: i64) -> Result + where + E: serde::de::Error, + { + Ok(Val::Num(v as f64)) + } + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + Ok(Val::Num(v as f64)) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + Ok(Val::Arr(ArrValue::Bytes(v.into()))) + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(Val::Null) + } + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(Val::Null) } - Self::Object(o) => { - let mut builder = ObjValueBuilder::with_capacity(o.len()); - for (k, v) in o { - builder - .member((&k as &str).into()) - .value(s.clone(), Self::into_untyped(v, s.clone())?)?; + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut out = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity); + + while let Some(val) = seq.next_element::()? { + out.push(val); } - Val::Obj(builder.build()) + + Ok(Val::Arr(ArrValue::Eager(Cc::new(out)))) } - }) - } - fn from_untyped(value: Val, s: State) -> Result { - Ok(match value { - Val::Bool(b) => Self::Bool(b), - Val::Null => Self::Null, - Val::Str(s) => Self::String((&s as &str).into()), - Val::Num(n) => Self::Number(if n.fract() <= f64::EPSILON { - (n as i64).into() - } else { - Number::from_f64(n).expect("jsonnet numbers can't be infinite or NaN") - }), - Val::Arr(a) => { - let mut out = Vec::with_capacity(a.len()); - for item in a.iter(s.clone()) { - out.push(Self::from_untyped(item?, s.clone())?); + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut out = map + .size_hint() + .map_or_else(ObjValueBuilder::new, ObjValueBuilder::with_capacity); + + while let Some((k, v)) = map.next_entry::, Val>()? { + // Jsonnet ignores duplicate keys + out.member(k.into()).value_unchecked(v); } - Self::Array(out) + + Ok(Val::Obj(out.build())) } - Val::Obj(o) => { - let mut out = Map::new(); - for key in o.fields( - #[cfg(feature = "exp-preserve-order")] - cfg!(feature = "exp-serde-preserve-order"), - ) { - out.insert( - (&key as &str).into(), - Self::from_untyped( - o.get(s.clone(), key)? - .expect("key is present in fields, so value should exist"), - s.clone(), - )?, - ); + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "any valid jsonnet value") + } + } + deserializer.deserialize_any(ValVisitor) + } +} + +impl Serialize for Val { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Val::Bool(v) => serializer.serialize_bool(*v), + Val::Null => serializer.serialize_none(), + Val::Str(s) => serializer.serialize_str(s), + Val::Num(n) => serializer.serialize_f64(*n), + Val::Arr(arr) => { + let mut seq = serializer.serialize_seq(Some(arr.len()))?; + for element in arr.iter(State::default()) { + // seq.serialize_element() } - Self::Object(out) + todo!() } - Val::Func(_) => throw!("tried to manifest function"), - }) + Val::Obj(_) => todo!(), + Val::Func(_) => Err(S::Error::custom("tried to manifest function")), + } } } --- a/crates/jrsonnet-stdlib/Cargo.toml +++ b/crates/jrsonnet-stdlib/Cargo.toml @@ -15,18 +15,9 @@ legacy-this-file = [] # Add order preservation flag to some functions exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"] -# Preserve order for files parsed via `std.parseJson` -# Shame it isn't possible to enable per parse call, instead of globally -exp-serde-preserve-order = [ - "serde_json/preserve_order", - "jrsonnet-evaluator/exp-serde-preserve-order", -] [dependencies] -jrsonnet-evaluator = { path = "../jrsonnet-evaluator", features = [ - # std.parseJson parses file via serde, then converts Value to evaluator Val - "serde_json", -], version = "0.4.2" } +jrsonnet-evaluator = { path = "../jrsonnet-evaluator", version = "0.4.2" } jrsonnet-macros = { path = "../jrsonnet-macros", version = "0.4.2" } jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" } jrsonnet-gcmodule = "0.3.4" --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -6,16 +6,13 @@ use jrsonnet_evaluator::{ error::{Error::*, Result}, - function::{builtin::Builtin, ArgLike, CallLocation, FuncVal, TlaArg}, + function::{builtin::Builtin, CallLocation, FuncVal, TlaArg}, gc::{GcHashMap, TraceBox}, - tb, throw, + tb, trace::PathResolver, - typed::{Any, Either, Either2, Either4, VecVal, M1}, - val::{equals, ArrValue}, Context, ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val, }; use jrsonnet_gcmodule::Cc; -use jrsonnet_macros::builtin; use jrsonnet_parser::Source; mod expr; --- a/crates/jrsonnet-stdlib/src/parse.rs +++ b/crates/jrsonnet-stdlib/src/parse.rs @@ -1,22 +1,20 @@ use jrsonnet_evaluator::{ error::{Error::RuntimeError, Result}, function::builtin, - typed::{Any, Typed}, - IStr, State, Val, + typed::Any, + IStr, Val, }; use serde::Deserialize; #[builtin] -pub fn builtin_parse_json(st: State, s: IStr) -> Result { - use serde_json::Value; - let value: Value = serde_json::from_str(&s) +pub fn builtin_parse_json(s: IStr) -> Result { + let value: Val = serde_json::from_str(&s) .map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?; - Ok(Any(Value::into_untyped(value, st)?)) + Ok(Any(value)) } #[builtin] -pub fn builtin_parse_yaml(st: State, s: IStr) -> Result { - use serde_json::Value; +pub fn builtin_parse_yaml(s: IStr) -> Result { use serde_yaml_with_quirks::DeserializingQuirks; let value = serde_yaml_with_quirks::Deserializer::from_str_with_quirks( &s, @@ -24,9 +22,8 @@ ); let mut out = vec![]; for item in value { - let value = Value::deserialize(item) + let val = Val::deserialize(item) .map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?; - let val = Value::into_untyped(value, st.clone())?; out.push(val); } Ok(Any(if out.is_empty() { --- /dev/null +++ b/flake.nix @@ -0,0 +1,28 @@ +{ + description = "Jrsonnet"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay.url = "github:oxalica/rust-overlay"; + }; + outputs = { nixpkgs, flake-utils, rust-overlay, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + }; + rust = ((pkgs.rustChannelOf { date = "2022-10-22"; channel = "nightly"; }).default.override { + extensions = [ "rust-src" ]; + }); + in + rec { + devShell = pkgs.mkShell { + nativeBuildInputs = with pkgs;[ + rust + cargo-edit + ]; + }; + } + ); +} -- gitstuff