All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator.scala Maven / Gradle / Ivy

The newest version!
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 pl.touk.nussknacker.engine.api.NodeId
import pl.touk.nussknacker.engine.api.context.ProcessCompilationError._
import pl.touk.nussknacker.engine.api.context.{PartSubGraphCompilationError, ProcessCompilationError, ValidationContext}
import pl.touk.nussknacker.engine.api.definition._
import pl.touk.nussknacker.engine.api.parameter.ParameterName
import pl.touk.nussknacker.engine.api.dict.DictDefinition
import pl.touk.nussknacker.engine.api.parameter.{
  ParameterValueInput,
  ValueInputWithDictEditor,
  ValueInputWithFixedValuesProvided
}
import pl.touk.nussknacker.engine.api.typed.typing.{Typed, Unknown}
import pl.touk.nussknacker.engine.api.validation.Validations.validateVariableName
import pl.touk.nussknacker.engine.compile.ExpressionCompiler
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}
import pl.touk.nussknacker.engine.graph.node.{
  DictIdFieldName,
  FixedValuesListFieldName,
  InitialValueFieldName,
  ParameterNameFieldName,
  qualifiedParamFieldName
}
import pl.touk.nussknacker.engine.language.dictWithLabel.DictKeyWithLabelExpressionParser

object FragmentParameterValidator {

  val permittedTypesForEditors: List[FragmentClazzRef] = List(
    FragmentClazzRef[java.lang.Boolean],
    FragmentClazzRef[String],
    FragmentClazzRef[java.lang.Long]
  )

  // This method doesn't fully validate valueEditor (see ValueEditorValidator.validateAndGetEditor comments)
  def validateAgainstClazzRefAndGetEditor(
      valueEditor: ParameterValueInput,
      initialValue: Option[FixedExpressionValue],
      refClazz: FragmentClazzRef,
      paramName: ParameterName,
      nodeIds: Set[String]
  ): ValidatedNel[PartSubGraphCompilationError, ParameterEditor] = {
    validateValueEditorSupportedType(valueEditor, refClazz, paramName, nodeIds)
      .andThen(_ =>
        ValueEditorValidator.validateAndGetEditor(
          valueEditor,
          initialValue,
          paramName,
          nodeIds
        )
      )
  }

  private def validateValueEditorSupportedType(
      valueEditor: ParameterValueInput,
      refClazz: FragmentClazzRef,
      paramName: ParameterName,
      nodeIds: Set[String]
  ): ValidatedNel[PartSubGraphCompilationError, Unit] = {
    if (permittedTypesForEditors.contains(refClazz))
      Valid(())
    else
      valueEditor match {
        case ValueInputWithFixedValuesProvided(_, _) =>
          invalidNel(UnsupportedFixedValuesType(paramName, refClazz.refClazzName, nodeIds))
        case ValueInputWithDictEditor(_, _) =>
          invalidNel(UnsupportedDictParameterEditorType(paramName, refClazz.refClazzName, nodeIds))
      }
  }

  def validateFixedExpressionValues(
      fragmentParameter: FragmentParameter,
      validationContext: ValidationContext, // localVariables must include this and other FragmentParameters
      expressionCompiler: ExpressionCompiler
  )(implicit nodeId: NodeId): Validated[NonEmptyList[PartSubGraphCompilationError], Unit] = {
    def fixedExpressionsCompilationErrors(
        fixedExpressions: Iterable[FixedExpressionValue],
        subFieldName: Option[String],
    ) = {
      fixedExpressions
        .map { fixedExpressionValue =>
          val expr = fragmentParameter.valueEditor match {
            case Some(ValueInputWithDictEditor(_, _)) =>
              if (fixedExpressionValue.expression.isBlank)
                valid(Expression(Language.DictKeyWithLabel, ""))
              else
                DictKeyWithLabelExpressionParser
                  .parseDictKeyWithLabelExpression(fixedExpressionValue.expression)
                  .leftMap(errs => errs.map(_.toProcessCompilationError(nodeId.id, fragmentParameter.name)))
                  .andThen(e => valid(Expression.dictKeyWithLabel(e.key, e.label)))
            case _ => valid(Expression.spel(fixedExpressionValue.expression))
          }

          expr.andThen(e =>
            expressionCompiler.compile(
              e,
              paramName = Some(fragmentParameter.name),
              validationCtx = validationContext,
              expectedType = validationContext(fragmentParameter.name.value),
            )
          )
        }
        .toList
        .sequence
        .leftMap(_.map {
          case e: ExpressionParserCompilationError =>
            ExpressionParserCompilationErrorInFragmentDefinition(
              message = e.message,
              nodeId = nodeId.id,
              paramName = fragmentParameter.name,
              subFieldName = subFieldName,
              originalExpr = e.originalExpr
            )
          case e => e
        })
    }

    val fixedValuesList = fragmentParameter.valueEditor match {
      case Some(ValueInputWithFixedValuesProvided(fixedValuesList, _)) => fixedValuesList
      case _                                                           => List.empty
    }

    List(
      fixedExpressionsCompilationErrors(
        fragmentParameter.initialValue,
        Some(InitialValueFieldName)
      ),
      fixedExpressionsCompilationErrors(
        fixedValuesList,
        Some(FixedValuesListFieldName)
      )
    ).sequence.map(_ => ())
  }

  def validateValueInputWithDictEditor(
      fragmentParameter: FragmentParameter,
      dictionaries: Map[String, DictDefinition],
      classLoader: ClassLoader
  )(implicit nodeId: NodeId): Validated[NonEmptyList[PartSubGraphCompilationError], Unit] =
    fragmentParameter.valueEditor match {
      case Some(ValueInputWithDictEditor(dictId, _)) =>
        validateNonEmptyDictId(dictId, fragmentParameter.name).andThen(_ =>
          dictionaries.get(dictId) match {
            case Some(dictDefinition) =>
              val fragmentParameterTypingResult = fragmentParameter.typ
                .toRuntimeClass(classLoader)
                .map(Typed(_))
                .getOrElse(Unknown)

              val dictValueType = dictDefinition.valueType(dictId)

              if (dictValueType.canBeSubclassOf(fragmentParameterTypingResult)) {
                Valid(())
              } else {
                invalidNel(
                  DictIsOfInvalidType(
                    dictId,
                    dictValueType,
                    fragmentParameterTypingResult,
                    nodeId.id,
                    qualifiedParamFieldName(fragmentParameter.name, Some(DictIdFieldName))
                  )
                )
              }
            case None =>
              invalidNel(
                DictNotDeclared(
                  dictId,
                  nodeId.id,
                  qualifiedParamFieldName(fragmentParameter.name, Some(DictIdFieldName))
                )
              )
          }
        )

      case _ => Valid(())
    }

  private def validateNonEmptyDictId(dictId: String, parameterName: ParameterName)(implicit nodeId: NodeId) =
    if (dictId.isBlank)
      invalidNel(
        EmptyMandatoryField(
          nodeId.id,
          qualifiedParamFieldName(parameterName, Some(DictIdFieldName))
        )
      )
    else
      valid(())

  def validateParameterNames(
      parameters: List[Parameter]
  )(implicit nodeId: NodeId): ValidatedNel[ProcessCompilationError, Unit] = {
    parameters
      .map(_.name)
      .groupBy(identity)
      .foldLeft(valid(()): ValidatedNel[ProcessCompilationError, Unit]) { case (acc, (paramName, group)) =>
        val duplicationError = if (group.size > 1) {
          invalid(DuplicateFragmentInputParameter(paramName, nodeId.toString)).toValidatedNel
        } else valid(())
        val validIdentifierError = validateVariableName(
          paramName.value,
          Some(qualifiedParamFieldName(paramName, Some(ParameterNameFieldName)))
        ).map(_ => ())
        acc.combine(duplicationError).combine(validIdentifierError)
      }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy