git.delta.rocks / jrsonnet / refs/commits / bbe146ee2257

difftreelog

feat refer to subfields of objects in string formatting

Yaroslav Bolyukin2023-06-14parent: #aeb8b35.patch.diff
in: master
Upstream issue: https://github.com/google/jsonnet/pull/1011

3 files changed

modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
1use std::{1use std::{
2 fmt::{Debug, Display},2 fmt::{Debug, Display},
3 path::PathBuf,3 path::PathBuf, cmp::Ordering,
4};4};
55
6use jrsonnet_gcmodule::Trace;6use jrsonnet_gcmodule::Trace;
9use jrsonnet_types::ValType;9use jrsonnet_types::ValType;
10use thiserror::Error;10use thiserror::Error;
1111
12use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError};12use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError, ObjValue};
1313
14fn format_found(list: &[IStr], what: &str) -> String {14pub(crate) fn format_found(list: &[IStr], what: &str) -> String {
15 if list.is_empty() {15 if list.is_empty() {
16 return String::new();16 return String::new();
17 }17 }
68 }68 }
69}69}
70
71pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {
72 let mut heap = Vec::new();
73 for field in v.fields_ex(
74 true,
75 #[cfg(feature = "exp-preserve-order")]
76 false,
77 ) {
78 let conf = strsim::jaro_winkler(field.as_str(), key.as_str());
79 if conf < 0.8 {
80 continue;
81 }
82 if field.as_str() == key.as_str() {
83 panic!("looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");
84 }
85 heap.push((conf, field));
86 }
87 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
88 heap.into_iter().map(|v| v.1).collect()
89}
7090
71type FunctionSignature = Vec<(Option<IStr>, bool)>;91type FunctionSignature = Vec<(Option<IStr>, bool)>;
7292
348 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {368 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {
349 return Err($w$(::$i)*$(($($tt)*))?.into())369 return Err($w$(::$i)*$(($($tt)*))?.into())
350 };370 };
371 ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {
372 return Err($w$(::$i)*$({$($tt)*})?.into())
373 };
351 ($l:literal) => {374 ($l:literal) => {
352 return Err($crate::error::ErrorKind::RuntimeError($l.into()).into())375 return Err($crate::error::ErrorKind::RuntimeError($l.into()).into())
353 };376 };
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -12,7 +12,7 @@
 use crate::{
 	arr::ArrValue,
 	destructure::evaluate_dest,
-	error::ErrorKind::*,
+	error::{ErrorKind::*, suggest_object_fields},
 	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
 	function::{CallLocation, FuncDesc, FuncVal},
 	throw,
@@ -466,31 +466,10 @@
 				|| format!("field <{key}> access"),
 				|| match v.get(key.clone().into_flat()) {
 					Ok(Some(v)) => Ok(v),
-					#[cfg(not(feature = "friendly-errors"))]
-					Ok(None) => throw!(NoSuchField(key.clone(), vec![])),
-					#[cfg(feature = "friendly-errors")]
 					Ok(None) => {
-						let mut heap = Vec::new();
-						for field in v.fields_ex(
-							true,
-							#[cfg(feature = "exp-preserve-order")]
-							false,
-						) {
-							let conf = strsim::jaro_winkler(
-								&field as &str,
-								&key.clone().into_flat() as &str,
-							);
-							if conf < 0.8 {
-								continue;
-							}
-							heap.push((conf, field));
-						}
-						heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
+						let suggestions = suggest_object_fields(&v, key.clone().into_flat());
 
-						throw!(NoSuchField(
-							key.clone().into_flat(),
-							heap.into_iter().map(|(_, v)| v).collect()
-						))
+						throw!(NoSuchField(key.clone().into_flat(), suggestions))
 					}
 					Err(e) => Err(e),
 				},
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -6,7 +6,12 @@
 use jrsonnet_types::ValType;
 use thiserror::Error;
 
-use crate::{error::ErrorKind::*, throw, typed::Typed, Error, ObjValue, Result, Val};
+use crate::{
+	error::{format_found, suggest_object_fields, ErrorKind::*},
+	throw,
+	typed::Typed,
+	Error, ObjValue, Result, Val,
+};
 
 #[derive(Debug, Clone, Error, Trace)]
 pub enum FormatError {
@@ -24,6 +29,15 @@
 	MappingKeysRequired,
 	#[error("no such format field: {0}")]
 	NoSuchFormatField(IStr),
+
+	#[error("expected subfield <{0}> to be an object, got {1} instead")]
+	SubfieldDidntYieldAnObject(IStr, ValType),
+	#[error("subfield not found: <[{full}]{current}>{}", format_found(.found, "subfield"))]
+	SubfieldNotFound {
+		current: IStr,
+		full: IStr,
+		found: Box<Vec<IStr>>,
+	},
 }
 
 impl From<FormatError> for Error {
@@ -691,6 +705,37 @@
 	Ok(out)
 }
 
+fn get_dotted_field(obj: ObjValue, field: &str) -> Result<Val> {
+	let mut current = Val::Obj(obj);
+	let mut name_offset = 0;
+	for component in field.split('.') {
+		let end_offset = name_offset + component.len();
+		current = if let Val::Obj(obj) = current {
+			if let Some(value) = obj.get(component.into())? {
+				value
+			} else {
+				let current = &field[name_offset..end_offset];
+				let full = &field[..name_offset];
+				let found = Box::new(suggest_object_fields(&obj, current.into()));
+				throw!(SubfieldNotFound {
+					current: current.into(),
+					full: full.into(),
+					found,
+				})
+			}
+		} else {
+			// No underflow may happen, initially we always start with an object
+			let subfield = &field[..name_offset - 1];
+			throw!(SubfieldDidntYieldAnObject(
+				subfield.into(),
+				current.value_type()
+			));
+		};
+		name_offset = end_offset + 1;
+	}
+	Ok(current)
+}
+
 pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {
 	let codes = parse_codes(str)?;
 	let mut out = String::new();
@@ -726,7 +771,7 @@
 					if let Some(v) = values.get(f.clone())? {
 						v
 					} else {
-						throw!(NoSuchFormatField(f));
+						get_dotted_field(values.clone(), &f)?
 					}
 				};