diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala index a0cc5551c2d..b248babe4be 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala @@ -351,6 +351,10 @@ object ProcessCompilationError { extends DuplicateFragmentOutputNames with ScenarioGraphLevelError + final case class EmptyMandatoryField(nodeId: String, qualifiedFieldName: ParameterName) + extends PartSubGraphCompilationError + with InASingleNode + final case class DictNotDeclared(dictId: String, nodeId: String, paramName: ParameterName) extends PartSubGraphCompilationError with InASingleNode diff --git a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala index f312d917bf4..083e7a71c96 100644 --- a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala +++ b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala @@ -211,6 +211,12 @@ object PrettyValidationErrors { paramName = Some(qualifiedParamFieldName(paramName = paramName, subFieldName = Some(ValidationExpressionFieldName))) ) + case EmptyMandatoryField(_, qualifiedFieldName) => + node( + message = s"This field is mandatory and cannot be empty", + description = s"This field is mandatory and cannot be empty", + paramName = Some(qualifiedFieldName) + ) case DictNotDeclared(dictId, _, paramName) => node( message = s"Dictionary not declared: $dictId", diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/validation/UIProcessValidatorSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/validation/UIProcessValidatorSpec.scala index c62fe7d3af7..67382902532 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/validation/UIProcessValidatorSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/validation/UIProcessValidatorSpec.scala @@ -612,7 +612,20 @@ class UIProcessValidatorSpec extends AnyFunSuite with Matchers with TableDrivenP valueCompileTimeValidation = None ), FragmentParameter( - ParameterName("subParam3_valid"), + ParameterName("subParam3"), + FragmentClazzRef[java.lang.String], + initialValue = None, + hintText = None, + valueEditor = Some( + ValueInputWithDictEditor( + dictId = "", + allowOtherValue = false + ) + ), + valueCompileTimeValidation = None + ), + FragmentParameter( + ParameterName("subParam4_valid"), FragmentClazzRef[java.lang.String], initialValue = Some(FixedExpressionValue("""{"key":"some string key","label":"some label"}""", "some label")), @@ -660,6 +673,14 @@ class UIProcessValidatorSpec extends AnyFunSuite with Matchers with TableDrivenP Some("$param.subParam2.$dictId"), NodeValidationErrorType.SaveAllowed, None + ), + NodeValidationError( + "EmptyMandatoryField", + "This field is mandatory and cannot be empty", + _, + Some("$param.subParam3.$dictId"), + NodeValidationErrorType.SaveAllowed, + None ) ) => } diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala index 871456f290b..3dc54a69668 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala @@ -138,37 +138,54 @@ object FragmentParameterValidator { )(implicit nodeId: NodeId): Validated[NonEmptyList[PartSubGraphCompilationError], Unit] = fragmentParameter.valueEditor match { case Some(ValueInputWithDictEditor(dictId, _)) => - dictionaries.get(dictId) match { - case Some(dictDefinition) => - val fragmentParameterTypingResult = fragmentParameter.typ - .toRuntimeClass(classLoader) - .map(Typed(_)) - .getOrElse(Unknown) + 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) + val dictValueType = dictDefinition.valueType(dictId) - if (dictValueType.canBeSubclassOf(fragmentParameterTypingResult)) { - Valid(()) - } else { + if (dictValueType.canBeSubclassOf(fragmentParameterTypingResult)) { + Valid(()) + } else { + invalidNel( + DictIsOfInvalidType( + dictId, + dictValueType, + fragmentParameterTypingResult, + nodeId.id, + qualifiedParamFieldName(fragmentParameter.name, Some(DictIdFieldName)) + ) + ) + } + case None => invalidNel( - DictIsOfInvalidType( + DictNotDeclared( 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] = {