difftreelog
test basic interop checks
in: master
7 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,4 +1,5 @@
use std::{
+ fmt::Debug,
path::{Path, PathBuf},
rc::Rc,
};
@@ -166,7 +167,7 @@
#[derive(Debug, Clone, Trace)]
pub struct StackTrace(pub Vec<StackTraceElement>);
-#[derive(Debug, Clone, Trace)]
+#[derive(Clone, Trace)]
pub struct LocError(Box<(Error, StackTrace)>);
impl LocError {
pub fn new(e: Error) -> Self {
@@ -186,6 +187,15 @@
&mut (self.0).1
}
}
+impl Debug for LocError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "{}", self.0 .0)?;
+ for el in self.0 .1 .0.iter() {
+ writeln!(f, "\t{:?}", el)?;
+ }
+ Ok(())
+ }
+}
pub type Result<V, E = LocError> = std::result::Result<V, E>;
crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -45,7 +45,6 @@
evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
}
}
-
pub trait ArgLike {
fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
}
@@ -169,6 +168,34 @@
}
}
+impl ArgsLike for [(); 0] {
+ fn unnamed_len(&self) -> usize {
+ 0
+ }
+
+ fn unnamed_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+}
+
impl<A: ArgLike> ArgsLike for [(IStr, A)] {
fn unnamed_len(&self) -> usize {
0
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth1use std::{ops::Deref, rc::Rc};23use gcmodule::Cc;4use jrsonnet_interner::IStr;5pub use jrsonnet_macros::Typed;6use jrsonnet_types::{ComplexValType, ValType};78use crate::{9 error::{Error::*, Result},10 throw,11 typed::CheckType,12 val::{ArrValue, FuncDesc, FuncVal, IndexableVal},13 ObjValue, ObjValueBuilder, State, Val,14};1516pub trait TypedObj: Typed {17 fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;18 fn parse(obj: &ObjValue) -> Result<Self>;19 fn into_object(self) -> Result<ObjValue> {20 let mut builder = ObjValueBuilder::new();21 self.serialize(&mut builder)?;22 Ok(builder.build())23 }24}2526pub trait Typed: Sized {27 const TYPE: &'static ComplexValType;28 fn into_untyped(typed: Self, s: State) -> Result<Val>;29 fn from_untyped(untyped: Val, s: State) -> Result<Self>;30}3132macro_rules! impl_int {33 ($($ty:ty)*) => {$(34 impl Typed for $ty {35 const TYPE: &'static ComplexValType =36 &ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));37 fn from_untyped(value: Val, s: State) -> Result<Self> {38 <Self as Typed>::TYPE.check(s, &value)?;39 match value {40 Val::Num(n) => {41 if n.trunc() != n {42 throw!(RuntimeError(43 format!(44 "cannot convert number with fractional part to {}",45 stringify!($ty)46 )47 .into()48 ))49 }50 Ok(n as Self)51 }52 _ => unreachable!(),53 }54 }55 fn into_untyped(value: Self, _: State) -> Result<Val> {56 Ok(Val::Num(value as f64))57 }58 }59 )*};60}6162impl_int!(i8 u8 i16 u16 i32 u32);6364macro_rules! impl_bounded_int {65 ($($name:ident = $ty:ty)*) => {$(66 #[derive(Clone, Copy)]67 pub struct $name<const MIN: $ty, const MAX: $ty>($ty);68 impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {69 pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {70 if value >= MIN && value <= MAX {71 Some(Self(value))72 } else {73 None74 }75 }76 pub const fn value(self) -> $ty {77 self.078 }79 }80 impl<const MIN: $ty, const MAX: $ty> Deref for $name<MIN, MAX> {81 type Target = $ty;82 fn deref(&self) -> &Self::Target {83 &self.084 }85 }8687 impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {88 const TYPE: &'static ComplexValType =89 &ComplexValType::BoundedNumber(90 Some(MIN as f64),91 Some(MAX as f64),92 );9394 fn from_untyped(value: Val, s: State) -> Result<Self> {95 <Self as Typed>::TYPE.check(s, &value)?;96 match value {97 Val::Num(n) => {98 if n.trunc() != n {99 throw!(RuntimeError(100 format!(101 "cannot convert number with fractional part to {}",102 stringify!($ty)103 )104 .into()105 ))106 }107 Ok(Self(n as $ty))108 }109 _ => unreachable!(),110 }111 }112113 fn into_untyped(value: Self, _: State) -> Result<Val> {114 Ok(Val::Num(value.0 as f64))115 }116 }117 )*};118}119120impl_bounded_int!(121 BoundedI8 = i8122 BoundedI16 = i16123 BoundedI32 = i32124 BoundedI64 = i64125 BoundedUsize = usize126);127128impl Typed for f64 {129 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);130131 fn into_untyped(value: Self, _: State) -> Result<Val> {132 Ok(Val::Num(value))133 }134135 fn from_untyped(value: Val, s: State) -> Result<Self> {136 <Self as Typed>::TYPE.check(s, &value)?;137 match value {138 Val::Num(n) => Ok(n),139 _ => unreachable!(),140 }141 }142}143144pub struct PositiveF64(pub f64);145impl Typed for PositiveF64 {146 const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);147148 fn into_untyped(value: Self, _: State) -> Result<Val> {149 Ok(Val::Num(value.0))150 }151152 fn from_untyped(value: Val, s: State) -> Result<Self> {153 <Self as Typed>::TYPE.check(s, &value)?;154 match value {155 Val::Num(n) => Ok(Self(n)),156 _ => unreachable!(),157 }158 }159}160impl Typed for usize {161 // It is possible to store 54 bits of precision in f64, but leaving u32::MAX here for compatibility162 const TYPE: &'static ComplexValType =163 &ComplexValType::BoundedNumber(Some(0.0), Some(4294967295.0));164165 fn into_untyped(value: Self, _: State) -> Result<Val> {166 if value > u32::MAX as Self {167 throw!(RuntimeError("number is too large".into()))168 }169 Ok(Val::Num(value as f64))170 }171172 fn from_untyped(value: Val, s: State) -> Result<Self> {173 <Self as Typed>::TYPE.check(s, &value)?;174 match value {175 Val::Num(n) => {176 if n.trunc() != n {177 throw!(RuntimeError(178 "cannot convert number with fractional part to usize".into()179 ))180 }181 Ok(n as Self)182 }183 _ => unreachable!(),184 }185 }186}187188impl Typed for IStr {189 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);190191 fn into_untyped(value: Self, _: State) -> Result<Val> {192 Ok(Val::Str(value))193 }194195 fn from_untyped(value: Val, s: State) -> Result<Self> {196 <Self as Typed>::TYPE.check(s, &value)?;197 match value {198 Val::Str(s) => Ok(s),199 _ => unreachable!(),200 }201 }202}203204impl Typed for String {205 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);206207 fn into_untyped(value: Self, _: State) -> Result<Val> {208 Ok(Val::Str(value.into()))209 }210211 fn from_untyped(value: Val, s: State) -> Result<Self> {212 <Self as Typed>::TYPE.check(s, &value)?;213 match value {214 Val::Str(s) => Ok(s.to_string()),215 _ => unreachable!(),216 }217 }218}219220impl Typed for char {221 const TYPE: &'static ComplexValType = &ComplexValType::Char;222223 fn into_untyped(value: Self, _: State) -> Result<Val> {224 Ok(Val::Str(value.to_string().into()))225 }226227 fn from_untyped(value: Val, s: State) -> Result<Self> {228 <Self as Typed>::TYPE.check(s, &value)?;229 match value {230 Val::Str(s) => Ok(s.chars().next().unwrap()),231 _ => unreachable!(),232 }233 }234}235236impl<T> Typed for Vec<T>237where238 T: Typed,239{240 const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);241242 fn into_untyped(value: Self, s: State) -> Result<Val> {243 let mut o = Vec::with_capacity(value.len());244 for i in value {245 o.push(T::into_untyped(i, s.clone())?);246 }247 Ok(Val::Arr(o.into()))248 }249250 fn from_untyped(value: Val, s: State) -> Result<Self> {251 <Self as Typed>::TYPE.check(s.clone(), &value)?;252 match value {253 Val::Arr(a) => {254 let mut o = Self::with_capacity(a.len());255 for i in a.iter(s.clone()) {256 o.push(T::from_untyped(i?, s.clone())?);257 }258 Ok(o)259 }260 _ => unreachable!(),261 }262 }263}264265/// To be used in Vec<Any>266/// Regular Val can't be used here, because it has wrong TryFrom::Error type267#[derive(Clone)]268pub struct Any(pub Val);269270impl Typed for Any {271 const TYPE: &'static ComplexValType = &ComplexValType::Any;272273 fn into_untyped(value: Self, _: State) -> Result<Val> {274 Ok(value.0)275 }276277 fn from_untyped(value: Val, _: State) -> Result<Self> {278 Ok(Self(value))279 }280}281282/// Specialization, provides faster TryFrom<VecVal> for Val283pub struct VecVal(pub Cc<Vec<Val>>);284285impl Typed for VecVal {286 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);287288 fn into_untyped(value: Self, _: State) -> Result<Val> {289 Ok(Val::Arr(ArrValue::Eager(value.0)))290 }291292 fn from_untyped(value: Val, s: State) -> Result<Self> {293 <Self as Typed>::TYPE.check(s.clone(), &value)?;294 match value {295 Val::Arr(a) => Ok(Self(a.evaluated(s)?)),296 _ => unreachable!(),297 }298 }299}300301/// Specialization302pub struct Bytes(pub Rc<[u8]>);303304impl Typed for Bytes {305 const TYPE: &'static ComplexValType =306 &ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));307308 fn into_untyped(value: Self, _: State) -> Result<Val> {309 Ok(Val::Arr(ArrValue::Bytes(value.0)))310 }311312 fn from_untyped(value: Val, s: State) -> Result<Self> {313 match value {314 Val::Arr(ArrValue::Bytes(bytes)) => Ok(Self(bytes)),315 _ => {316 <Self as Typed>::TYPE.check(s.clone(), &value)?;317 match value {318 Val::Arr(a) => {319 let mut out = Vec::with_capacity(a.len());320 for e in a.iter(s.clone()) {321 let r = e?;322 out.push(u8::from_untyped(r, s.clone())?);323 }324 Ok(Self(out.into()))325 }326 _ => unreachable!(),327 }328 }329 }330 }331}332333pub struct M1;334impl Typed for M1 {335 const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));336337 fn into_untyped(_: Self, _: State) -> Result<Val> {338 Ok(Val::Num(-1.0))339 }340341 fn from_untyped(value: Val, s: State) -> Result<Self> {342 <Self as Typed>::TYPE.check(s, &value)?;343 Ok(Self)344 }345}346347macro_rules! decl_either {348 ($($name: ident, $($id: ident)*);*) => {$(349 pub enum $name<$($id),*> {350 $($id($id)),*351 }352 impl<$($id),*> Typed for $name<$($id),*>353 where354 $($id: Typed,)*355 {356 const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);357358 fn into_untyped(value: Self, s: State) -> Result<Val> {359 match value {$(360 $name::$id(v) => $id::into_untyped(v, s)361 ),*}362 }363364 fn from_untyped(value: Val, s: State) -> Result<Self> {365 $(366 if $id::TYPE.check(s.clone(), &value).is_ok() {367 $id::from_untyped(value, s.clone()).map(Self::$id)368 } else369 )* {370 <Self as Typed>::TYPE.check(s, &value)?;371 unreachable!()372 }373 }374 }375 )*}376}377decl_either!(378 Either1, A;379 Either2, A B;380 Either3, A B C;381 Either4, A B C D;382 Either5, A B C D E;383 Either6, A B C D E F;384 Either7, A B C D E F G385);386#[macro_export]387macro_rules! Either {388 ($a:ty) => {Either1<$a>};389 ($a:ty, $b:ty) => {Either2<$a, $b>};390 ($a:ty, $b:ty, $c:ty) => {Either3<$a, $b, $c>};391 ($a:ty, $b:ty, $c:ty, $d:ty) => {Either4<$a, $b, $c, $d>};392 ($a:ty, $b:ty, $c:ty, $d:ty, $e:ty) => {Either5<$a, $b, $c, $d, $e>};393 ($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {Either6<$a, $b, $c, $d, $e, $f>};394 ($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {Either7<$a, $b, $c, $d, $e, $f, $g>};395}396397pub type MyType = Either![u32, f64, String];398399impl Typed for ArrValue {400 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);401402 fn into_untyped(value: Self, _: State) -> Result<Val> {403 Ok(Val::Arr(value))404 }405406 fn from_untyped(value: Val, s: State) -> Result<Self> {407 <Self as Typed>::TYPE.check(s, &value)?;408 match value {409 Val::Arr(a) => Ok(a),410 _ => unreachable!(),411 }412 }413}414415impl Typed for FuncVal {416 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);417418 fn into_untyped(value: Self, _: State) -> Result<Val> {419 Ok(Val::Func(value))420 }421422 fn from_untyped(value: Val, s: State) -> Result<Self> {423 <Self as Typed>::TYPE.check(s, &value)?;424 match value {425 Val::Func(a) => Ok(a),426 _ => unreachable!(),427 }428 }429}430431impl Typed for Cc<FuncDesc> {432 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);433434 fn into_untyped(value: Self, _: State) -> Result<Val> {435 Ok(Val::Func(FuncVal::Normal(value)))436 }437438 fn from_untyped(value: Val, s: State) -> Result<Self> {439 <Self as Typed>::TYPE.check(s, &value)?;440 match value {441 Val::Func(FuncVal::Normal(desc)) => Ok(desc),442 Val::Func(_) => throw!(RuntimeError("expected normal function, not builtin".into())),443 _ => unreachable!(),444 }445 }446}447448impl Typed for ObjValue {449 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);450451 fn into_untyped(value: Self, _: State) -> Result<Val> {452 Ok(Val::Obj(value))453 }454455 fn from_untyped(value: Val, s: State) -> Result<Self> {456 <Self as Typed>::TYPE.check(s, &value)?;457 match value {458 Val::Obj(a) => Ok(a),459 _ => unreachable!(),460 }461 }462}463464impl Typed for bool {465 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);466467 fn into_untyped(value: Self, _: State) -> Result<Val> {468 Ok(Val::Bool(value))469 }470471 fn from_untyped(value: Val, s: State) -> Result<Self> {472 <Self as Typed>::TYPE.check(s, &value)?;473 match value {474 Val::Bool(a) => Ok(a),475 _ => unreachable!(),476 }477 }478}479impl Typed for IndexableVal {480 const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[481 &ComplexValType::Simple(ValType::Arr),482 &ComplexValType::Simple(ValType::Str),483 ]);484485 fn into_untyped(value: Self, _: State) -> Result<Val> {486 match value {487 IndexableVal::Str(s) => Ok(Val::Str(s)),488 IndexableVal::Arr(a) => Ok(Val::Arr(a)),489 }490 }491492 fn from_untyped(value: Val, s: State) -> Result<Self> {493 <Self as Typed>::TYPE.check(s, &value)?;494 value.into_indexable()495 }496}497498pub struct Null;499impl Typed for Null {500 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);501502 fn into_untyped(_: Self, _: State) -> Result<Val> {503 Ok(Val::Null)504 }505506 fn from_untyped(value: Val, s: State) -> Result<Self> {507 <Self as Typed>::TYPE.check(s, &value)?;508 Ok(Self)509 }510}crates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/builtin.rs
@@ -0,0 +1,105 @@
+mod common;
+
+use std::path::PathBuf;
+
+use gcmodule::Cc;
+use jrsonnet_evaluator::{
+ error::Result,
+ function::{builtin, Builtin, CallLocation},
+ gc::TraceBox,
+ typed::Typed,
+ val::FuncVal,
+ State, Val,
+};
+
+#[builtin]
+fn a() -> Result<u32> {
+ Ok(1)
+}
+
+#[test]
+fn basic_function() -> Result<()> {
+ let s = State::default();
+ let a: a = a {};
+ let v = u32::from_untyped(
+ a.call(
+ s.clone(),
+ s.create_default_context(),
+ CallLocation::native(),
+ &[],
+ )?,
+ s.clone(),
+ )?;
+
+ ensure_eq!(v, 1);
+ Ok(())
+}
+
+#[builtin]
+fn native_add(a: u32, b: u32) -> Result<u32> {
+ Ok(a + b)
+}
+
+#[test]
+fn call_from_code() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.settings_mut().globals.insert(
+ "nativeAdd".into(),
+ Val::Func(FuncVal::StaticBuiltin(native_add::INST)),
+ );
+
+ let v = s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "
+ assert nativeAdd(1, 2) == 3;
+ assert nativeAdd(100, 200) == 300;
+ null
+ "
+ .into(),
+ )?;
+ ensure_val_eq!(s.clone(), v, Val::Null);
+ Ok(())
+}
+
+#[builtin(fields(
+ a: u32
+))]
+fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
+ Ok(this.a + b)
+}
+
+#[builtin]
+fn curry_add(a: u32) -> Result<FuncVal> {
+ Ok(FuncVal::Builtin(Cc::new(TraceBox(Box::new(curried_add {
+ a,
+ })))))
+}
+
+#[test]
+fn nonstatic_builtin() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.settings_mut().globals.insert(
+ "curryAdd".into(),
+ Val::Func(FuncVal::StaticBuiltin(curry_add::INST)),
+ );
+
+ let v = s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "
+ local a = curryAdd(1);
+ local b = curryAdd(4);
+
+ assert a(2) == 3;
+ assert a(200) == 201;
+
+ assert b(2) == 6;
+ assert b(200) == 204;
+ null
+ "
+ .into(),
+ )?;
+ ensure_val_eq!(s.clone(), v, Val::Null);
+ Ok(())
+}
crates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/common.rs
@@ -0,0 +1,25 @@
+#[macro_export]
+macro_rules! ensure_eq {
+ ($a:expr, $b:expr $(,)?) => {{
+ if $a != $b {
+ ::jrsonnet_evaluator::throw_runtime!(
+ "assertion failed: a != b\na={:#?}\nb={:#?}",
+ $a,
+ $b,
+ )
+ }
+ }};
+}
+
+#[macro_export]
+macro_rules! ensure_val_eq {
+ ($s:expr, $a:expr, $b:expr) => {{
+ if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
+ ::jrsonnet_evaluator::throw_runtime!(
+ "assertion failed: a != b\na={:#?}\nb={:#?}",
+ $a.to_json($s.clone(), 2)?,
+ $b.to_json($s.clone(), 2)?,
+ )
+ }
+ }};
+}
crates/jrsonnet-evaluator/tests/typed_obj.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/typed_obj.rs
@@ -0,0 +1,194 @@
+mod common;
+
+use std::{fmt::Debug, path::PathBuf};
+
+use jrsonnet_evaluator::{error::Result, typed::Typed, State};
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct A {
+ a: u32,
+ b: u16,
+}
+
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
+ let untyped = T::into_untyped(value.clone(), s.clone())?;
+ let value2 = T::from_untyped(untyped.clone(), s.clone())?;
+ ensure_eq!(value, value2);
+ let untyped2 = T::into_untyped(value2, s.clone())?;
+ ensure_val_eq!(s, untyped, untyped2);
+
+ Ok(())
+}
+
+#[test]
+fn simple_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let a = A::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(a, A { a: 1, b: 2 });
+ test_roundtrip(a.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct B {
+ a: u32,
+ #[typed(rename = "c")]
+ b: u16,
+}
+
+#[test]
+fn renamed_field() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let b = B::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, c: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(b, B { a: 1, b: 2 });
+ ensure_eq!(
+ &B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
+ "{a: 1, c: 2}",
+ );
+ test_roundtrip(b.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct ObjectKind {
+ #[typed(rename = "apiVersion")]
+ api_version: String,
+ #[typed(rename = "kind")]
+ kind: String,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct Object {
+ #[typed(flatten)]
+ kind: ObjectKind,
+ b: u16,
+}
+
+#[test]
+fn flattened_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let obj = Object::from_untyped(
+ s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "{apiVersion: 'ver', kind: 'kind', b: 2}".into(),
+ )?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ obj,
+ Object {
+ kind: ObjectKind {
+ api_version: "ver".into(),
+ kind: "kind".into(),
+ },
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
+ );
+ test_roundtrip(obj.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct C {
+ a: Option<u32>,
+ b: u16,
+}
+
+#[test]
+fn optional_field_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(c, C { a: Some(1), b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"a": 1, "b": 2}"#,
+ );
+ test_roundtrip(c.clone(), s.clone())?;
+ Ok(())
+}
+
+#[test]
+fn optional_field_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(c, C { a: None, b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(c.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct D {
+ #[typed(flatten(ok))]
+ e: Option<E>,
+ b: u16,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct E {
+ v: u32,
+}
+
+#[test]
+fn flatten_optional_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v:1}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ d,
+ D {
+ e: Some(E { v: 1 }),
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2, "v": 1}"#,
+ );
+ test_roundtrip(d.clone(), s.clone())?;
+ Ok(())
+}
+
+#[test]
+fn flatten_optional_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v: '1'}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(d, D { e: None, b: 2 });
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(d.clone(), s.clone())?;
+ Ok(())
+}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -6,7 +6,7 @@
parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
- token::Comma,
+ token::{self, Comma},
Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
PathArguments, Result, ReturnType, Token, Type,
};
@@ -90,6 +90,7 @@
syn::custom_keyword!(fields);
syn::custom_keyword!(rename);
syn::custom_keyword!(flatten);
+ syn::custom_keyword!(ok);
}
struct EmptyAttr;
@@ -135,7 +136,7 @@
}
impl ArgInfo {
- fn parse(arg: &FnArg) -> Result<Self> {
+ fn parse(name: &str, arg: &FnArg) -> Result<Self> {
let arg = match arg {
FnArg::Receiver(_) => unreachable!(),
FnArg::Typed(a) => a,
@@ -149,8 +150,6 @@
return Ok(Self::State);
} else if type_is_path(ty, "CallLocation").is_some() {
return Ok(Self::Location);
- } else if type_is_path(ty, "Self").is_some() {
- return Ok(Self::This);
} else if type_is_path(ty, "LazyVal").is_some() {
return Ok(Self::Lazy {
is_option: false,
@@ -158,6 +157,11 @@
});
}
+ match &ty as &Type {
+ Type::Reference(r) if type_is_path(&r.elem, &name).is_some() => return Ok(Self::This),
+ _ => {}
+ }
+
let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {
if type_is_path(ty, "LazyVal").is_some() {
return Ok(Self::Lazy {
@@ -230,11 +234,12 @@
return Err(Error::new(result.span(), "return value should be result"));
};
+ let name = fun.sig.ident.to_string();
let args = fun
.sig
.inputs
.iter()
- .map(ArgInfo::parse)
+ .map(|arg| ArgInfo::parse(&name, arg))
.collect::<Result<Vec<_>>>()?;
let params_desc = args.iter().flat_map(|a| match a {
@@ -343,9 +348,9 @@
}
const _: () = {
use ::jrsonnet_evaluator::{
- State,
+ State, Val,
function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
- error::Result, Context,
+ error::Result, Context, typed::Typed,
parser::ExprLocation,
};
const PARAMS: &'static [BuiltinParam] = &[
@@ -379,6 +384,9 @@
struct TypedAttr {
rename: Option<String>,
flatten: bool,
+ /// flatten(ok) strategy for flattened optionals
+ /// field would be None in case of any parsing error (as in serde)
+ flatten_ok: bool,
}
impl Parse for TypedAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
@@ -399,6 +407,17 @@
} else if lookahead.peek(kw::flatten) {
input.parse::<kw::flatten>()?;
out.flatten = true;
+ if input.peek(token::Paren) {
+ let content;
+ parenthesized!(content in input);
+ let lookahead = content.lookahead1();
+ if lookahead.peek(kw::ok) {
+ content.parse::<kw::ok>()?;
+ out.flatten_ok = true;
+ } else {
+ return Err(lookahead.error());
+ }
+ }
} else if input.is_empty() {
break;
} else {
@@ -417,75 +436,101 @@
}
}
-struct TypedField<'f>(&'f syn::Field, TypedAttr);
-impl<'f> TypedField<'f> {
- fn try_new(field: &'f syn::Field) -> Result<Self> {
+struct TypedField {
+ attr: TypedAttr,
+ ident: Ident,
+ ty: Type,
+ is_option: bool,
+}
+impl TypedField {
+ fn parse(field: &syn::Field) -> Result<Self> {
let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();
- if field.ident.is_none() {
+ let ident = if let Some(ident) = field.ident.clone() {
+ ident
+ } else {
return Err(Error::new(
field.span(),
"this field should appear in output object, but it has no visible name",
));
+ };
+ let (is_option, ty) = if let Some(ty) = extract_type_from_option(&field.ty)? {
+ (true, ty.clone())
+ } else {
+ (false, field.ty.clone())
+ };
+ if is_option && attr.flatten {
+ if !attr.flatten_ok {
+ return Err(Error::new(
+ field.span(),
+ "strategy should be set when flattening Option",
+ ));
+ }
+ } else {
+ if attr.flatten_ok {
+ return Err(Error::new(
+ field.span(),
+ "flatten(ok) is only useable on optional fields",
+ ));
+ }
}
- Ok(Self(field, attr))
- }
- fn ident(&self) -> Ident {
- self.0
- .ident
- .clone()
- .expect("constructor disallows fields without name")
+ Ok(Self {
+ attr,
+ ident,
+ ty,
+ is_option,
+ })
}
/// None if this field is flattened in jsonnet output
fn name(&self) -> Option<String> {
- if self.1.flatten {
+ if self.attr.flatten {
return None;
}
Some(
- self.1
+ self.attr
.rename
.clone()
- .unwrap_or_else(|| self.ident().to_string()),
+ .unwrap_or_else(|| self.ident.to_string()),
)
}
fn expand_field(&self) -> Option<TokenStream> {
- if self.is_option() {
+ if self.is_option {
return None;
}
let name = self.name()?;
- let ty = &self.0.ty;
+ let ty = &self.ty;
Some(quote! {
(#name, <#ty>::TYPE)
})
}
fn expand_parse(&self) -> TokenStream {
- let ident = self.ident();
- let ty = &self.0.ty;
- if self.1.flatten {
+ let ident = &self.ident;
+ let ty = &self.ty;
+ if self.attr.flatten {
// optional flatten is handled in same way as serde
- return if self.is_option() {
+ return if self.is_option {
quote! {
- #ident: <#ty>::parse(&obj).ok(),
+ #ident: <#ty>::parse(&obj, s.clone()).ok(),
}
} else {
quote! {
- #ident: <#ty>::parse(&obj)?,
+ #ident: <#ty>::parse(&obj, s.clone())?,
}
};
};
let name = self.name().unwrap();
- let value = if let Some(ty) = self.as_option() {
+ let value = if self.is_option {
quote! {
- if let Some(value) = obj.get(#name.into())? {
- Some(<#ty>::try_from(vakue)?)
+ if let Some(value) = obj.get(s.clone(), #name.into())? {
+ Some(<#ty>::from_untyped(value, s.clone())?)
} else {
None
}
}
} else {
quote! {
- <#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?
+ <#ty>::from_untyped(obj.get(s.clone(), #name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?, s.clone())?
}
};
@@ -493,39 +538,33 @@
#ident: #value,
}
}
- fn expand_serialize(&self) -> TokenStream {
- let ident = self.ident();
- if let Some(name) = self.name() {
- if self.is_option() {
+ fn expand_serialize(&self) -> Result<TokenStream> {
+ let ident = &self.ident;
+ let ty = &self.ty;
+ Ok(if let Some(name) = self.name() {
+ if self.is_option {
quote! {
if let Some(value) = self.#ident {
- out.member(#name.into()).value(value.try_into()?)?;
+ out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(value, s.clone())?)?;
}
}
} else {
quote! {
- out.member(#name.into()).value(self.#ident.try_into()?)?;
+ out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(self.#ident, s.clone())?)?;
}
}
- } else if self.is_option() {
+ } else if self.is_option {
quote! {
if let Some(value) = self.#ident {
- value.serialize(out)?;
+ value.serialize(s.clone(), out)?;
}
}
} else {
quote! {
- self.#ident.serialize(out)?;
+ self.#ident.serialize(s.clone(), out)?;
}
- }
- }
-
- fn as_option(&self) -> Option<&Type> {
- extract_type_from_option(&self.0.ty).unwrap()
+ })
}
- fn is_option(&self) -> bool {
- self.as_option().is_some()
- }
}
#[proc_macro_derive(Typed, attributes(typed))]
@@ -548,7 +587,7 @@
let fields = data
.fields
.iter()
- .map(TypedField::try_new)
+ .map(TypedField::parse)
.collect::<Result<Vec<_>>>()?;
let typed = {
@@ -566,12 +605,12 @@
fn from_untyped(value: Val, s: State) -> Result<Self> {
let obj = value.as_obj().expect("shape is correct");
- Self::parse(&obj)
+ Self::parse(&obj, s)
}
fn into_untyped(value: Self, s: State) -> Result<Val> {
let mut out = ObjValueBuilder::new();
- value.serialize(&mut out)?;
+ value.serialize(s, &mut out)?;
Ok(Val::Obj(out.build()))
}
@@ -580,26 +619,29 @@
};
let fields_parse = fields.iter().map(TypedField::expand_parse);
- let fields_serialize = fields.iter().map(TypedField::expand_serialize);
+ let fields_serialize = fields
+ .iter()
+ .map(TypedField::expand_serialize)
+ .collect::<Result<Vec<_>>>()?;
Ok(quote! {
const _: () = {
use ::jrsonnet_evaluator::{
typed::{ComplexValType, Typed, TypedObj, CheckType},
- Val,
- error::{LocError, Error},
+ Val, State,
+ error::{LocError, Error, Result},
ObjValueBuilder, ObjValue,
};
#typed
- impl #ident {
- fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {
+ impl TypedObj for #ident {
+ fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<(), LocError> {
#(#fields_serialize)*
Ok(())
}
- fn parse(obj: &ObjValue) -> Result<Self, LocError> {
+ fn parse(obj: &ObjValue, s: State) -> Result<Self, LocError> {
Ok(Self {
#(#fields_parse)*
})