difftreelog
refactor drop runtime_error! usage
in: master
4 files changed
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth1#![allow(non_snake_case)]23use jrsonnet_evaluator::{4 Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, bail, error,5 function::{NativeFn, builtin},6 runtime_error,7 typed::{BoundedUsize, Either2, FromUntyped},8 val::{ArrValue, IndexableVal, equals},9};1011pub fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {12 if let Some(on_empty) = on_empty {13 on_empty.evaluate()14 } else {15 bail!("expected non-empty array")16 }17}1819#[builtin]20pub fn builtin_make_array(sz: u32, func: NativeFn!((u32,) -> Val)) -> Result<ArrValue> {21 if sz == 0 {22 return Ok(ArrValue::empty());23 }24 // Try eager evaluation: call func(i) immediately for each element.25 'eager: {26 let mut out = Vec::with_capacity(sz as usize);27 for i in 0..sz {28 match func.call(i) {29 Ok(v) => out.push(v),30 Err(_) => break 'eager,31 }32 }33 return Ok(ArrValue::new(out));34 }35 Ok(ArrValue::make(sz, func))36}3738#[builtin]39pub fn builtin_repeat(what: Either![IStr, ArrValue], count: u32) -> Result<Val> {40 Ok(match what {41 Either2::A(s) => Val::string(s.repeat(count as usize)),42 Either2::B(arr) => Val::Arr(43 ArrValue::repeated(arr, count)44 .ok_or_else(|| runtime_error!("repeated length overflow"))?,45 ),46 })47}4849#[builtin]50pub fn builtin_slice(51 indexable: IndexableVal,52 index: Option<Option<i32>>,53 end: Option<Option<i32>>,54 step: Option<Option<BoundedUsize<1, { i32::MAX as usize }>>>,55) -> Result<Val> {56 indexable57 .slice(index.flatten(), end.flatten(), step.flatten())58 .map(Val::from)59}6061#[builtin]62pub fn builtin_map(func: NativeFn!((Val) -> Val), arr: IndexableVal) -> ArrValue {63 let arr = arr.to_array();64 arr.map(func)65}6667#[builtin]68pub fn builtin_map_with_index(func: NativeFn!((u32, Val) -> Val), arr: IndexableVal) -> ArrValue {69 let arr = arr.to_array();70 arr.map_with_index(func)71}7273#[builtin]74pub fn builtin_map_with_key(75 func: NativeFn!((IStr, Val) -> Val),76 obj: ObjValue,77) -> Result<ObjValue> {78 let mut out = ObjValueBuilder::new();79 for (k, v) in obj.iter(80 // Makes sense mapped object should be ordered the same way, should not break anything when the output is not ordered (the default).81 // The thrown error might be different, but jsonnet82 // does not specify the evaluation order.83 #[cfg(feature = "exp-preserve-order")]84 true,85 ) {86 let v = v?;87 out.field(k.clone()).value(func.call(k, v)?);88 }89 Ok(out.build())90}9192#[builtin]93pub fn builtin_flatmap(94 func: NativeFn!((Either![String, Val]) -> Val),95 arr: IndexableVal,96) -> Result<IndexableVal> {97 use std::fmt::Write;98 match arr {99 IndexableVal::Str(str) => {100 let mut out = String::new();101 for c in str.chars() {102 match func.call(Either2::A(c.to_string()))? {103 Val::Str(o) => write!(out, "{o}").unwrap(),104 Val::Null => {}105 _ => bail!("in std.join all items should be strings"),106 }107 }108 Ok(IndexableVal::Str(out.into()))109 }110 IndexableVal::Arr(a) => {111 let mut out = Vec::new();112 for el in a.iter() {113 let el = el?;114 match func.call(Either2::B(el))? {115 Val::Arr(o) => {116 for oe in o.iter() {117 out.push(oe?);118 }119 }120 Val::Null => {}121 _ => bail!("in std.join all items should be arrays"),122 }123 }124 Ok(IndexableVal::Arr(out.into()))125 }126 }127}128129type FilterFunc = NativeFn!((Thunk<Val>) -> bool);130131#[builtin]132pub fn builtin_filter(func: FilterFunc, arr: ArrValue) -> Result<ArrValue> {133 arr.filter(func)134}135136#[builtin]137pub fn builtin_filter_map(138 filter_func: FilterFunc,139 map_func: NativeFn!((Val) -> Val),140 arr: ArrValue,141) -> Result<ArrValue> {142 Ok(arr.filter(filter_func)?.map(map_func))143}144145#[builtin]146pub fn builtin_foldl(147 func: NativeFn!((Val, Either![Val, char]) -> Val),148 arr: Either![ArrValue, IStr],149 init: Val,150) -> Result<Val> {151 let mut acc = init;152 match arr {153 Either2::A(arr) => {154 for i in arr.iter() {155 acc = func.call(acc, Either2::A(i?))?;156 }157 }158 Either2::B(arr) => {159 for c in arr.chars() {160 acc = func.call(acc, Either2::B(c))?;161 }162 }163 }164 Ok(acc)165}166167#[builtin]168pub fn builtin_foldr(169 func: NativeFn!((Either![Val, char], Val) -> Val),170 arr: Either![ArrValue, IStr],171 init: Val,172) -> Result<Val> {173 let mut acc = init;174 match arr {175 Either2::A(arr) => {176 for i in arr.iter().rev() {177 acc = func.call(Either2::A(i?), acc)?;178 }179 }180 Either2::B(arr) => {181 for c in arr.chars().rev() {182 acc = func.call(Either2::B(c), acc)?;183 }184 }185 }186 Ok(acc)187}188189#[builtin]190pub fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {191 if to < from {192 return Ok(ArrValue::empty());193 }194 Ok(ArrValue::range_inclusive(from, to))195}196197#[builtin]198pub fn builtin_join(sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {199 use std::fmt::Write;200 Ok(match sep {201 IndexableVal::Arr(joiner_items) => {202 let mut out = Vec::new();203204 let mut first = true;205 for item in arr.iter() {206 let item = item?.clone();207 if let Val::Arr(items) = item {208 if !first {209 out.reserve(joiner_items.len() as usize);210 // TODO: extend211 for item in joiner_items.iter() {212 out.push(item?);213 }214 }215 first = false;216 out.reserve(items.len() as usize);217 for item in items.iter() {218 out.push(item?);219 }220 } else if matches!(item, Val::Null) {221 } else {222 bail!("in std.join all items should be arrays");223 }224 }225226 IndexableVal::Arr(out.into())227 }228 IndexableVal::Str(sep) => {229 let mut out = String::new();230231 let mut first = true;232 for item in arr.iter() {233 let item = item?.clone();234 if let Val::Str(item) = item {235 if !first {236 out += &sep;237 }238 first = false;239 write!(out, "{item}").unwrap();240 } else if matches!(item, Val::Null) {241 } else {242 bail!("in std.join all items should be strings");243 }244 }245246 IndexableVal::Str(out.into())247 }248 })249}250251#[builtin]252pub fn builtin_lines(arr: ArrValue) -> Result<IndexableVal> {253 builtin_join(254 IndexableVal::Str("\n".into()),255 ArrValue::extended(arr, ArrValue::new(vec![Val::string("")]))256 .ok_or_else(|| error!("array is too large"))?,257 )258}259260#[builtin]261pub fn builtin_resolve_path(f: String, r: String) -> String {262 let Some(pos) = f.rfind('/') else {263 return r;264 };265 format!("{}{}", &f[..=pos], r)266}267268pub fn deep_join_inner(out: &mut String, arr: IndexableVal) -> Result<()> {269 use std::fmt::Write;270 match arr {271 IndexableVal::Str(s) => write!(out, "{s}").expect("no error"),272 IndexableVal::Arr(arr) => {273 for ele in arr.iter() {274 let indexable = IndexableVal::from_untyped(ele?)?;275 deep_join_inner(out, indexable)?;276 }277 }278 }279 Ok(())280}281282#[builtin]283pub fn builtin_deep_join(arr: IndexableVal) -> Result<String> {284 let mut out = String::new();285 deep_join_inner(&mut out, arr)?;286 Ok(out)287}288289#[builtin]290pub fn builtin_reverse(arr: ArrValue) -> ArrValue {291 arr.reversed()292}293294#[builtin]295pub fn builtin_any(arr: ArrValue) -> Result<bool> {296 for v in arr.iter() {297 let v = bool::from_untyped(v?)?;298 if v {299 return Ok(true);300 }301 }302 Ok(false)303}304305#[builtin]306pub fn builtin_all(arr: ArrValue) -> Result<bool> {307 for v in arr.iter() {308 let v = bool::from_untyped(v?)?;309 if !v {310 return Ok(false);311 }312 }313 Ok(true)314}315316#[builtin]317pub fn builtin_member(arr: IndexableVal, x: Val) -> Result<bool> {318 match arr {319 IndexableVal::Str(str) => {320 let x: IStr = IStr::from_untyped(x)?;321 Ok(!x.is_empty() && str.contains(&*x))322 }323 IndexableVal::Arr(a) => {324 for item in a.iter() {325 let item = item?;326 if equals(&item, &x)? {327 return Ok(true);328 }329 }330 Ok(false)331 }332 }333}334335#[builtin]336pub fn builtin_find(value: Val, arr: ArrValue) -> Result<Vec<usize>> {337 let mut out = Vec::new();338 for (i, ele) in arr.iter().enumerate() {339 let ele = ele?;340 if equals(&ele, &value)? {341 out.push(i);342 }343 }344 Ok(out)345}346347#[builtin]348pub fn builtin_contains(arr: IndexableVal, elem: Val) -> Result<bool> {349 builtin_member(arr, elem)350}351352#[builtin]353pub fn builtin_count(arr: ArrValue, x: Val) -> Result<usize> {354 let mut count = 0;355 for item in arr.iter() {356 if equals(&item?, &x)? {357 count += 1;358 }359 }360 Ok(count)361}362363#[builtin]364pub fn builtin_avg(arr: Vec<f64>, onEmpty: Option<Thunk<Val>>) -> Result<Val> {365 if arr.is_empty() {366 return eval_on_empty(onEmpty);367 }368 #[expect(369 clippy::cast_precision_loss,370 reason = "array sizes are bounded to i32 len"371 )]372 Ok(Val::try_num(arr.iter().sum::<f64>() / (arr.len() as f64))?)373}374375#[builtin]376pub fn builtin_remove_at(arr: ArrValue, at: i32) -> Result<ArrValue> {377 let newArrLeft = arr.clone().slice(None, Some(at), None);378 let newArrRight = arr.slice(Some(at + 1), None, None);379380 Ok(ArrValue::extended(newArrLeft, newArrRight).ok_or_else(|| error!("array is too large"))?)381}382383#[builtin]384pub fn builtin_remove(arr: ArrValue, elem: Val) -> Result<ArrValue> {385 for (index, item) in arr.iter().enumerate() {386 if equals(&item?, &elem)? {387 #[expect(388 clippy::cast_possible_truncation,389 clippy::cast_possible_wrap,390 reason = "array sizes are bounded to i32 len"391 )]392 return builtin_remove_at(arr.clone(), index as i32);393 }394 }395 Ok(arr)396}397398#[builtin]399pub fn builtin_flatten_arrays(arrs: Vec<ArrValue>) -> Result<ArrValue> {400 pub fn flatten_inner(values: &[ArrValue]) -> Result<ArrValue> {401 if values.len() == 1 {402 return Ok(values[0].clone());403 } else if values.len() == 2 {404 return ArrValue::extended(values[0].clone(), values[1].clone())405 .ok_or_else(|| error!("array is too large"));406 }407 let (a, b) = values.split_at(values.len() / 2);408 ArrValue::extended(flatten_inner(a)?, flatten_inner(b)?)409 .ok_or_else(|| error!("array is too large"))410 }411 if arrs.is_empty() {412 return Ok(ArrValue::empty());413 } else if arrs.len() == 1 {414 return Ok(arrs.into_iter().next().expect("single"));415 }416 flatten_inner(&arrs)417}418419#[builtin]420pub fn builtin_flatten_deep_array(value: Val) -> Result<Vec<Val>> {421 fn process(value: Val, out: &mut Vec<Val>) -> Result<()> {422 match value {423 Val::Arr(arr) => {424 for ele in arr.iter() {425 process(ele?, out)?;426 }427 }428 _ => out.push(value),429 }430 Ok(())431 }432 let mut out = Vec::new();433 process(value, &mut out)?;434 Ok(out)435}436437#[builtin]438pub fn builtin_prune(439 a: Val,440441 #[default(false)]442 #[cfg(feature = "exp-preserve-order")]443 preserve_order: bool,444) -> Result<Val> {445 fn is_content(val: &Val) -> bool {446 match val {447 Val::Null => false,448 Val::Arr(a) => !a.is_empty(),449 Val::Obj(o) => !o.is_empty(),450 _ => true,451 }452 }453 Ok(match a {454 Val::Arr(a) => {455 let mut out = Vec::new();456 for (i, ele) in a.iter().enumerate() {457 let ele = ele458 .and_then(|v| {459 builtin_prune(460 v,461 #[cfg(feature = "exp-preserve-order")]462 preserve_order,463 )464 })465 .with_description(|| format!("elem <{i}> pruning"))?;466 if is_content(&ele) {467 out.push(ele);468 }469 }470 Val::arr(out)471 }472 Val::Obj(o) => {473 let mut out = ObjValueBuilder::new();474 for (name, value) in o.iter(475 #[cfg(feature = "exp-preserve-order")]476 preserve_order,477 ) {478 let value = value479 .and_then(|v| {480 builtin_prune(481 v,482 #[cfg(feature = "exp-preserve-order")]483 preserve_order,484 )485 })486 .with_description(|| format!("field <{name}> pruning"))?;487 if !is_content(&value) {488 continue;489 }490 out.field(name).value(value);491 }492 Val::Obj(out.build())493 }494 _ => a,495 })496}1#![allow(non_snake_case)]23use jrsonnet_evaluator::{4 Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, bail, error,5 function::{NativeFn, builtin},6 typed::{BoundedUsize, Either2, FromUntyped},7 val::{ArrValue, IndexableVal, equals},8};910pub fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {11 if let Some(on_empty) = on_empty {12 on_empty.evaluate()13 } else {14 bail!("expected non-empty array")15 }16}1718#[builtin]19pub fn builtin_make_array(sz: u32, func: NativeFn!((u32,) -> Val)) -> Result<ArrValue> {20 if sz == 0 {21 return Ok(ArrValue::empty());22 }23 // Try eager evaluation: call func(i) immediately for each element.24 'eager: {25 let mut out = Vec::with_capacity(sz as usize);26 for i in 0..sz {27 match func.call(i) {28 Ok(v) => out.push(v),29 Err(_) => break 'eager,30 }31 }32 return Ok(ArrValue::new(out));33 }34 Ok(ArrValue::make(sz, func))35}3637#[builtin]38pub fn builtin_repeat(what: Either![IStr, ArrValue], count: u32) -> Result<Val> {39 Ok(match what {40 Either2::A(s) => Val::string(s.repeat(count as usize)),41 Either2::B(arr) => Val::Arr(42 ArrValue::repeated(arr, count).ok_or_else(|| error!("repeated length overflow"))?,43 ),44 })45}4647#[builtin]48pub fn builtin_slice(49 indexable: IndexableVal,50 index: Option<Option<i32>>,51 end: Option<Option<i32>>,52 step: Option<Option<BoundedUsize<1, { i32::MAX as usize }>>>,53) -> Result<Val> {54 indexable55 .slice(index.flatten(), end.flatten(), step.flatten())56 .map(Val::from)57}5859#[builtin]60pub fn builtin_map(func: NativeFn!((Val) -> Val), arr: IndexableVal) -> ArrValue {61 let arr = arr.to_array();62 arr.map(func)63}6465#[builtin]66pub fn builtin_map_with_index(func: NativeFn!((u32, Val) -> Val), arr: IndexableVal) -> ArrValue {67 let arr = arr.to_array();68 arr.map_with_index(func)69}7071#[builtin]72pub fn builtin_map_with_key(73 func: NativeFn!((IStr, Val) -> Val),74 obj: ObjValue,75) -> Result<ObjValue> {76 let mut out = ObjValueBuilder::new();77 for (k, v) in obj.iter(78 // Makes sense mapped object should be ordered the same way, should not break anything when the output is not ordered (the default).79 // The thrown error might be different, but jsonnet80 // does not specify the evaluation order.81 #[cfg(feature = "exp-preserve-order")]82 true,83 ) {84 let v = v?;85 out.field(k.clone()).value(func.call(k, v)?);86 }87 Ok(out.build())88}8990#[builtin]91pub fn builtin_flatmap(92 func: NativeFn!((Either![String, Val]) -> Val),93 arr: IndexableVal,94) -> Result<IndexableVal> {95 use std::fmt::Write;96 match arr {97 IndexableVal::Str(str) => {98 let mut out = String::new();99 for c in str.chars() {100 match func.call(Either2::A(c.to_string()))? {101 Val::Str(o) => write!(out, "{o}").unwrap(),102 Val::Null => {}103 _ => bail!("in std.join all items should be strings"),104 }105 }106 Ok(IndexableVal::Str(out.into()))107 }108 IndexableVal::Arr(a) => {109 let mut out = Vec::new();110 for el in a.iter() {111 let el = el?;112 match func.call(Either2::B(el))? {113 Val::Arr(o) => {114 for oe in o.iter() {115 out.push(oe?);116 }117 }118 Val::Null => {}119 _ => bail!("in std.join all items should be arrays"),120 }121 }122 Ok(IndexableVal::Arr(out.into()))123 }124 }125}126127type FilterFunc = NativeFn!((Thunk<Val>) -> bool);128129#[builtin]130pub fn builtin_filter(func: FilterFunc, arr: ArrValue) -> Result<ArrValue> {131 arr.filter(func)132}133134#[builtin]135pub fn builtin_filter_map(136 filter_func: FilterFunc,137 map_func: NativeFn!((Val) -> Val),138 arr: ArrValue,139) -> Result<ArrValue> {140 Ok(arr.filter(filter_func)?.map(map_func))141}142143#[builtin]144pub fn builtin_foldl(145 func: NativeFn!((Val, Either![Val, char]) -> Val),146 arr: Either![ArrValue, IStr],147 init: Val,148) -> Result<Val> {149 let mut acc = init;150 match arr {151 Either2::A(arr) => {152 for i in arr.iter() {153 acc = func.call(acc, Either2::A(i?))?;154 }155 }156 Either2::B(arr) => {157 for c in arr.chars() {158 acc = func.call(acc, Either2::B(c))?;159 }160 }161 }162 Ok(acc)163}164165#[builtin]166pub fn builtin_foldr(167 func: NativeFn!((Either![Val, char], Val) -> Val),168 arr: Either![ArrValue, IStr],169 init: Val,170) -> Result<Val> {171 let mut acc = init;172 match arr {173 Either2::A(arr) => {174 for i in arr.iter().rev() {175 acc = func.call(Either2::A(i?), acc)?;176 }177 }178 Either2::B(arr) => {179 for c in arr.chars().rev() {180 acc = func.call(Either2::B(c), acc)?;181 }182 }183 }184 Ok(acc)185}186187#[builtin]188pub fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {189 if to < from {190 return Ok(ArrValue::empty());191 }192 Ok(ArrValue::range_inclusive(from, to))193}194195#[builtin]196pub fn builtin_join(sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {197 use std::fmt::Write;198 Ok(match sep {199 IndexableVal::Arr(joiner_items) => {200 let mut out = Vec::new();201202 let mut first = true;203 for item in arr.iter() {204 let item = item?.clone();205 if let Val::Arr(items) = item {206 if !first {207 out.reserve(joiner_items.len() as usize);208 // TODO: extend209 for item in joiner_items.iter() {210 out.push(item?);211 }212 }213 first = false;214 out.reserve(items.len() as usize);215 for item in items.iter() {216 out.push(item?);217 }218 } else if matches!(item, Val::Null) {219 } else {220 bail!("in std.join all items should be arrays");221 }222 }223224 IndexableVal::Arr(out.into())225 }226 IndexableVal::Str(sep) => {227 let mut out = String::new();228229 let mut first = true;230 for item in arr.iter() {231 let item = item?.clone();232 if let Val::Str(item) = item {233 if !first {234 out += &sep;235 }236 first = false;237 write!(out, "{item}").unwrap();238 } else if matches!(item, Val::Null) {239 } else {240 bail!("in std.join all items should be strings");241 }242 }243244 IndexableVal::Str(out.into())245 }246 })247}248249#[builtin]250pub fn builtin_lines(arr: ArrValue) -> Result<IndexableVal> {251 builtin_join(252 IndexableVal::Str("\n".into()),253 ArrValue::extended(arr, ArrValue::new(vec![Val::string("")]))254 .ok_or_else(|| error!("array is too large"))?,255 )256}257258#[builtin]259pub fn builtin_resolve_path(f: String, r: String) -> String {260 let Some(pos) = f.rfind('/') else {261 return r;262 };263 format!("{}{}", &f[..=pos], r)264}265266pub fn deep_join_inner(out: &mut String, arr: IndexableVal) -> Result<()> {267 use std::fmt::Write;268 match arr {269 IndexableVal::Str(s) => write!(out, "{s}").expect("no error"),270 IndexableVal::Arr(arr) => {271 for ele in arr.iter() {272 let indexable = IndexableVal::from_untyped(ele?)?;273 deep_join_inner(out, indexable)?;274 }275 }276 }277 Ok(())278}279280#[builtin]281pub fn builtin_deep_join(arr: IndexableVal) -> Result<String> {282 let mut out = String::new();283 deep_join_inner(&mut out, arr)?;284 Ok(out)285}286287#[builtin]288pub fn builtin_reverse(arr: ArrValue) -> ArrValue {289 arr.reversed()290}291292#[builtin]293pub fn builtin_any(arr: ArrValue) -> Result<bool> {294 for v in arr.iter() {295 let v = bool::from_untyped(v?)?;296 if v {297 return Ok(true);298 }299 }300 Ok(false)301}302303#[builtin]304pub fn builtin_all(arr: ArrValue) -> Result<bool> {305 for v in arr.iter() {306 let v = bool::from_untyped(v?)?;307 if !v {308 return Ok(false);309 }310 }311 Ok(true)312}313314#[builtin]315pub fn builtin_member(arr: IndexableVal, x: Val) -> Result<bool> {316 match arr {317 IndexableVal::Str(str) => {318 let x: IStr = IStr::from_untyped(x)?;319 Ok(!x.is_empty() && str.contains(&*x))320 }321 IndexableVal::Arr(a) => {322 for item in a.iter() {323 let item = item?;324 if equals(&item, &x)? {325 return Ok(true);326 }327 }328 Ok(false)329 }330 }331}332333#[builtin]334pub fn builtin_find(value: Val, arr: ArrValue) -> Result<Vec<usize>> {335 let mut out = Vec::new();336 for (i, ele) in arr.iter().enumerate() {337 let ele = ele?;338 if equals(&ele, &value)? {339 out.push(i);340 }341 }342 Ok(out)343}344345#[builtin]346pub fn builtin_contains(arr: IndexableVal, elem: Val) -> Result<bool> {347 builtin_member(arr, elem)348}349350#[builtin]351pub fn builtin_count(arr: ArrValue, x: Val) -> Result<usize> {352 let mut count = 0;353 for item in arr.iter() {354 if equals(&item?, &x)? {355 count += 1;356 }357 }358 Ok(count)359}360361#[builtin]362pub fn builtin_avg(arr: Vec<f64>, onEmpty: Option<Thunk<Val>>) -> Result<Val> {363 if arr.is_empty() {364 return eval_on_empty(onEmpty);365 }366 #[expect(367 clippy::cast_precision_loss,368 reason = "array sizes are bounded to i32 len"369 )]370 Ok(Val::try_num(arr.iter().sum::<f64>() / (arr.len() as f64))?)371}372373#[builtin]374pub fn builtin_remove_at(arr: ArrValue, at: i32) -> Result<ArrValue> {375 let newArrLeft = arr.clone().slice(None, Some(at), None);376 let newArrRight = arr.slice(Some(at + 1), None, None);377378 Ok(ArrValue::extended(newArrLeft, newArrRight).ok_or_else(|| error!("array is too large"))?)379}380381#[builtin]382pub fn builtin_remove(arr: ArrValue, elem: Val) -> Result<ArrValue> {383 for (index, item) in arr.iter().enumerate() {384 if equals(&item?, &elem)? {385 #[expect(386 clippy::cast_possible_truncation,387 clippy::cast_possible_wrap,388 reason = "array sizes are bounded to i32 len"389 )]390 return builtin_remove_at(arr.clone(), index as i32);391 }392 }393 Ok(arr)394}395396#[builtin]397pub fn builtin_flatten_arrays(arrs: Vec<ArrValue>) -> Result<ArrValue> {398 pub fn flatten_inner(values: &[ArrValue]) -> Result<ArrValue> {399 if values.len() == 1 {400 return Ok(values[0].clone());401 } else if values.len() == 2 {402 return ArrValue::extended(values[0].clone(), values[1].clone())403 .ok_or_else(|| error!("array is too large"));404 }405 let (a, b) = values.split_at(values.len() / 2);406 ArrValue::extended(flatten_inner(a)?, flatten_inner(b)?)407 .ok_or_else(|| error!("array is too large"))408 }409 if arrs.is_empty() {410 return Ok(ArrValue::empty());411 } else if arrs.len() == 1 {412 return Ok(arrs.into_iter().next().expect("single"));413 }414 flatten_inner(&arrs)415}416417#[builtin]418pub fn builtin_flatten_deep_array(value: Val) -> Result<Vec<Val>> {419 fn process(value: Val, out: &mut Vec<Val>) -> Result<()> {420 match value {421 Val::Arr(arr) => {422 for ele in arr.iter() {423 process(ele?, out)?;424 }425 }426 _ => out.push(value),427 }428 Ok(())429 }430 let mut out = Vec::new();431 process(value, &mut out)?;432 Ok(out)433}434435#[builtin]436pub fn builtin_prune(437 a: Val,438439 #[default(false)]440 #[cfg(feature = "exp-preserve-order")]441 preserve_order: bool,442) -> Result<Val> {443 fn is_content(val: &Val) -> bool {444 match val {445 Val::Null => false,446 Val::Arr(a) => !a.is_empty(),447 Val::Obj(o) => !o.is_empty(),448 _ => true,449 }450 }451 Ok(match a {452 Val::Arr(a) => {453 let mut out = Vec::new();454 for (i, ele) in a.iter().enumerate() {455 let ele = ele456 .and_then(|v| {457 builtin_prune(458 v,459 #[cfg(feature = "exp-preserve-order")]460 preserve_order,461 )462 })463 .with_description(|| format!("elem <{i}> pruning"))?;464 if is_content(&ele) {465 out.push(ele);466 }467 }468 Val::arr(out)469 }470 Val::Obj(o) => {471 let mut out = ObjValueBuilder::new();472 for (name, value) in o.iter(473 #[cfg(feature = "exp-preserve-order")]474 preserve_order,475 ) {476 let value = value477 .and_then(|v| {478 builtin_prune(479 v,480 #[cfg(feature = "exp-preserve-order")]481 preserve_order,482 )483 })484 .with_description(|| format!("field <{name}> pruning"))?;485 if !is_content(&value) {486 continue;487 }488 out.field(name).value(value);489 }490 Val::Obj(out.build())491 }492 _ => a,493 })494}crates/jrsonnet-stdlib/src/encoding.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/encoding.rs
+++ b/crates/jrsonnet-stdlib/src/encoding.rs
@@ -1,8 +1,7 @@
use base64::{Engine, engine::general_purpose::STANDARD};
use jrsonnet_evaluator::{
- IBytes, IStr, Result, bail,
+ IBytes, IStr, Result, bail, error,
function::builtin,
- runtime_error,
typed::{Either, Either2},
};
@@ -35,7 +34,7 @@
pub fn builtin_base64_decode_bytes(str: IStr) -> Result<IBytes> {
Ok(STANDARD
.decode(str.as_bytes())
- .map_err(|e| runtime_error!("invalid base64: {e}"))?
+ .map_err(|e| error!("invalid base64: {e}"))?
.as_slice()
.into())
}
@@ -44,10 +43,10 @@
pub fn builtin_base64_decode(str: IStr, #[default(false)] lossy: bool) -> Result<String> {
let bytes = STANDARD
.decode(str.as_bytes())
- .map_err(|e| runtime_error!("invalid base64: {e}"))?;
+ .map_err(|e| error!("invalid base64: {e}"))?;
if lossy {
Ok(String::from_utf8_lossy(&bytes).to_string())
} else {
- String::from_utf8(bytes).map_err(|e| runtime_error!("bad utf8: {e}"))
+ String::from_utf8(bytes).map_err(|e| error!("bad utf8: {e}"))
}
}
crates/jrsonnet-stdlib/src/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/parse.rs
+++ b/crates/jrsonnet-stdlib/src/parse.rs
@@ -1,10 +1,9 @@
-use jrsonnet_evaluator::{IStr, Result, Val, function::builtin, runtime_error};
+use jrsonnet_evaluator::{IStr, Result, Val, error, function::builtin};
use serde_saphyr::options;
#[builtin]
pub fn builtin_parse_json(str: IStr) -> Result<Val> {
- let value: Val =
- serde_json::from_str(&str).map_err(|e| runtime_error!("failed to parse json: {e}"))?;
+ let value: Val = serde_json::from_str(&str).map_err(|e| error!("failed to parse json: {e}"))?;
Ok(value)
}
@@ -21,7 +20,7 @@
budget: None,
},
)
- .map_err(|e| runtime_error!("failed to parse yaml: {e}"))?;
+ .map_err(|e| error!("failed to parse yaml: {e}"))?;
// saphyr and other yaml implementations disagree on how to handle an empty document in multi-document stream.
// Saphyr only considers document started after anything is emitted after the document delimiter
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -206,17 +206,15 @@
#[builtin]
pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {
use Either2::*;
- use jrsonnet_evaluator::runtime_error;
+ use jrsonnet_evaluator::error;
Ok(match v {
- A(a) => {
- Val::BigInt(Box::new(a.to_string().parse().map_err(|e| {
- runtime_error!("number is not convertible to bigint: {e}")
- })?))
- }
+ A(a) => Val::BigInt(Box::new(
+ a.to_string()
+ .parse()
+ .map_err(|e| error!("number is not convertible to bigint: {e}"))?,
+ )),
B(b) => Val::BigInt(Box::new(
- b.as_str()
- .parse()
- .map_err(|e| runtime_error!("bad bigint: {e}"))?,
+ b.as_str().parse().map_err(|e| error!("bad bigint: {e}"))?,
)),
})
}