difftreelog
feat refer to subfields of objects in string formatting
in: master
Upstream issue: https://github.com/google/jsonnet/pull/1011
3 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,6 +1,6 @@
use std::{
fmt::{Debug, Display},
- path::PathBuf,
+ path::PathBuf, cmp::Ordering,
};
use jrsonnet_gcmodule::Trace;
@@ -9,9 +9,9 @@
use jrsonnet_types::ValType;
use thiserror::Error;
-use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError};
+use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError, ObjValue};
-fn format_found(list: &[IStr], what: &str) -> String {
+pub(crate) fn format_found(list: &[IStr], what: &str) -> String {
if list.is_empty() {
return String::new();
}
@@ -68,6 +68,26 @@
}
}
+pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {
+ 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.as_str());
+ if conf < 0.8 {
+ continue;
+ }
+ if field.as_str() == key.as_str() {
+ panic!("looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");
+ }
+ heap.push((conf, field));
+ }
+ heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
+ heap.into_iter().map(|v| v.1).collect()
+}
+
type FunctionSignature = Vec<(Option<IStr>, bool)>;
/// Possible errors
@@ -348,6 +368,9 @@
($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {
return Err($w$(::$i)*$(($($tt)*))?.into())
};
+ ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {
+ return Err($w$(::$i)*$({$($tt)*})?.into())
+ };
($l:literal) => {
return Err($crate::error::ErrorKind::RuntimeError($l.into()).into())
};
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth12use crate::{12use crate::{13 arr::ArrValue,13 arr::ArrValue,14 destructure::evaluate_dest,14 destructure::evaluate_dest,15 error::ErrorKind::*,15 error::{ErrorKind::*, suggest_object_fields},16 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},16 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},17 function::{CallLocation, FuncDesc, FuncVal},17 function::{CallLocation, FuncDesc, FuncVal},18 throw,18 throw,466 || format!("field <{key}> access"),466 || format!("field <{key}> access"),467 || match v.get(key.clone().into_flat()) {467 || match v.get(key.clone().into_flat()) {468 Ok(Some(v)) => Ok(v),468 Ok(Some(v)) => Ok(v),469 #[cfg(not(feature = "friendly-errors"))]470 Ok(None) => throw!(NoSuchField(key.clone(), vec![])),471 #[cfg(feature = "friendly-errors")]472 Ok(None) => {469 Ok(None) => {473 let mut heap = Vec::new();474 for field in v.fields_ex(475 true,476 #[cfg(feature = "exp-preserve-order")]477 false,478 ) {479 let conf = strsim::jaro_winkler(470 let suggestions = suggest_object_fields(&v, key.clone().into_flat());480 &field as &str,481 &key.clone().into_flat() as &str,482 );483 if conf < 0.8 {484 continue;485 }486 heap.push((conf, field));487 }488 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));489471490 throw!(NoSuchField(472 throw!(NoSuchField(key.clone().into_flat(), suggestions))491 key.clone().into_flat(),492 heap.into_iter().map(|(_, v)| v).collect()493 ))494 }473 }495 Err(e) => Err(e),474 Err(e) => Err(e),crates/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)?
}
};