Skip to content

Commit

Permalink
[NU-7164] Add parser to FragmentValidator
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotr Rudnicki committed Nov 20, 2024
1 parent e1138f4 commit a2ea8eb
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pl.touk.nussknacker.engine.compile.nodecompilation
import cats.data.Validated.{Valid, invalid, invalidNel, valid}
import cats.data.{NonEmptyList, Validated, ValidatedNel}
import cats.implicits.toTraverseOps
import org.apache.commons.lang3.ClassUtils
import pl.touk.nussknacker.engine.api.NodeId
import pl.touk.nussknacker.engine.api.context.ProcessCompilationError._
import pl.touk.nussknacker.engine.api.context.{PartSubGraphCompilationError, ProcessCompilationError, ValidationContext}
Expand All @@ -14,9 +15,11 @@ import pl.touk.nussknacker.engine.api.parameter.{
ValueInputWithDictEditor,
ValueInputWithFixedValuesProvided
}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, Unknown}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult, Unknown}
import pl.touk.nussknacker.engine.api.validation.Validations.validateVariableName
import pl.touk.nussknacker.engine.compile.ExpressionCompiler
import pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator.permittedTypesForEditors
import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition
import pl.touk.nussknacker.engine.graph.expression.Expression
import pl.touk.nussknacker.engine.graph.expression.Expression.Language
import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.{FragmentClazzRef, FragmentParameter}
Expand All @@ -29,6 +32,8 @@ import pl.touk.nussknacker.engine.graph.node.{
}
import pl.touk.nussknacker.engine.language.dictWithLabel.DictKeyWithLabelExpressionParser

import scala.util.Try

object FragmentParameterValidator {

val permittedTypesForEditors: List[FragmentClazzRef] = List(
Expand All @@ -37,6 +42,10 @@ object FragmentParameterValidator {
FragmentClazzRef[java.lang.Long]
)

}

case class FragmentParameterValidator(classDefinitions: Set[ClassDefinition] = Set.empty) {

// This method doesn't fully validate valueEditor (see ValueEditorValidator.validateAndGetEditor comments)
def validateAgainstClazzRefAndGetEditor(
valueEditor: ParameterValueInput,
Expand Down Expand Up @@ -137,6 +146,40 @@ object FragmentParameterValidator {
).sequence.map(_ => ())
}

private def parseClassNameToTypingResult(classLoader: ClassLoader, className: String): Try[TypingResult] = {
/*
TODO: Write this parser in a way that handles arbitrary depth expressions
One should not use regexes for doing so and rather build AST
*/
def resolveInnerClass(simpleClassName: String): TypingResult =
classDefinitions
.find(classDefinition => classDefinition.clazzName.display == simpleClassName)
.fold(
// This is fallback - it may be removed and `ClassNotFound` exception may be thrown here after cleaning up the mess with `FragmentClazzRef` class
Typed(ClassUtils.getClass(classLoader, simpleClassName))
) { classDefinition =>
classDefinition.clazzName
}

val mapPattern = "Map\\[(.+),(.+)\\]".r
val listPattern = "List\\[(.+)\\]".r
val setPattern = "Set\\[(.+)\\]".r

Try(className match {
case mapPattern(x, y) =>
val resolvedFirstTypeParam = resolveInnerClass(x)
val resolvedSecondTypeParam = resolveInnerClass(y)
Typed.genericTypeClass[java.util.Map[_, _]](List(resolvedFirstTypeParam, resolvedSecondTypeParam))
case listPattern(x) =>
val resolvedTypeParam = resolveInnerClass(x)
Typed.genericTypeClass[java.util.List[_]](List(resolvedTypeParam))
case setPattern(x) =>
val resolvedTypeParam = resolveInnerClass(x)
Typed.genericTypeClass[java.util.Set[_]](List(resolvedTypeParam))
case simpleClassName => resolveInnerClass(simpleClassName)
})
}

def validateValueInputWithDictEditor(
fragmentParameter: FragmentParameter,
dictionaries: Map[String, DictDefinition],
Expand All @@ -147,9 +190,10 @@ object FragmentParameterValidator {
validateNonEmptyDictId(dictId, fragmentParameter.name).andThen(_ =>
dictionaries.get(dictId) match {
case Some(dictDefinition) =>
val fragmentParameterTypingResult = fragmentParameter.typ
.toRuntimeClass(classLoader)
.map(Typed(_))
val fragmentParameterTypingResult = parseClassNameToTypingResult(
classLoader,
fragmentParameter.typ.refClazzName
)
.getOrElse(Unknown)

val dictValueType = dictDefinition.valueType(dictId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import pl.touk.nussknacker.engine.api.parameter.ParameterName
import pl.touk.nussknacker.engine.api.process.{ComponentUseCase, Source}
import pl.touk.nussknacker.engine.api.typed.ReturningType
import pl.touk.nussknacker.engine.api.typed.typing.{TypingResult, Unknown}
import pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator.validateParameterNames
import pl.touk.nussknacker.engine.compile.nodecompilation.NodeCompiler.NodeCompilationResult
import pl.touk.nussknacker.engine.compile.{
ComponentExecutorFactory,
Expand Down Expand Up @@ -100,6 +99,8 @@ class NodeCompiler(
new DynamicNodeValidator(expressionCompiler, globalVariablesPreparer, parametersEvaluator)
private val builtInNodeCompiler = new BuiltInNodeCompiler(expressionCompiler)

private val fragmentParameterValidator = FragmentParameterValidator(fragmentDefinitionExtractor.classDefinitions)

def compileSource(
nodeData: SourceNodeData
)(implicit jobData: JobData, nodeId: NodeId): NodeCompilationResult[Source] = nodeData match {
Expand Down Expand Up @@ -165,7 +166,7 @@ class NodeCompiler(
)
}

val parameterNameValidation = validateParameterNames(parameterDefinitions.value)
val parameterNameValidation = fragmentParameterValidator.validateParameterNames(parameterDefinitions.value)

// by relying on name for the field names used on FE, we display the same errors under all fields with the
// duplicated name
Expand Down Expand Up @@ -195,7 +196,7 @@ class NodeCompiler(

val fixedValuesErrors = fragmentInputDefinition.parameters
.map { param =>
FragmentParameterValidator.validateFixedExpressionValues(
fragmentParameterValidator.validateFixedExpressionValues(
param,
validationContext,
expressionCompiler
Expand All @@ -206,7 +207,7 @@ class NodeCompiler(

val dictValueEditorErrors = fragmentInputDefinition.parameters
.map { param =>
FragmentParameterValidator.validateValueInputWithDictEditor(param, expressionConfig.dictionaries, classLoader)
fragmentParameterValidator.validateValueInputWithDictEditor(param, expressionConfig.dictionaries, classLoader)
}
.sequence
.map(_ => ())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import scala.util.Try
*/
class FragmentParametersDefinitionExtractor(
classLoader: ClassLoader,
classDefinitions: Set[ClassDefinition] = Set.empty
val classDefinitions: Set[ClassDefinition] = Set.empty
) {

def extractParametersDefinition(
Expand Down Expand Up @@ -80,7 +80,7 @@ class FragmentParametersDefinitionExtractor(

val (extractedEditor, validationErrors) = fragmentParameter.valueEditor
.map(editor =>
FragmentParameterValidator.validateAgainstClazzRefAndGetEditor(
FragmentParameterValidator(classDefinitions).validateAgainstClazzRefAndGetEditor(
valueEditor = editor,
initialValue = fragmentParameter.initialValue,
refClazz = fragmentParameter.typ,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers {
forAll(permittedTypesForEditors) { fragmentParameterType: FragmentClazzRef =>
{
val dictId = "someDictId"
val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor(
val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor(
valueEditor = ValueInputWithDictEditor(dictId, allowOtherValue = false),
initialValue = None,
refClazz = fragmentParameterType,
Expand All @@ -39,7 +39,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers {
forAll(permittedTypesForEditors) { fragmentParameterType: FragmentClazzRef =>
{
val fixedValuesList = List(FixedExpressionValue("someExpression", "someLabel"))
val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor(
val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor(
valueEditor = ValueInputWithFixedValuesProvided(fixedValuesList, allowOtherValue = false),
initialValue = None,
refClazz = fragmentParameterType,
Expand All @@ -55,7 +55,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers {
val paramName = ParameterName("someParamName")
val invalidParameterType = FragmentClazzRef[java.lang.Double]
val nodeIds = Set("someNodeId")
val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor(
val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor(
valueEditor = ValueInputWithDictEditor("someDictId", allowOtherValue = false),
initialValue = None,
refClazz = invalidParameterType,
Expand All @@ -71,7 +71,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers {
val paramName = ParameterName("someParamName")
val invalidParameterType = FragmentClazzRef[java.lang.Double]
val nodeIds = Set("someNodeId")
val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor(
val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor(
valueEditor = ValueInputWithFixedValuesProvided(
List(FixedExpressionValue("someExpression", "someLabel")),
allowOtherValue = false
Expand Down

0 comments on commit a2ea8eb

Please sign in to comment.