git.delta.rocks / jrsonnet / refs/commits / 48e38361db94

difftreelog

source

crates/jrsonnet-stdlib/src/misc.rs5.6 KiBsourcehistory
1use std::{cell::RefCell, collections::BTreeSet};23use jrsonnet_evaluator::{4	Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val, bail,5	error::{ErrorKind::*, Result},6	function::{CallLocation, FuncVal, builtin},7	manifest::JsonFormat,8	typed::{Either2, Either4},9	val::{ArrValue, equals},10};11use jrsonnet_gcmodule::Cc;1213use crate::Settings;1415#[builtin]16pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> usize {17	use Either4::*;18	match x {19		A(x) => x.chars().count(),20		B(x) => x.len(),21		C(x) => x.len(),22		D(f) => f.params_len(),23	}24}2526#[builtin]27pub fn builtin_get(28	o: ObjValue,29	f: IStr,30	default: Option<Thunk<Val>>,31	#[default(true)] inc_hidden: bool,32) -> Result<Val> {33	let do_default = move || {34		let Some(default) = default else {35			return Ok(Val::Null);36		};37		default.evaluate()38	};39	// Happy path for invisible fields40	if !inc_hidden && !o.has_field_ex(f.clone(), false) {41		return do_default();42	}43	let Some(v) = o.get(f)? else {44		return do_default();45	};46	Ok(v)47}4849#[builtin(fields(50	settings: Cc<RefCell<Settings>>,51))]52pub fn builtin_ext_var(this: &builtin_ext_var, x: IStr) -> Result<Val> {53	this.settings54		.borrow()55		.ext_vars56		.get(&x)57		.cloned()58		.ok_or_else(|| UndefinedExternalVariable(x))?59		.evaluate_tailstrict()60}6162#[builtin(fields(63	settings: Cc<RefCell<Settings>>,64))]65pub fn builtin_native(this: &builtin_native, x: IStr) -> Val {66	this.settings67		.borrow()68		.ext_natives69		.get(&x)70		.cloned()71		.map_or(Val::Null, Val::Func)72}7374#[builtin(fields(75	settings: Cc<RefCell<Settings>>,76))]77pub fn builtin_trace(78	this: &builtin_trace,79	loc: CallLocation,80	str: Val,81	rest: Option<Thunk<Val>>,82) -> Result<Val> {83	this.settings.borrow().trace_printer.print_trace(84		loc,85		match &str {86			Val::Str(s) => s.clone().into_flat(),87			Val::Func(f) => format!("{f:?}").into(),88			v => v.manifest(JsonFormat::debug())?.into(),89		},90	);91	rest.map_or_else(|| Ok(str), |rest| rest.evaluate())92}9394#[allow(clippy::comparison_chain)]95#[builtin]96pub fn builtin_starts_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {97	Ok(match (a, b) {98		(Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),99		(Either2::B(a), Either2::B(b)) => {100			if b.len() > a.len() {101				return Ok(false);102			} else if b.len() == a.len() {103				return equals(&Val::Arr(a), &Val::Arr(b));104			}105			for (a, b) in a.iter().take(b.len()).zip(b.iter()) {106				let a = a?;107				let b = b?;108				if !equals(&a, &b)? {109					return Ok(false);110				}111			}112			true113		}114		_ => bail!("both arguments should be of the same type"),115	})116}117118#[allow(clippy::comparison_chain)]119#[builtin]120pub fn builtin_ends_with(a: Either![IStr, ArrValue], b: Either![IStr, ArrValue]) -> Result<bool> {121	Ok(match (a, b) {122		(Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),123		(Either2::B(a), Either2::B(b)) => {124			if b.len() > a.len() {125				return Ok(false);126			} else if b.len() == a.len() {127				return equals(&Val::Arr(a), &Val::Arr(b));128			}129			let a_len = a.len();130			for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {131				let a = a?;132				let b = b?;133				if !equals(&a, &b)? {134					return Ok(false);135				}136			}137			true138		}139		_ => bail!("both arguments should be of the same type"),140	})141}142143#[builtin]144pub fn builtin_assert_equal(a: Val, b: Val) -> Result<bool> {145	if equals(&a, &b)? {146		return Ok(true);147	}148	// TODO: Use debug output format149	let format = JsonFormat::std_to_json(150		"  ".to_owned(),151		"\n",152		": ",153		#[cfg(feature = "exp-preserve-order")]154		true,155	);156	let a = if let Some(a) = a.as_str() {157		format!("<A>\n{a}\n</A>")158	} else {159		a.manifest(&format).description("<a> manifestification")?160	};161	let b = if let Some(b) = b.as_str() {162		format!("<B>\n{b}\n</B>")163	} else {164		b.manifest(&format).description("<b> manifestification")?165	};166	bail!("assertion failed: A != B\nA: {a}\nB: {b}")167}168169#[builtin]170pub fn builtin_merge_patch(target: Val, patch: Val) -> Result<Val> {171	let Some(patch) = patch.as_obj() else {172		return Ok(patch);173	};174	let target = target.as_obj().unwrap_or_else(ObjValue::empty);175	let target_fields = target176		.fields(177			// FIXME: Makes no sense to preserve order for BTreeSet, it would be better to use IndexSet here?178			// But IndexSet won't allow fast ordered union...179			// // Makes sense to preserve source ordering where possible.180			// // May affect evaluation order, but it is not specified by jsonnet spec.181			// #[cfg(feature = "exp-preserve-order")]182			// true,183			#[cfg(feature = "exp-preserve-order")]184			false,185		)186		.into_iter()187		.collect::<BTreeSet<IStr>>();188	let patch_fields = patch189		.fields(190			// No need to look at the patch field order, I think?191			// New fields (that will be appended at the end) will be alphabeticaly-ordered,192			// but it is fine for jsonpatch, I don't think people write jsonpatch in jsonnet,193			// when they can use mixins.194			#[cfg(feature = "exp-preserve-order")]195			false,196		)197		.into_iter()198		.collect::<BTreeSet<IStr>>();199200	let mut out = ObjValueBuilder::new();201	for field in target_fields.union(&patch_fields) {202		let Some(field_patch) = patch.get(field.clone())? else {203			// All lazy fields might be unified into a single filtered object core instead of creating a thunk per, but this implementation is good enough.204			let target_field = target.get_lazy(field.clone()).expect(205				"we're iterating over fields union, if field is missing in patch - it exists in target",206			);207			out.field(field.clone()).thunk(target_field);208			continue;209		};210		if matches!(field_patch, Val::Null) {211			continue;212		}213		let field_target = target.get(field.clone())?.unwrap_or(Val::Null);214		out.field(field.clone())215			.value(builtin_merge_patch(field_target, field_patch)?);216	}217	Ok(out.build().into())218}