-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NU-1800] Add template lazy param #7162
base: staging
Are you sure you want to change the base?
Conversation
WalkthroughThe changes in this pull request involve enhancements to dependency management and functionality within the project. A new dependency, Changes
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
...c/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (10)
engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala (1)
35-53
: Consider expanding test coverage for robustness.While the happy path is tested, consider adding test cases for:
- Edge cases (empty input, null values)
- Error scenarios (invalid templates)
- More detailed assertions about the output structure
Example additional test cases:
test("should handle empty input") { // Test with empty string input } test("should handle null values") { // Test with null input } test("should fail gracefully with invalid template") { // Test with malformed template }scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/InterpreterSpec.scala (1)
1089-1103
: Consider consolidating redundant test cases.These two individual test cases are already covered by the table-driven tests above. Consider removing them to avoid test duplication and maintain a more concise test suite.
- test("spel template ast operation parameter should work for single literal value") { - val process = ScenarioBuilder - .streaming("test") - .source("start", "transaction-source") - .enricher( - "ex", - "out", - "templateAstOperationService", - "template" -> Expression.spelTemplate("Hello") - ) - .buildSimpleVariable("result-end", resultVariable, "#out".spel) - .emptySink("end-end", "dummySink") - - interpretProcess(process, Transaction(msisdn = "foo")) should equal("[Hello]-literal") - } - - test("spel template ast operation parameter should work for single templated function call expression") { - val process = ScenarioBuilder - .streaming("test") - .source("start", "transaction-source") - .enricher( - "ex", - "out", - "templateAstOperationService", - "template" -> Expression.spelTemplate("#{#input.msisdn.toString()}") - ) - .buildSimpleVariable("result-end", resultVariable, "#out".spel) - .emptySink("end-end", "dummySink") - - interpretProcess(process, Transaction(msisdn = "foo")) should equal("[foo]-templated") - }Also applies to: 1105-1119
scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/testcomponents/SpelTemplateAstOperationService.scala (1)
59-61
: Handle potential exceptions during template evaluationThe call to
template.evaluate(context)
may throw exceptions if the evaluation fails. It's advisable to handle any potential exceptions to prevent unexpected runtime errors.Apply this diff to handle exceptions during template evaluation:
case template: TemplatedPart => - s"[${template.evaluate(context)}]-templated" + try { + s"[${template.evaluate(context)}]-templated" + } catch { + case e: Exception => + s"[Error evaluating template: ${e.getMessage}]" + }scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (1)
64-69
: Consider type safety in casting withasInstanceOf[T]
In the
LazyParmeterEvaluator.evaluate
method, the result is cast using.asInstanceOf[T]
. While this may be necessary, consider verifying the safety of this cast or using type-safe methods to prevent potentialClassCastException
at runtime.components-api/src/main/scala/pl/touk/nussknacker/engine/api/LazyParameter.scala (3)
3-3
: Review the necessity of the import statementEnsure that the import of
TemplateExpression
is required in this file. If it's not directly used, consider removing it to keep the imports clean.
72-74
: Consider adding parentheses totemplateExpression
methodFor consistency and clarity, consider adding parentheses to the
templateExpression
method, especially if it performs computations.Apply this diff to add parentheses:
- def templateExpression: TemplateExpression + def templateExpression(): TemplateExpression
83-85
: Changeevaluate
fromval
todef
In the
TemplatedPart
trait, changingevaluate
from aval
to adef
may better represent that it performs an evaluation each time it's called.Apply this diff:
- val evaluate: Evaluate[String] + def evaluate: Evaluate[String]scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala (3)
87-87
: Remove redundantval
in case class parameterIn Scala, the
val
keyword in case class parameters is redundant since they areval
s by default.Apply this diff to remove the redundant
val
:- final case class NonTemplatedValue(val value: String) extends SpelTemplateSubexpression + final case class NonTemplatedValue(value: String) extends SpelTemplateSubexpression
84-94
: Add unit tests for new subexpression classesTo ensure the correctness and reliability of the new
SpelTemplateSubexpression
trait and its case classes, consider adding unit tests that cover their behavior and interactions.
107-134
: Add unit tests for thesubexpressions
methodThe
subexpressions
method introduces important functionality in parsing and handling expressions. Adding unit tests covering various scenarios—such as composite expressions, literal expressions, and SpEL expressions—will help verify its correctness.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (9)
build.sbt
(1 hunks)components-api/src/main/scala/pl/touk/nussknacker/engine/api/LazyParameter.scala
(2 hunks)engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala
(1 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala
(2 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameterCreator.scala
(3 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ParameterEvaluator.scala
(1 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala
(3 hunks)scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/InterpreterSpec.scala
(5 hunks)scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/testcomponents/SpelTemplateAstOperationService.scala
(1 hunks)
🔇 Additional comments (14)
engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala (3)
1-25
: LGTM! Well-structured test class with appropriate testing traits.
The test class follows Scala testing best practices by extending essential testing traits and organizing imports properly.
27-33
: Consider adding documentation for the test runner configuration.
While the setup is correct, it would be helpful to document:
- Why an empty configuration is used
- Why batch execution mode is specifically chosen
Let's verify the SpelTemplateAstOperationService registration:
1-55
: Verify test file naming convention.
The test file name TemplateLazyParameterTest
appears appropriate, but let's verify it follows the project's test naming conventions.
✅ Verification successful
Test file naming follows project conventions
Based on the analysis of test files in the same directory, the naming convention TemplateLazyParameterTest
follows the established pattern where test class names:
- End with "Test" suffix
- Use PascalCase
- Describe the functionality being tested
Looking at neighboring test files like TableSourceTest
, TableJoinTest
, etc., confirms this is consistent with the project's conventions.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for other test files in the same directory to verify naming patterns
fd -e scala . | rg "Test.scala$"
# Search for any documentation about test naming conventions
fd -e md . | xargs rg -l "test.*naming|naming.*convention"
Length of output: 20579
Script:
#!/bin/bash
# Let's analyze test files in the same directory to understand local naming conventions
fd -e scala "engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/"
# Also check for any test base classes that might define conventions
ast-grep --pattern 'class $_ extends $$$Test$$$'
Length of output: 786
Script:
#!/bin/bash
# Search for test files in the same directory with corrected fd command
fd --full-path ".*engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/.*Test\.scala$"
# Look for base test classes in the project
ast-grep --pattern 'class $name extends $$$Test {
$$$
}'
Length of output: 1572
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameterCreator.scala (3)
45-53
: Well-structured factory method implementation
Good use of the Factory pattern to encapsulate the creation logic. The code is clean, well-organized, and all necessary parameters are properly passed to the factory method.
97-98
: Verify template parameter pass-through behavior
The TemplateLazyParameter[T]
is handled as a pass-through, similar to CustomLazyParameter[T]
. Please verify if template parameters require any transformation or preparation before evaluation, similar to how ProductLazyParameter
and SequenceLazyParameter
are handled.
#!/bin/bash
# Search for TemplateLazyParameter implementations to understand their evaluation requirements
ast-grep --pattern 'trait TemplateLazyParameter[T] {
$$$
}'
ast-grep --pattern 'class $_ extends TemplateLazyParameter[$_] {
$$$
}'
23-23
: Verify the impact of return type change
The return type generalization from EvaluableLazyParameter[T]
to LazyParameter[T]
could be a breaking change if any code explicitly depends on EvaluableLazyParameter[T]
.
✅ Verification successful
Return type change is safe and part of internal refactoring
The change from EvaluableLazyParameter[T]
to LazyParameter[T]
is safe because:
EvaluableLazyParameter
is handled internally within the same file through pattern matching- The change is part of a strategy-based factory pattern where
EvaluableLazyParameter
is one of the concrete implementations - All usages properly handle the more generic
LazyParameter
type through pattern matching - No external code directly depends on the concrete
EvaluableLazyParameter
return type
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for direct usage of EvaluableLazyParameter type
rg -t scala "EvaluableLazyParameter\[.*?\]" -A 3
Length of output: 139
Script:
#!/bin/bash
# Let's try a more comprehensive search for EvaluableLazyParameter
rg "EvaluableLazyParameter" -l
# Also search for LazyParameter to understand the type hierarchy
rg "LazyParameter" -l
# Search for direct usage of create method
rg "create\(.*deps.*\).*:.*LazyParameter" -t scala
Length of output: 14129
Script:
#!/bin/bash
# Let's check for references to EvaluableLazyParameterCreator.create to see how it's used
rg -t scala "EvaluableLazyParameterCreator.*create" -B 2 -A 2
# Also check for type casts or pattern matches that might depend on EvaluableLazyParameter
rg -t scala "case.*EvaluableLazyParameter|asInstanceOf\[EvaluableLazyParameter" -B 2 -A 2
Length of output: 3040
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ParameterEvaluator.scala (1)
98-105
: LGTM! Good use of factory pattern for parameter creation.
The refactoring to use EvaluableLazyParameterFactory.build
improves the code structure by:
- Centralizing parameter creation logic
- Making the system more extensible for new parameter types
- Providing better separation of concerns
Let's verify consistent usage of the factory pattern:
✅ Verification successful
Factory pattern implementation is correctly used
The direct instantiations found in the codebase are for different purposes and don't conflict with the factory pattern implementation:
EvaluableLazyParameterFactory.build
is used for creating parameter instances based on editor languagenew EvaluableLazyParameterCreator
is a different class that extendsCustomLazyParameter
new EvaluableLazyParameterCreatorDeps
is just a dependency container- The direct
new EvaluableLazyParameter
instantiation inEvaluableLazyParameter.scala
is within the factory itself
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any direct instantiations of EvaluableLazyParameter that should use the factory
# Search for new EvaluableLazyParameter instances
rg "new EvaluableLazyParameter" --type scala
# Search for all usages of the factory
rg "EvaluableLazyParameterFactory.build" --type scala
Length of output: 770
Script:
#!/bin/bash
# Let's check the content of EvaluableLazyParameter.scala to understand the relationship between factory and direct instantiation
rg -A 10 "object EvaluableLazyParameterFactory" --type scala
# Also check the EvaluableLazyParameterCreator usage context
rg -A 5 "class EvaluableLazyParameterCreator" --type scala
Length of output: 3595
scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/InterpreterSpec.scala (2)
1027-1041
: LGTM! Clear and focused test case.
The test case effectively validates the combination of literal and templated values in SpEL expressions.
1043-1087
: Well-structured table-driven tests with comprehensive coverage.
Good use of ScalaTest's table-driven testing to cover multiple scenarios:
- Templated value and literal value combination
- Single literal value
- Single templated function call
- Empty value
The test cases are well-organized and provide clear descriptions of each scenario.
build.sbt (1)
1819-1820
: LGTM! Dependencies added for template lazy parameter testing
The addition of test dependencies to flinkTableApiComponents
and scenarioCompiler
is appropriate for implementing and testing the template lazy parameter feature.
scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/testcomponents/SpelTemplateAstOperationService.scala (2)
17-79
: Overall implementation looks good
The code is well-structured and follows the project's conventions. The implementation correctly handles the template parameter and processes the parts as expected.
56-58
: 🛠️ Refactor suggestion
Handle potential extraction and casting failures
Using extractValueUnsafe
and casting with asInstanceOf
can lead to runtime exceptions if the parameter extraction fails or if the type casting is invalid. Consider using extractValue
and handling the possible None
case to make the code safer.
Apply this diff to safely extract and cast the parameter:
- val lazyParam = spelTemplateParameter
- .extractValueUnsafe(params)
- .asInstanceOf[TemplateLazyParameter[String]]
+ val lazyParamOption = spelTemplateParameter
+ .extractValue(params)
+ .flatMap(_.as[TemplateLazyParameter[String]])
+ lazyParamOption match {
+ case Some(lazyParam) =>
+ val result = lazyParam.templateExpression.parts.map {
+ case NonTemplatedPart(value) => s"[$value]-literal"
+ case template: TemplatedPart => s"[${template.evaluate(context)}]-templated"
+ }.mkString
+ Future.successful(result)
+ case None =>
+ Future.failed(new IllegalArgumentException("Template parameter is missing or invalid"))
+ }
Likely invalid or redundant comment.
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (1)
28-60
: SpelTemplateEvaluableLazyParameter
implementation is correct
The new class SpelTemplateEvaluableLazyParameter
appropriately extends TemplateLazyParameter
and correctly implements the evaluate
and templateExpression
methods. The handling of SpEL expressions and subexpressions is well-structured.
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala (1)
31-32
: Imports are appropriate for the new functionality
The added imports align with the new code and are necessary for the introduced features.
override val evaluate: Evaluate[T] = | ||
LazyParmeterEvaluator.evaluate(compiledParameter, expressionEvaluator, nodeId, jobData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in LazyParmeterEvaluator
class name
The class name LazyParmeterEvaluator
is misspelled throughout the code. It should be LazyParameterEvaluator
to maintain consistency and avoid confusion.
Apply the following diff to correct the typo:
-class LazyParmeterEvaluator {
+class LazyParameterEvaluator {
...
-override val evaluate: Evaluate[T] =
- LazyParmeterEvaluator.evaluate(compiledParameter, expressionEvaluator, nodeId, jobData)
+override val evaluate: Evaluate[T] =
+ LazyParameterEvaluator.evaluate(compiledParameter, expressionEvaluator, nodeId, jobData)
Also applies to: 35-36, 62-64
Some(parsed.parsed match { | ||
case compositeExpr: CompositeStringExpression => | ||
compositeExpr.getExpressions.toList.map { | ||
case lit: LiteralExpression => NonTemplatedValue(lit.getExpressionString) | ||
case spelExpr: org.springframework.expression.spel.standard.SpelExpression => | ||
createTemplatedExpression(spelExpr) | ||
} | ||
case spelExpr: org.springframework.expression.spel.standard.SpelExpression => | ||
List(createTemplatedExpression(spelExpr)) | ||
case litExpr: LiteralExpression => List(NonTemplatedValue(litExpr.getExpressionString)) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Handle unexpected expression types in pattern matching
In the subexpressions
method, pattern matching on parsed.parsed
may throw a MatchError
if an unexpected expression type is encountered. Consider adding a default case to handle unforeseen types gracefully and provide informative error messages.
Apply this diff to include a default case:
case litExpr: LiteralExpression => List(NonTemplatedValue(litExpr.getExpressionString))
+ case other =>
+ throw new IllegalArgumentException(s"Unsupported expression type: ${other.getClass.getName}")
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
Some(parsed.parsed match { | |
case compositeExpr: CompositeStringExpression => | |
compositeExpr.getExpressions.toList.map { | |
case lit: LiteralExpression => NonTemplatedValue(lit.getExpressionString) | |
case spelExpr: org.springframework.expression.spel.standard.SpelExpression => | |
createTemplatedExpression(spelExpr) | |
} | |
case spelExpr: org.springframework.expression.spel.standard.SpelExpression => | |
List(createTemplatedExpression(spelExpr)) | |
case litExpr: LiteralExpression => List(NonTemplatedValue(litExpr.getExpressionString)) | |
}) | |
Some(parsed.parsed match { | |
case compositeExpr: CompositeStringExpression => | |
compositeExpr.getExpressions.toList.map { | |
case lit: LiteralExpression => NonTemplatedValue(lit.getExpressionString) | |
case spelExpr: org.springframework.expression.spel.standard.SpelExpression => | |
createTemplatedExpression(spelExpr) | |
} | |
case spelExpr: org.springframework.expression.spel.standard.SpelExpression => | |
List(createTemplatedExpression(spelExpr)) | |
case litExpr: LiteralExpression => List(NonTemplatedValue(litExpr.getExpressionString)) | |
case other => | |
throw new IllegalArgumentException(s"Unsupported expression type: ${other.getClass.getName}") | |
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (5)
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (2)
54-56
: Improve error messages for better debuggingThe error messages could be more descriptive to help with debugging:
- Include the actual expression type received
- Explain what types are expected
- throw new IllegalStateException("Non SpEL-template expression received in SpelTemplateLazyParameter") + throw new IllegalStateException(s"Expected SpEL template expression but got: ${expression.getClass.getSimpleName}") - case _ => throw new IllegalStateException("Non SpEL expression received in SpelTemplateLazyParameter") + case other => throw new IllegalStateException(s"Expected SpelExpression but got: ${other.getClass.getSimpleName}")
42-52
: Consider simplifying nested pattern matchingThe nested pattern matching could be simplified using a more functional approach with map and pattern matching in a single expression.
- val templateParts = subexpressions.map { - case TemplatedExpression(expression) => { - new TemplateExpressionPart.TemplatedPart { - override val evaluate: Evaluate[String] = context => { - expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData).value - } - } - } - case NonTemplatedValue(value) => TemplateExpressionPart.NonTemplatedPart(value) - } - TemplateExpression(templateParts) + TemplateExpression(subexpressions.map { + case TemplatedExpression(expression) => + TemplateExpressionPart.TemplatedPart(context => + expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData).value) + case NonTemplatedValue(value) => + TemplateExpressionPart.NonTemplatedPart(value) + })scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala (3)
84-93
: LGTM! Consider adding scaladoc.The sealed trait hierarchy is well-designed for handling template expressions. Consider adding scaladoc to document:
- The purpose of SpelTemplateSubexpression
- When to use each case class
- Expected behavior of the evaluate method
+/** + * Represents parts of a template expression, which can be either static text (NonTemplatedValue) + * or dynamic expressions that need evaluation (TemplatedExpression). + */ sealed trait SpelTemplateSubexpression object SpelTemplateSubexpression { + /** + * Represents a static part of the template that doesn't require evaluation. + * @param value The static string value + */ final case class NonTemplatedValue(val value: String) extends SpelTemplateSubexpression + /** + * Represents a dynamic part of the template that requires evaluation. + * @param expression The SpelExpression to be evaluated + */ final case class TemplatedExpression(expression: SpelExpression) extends SpelTemplateSubexpression { def evaluate: (Context, Map[String, Any]) => String = expression.evaluate[String] } }
107-136
: Consider refactoring for improved maintainability.The method implementation is functionally correct but could benefit from some improvements:
- Move helper method to companion object:
object SpelTemplateSubexpression { + private def createTemplatedExpression( + expression: org.springframework.expression.spel.standard.SpelExpression, + parser: () => ValidatedNel[ExpressionParseError, Expression], + evaluationContextPreparer: EvaluationContextPreparer + ): TemplatedExpression = { + val parsedTemplateExpr = ParsedSpelExpression(expression.getExpressionString, parser, expression) + val compiledExpr = new SpelExpression( + parsedTemplateExpr, + typing.Typed[String], + Standard, + evaluationContextPreparer + ) + TemplatedExpression(compiledExpr) + } }
- Simplify pattern matching:
- flavour.languageId match { - case Language.SpelTemplate => - Some(parsed.parsed match { + Option.when(flavour.languageId == Language.SpelTemplate) { + parsed.parsed match {
- Add documentation:
+ /** + * Decomposes the expression into template subexpressions if this is a template expression. + * @return Some(List[SpelTemplateSubexpression]) for template expressions, None otherwise + */ def templateSubexpressions: Option[List[SpelTemplateSubexpression]] = {
130-131
: Enhance error message for unsupported expression types.The error message could be more descriptive to help with debugging.
- throw new IllegalArgumentException(s"Unsupported expression type: [${other.getClass.getName}]") + throw new IllegalArgumentException( + s"Unsupported expression type in template: [${other.getClass.getName}]. " + + "Expected CompositeStringExpression, SpelExpression, or LiteralExpression." + )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala
(2 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala
(3 hunks)
🔇 Additional comments (2)
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (2)
Line range hint 15-26
: LGTM! Clean implementation of EvaluableLazyParameter
The class properly implements CustomLazyParameter with type safety and clear delegation to LazyParameterEvaluator.
88-104
: Consider decoupling parameter type determination from editor type
As noted in the previous review, basing the parameter type on the editor definition creates tight coupling. Consider:
- Adding an explicit
isTemplateParameter
flag toParameterDef
- Validating editor compatibility separately
This would make the system more maintainable and the intent more explicit.
Let's check if this pattern is used elsewhere in the codebase:
def evaluate[T <: AnyRef]( | ||
compiledParameter: BaseCompiledParameter, | ||
expressionEvaluator: ExpressionEvaluator, | ||
nodeId: NodeId, | ||
jobData: JobData | ||
) = | ||
this(compiledParameter, expressionEvaluator, nodeId, jobData, compiledParameter.typingInfo.typingResult) | ||
|
||
override val evaluate: Evaluate[T] = { ctx: Context => | ||
): Evaluate[T] = { ctx: Context => | ||
expressionEvaluator | ||
.evaluateParameter(compiledParameter, ctx)(nodeId, jobData) | ||
.value | ||
.asInstanceOf[T] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for type casting
The unsafe cast to type T could fail at runtime. Consider adding proper error handling.
def evaluate[T <: AnyRef](
compiledParameter: BaseCompiledParameter,
expressionEvaluator: ExpressionEvaluator,
nodeId: NodeId,
jobData: JobData
): Evaluate[T] = { ctx: Context =>
expressionEvaluator
.evaluateParameter(compiledParameter, ctx)(nodeId, jobData)
.value
- .asInstanceOf[T]
+ match {
+ case value: T => value
+ case other => throw new ClassCastException(
+ s"Expected type ${compiledParameter.returnType} but got: ${other.getClass.getSimpleName}"
+ )
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
def evaluate[T <: AnyRef]( | |
compiledParameter: BaseCompiledParameter, | |
expressionEvaluator: ExpressionEvaluator, | |
nodeId: NodeId, | |
jobData: JobData | |
) = | |
this(compiledParameter, expressionEvaluator, nodeId, jobData, compiledParameter.typingInfo.typingResult) | |
override val evaluate: Evaluate[T] = { ctx: Context => | |
): Evaluate[T] = { ctx: Context => | |
expressionEvaluator | |
.evaluateParameter(compiledParameter, ctx)(nodeId, jobData) | |
.value | |
.asInstanceOf[T] | |
def evaluate[T <: AnyRef]( | |
compiledParameter: BaseCompiledParameter, | |
expressionEvaluator: ExpressionEvaluator, | |
nodeId: NodeId, | |
jobData: JobData | |
): Evaluate[T] = { ctx: Context => | |
expressionEvaluator | |
.evaluateParameter(compiledParameter, ctx)(nodeId, jobData) | |
.value | |
match { | |
case value: T => value | |
case other => throw new ClassCastException( | |
s"Expected type ${compiledParameter.returnType} but got: ${other.getClass.getSimpleName}" | |
) | |
} |
...c/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala
Outdated
Show resolved
Hide resolved
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala
Outdated
Show resolved
Hide resolved
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala
Outdated
Show resolved
Hide resolved
interpretProcess(process, Transaction(msisdn = "foo")) should equal("[Hello]-literal") | ||
} | ||
|
||
test("spel template ast operation parameter should work for single templated function call expression") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have a test for the case when PostponedEvaluatorLazyParameterStrategy
is used. AFAIK it is used in source/sink/custom nodes combined with Flink engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this test is in TemplateLazyParameterTest. But weirdly, the test passes without changes in EvaluableLazyParameterCreator :/ still trying to figure it out
Also I parameterized all test cases into one test and forgot to delete these tests here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so the EvaluableLazyParameterCreator
doesnt get used when its parameter of EagerService. I tested it on a source and its not working. Unfortunately I see no simple way to add support for it.
Right now I'm trying an approach with an EvaluableLazyTemplateParameterCreator - something like EvaluableLazyParameterCreator but with a custom dedicated function in FlinkLazyParameterFunctionHelper
that doesnt evaluate the parameter like normal, but allows access to template subexpression. I'm hoping it works, but am having some trouble with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Way more complex than I hoped :( need some advice. let's talk about it tomorrow if you'll have the time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (9)
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (2)
38-57
: Enhance error handling and simplify pattern matchingThe template expression handling could be improved in several ways:
- Error messages should be more descriptive and include actual expression types
- The nested pattern matching could be simplified
Consider this refactoring:
override def templateExpression: TemplateExpression = compiledParameter.expression match { case expression: SpelExpression => - expression.templateSubexpressions match { - case Some(subexpressions) => - val templateParts = subexpressions.map { - case Placeholder(expression) => { - new TemplateExpressionPart.Placeholder { - override val evaluate: Evaluate[String] = context => { - expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData).value - } - } - } - case Literal(value) => TemplateExpressionPart.Literal(value) - } - TemplateExpression(templateParts) - case None => - throw new IllegalStateException("Non SpEL-template expression received in SpelTemplateLazyParameter") - } - case _ => throw new IllegalStateException("Non SpEL expression received in SpelTemplateLazyParameter") + expression.templateSubexpressions.map { subexpressions => + val templateParts = subexpressions.map { + case Placeholder(expr) => new TemplateExpressionPart.Placeholder { + override val evaluate: Evaluate[String] = context => + expressionEvaluator.evaluate[String](expr, "expressionId", nodeId.id, context)(jobData).value + } + case Literal(value) => TemplateExpressionPart.Literal(value) + } + TemplateExpression(templateParts) + }.getOrElse(throw new IllegalStateException( + s"Expression ${expression} is not a SpEL template expression" + )) + case other => throw new IllegalStateException( + s"Expected SpEL expression but got: ${other.getClass.getName}" + )
35-36
: Consider extracting common evaluation logicThe evaluate implementation is duplicated from EvaluableLazyParameter. Consider extracting this to a trait or using composition to avoid duplication.
components-api/src/main/scala/pl/touk/nussknacker/engine/api/LazyParameter.scala (2)
72-74
: Add documentation for the new traitConsider adding scaladoc to explain:
- The purpose and use cases of
TemplateLazyParameter
- The relationship between template expressions and lazy parameter evaluation
- Example usage demonstrating template interpolation
Example documentation:
/** * Represents a lazy parameter that supports template expressions with interpolated values. * Template expressions consist of literal parts and placeholders that are evaluated lazily. * * Example usage: * {{{ * val template = new TemplateLazyParameter[String] { * def templateExpression = TemplateExpression(List( * Literal("Hello "), * Placeholder(ctx => ctx.name) * )) * // ... other implementations * } * }}} * * @tparam T the type of the evaluated expression */
83-85
: Consider making Placeholder's evaluate type more flexibleThe
Placeholder
trait'sevaluate
method is fixed to returnString
. Consider parameterizing it to support different types of interpolated values:- trait Placeholder extends TemplateExpressionPart { - val evaluate: Evaluate[String] - } + trait Placeholder[T] extends TemplateExpressionPart { + val evaluate: Evaluate[T] + def asString: Evaluate[String] // For template rendering + }This would allow for type-safe interpolation of non-string values while maintaining the ability to render them as strings in the final template.
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala (4)
103-103
: Add documentation for the templateSubexpressions methodConsider adding ScalaDoc to explain the purpose of this method, its return type semantics, and when it should be used.
104-113
: Consider extracting placeholder creation logicThe
createEvaluablePlaceholder
helper function uses hardcoded values for typing and flavor. Consider:
- Making these parameters configurable
- Moving this logic to a separate factory method
- Adding error handling for parser failures
- def createEvaluablePlaceholder(expression: org.springframework.expression.spel.standard.SpelExpression) = { + private def createPlaceholder( + expression: org.springframework.expression.spel.standard.SpelExpression, + expectedType: TypingResult = typing.Typed[String], + exprFlavor: Flavour = Standard + ): Placeholder = { val parsedTemplateExpr = ParsedSpelExpression(expression.getExpressionString, parsed.parser, expression) val compiledExpr = new SpelExpression( parsedTemplateExpr, - typing.Typed[String], - Standard, + expectedType, + exprFlavor, evaluationContextPreparer ) Placeholder(compiledExpr) }
114-131
: Simplify nested pattern matching logicThe current implementation has deep nesting which could be simplified using early returns or pattern matching decomposition.
Consider restructuring like this:
def templateSubexpressions: Option[List[SpelTemplateExpressionPart]] = { if (flavour.languageId != Language.SpelTemplate) return None Some(parsed.parsed match { case compositeExpr: CompositeStringExpression => compositeExpr.getExpressions.toList.map(expressionToPart) case expr => List(expressionToPart(expr)) }) } private def expressionToPart(expr: Expression): SpelTemplateExpressionPart = expr match { case lit: LiteralExpression => Literal(lit.getExpressionString) case spelExpr: org.springframework.expression.spel.standard.SpelExpression => createPlaceholder(spelExpr) case other => throw new IllegalArgumentException( s"Unsupported expression type: [${other.getClass.getName}]. Expected: LiteralExpression or SpelExpression" ) }
126-127
: Enhance error message for unsupported expression typesThe current error message could be more helpful by including:
- The expected types
- The actual expression content
- Guidance on how to fix the issue
- throw new IllegalArgumentException(s"Unsupported expression type: [${other.getClass.getName}]") + throw new IllegalArgumentException( + s"""Unsupported expression type: [${other.getClass.getName}] for expression: '${other.getExpressionString}' + |Expected: LiteralExpression or SpelExpression for template parsing. + |Please ensure the expression follows the template syntax.""".stripMargin)scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/InterpreterSpec.scala (1)
1028-1053
: Consider adding more test cases for comprehensive coverage.The current test cases cover basic scenarios well. Consider adding these additional cases for more thorough testing:
- Multiple templated expressions (e.g.,
Hello#{#input.msisdn}#{#input.accountId}
)- Nested function calls (e.g.,
#{#input.msisdn.toLowerCase().trim()}
)- Special characters in templates (e.g.,
Hello @#{#input.msisdn}!
)val testCases = Seq( // ... existing test cases ... + ( + "multiple templated expressions", + s"Hello#{#input.msisdn}#{#input.accountId}", + Transaction(msisdn = "foo", accountId = "123"), + "[Hello]-literal[foo]-templated[123]-templated" + ), + ( + "nested function calls in template", + "#{#input.msisdn.toLowerCase().trim()}", + Transaction(msisdn = "FOO "), + "[foo]-templated" + ), + ( + "special characters in template", + "Hello @#{#input.msisdn}!", + Transaction(msisdn = "foo"), + "[Hello @]-literal[foo]-templated[!]-literal" + ), )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
components-api/src/main/scala/pl/touk/nussknacker/engine/api/LazyParameter.scala
(2 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala
(2 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala
(3 hunks)scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/InterpreterSpec.scala
(5 hunks)scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/testcomponents/SpelTemplateAstOperationService.scala
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/testcomponents/SpelTemplateAstOperationService.scala
🔇 Additional comments (7)
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (3)
Line range hint 15-26
: LGTM: Clean implementation with good separation of concerns
The class implementation is clean and follows good practices with proper type parameterization and delegation of evaluation logic.
64-73
: Reusing past review comment about type casting safety
The unsafe type casting issue mentioned in the previous review is still present and should be addressed.
80-105
: Consider decoupling parameter type determination from editor implementation
While the current implementation works, it tightly couples parameter type determination to editor implementation. As mentioned in previous reviews, consider:
- Adding an explicit
isTemplateParameter
flag toParameter
- Using expression type-based determination instead of editor-based determination
This would make the system more flexible and easier to extend in the future.
Let's verify the current usage of editor types in the codebase:
components-api/src/main/scala/pl/touk/nussknacker/engine/api/LazyParameter.scala (1)
3-3
: LGTM: Import statement is correctly placed
The self-reference import for TemplateExpression
follows Scala's idiomatic practices.
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala (1)
84-89
: Consider revising the naming scheme for template expression parts
As mentioned in the previous review, the current naming scheme (SpelTemplateExpressionPart
, Literal
, Placeholder
) could be improved to be more intuitive in spoken language and documentation. Consider alternatives that better describe the role of each part in template expressions.
Some alternative naming suggestions:
SpelTemplateSegment
withStaticSegment
/DynamicSegment
TemplateElement
withTextElement
/ExpressionElement
scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/InterpreterSpec.scala (2)
9-10
: LGTM: Import for table-driven tests.
The import for table-driven property tests is appropriate for the new parameterized test case.
1027-1071
: LGTM: Well-structured table-driven test for template AST operations.
The test implementation is clean and covers essential scenarios using a table-driven approach with clear test case descriptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (4)
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (2)
52-54
: Improve error messages for better debuggingThe error messages in the exception cases could be more descriptive to help with debugging. Include more context about the actual expression type received.
- throw new IllegalStateException("Non SpEL-template expression received in SpelTemplateLazyParameter") + throw new IllegalStateException(s"Expected SpEL-template expression but got: ${expression.templateSubexpressions}") - case _ => throw new IllegalStateException("Non SpEL expression received in SpelTemplateLazyParameter") + case other => throw new IllegalStateException(s"Expected SpelExpression but got: ${other.getClass.getSimpleName}")
41-49
: Consider simplifying the pattern matching logicThe nested pattern matching with a single-expression block can be simplified for better readability.
- case Placeholder(expression) => { - new TemplateExpressionPart.Placeholder { - override val evaluate: Evaluate[String] = context => { - expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData).value - } - } - } + case Placeholder(expression) => new TemplateExpressionPart.Placeholder { + override val evaluate: Evaluate[String] = context => + expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData).value + }scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala (2)
84-89
: Consider revising the naming based on previous feedbackThe structure using a sealed trait with final case classes is well-designed. However, as mentioned in the previous review, the current naming might be confusing in spoken language. Consider alternatives that better describe the parts of a template:
-sealed trait SpelTemplateExpressionPart -object SpelTemplateExpressionPart { - final case class Literal(value: String) extends SpelTemplateExpressionPart - final case class Placeholder(expression: SpelExpression) extends SpelTemplateExpressionPart +sealed trait TemplateSegment +object TemplateSegment { + final case class StaticText(value: String) extends TemplateSegment + final case class EvaluableExpression(expression: SpelExpression) extends TemplateSegmentThis makes it clearer that a template consists of static text segments and evaluable expression segments.
103-122
: Consider optimizing template parsing performanceThe implementation is functionally correct but could benefit from performance optimizations:
The creation of new SpelExpression instances inside
parseTemplate
could be expensive, especially for complex templates. Consider caching or reusing instances where possible.The recursive parsing of composite expressions could be optimized using a more efficient collection transformation.
Here's a suggested optimization:
def templateSubexpressions: Option[List[SpelTemplateExpressionPart]] = { def parseTemplate(expression: Expression): List[SpelTemplateExpressionPart] = expression match { case lit: LiteralExpression => List(Literal(lit.getExpressionString)) case spelExpr: org.springframework.expression.spel.standard.SpelExpression => - val parsedTemplateExpr = ParsedSpelExpression(spelExpr.getExpressionString, parsed.parser, spelExpr) - val compiledExpr = new SpelExpression( - parsedTemplateExpr, - typing.Typed[String], - Standard, - evaluationContextPreparer - ) - List(Placeholder(compiledExpr)) + // Cache the parsed expression using memoization or a cache map + List(Placeholder(getOrCreateSpelExpression(spelExpr))) case compositeExpr: CompositeStringExpression => - compositeExpr.getExpressions.toList.flatMap(parseTemplate) + // Use more efficient collection transformation + compositeExpr.getExpressions.iterator.map(parseTemplate).flatten.toList case other => throw new IllegalArgumentException(s"Unsupported expression type: [${other.getClass.getName}]") } flavour.languageId match { case Language.SpelTemplate => Some(parseTemplate(parsed.parsed)) case _ => None } } +private def getOrCreateSpelExpression(spelExpr: org.springframework.expression.spel.standard.SpelExpression): SpelExpression = { + // Implement caching logic here + val parsedTemplateExpr = ParsedSpelExpression(spelExpr.getExpressionString, parsed.parser, spelExpr) + new SpelExpression( + parsedTemplateExpr, + typing.Typed[String], + Standard, + evaluationContextPreparer + ) +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala
(1 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala
(2 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameterCreator.scala
(3 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ParameterEvaluator.scala
(1 hunks)scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala
- scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ParameterEvaluator.scala
🔇 Additional comments (5)
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameter.scala (1)
85-101
: Consider alternative approaches to parameter type determination
The current implementation uses language-based pattern matching to determine the parameter type. As discussed in previous reviews, there might be more robust alternatives:
- Add an
isTemplateParameter
flag inParameter
definition - Use pattern matching on the expression type itself
The current approach couples the parameter type to the language, which might be too restrictive.
Let's verify the expression types in use:
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/EvaluableLazyParameterCreator.scala (2)
45-52
: LGTM! Good use of the factory pattern.
The refactoring to use EvaluableLazyParameterFactory.build
improves encapsulation and maintainability by centralizing the creation logic in a dedicated factory.
23-23
: Verify the impact of the API change.
The return type change from EvaluableLazyParameter[T]
to LazyParameter[T]
makes the API more flexible but could affect existing code that expects specifically EvaluableLazyParameter[T]
.
✅ Verification successful
Let me gather more context about the usage patterns to better understand the impact.
Let me search for one more pattern to understand how the return value is used.
Based on the evidence gathered from the shell scripts, I can now provide a final response:
Return type change from EvaluableLazyParameter[T]
to LazyParameter[T]
is safe
The change is well-contained and safe because:
- The implementation still handles
EvaluableLazyParameter
through pattern matching as seen in the original file - No direct dependencies on
EvaluableLazyParameter
return type were found in the codebase - The default strategy in
ParameterEvaluator
already works with the genericLazyParameter
type - This change provides better flexibility while maintaining backward compatibility
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for direct usage of EvaluableLazyParameter type to identify potential breaking changes
rg -t scala "EvaluableLazyParameter\[.*?\]" --no-heading
Length of output: 1197
Script:
#!/bin/bash
# Search for pattern matching and type casting involving LazyParameter and EvaluableLazyParameter
rg -t scala "LazyParameter.*?[=:].*?Evaluable" -A 2 --no-heading
# Search for create method invocations
ast-grep --pattern 'create($$$)'
Length of output: 3752
Script:
#!/bin/bash
# Look for usages of the create method in EvaluableLazyParameterCreator and how its return value is handled
rg -t scala "EvaluableLazyParameterCreator.*?create" -B 2 -A 4 --no-heading
# Also check for any pattern matching on LazyParameter
ast-grep --pattern 'case $_: LazyParameter[$_] => $$$'
Length of output: 134
scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpression.scala (2)
31-32
: LGTM: Import statements are well-organized
The new imports are correctly scoped and necessary for the template expression functionality.
103-122
: Verify template parsing edge cases
The implementation should be tested with various template patterns to ensure robustness.
✅ Verification successful
Template parsing implementation appears sufficiently tested
The implementation has adequate test coverage and error handling:
- Template expressions are tested in
TemplateLazyParameterTest
with real-world usage patterns (e.g.,Hello#{#input}
) - Error handling for unsupported expressions is implemented
- The implementation handles all standard SpEL template cases: literals, SpEL expressions, and composite expressions
- The code is used in production scenarios as evidenced by the test cases
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for template expression test cases to verify coverage
rg -l "SpelTemplate|templateSubexpressions" --type scala --glob "*Test*.scala"
# Look for actual template usage in the codebase
ast-grep --pattern 'Language.SpelTemplate'
Length of output: 6742
Script:
#!/bin/bash
# Let's examine the test files to see how template expressions are tested
rg -A 10 "SpelTemplate|templateSubexpressions" scenario-api/src/test/scala/pl/touk/nussknacker/engine/ScenarioApiShowcasesTest.scala
# Check template lazy parameter tests
rg -A 10 "SpelTemplate|templateSubexpressions" engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala
# Look for actual template expression implementations
ast-grep --pattern 'class $_ {
$$$
templateSubexpressions$$$
$$$
}'
Length of output: 1490
Script:
#!/bin/bash
# Let's look for test implementations of SpelTemplateAstOperationService
rg -A 15 "class SpelTemplateAstOperationService"
# Search for any test cases involving template expressions
rg -A 10 "test.*template" --type scala --glob "*Test*.scala"
# Look for template-related error handling tests
rg "throw.*IllegalArgumentException.*Unsupported expression" --type scala
Length of output: 2094
override val evaluate: Evaluate[String] = context => { | ||
expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData).value | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for expression evaluation
The expression evaluation should handle potential errors to prevent runtime exceptions.
- override val evaluate: Evaluate[String] = context => {
- expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData).value
- }
+ override val evaluate: Evaluate[String] = context => {
+ val result = expressionEvaluator.evaluate[String](expression, "expressionId", nodeId.id, context)(jobData)
+ if (result.isRight) result.value
+ else throw new IllegalStateException(s"Failed to evaluate template expression: ${result.left.get}")
+ }
Committable suggestion skipped: line range outside the PR's diff.
case p: CustomLazyParameter[T] => p | ||
case p: TemplateLazyParameter[T] => p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review pattern matching order for potential issues.
The case p: CustomLazyParameter[T]
might catch instances of EvaluableLazyParameterCreator
before they reach the more specific case above, potentially preventing proper evaluation of nested creators. Consider:
- Moving these cases before the
EvaluableLazyParameterCreator
case, or - Making the pattern matching more specific to exclude
EvaluableLazyParameterCreator
Here's a suggested fix:
- case p: CustomLazyParameter[T] => p
- case p: TemplateLazyParameter[T] => p
+ case p: TemplateLazyParameter[T] => p
+ case p: CustomLazyParameter[T] if !p.isInstanceOf[EvaluableLazyParameterCreator[_]] => p
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
case p: CustomLazyParameter[T] => p | |
case p: TemplateLazyParameter[T] => p | |
case p: TemplateLazyParameter[T] => p | |
case p: CustomLazyParameter[T] if !p.isInstanceOf[EvaluableLazyParameterCreator[_]] => p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (3)
engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala (3)
62-62
: Remove debug println statementDebug print statements should be removed before merging.
- println(result)
51-68
: Consider adding more test casesWhile the current test case validates basic functionality, consider adding tests for:
- Empty input values
- Special characters in template
- Multiple placeholders
- Error cases (invalid templates)
72-76
: Add documentation for the sink implementationPlease add ScalaDoc comments explaining:
- The purpose of this sink
- Expected input format
- Template processing behavior
- Any limitations or requirements
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala
(1 hunks)
🔇 Additional comments (3)
engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/TemplateLazyParameterTest.scala (3)
1-42
: LGTM! Well-structured test class setup
The test class follows best practices by extending appropriate testing traits and importing necessary dependencies.
43-50
: LGTM! Appropriate test runner configuration
The runner is well-configured with lazy initialization and batch mode execution, properly registering the template operation sink component.
95-95
: Verify empty nodeDependencies list
The nodeDependencies
method returns an empty list. Please verify if this is intentional and add a comment explaining why no dependencies are needed.
override def registerSink( | ||
dataStream: DataStream[ValueWithContext[String]], | ||
flinkNodeContext: FlinkCustomNodeContext | ||
): DataStreamSink[_] = { | ||
println(dataStream) | ||
dataStream.addSink(new SinkFunction[ValueWithContext[String]] { | ||
override def invoke(value: ValueWithContext[String], context: SinkFunction.Context): Unit = { | ||
println(value) | ||
println("debug") | ||
|
||
} | ||
}) | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implement proper sink functionality
The current sink implementation only prints to console and effectively discards the data. Consider:
- Implementing actual data persistence
- Adding error handling
- Implementing proper cleanup in close method
println(dataStream) | ||
dataStream.addSink(new SinkFunction[ValueWithContext[String]] { | ||
override def invoke(value: ValueWithContext[String], context: SinkFunction.Context): Unit = { | ||
println(value) | ||
println("debug") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove debug println statements from production code
Production code should not contain debug print statements. Consider using proper logging if debugging information is needed.
- println(dataStream)
dataStream.addSink(new SinkFunction[ValueWithContext[String]] {
override def invoke(value: ValueWithContext[String], context: SinkFunction.Context): Unit = {
- println(value)
- println("debug")
+ // Add actual sink implementation here
}
Committable suggestion skipped: line range outside the PR's diff.
} | ||
TemplateExpression(templateParts) | ||
case None => | ||
throw new IllegalStateException("Non SpEL-template expression received in SpelTemplateLazyParameter") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get rid of this exceptions by moving this logic to factory method and check templateSubexpressions
instead of expression langage
@@ -92,6 +100,27 @@ class SpelExpression( | |||
|
|||
override val language: Language = flavour.languageId | |||
|
|||
def templateSubexpressions: Option[List[SpelTemplateExpressionPart]] = { | |||
def parseTemplate(expression: Expression): List[SpelTemplateExpressionPart] = expression match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it should be rather called either parseExpression or parseParts
def parseTemplate(expression: Expression): List[SpelTemplateExpressionPart] = expression match { | ||
case lit: LiteralExpression => List(Literal(lit.getExpressionString)) | ||
case spelExpr: org.springframework.expression.spel.standard.SpelExpression => | ||
val parsedTemplateExpr = ParsedSpelExpression(spelExpr.getExpressionString, parsed.parser, spelExpr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a template expression
override val evaluate: Evaluate[T] = | ||
LazyParameterEvaluator.evaluate(compiledParameter, expressionEvaluator, nodeId, jobData) | ||
|
||
override def templateExpression: TemplateExpression = compiledParameter.expression match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to evaluate it during each invocation
Describe your changes
Checklist before merge
Summary by CodeRabbit
Release Notes
New Features
TemplateLazyParameter
for enhanced parameter expression handling, supporting both static and dynamic components.SpelTemplateAstOperationService
for processing SpEL templates, improving template evaluation capabilities.Bug Fixes
Tests
templateAstOperationService
with various SpEL template expressions.