Add leniency to missing array values in mustache (#126550)

In mustache, this change returns null values which convert to empty strings 
instead of throwing an exception when users have a template with 
something like a.8 where the index 8 is out of bounds. This matches the 
behavior for non-existent keys like a.d.

Closes #55200
This commit is contained in:
Jack Conradson 2025-04-09 14:51:26 -07:00 committed by GitHub
parent 31e5678c40
commit 3d54cc3e52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 30 additions and 4 deletions

View file

@ -0,0 +1,6 @@
pr: 126550
summary: Add leniency to missing array values in mustache
area: Infra/Scripting
type: bug
issues:
- 55200

View file

@ -111,11 +111,11 @@ final class CustomReflectionObjectHandler extends ReflectionObjectHandler {
if ("size".equals(key)) {
return size();
} else if (key instanceof Number number) {
return Array.get(array, number.intValue());
return number.intValue() >= 0 && number.intValue() < length ? Array.get(array, number.intValue()) : null;
}
try {
int index = Integer.parseInt(key.toString());
return Array.get(array, index);
return index >= 0 && index < length ? Array.get(array, index) : null;
} catch (NumberFormatException nfe) {
// if it's not a number it is as if the key doesn't exist
return null;
@ -169,11 +169,11 @@ final class CustomReflectionObjectHandler extends ReflectionObjectHandler {
if ("size".equals(key)) {
return col.size();
} else if (key instanceof Number number) {
return Iterables.get(col, number.intValue());
return number.intValue() >= 0 && number.intValue() < col.size() ? Iterables.get(col, number.intValue()) : null;
}
try {
int index = Integer.parseInt(key.toString());
return Iterables.get(col, index);
return index >= 0 && index < col.size() ? Iterables.get(col, index) : null;
} catch (NumberFormatException nfe) {
// if it's not a number it is as if the key doesn't exist
return null;

View file

@ -141,6 +141,26 @@ public class MustacheTests extends ESTestCase {
assertThat(output, both(containsString("foo")).and(containsString("bar")));
}
public void testMapInArrayBadAccess() throws Exception {
String template = "{{data.0.key}} {{data.2.key}}";
TemplateScript.Factory factory = engine.compile(null, template, TemplateScript.CONTEXT, Collections.emptyMap());
Map<String, Object> vars = new HashMap<>();
vars.put("data", new Object[] { singletonMap("key", "foo"), singletonMap("key", "bar") });
// assertThat(factory.newInstance(vars).execute(), equalTo("foo "));
vars.put("data", Arrays.asList(singletonMap("key", "foo"), singletonMap("key", "bar")));
factory.newInstance(vars);
assertThat(factory.newInstance(vars).execute(), equalTo("foo "));
// HashSet iteration order isn't fixed
Set<Object> setData = new HashSet<>();
setData.add(singletonMap("key", "foo"));
setData.add(singletonMap("key", "bar"));
vars.put("data", setData);
String output = factory.newInstance(vars).execute();
assertThat(output, both(containsString("foo")).and(not(containsString("bar"))));
}
public void testSizeAccessForCollectionsAndArrays() throws Exception {
String[] randomArrayValues = generateRandomStringArray(10, 20, false);
List<String> randomList = Arrays.asList(generateRandomStringArray(10, 20, false));