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.rsdiffbeforeafterboth1use std::{1use std::{2 fmt::{Debug, Display},2 fmt::{Debug, Display},3 path::PathBuf,3 path::PathBuf, cmp::Ordering,4};4};556use jrsonnet_gcmodule::Trace;6use jrsonnet_gcmodule::Trace;9use jrsonnet_types::ValType;9use jrsonnet_types::ValType;10use thiserror::Error;10use thiserror::Error;111112use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError};12use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError, ObjValue};131314fn 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}7071pub(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}709071type FunctionSignature = Vec<(Option<IStr>, bool)>;91type FunctionSignature = Vec<(Option<IStr>, bool)>;7292348 ($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 };crates/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),
},
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)?
}
};