Skip to content

Commit 0cea262

Browse files
sunyuhan1998ilayaperumalg
authored andcommitted
fix: StTemplateRender property access syntax
- Fixed the variable recognition issue in property access syntax within `StTemplateRenderer` - Added corresponding unit tests Signed-off-by: Sun Yuhan <[email protected]>
1 parent 1a35e8a commit 0cea262

File tree

2 files changed

+74
-8
lines changed

2 files changed

+74
-8
lines changed

spring-ai-template-st/src/main/java/org/springframework/ai/template/st/StTemplateRenderer.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* is shared between threads.
5050
*
5151
* @author Thomas Vitale
52+
* @author Sun Yuhan
5253
* @since 1.0.0
5354
*/
5455
public class StTemplateRenderer implements TemplateRenderer {
@@ -165,16 +166,25 @@ private Set<String> getInputVariables(ST st) {
165166
else if (token.getType() == STLexer.RDELIM) {
166167
isInsideList = false;
167168
}
168-
// Only add IDs that are not function calls (i.e., not immediately followed by
169+
// Handle regular variables - only add IDs that are at the start of an
170+
// expression
169171
else if (!isInsideList && token.getType() == STLexer.ID) {
172+
// Check if this ID is a function call
170173
boolean isFunctionCall = (i + 1 < tokens.size() && tokens.get(i + 1).getType() == STLexer.LPAREN);
171-
boolean isDotProperty = (i > 0 && tokens.get(i - 1).getType() == STLexer.DOT);
172-
// Only add as variable if:
173-
// - Not a function call
174-
// - Not a built-in function used as property (unless validateStFunctions)
175-
if (!isFunctionCall && (!Compiler.funcs.containsKey(token.getText()) || this.validateStFunctions
176-
|| !(isDotProperty && Compiler.funcs.containsKey(token.getText())))) {
177-
inputVariables.add(token.getText());
174+
175+
// Check if this ID is at the beginning of an expression (not a property
176+
// access)
177+
boolean isAfterDot = (i > 0 && tokens.get(i - 1).getType() == STLexer.DOT);
178+
179+
// Only add IDs that are:
180+
// 1. Not function calls
181+
// 2. Not property values (not preceded by a dot)
182+
// 3. Either not built-in functions or we're validating functions
183+
if (!isFunctionCall && !isAfterDot) {
184+
String varName = token.getText();
185+
if (!Compiler.funcs.containsKey(varName) || this.validateStFunctions) {
186+
inputVariables.add(varName);
187+
}
178188
}
179189
}
180190
}

spring-ai-template-st/src/test/java/org/springframework/ai/template/st/StTemplateRendererTests.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,60 @@ void shouldRenderTemplateWithBuiltInFunctions() {
297297
assertThat(result).isEqualTo("Hello!");
298298
}
299299

300+
/**
301+
* Tests that property access syntax like {test.name} is correctly handled. The
302+
* top-level variable 'test' should be identified as required, but 'name' should not.
303+
*/
304+
@Test
305+
void shouldHandlePropertyAccessSyntax() {
306+
StTemplateRenderer renderer = StTemplateRenderer.builder().build();
307+
Map<String, Object> variables = new HashMap<>();
308+
variables.put("test", Map.of("name", "Spring AI"));
309+
310+
String result = renderer.apply("Hello {test.name}!", variables);
311+
312+
assertThat(result).isEqualTo("Hello Spring AI!");
313+
}
314+
315+
/**
316+
* Tests that deep property access syntax like {test.tom.name} is correctly handled.
317+
* Only the top-level variable 'test' should be identified as required.
318+
*/
319+
@Test
320+
void shouldHandleDeepPropertyAccessSyntax() {
321+
StTemplateRenderer renderer = StTemplateRenderer.builder().build();
322+
Map<String, Object> variables = new HashMap<>();
323+
variables.put("test", Map.of("tom", Map.of("name", "Spring AI")));
324+
325+
String result = renderer.apply("Hello {test.tom.name}!", variables);
326+
327+
assertThat(result).isEqualTo("Hello Spring AI!");
328+
}
329+
330+
/**
331+
* Tests validation behavior with property access syntax. Should only require the
332+
* top-level variable, not the property names.
333+
*/
334+
@Test
335+
void shouldValidatePropertyAccessCorrectly() {
336+
StTemplateRenderer renderer = StTemplateRenderer.builder().build();
337+
Map<String, Object> variables = new HashMap<>();
338+
// Only provide the top-level variable, not the properties
339+
variables.put("user", Map.of("profile", Map.of("name", "John")));
340+
341+
// This should work fine since we provide the required top-level variable
342+
String result = renderer.apply("Hello {user.profile.name}!", variables);
343+
assertThat(result).isEqualTo("Hello John!");
344+
345+
// Test with missing top-level variable - should throw exception
346+
Map<String, Object> missingVariables = new HashMap<>();
347+
// Wrong: providing nested variable instead of top-level
348+
missingVariables.put("profile", Map.of("name", "John"));
349+
350+
assertThatThrownBy(() -> renderer.apply("Hello {user.profile.name}!", missingVariables))
351+
.isInstanceOf(IllegalStateException.class)
352+
.hasMessageContaining(
353+
"Not all variables were replaced in the template. Missing variable names are: [user]");
354+
}
355+
300356
}

0 commit comments

Comments
 (0)