diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ConversionDeterminer.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ConversionDeterminer.scala new file mode 100644 index 00000000000..b998edab8c3 --- /dev/null +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ConversionDeterminer.scala @@ -0,0 +1,83 @@ +package pl.touk.nussknacker.engine.api.typed + +import cats.data.Validated.condNel +import cats.data.{NonEmptyList, Validated, ValidatedNel} +import cats.implicits.catsSyntaxValidatedId +import org.apache.commons.lang3.ClassUtils +import pl.touk.nussknacker.engine.api.typed.supertype.NumberTypesPromotionStrategy.AllNumbers +import pl.touk.nussknacker.engine.api.typed.typing.{ + SingleTypingResult, + TypedClass, + TypedNull, + TypedObjectWithValue, + TypedUnion, + TypingResult, + Unknown +} + +trait ConversionDeterminer { + + def singleCanBeConvertedTo( + result: typing.SingleTypingResult, + result1: typing.SingleTypingResult + ): ValidatedNel[String, Unit] + + /** + * This method checks if `givenType` can by subclass of `superclassCandidate` + * It will return true if `givenType` is equals to `superclassCandidate` or `givenType` "extends" `superclassCandidate` + */ + def canBeConvertedTo(givenType: TypingResult, superclassCandidate: TypingResult): ValidatedNel[String, Unit] = { + (givenType, superclassCandidate) match { + case (_, Unknown) => ().validNel + case (Unknown, _) => ().validNel + case (TypedNull, other) => canNullBeConvertedTo(other) + case (_, TypedNull) => s"No type can be subclass of ${TypedNull.display}".invalidNel + case (given: SingleTypingResult, superclass: TypedUnion) => + canBeConvertedTo(NonEmptyList.one(given), superclass.possibleTypes) + case (given: TypedUnion, superclass: SingleTypingResult) => + canBeConvertedTo(given.possibleTypes, NonEmptyList.one(superclass)) + case (given: SingleTypingResult, superclass: SingleTypingResult) => singleCanBeConvertedTo(given, superclass) + case (given: TypedUnion, superclass: TypedUnion) => + canBeConvertedTo(given.possibleTypes, superclass.possibleTypes) + } + } + + private def canNullBeConvertedTo(result: TypingResult): ValidatedNel[String, Unit] = result match { + // TODO: Null should not be subclass of typed map that has all values assigned. + case TypedObjectWithValue(_, _) => s"${TypedNull.display} cannot be subclass of type with value".invalidNel + case _ => ().validNel + } + + def canBeConvertedTo( + givenTypes: NonEmptyList[SingleTypingResult], + superclassCandidates: NonEmptyList[SingleTypingResult] + ): ValidatedNel[String, Unit] = { + // Would be more safety to do givenTypes.forAll(... superclassCandidates.exists ...) - we wil protect against + // e.g. (String | Int).canBeSubclassOf(String) which can fail in runtime for Int, but on the other hand we can't block user's intended action. + // He/she could be sure that in this type, only String will appear. He/she also can't easily downcast (String | Int) to String so leaving here + // "double exists" looks like a good tradeoff + condNel( + givenTypes.exists(given => superclassCandidates.exists(singleCanBeConvertedTo(given, _).isValid)), + (), + s"""None of the following types: + |${givenTypes.map(" - " + _.display).toList.mkString(",\n")} + |can be a subclass of any of: + |${superclassCandidates.map(" - " + _.display).toList.mkString(",\n")}""".stripMargin + ) + } + + def isStrictSubclass(givenClass: TypedClass, givenSuperclass: TypedClass): Validated[NonEmptyList[String], Unit] = { + condNel( + givenClass == givenSuperclass, + (), + f"${givenClass.display} and ${givenSuperclass.display} are not the same" + ) orElse + condNel( + isAssignable(givenClass.klass, givenSuperclass.klass), + (), + s"${givenClass.klass} is not assignable from ${givenSuperclass.klass}" + ) + } + + def isAssignable(from: Class[_], to: Class[_]): Boolean +} diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/CanBeSubclassDeterminer.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ImplicitConversionDeterminer.scala similarity index 62% rename from components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/CanBeSubclassDeterminer.scala rename to components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ImplicitConversionDeterminer.scala index 9545ab30dd6..f980620ae77 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/CanBeSubclassDeterminer.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ImplicitConversionDeterminer.scala @@ -1,7 +1,7 @@ package pl.touk.nussknacker.engine.api.typed import cats.data.Validated._ -import cats.data.{NonEmptyList, ValidatedNel} +import cats.data.ValidatedNel import cats.implicits.{catsSyntaxValidatedId, _} import org.apache.commons.lang3.ClassUtils import pl.touk.nussknacker.engine.api.typed.typing._ @@ -14,42 +14,17 @@ import pl.touk.nussknacker.engine.api.typed.typing._ * conversion for types not in the same jvm class hierarchy like boxed Integer to boxed Long and so on". * WARNING: Evaluation of SpEL expressions fit into this spirit, for other language evaluation engines you need to provide such a compatibility. */ -trait CanBeSubclassDeterminer { +object ImplicitConversionDeterminer extends ConversionDeterminer { private val javaMapClass = classOf[java.util.Map[_, _]] private val javaListClass = classOf[java.util.List[_]] private val arrayOfAnyRefClass = classOf[Array[AnyRef]] - /** - * This method checks if `givenType` can by subclass of `superclassCandidate` - * It will return true if `givenType` is equals to `superclassCandidate` or `givenType` "extends" `superclassCandidate` - */ - def canBeSubclassOf(givenType: TypingResult, superclassCandidate: TypingResult): ValidatedNel[String, Unit] = { - (givenType, superclassCandidate) match { - case (_, Unknown) => ().validNel - case (Unknown, _) => ().validNel - case (TypedNull, other) => canNullBeSubclassOf(other) - case (_, TypedNull) => s"No type can be subclass of ${TypedNull.display}".invalidNel - case (given: SingleTypingResult, superclass: TypedUnion) => - canBeSubclassOf(NonEmptyList.one(given), superclass.possibleTypes) - case (given: TypedUnion, superclass: SingleTypingResult) => - canBeSubclassOf(given.possibleTypes, NonEmptyList.one(superclass)) - case (given: SingleTypingResult, superclass: SingleTypingResult) => singleCanBeSubclassOf(given, superclass) - case (given: TypedUnion, superclass: TypedUnion) => canBeSubclassOf(given.possibleTypes, superclass.possibleTypes) - } - } - - private def canNullBeSubclassOf(result: TypingResult): ValidatedNel[String, Unit] = result match { - // TODO: Null should not be subclass of typed map that has all values assigned. - case TypedObjectWithValue(_, _) => s"${TypedNull.display} cannot be subclass of type with value".invalidNel - case _ => ().validNel - } - - protected def singleCanBeSubclassOf( + def singleCanBeConvertedTo( givenType: SingleTypingResult, superclassCandidate: SingleTypingResult ): ValidatedNel[String, Unit] = { - val objTypeRestriction = classCanBeSubclassOf(givenType, superclassCandidate.runtimeObjType) + val objTypeRestriction = classCanBeConvertedTo(givenType, superclassCandidate) val typedObjectRestrictions = (_: Unit) => superclassCandidate match { case superclass: TypedObjectTypingResult => @@ -65,7 +40,7 @@ trait CanBeSubclassDeterminer { s"Field '$name' is lacking".invalidNel case Some(givenFieldType) => condNel( - canBeSubclassOf(givenFieldType, typ).isValid, + canBeConvertedTo(givenFieldType, typ).isValid, (), s"Field '$name' is of the wrong type. Expected: ${givenFieldType.display}, actual: ${typ.display}" ) @@ -123,29 +98,32 @@ trait CanBeSubclassDeterminer { (typedObjectRestrictions combine dictRestriction combine taggedValueRestriction combine dataValueRestriction) } - protected def classCanBeSubclassOf( + private def classCanBeConvertedTo( givenType: SingleTypingResult, - superclassCandidate: TypedClass + superclassCandidate: SingleTypingResult ): ValidatedNel[String, Unit] = { - val givenClass = givenType.runtimeObjType + val givenClass = givenType.runtimeObjType + val givenSuperclass = superclassCandidate.runtimeObjType - val equalClassesOrCanAssign = - condNel( - givenClass == superclassCandidate, - (), - f"${givenClass.display} and ${superclassCandidate.display} are not the same" - ) orElse - isAssignable(givenClass.klass, superclassCandidate.klass) + val equalClassesOrCanAssign = isStrictSubclass(givenClass, givenSuperclass) + val canBeSubclass = equalClassesOrCanAssign andThen (_ => typeParametersMatches(givenClass, givenSuperclass)) + canBeSubclass orElse canBeConvertedTo(givenType, givenSuperclass) + } - val canBeSubclass = equalClassesOrCanAssign andThen (_ => typeParametersMatches(givenClass, superclassCandidate)) - canBeSubclass orElse canBeConvertedTo(givenType, superclassCandidate) + // TODO: Conversions should be checked during typing, not during generic usage of TypingResult.canBeSubclassOf(...) + def canBeConvertedTo( + givenType: SingleTypingResult, + superclassCandidate: TypedClass + ): ValidatedNel[String, Unit] = { + val errMsgPrefix = s"${givenType.runtimeObjType.display} cannot be converted to ${superclassCandidate.display}" + condNel(TypeConversionHandler.canBeConvertedTo(givenType, superclassCandidate), (), errMsgPrefix) } private def typeParametersMatches(givenClass: TypedClass, superclassCandidate: TypedClass) = { def canBeSubOrSuperclass(givenClassParam: TypingResult, superclassParam: TypingResult) = condNel( - canBeSubclassOf(givenClassParam, superclassParam).isValid || - canBeSubclassOf(superclassParam, givenClassParam).isValid, + canBeConvertedTo(givenClassParam, superclassParam).isValid || + canBeConvertedTo(superclassParam, givenClassParam).isValid, (), f"None of ${givenClassParam.display} and ${superclassParam.display} is a subclass of another" ) @@ -154,18 +132,18 @@ trait CanBeSubclassDeterminer { case (TypedClass(_, givenElementParam :: Nil), TypedClass(superclass, superclassParam :: Nil)) // Array are invariant but we have built-in conversion between array types - this check should be moved outside this class when we move away canBeConvertedTo as well if javaListClass.isAssignableFrom(superclass) || arrayOfAnyRefClass.isAssignableFrom(superclass) => - canBeSubclassOf(givenElementParam, superclassParam) + canBeConvertedTo(givenElementParam, superclassParam) case ( TypedClass(_, givenKeyParam :: givenValueParam :: Nil), TypedClass(superclass, superclassKeyParam :: superclassValueParam :: Nil) ) if javaMapClass.isAssignableFrom(superclass) => // Map's key generic param is invariant. We can't just check givenKeyParam == superclassKeyParam because of Unknown type which is a kind of wildcard condNel( - canBeSubclassOf(givenKeyParam, superclassKeyParam).isValid && - canBeSubclassOf(superclassKeyParam, givenKeyParam).isValid, + canBeConvertedTo(givenKeyParam, superclassKeyParam).isValid && + canBeConvertedTo(superclassKeyParam, givenKeyParam).isValid, (), s"Key types of Maps ${givenKeyParam.display} and ${superclassKeyParam.display} are not equals" - ) andThen (_ => canBeSubclassOf(givenValueParam, superclassValueParam)) + ) andThen (_ => canBeConvertedTo(givenValueParam, superclassValueParam)) case _ => // for unknown types we are lax - the generic type may be co- contra- or in-variant - and we don't want to // return validation errors in this case. It's better to accept to much than too little @@ -179,36 +157,8 @@ trait CanBeSubclassDeterminer { } } - private def canBeSubclassOf( - givenTypes: NonEmptyList[SingleTypingResult], - superclassCandidates: NonEmptyList[SingleTypingResult] - ): ValidatedNel[String, Unit] = { - // Would be more safety to do givenTypes.forAll(... superclassCandidates.exists ...) - we wil protect against - // e.g. (String | Int).canBeSubclassOf(String) which can fail in runtime for Int, but on the other hand we can't block user's intended action. - // He/she could be sure that in this type, only String will appear. He/she also can't easily downcast (String | Int) to String so leaving here - // "double exists" looks like a good tradeoff - condNel( - givenTypes.exists(given => superclassCandidates.exists(singleCanBeSubclassOf(given, _).isValid)), - (), - s"""None of the following types: - |${givenTypes.map(" - " + _.display).toList.mkString(",\n")} - |can be a subclass of any of: - |${superclassCandidates.map(" - " + _.display).toList.mkString(",\n")}""".stripMargin - ) - } - - // TODO: Conversions should be checked during typing, not during generic usage of TypingResult.canBeSubclassOf(...) - private def canBeConvertedTo( - givenType: SingleTypingResult, - superclassCandidate: TypedClass - ): ValidatedNel[String, Unit] = { - val errMsgPrefix = s"${givenType.runtimeObjType.display} cannot be converted to ${superclassCandidate.display}" - condNel(TypeConversionHandler.canBeConvertedTo(givenType, superclassCandidate), (), errMsgPrefix) - } - // we use explicit autoboxing = true flag, as ClassUtils in commons-lang3:3.3 (used in Flink) cannot handle JDK 11... - private def isAssignable(from: Class[_], to: Class[_]): ValidatedNel[String, Unit] = - condNel(ClassUtils.isAssignable(from, to, true), (), s"$to is not assignable from $from") -} + override def isAssignable(from: Class[_], to: Class[_]): Boolean = + ClassUtils.isAssignable(from, to, true) -object CanBeSubclassDeterminer extends CanBeSubclassDeterminer +} diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala index fa37bc54a40..a9cb2846918 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala @@ -15,9 +15,9 @@ object NumberTypeUtils { else if (typ == Typed[java.lang.Double]) java.lang.Double.valueOf(0) else if (typ == Typed[java.math.BigDecimal]) java.math.BigDecimal.ZERO // in case of some unions - else if (typ.canBeSubclassOf(Typed[java.lang.Integer])) java.lang.Integer.valueOf(0) + else if (typ.canBeImplicitlyConvertedTo(Typed[java.lang.Integer])) java.lang.Integer.valueOf(0) // double is quite safe - it can be converted to any Number - else if (typ.canBeSubclassOf(Typed[Number])) java.lang.Double.valueOf(0) + else if (typ.canBeImplicitlyConvertedTo(Typed[Number])) java.lang.Double.valueOf(0) else throw new IllegalArgumentException(s"Not expected type: ${typ.display}, should be Number") } diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/SubclassDeterminer.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/SubclassDeterminer.scala new file mode 100644 index 00000000000..4fcf9a0aa01 --- /dev/null +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/SubclassDeterminer.scala @@ -0,0 +1,35 @@ +package pl.touk.nussknacker.engine.api.typed + +import cats.data.Validated.condNel +import cats.data.ValidatedNel +import org.apache.commons.lang3.ClassUtils +import pl.touk.nussknacker.engine.api.typed.supertype.NumberTypesPromotionStrategy.AllNumbers +import pl.touk.nussknacker.engine.api.typed.typing.{SingleTypingResult, TypedClass, TypingResult} + +object SubclassDeterminer extends ConversionDeterminer { + + def singleCanBeConvertedTo( + givenType: SingleTypingResult, + superclassCandidate: SingleTypingResult + ): ValidatedNel[String, Unit] = { + val givenClass = givenType.runtimeObjType + val givenSuperclas = superclassCandidate.runtimeObjType + + isStrictSubclass(givenClass, givenSuperclas) + } + + def canBeStrictSubclassOf(givenType: TypingResult, superclassCandidate: TypingResult): ValidatedNel[String, Unit] = { + this.canBeConvertedTo(givenType, superclassCandidate) + } + + override def isAssignable(from: Class[_], to: Class[_]): Boolean = { + (from, to) match { + case (f, t) if ClassUtils.isAssignable(f, t, true) => true + // Number double check by hand because lang3 can incorrectly throw false when dealing with java types + case (f, t) if AllNumbers.contains(f) && AllNumbers.contains(t) => + AllNumbers.indexOf(f) >= AllNumbers.indexOf(t) + case _ => false + } + } + +} diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala index cc91f255239..8312ff4ee15 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala @@ -20,8 +20,8 @@ object TypeConversionHandler { /** * java.math.BigDecimal is quite often returned as a wrapper for all kind of numbers (floating and without floating point). - * Given to this we cannot to be sure if conversion is safe or not based on type (without scale knowledge). - * So we have two options: enforce user to convert to some type without floating point (e.g. BigInteger) or be loose in this point. + * Given to this we cannot be sure if conversion is safe or not based on type (without scale knowledge). + * So we have two options: force user to convert to some type without floating point (e.g. BigInteger) or be loose in this point. * Be default we will be loose. */ // TODO: Add feature flag: strictBigDecimalChecking (default false?) @@ -68,6 +68,11 @@ object TypeConversionHandler { handleStringToValueClassConversions(givenType, superclassCandidate) } + def canBeStrictlyConvertedTo(givenType: SingleTypingResult, superclassCandidate: TypedClass): Boolean = { + handleStrictNumberConversions(givenType.runtimeObjType, superclassCandidate) || + handleStringToValueClassConversions(givenType, superclassCandidate) + } + // See org.springframework.core.convert.support.NumberToNumberConverterFactory private def handleNumberConversions(givenClass: TypedClass, superclassCandidate: TypedClass): Boolean = { val boxedGivenClass = ClassUtils.primitiveToWrapper(givenClass.klass) @@ -84,6 +89,32 @@ object TypeConversionHandler { } } + // See org.springframework.core.convert.support.NumberToNumberConverterFactory + private def handleStrictNumberConversions(givenClass: TypedClass, superclassCandidate: TypedClass): Boolean = { + val boxedGivenClass = ClassUtils.primitiveToWrapper(givenClass.klass) + val boxedSuperclassCandidate = ClassUtils.primitiveToWrapper(superclassCandidate.klass) + // We can't check precision here so we need to be loose here + // TODO: Add feature flag: strictNumberPrecisionChecking (default false?) + + def isFloating(candidate: Class[_]): Boolean = { + NumberTypesPromotionStrategy.isFloatingNumber(candidate) || candidate == classOf[java.math.BigDecimal] + } + def isDecimalNumber(candidate: Class[_]): Boolean = { + NumberTypesPromotionStrategy.isDecimalNumber(candidate) + } + + boxedSuperclassCandidate match { + case candidate if isFloating(candidate) => + ClassUtils.isAssignable(boxedGivenClass, classOf[Number], true) + + case candidate if isDecimalNumber(candidate) => + SubclassDeterminer.isAssignable(boxedGivenClass, candidate) + + case _ => false + } + + } + private def handleStringToValueClassConversions( givenType: SingleTypingResult, superclassCandidate: TypedClass diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala index ca5cbde9597..c2be6cdcf72 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala @@ -27,15 +27,11 @@ object typing { // TODO: Rename to Typed, maybe NuType? sealed trait TypingResult { - // TODO: We should split this method into two or three methods: - // - Simple, strictly checking subclassing similar to isAssignable, where we don't do heuristics like - // Any can be subclass of Int, or for Union of Int and String can be subclass of Int - // - The one with heuristics considering limitations of our tool like poor support for generics, lack - // of casting allowing things described above - // - The one that allow things above + SPeL conversions like any Number to any Number conversion, - // String to LocalDate etc. This one should be accessible only for context where SPeL is used - final def canBeSubclassOf(typingResult: TypingResult): Boolean = - CanBeSubclassDeterminer.canBeSubclassOf(this, typingResult).isValid + final def canBeImplicitlyConvertedTo(typingResult: TypingResult): Boolean = + ImplicitConversionDeterminer.canBeConvertedTo(this, typingResult).isValid + + def canBeStrictSubclassOf(typingResult: TypingResult): Boolean = + SubclassDeterminer.canBeStrictSubclassOf(this, typingResult).isValid def valueOpt: Option[Any] @@ -466,7 +462,9 @@ object typing { case class CastTypedValue[T: TypeTag]() { def unapply(typingResult: TypingResult): Option[TypingResultTypedValue[T]] = { - Option(typingResult).filter(_.canBeSubclassOf(Typed.fromDetailedType[T])).map(new TypingResultTypedValue(_)) + Option(typingResult) + .filter(_.canBeImplicitlyConvertedTo(Typed.fromDetailedType[T])) + .map(new TypingResultTypedValue(_)) } } diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/SubclassDeterminerSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/SubclassDeterminerSpec.scala new file mode 100644 index 00000000000..be7be9e72f7 --- /dev/null +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/SubclassDeterminerSpec.scala @@ -0,0 +1,41 @@ +package pl.touk.nussknacker.engine.api.typed + +import org.apache.commons.lang3.ClassUtils +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers + +class SubclassDeterminerSpec extends AnyFunSuite with Matchers { + + test("Should validate assignability for decimal types") { + SubclassDeterminer.isAssignable(classOf[java.lang.Long], classOf[java.lang.Integer]) shouldBe false + SubclassDeterminer.isAssignable(classOf[Number], classOf[Integer]) shouldBe false + SubclassDeterminer.isAssignable(classOf[Integer], classOf[java.lang.Short]) shouldBe false + + SubclassDeterminer.isAssignable(classOf[Integer], classOf[java.lang.Long]) shouldBe true + SubclassDeterminer.isAssignable(classOf[Integer], classOf[Number]) shouldBe true + SubclassDeterminer.isAssignable(classOf[java.lang.Short], classOf[Integer]) shouldBe true + } + + test("Should validate assignability for numerical types") { + SubclassDeterminer.isAssignable(classOf[java.lang.Long], classOf[java.lang.Double]) shouldBe true + SubclassDeterminer.isAssignable(classOf[java.lang.Float], classOf[Double]) shouldBe true + + SubclassDeterminer.isAssignable(classOf[Integer], classOf[java.lang.Float]) shouldBe true + SubclassDeterminer.isAssignable(classOf[java.lang.Long], classOf[java.lang.Double]) shouldBe true + } + + // to check if autoboxing lang3 is failing - we can remove our fallback from SubclassDeterminer.isAssignable if the lib works properly + test("Should check if lang3 fails for certain isAssignable cases") { + ClassUtils.isAssignable( + classOf[Integer], + classOf[java.lang.Long], + true + ) shouldBe false // should be true in reality, but currently the lib is bugged + ClassUtils.isAssignable( + classOf[java.lang.Short], + classOf[Integer], + true + ) shouldBe false // should be true in reality, but currently the lib is bugged + } + +} diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala index 6e8e4ce6708..f3e50d76e35 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala @@ -60,20 +60,20 @@ class TypedFromInstanceTest extends AnyFunSuite with Matchers with LoneElement w } test("should type empty list") { - Typed.fromInstance(Nil).canBeSubclassOf(Typed(classOf[List[_]])) shouldBe true - Typed.fromInstance(Nil.asJava).canBeSubclassOf(Typed(classOf[java.util.List[_]])) shouldBe true + Typed.fromInstance(Nil).canBeImplicitlyConvertedTo(Typed(classOf[List[_]])) shouldBe true + Typed.fromInstance(Nil.asJava).canBeImplicitlyConvertedTo(Typed(classOf[java.util.List[_]])) shouldBe true } test("should type lists and return union of types coming from all elements") { def checkTypingResult(obj: Any, klass: Class[_], paramTypingResult: TypingResult): Unit = { val typingResult = Typed.fromInstance(obj) - typingResult.canBeSubclassOf(Typed(klass)) shouldBe true + typingResult.canBeImplicitlyConvertedTo(Typed(klass)) shouldBe true typingResult.withoutValue .asInstanceOf[TypedClass] .params .loneElement - .canBeSubclassOf(paramTypingResult) shouldBe true + .canBeImplicitlyConvertedTo(paramTypingResult) shouldBe true } def checkNotASubclassOfOtherParamTypingResult(obj: Any, otherParamTypingResult: TypingResult): Unit = { @@ -82,7 +82,7 @@ class TypedFromInstanceTest extends AnyFunSuite with Matchers with LoneElement w .asInstanceOf[TypedClass] .params .loneElement - .canBeSubclassOf(otherParamTypingResult) shouldBe false + .canBeImplicitlyConvertedTo(otherParamTypingResult) shouldBe false } val listOfSimpleObjects = List[Any](1.1, 2) diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultErrorMessagesSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultErrorMessagesSpec.scala index 5483df1a9a8..e55fda73496 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultErrorMessagesSpec.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultErrorMessagesSpec.scala @@ -13,11 +13,11 @@ class TypingResultErrorMessagesSpec extends AnyFunSuite with Matchers with Optio private def list(arg: TypingResult) = Typed.genericTypeClass[java.util.List[_]](List(arg)) - import CanBeSubclassDeterminer.canBeSubclassOf + import ImplicitConversionDeterminer.canBeConvertedTo test("determine if can be subclass for typed object") { - canBeSubclassOf( + canBeConvertedTo( typeMap( "field1" -> Typed[String], "field2" -> Typed[Int], @@ -38,7 +38,7 @@ class TypingResultErrorMessagesSpec extends AnyFunSuite with Matchers with Optio ) .invalid - canBeSubclassOf( + canBeConvertedTo( typeMap("field1" -> list(typeMap("field2a" -> Typed[String], "field3" -> Typed[Int]))), typeMap("field1" -> list(typeMap("field2" -> Typed[String]))) ) shouldBe NonEmptyList @@ -49,30 +49,30 @@ class TypingResultErrorMessagesSpec extends AnyFunSuite with Matchers with Optio } test("determine if can be subclass for class") { - canBeSubclassOf(Typed.fromDetailedType[Set[BigDecimal]], Typed.fromDetailedType[Set[String]]) shouldBe + canBeConvertedTo(Typed.fromDetailedType[Set[BigDecimal]], Typed.fromDetailedType[Set[String]]) shouldBe "Set[BigDecimal] cannot be converted to Set[String]".invalidNel } test("determine if can be subclass for tagged value") { - canBeSubclassOf( + canBeConvertedTo( Typed.tagged(Typed.typedClass[String], "tag1"), Typed.tagged(Typed.typedClass[String], "tag2") ) shouldBe "Tagged values have unequal tags: tag1 and tag2".invalidNel - canBeSubclassOf(Typed.typedClass[String], Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe + canBeConvertedTo(Typed.typedClass[String], Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe "The type is not a tagged value".invalidNel } test("determine if can be subclass for object with value") { - canBeSubclassOf(Typed.fromInstance(2), Typed.fromInstance(3)) shouldBe + canBeConvertedTo(Typed.fromInstance(2), Typed.fromInstance(3)) shouldBe "Types with value have different values: 2 and 3".invalidNel } test("determine if can be subclass for null") { - canBeSubclassOf(Typed[String], TypedNull) shouldBe + canBeConvertedTo(Typed[String], TypedNull) shouldBe "No type can be subclass of Null".invalidNel - canBeSubclassOf(TypedNull, Typed.fromInstance(1)) shouldBe + canBeConvertedTo(TypedNull, Typed.fromInstance(1)) shouldBe "Null cannot be subclass of type with value".invalidNel } diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala index ec38ad9cf2a..189698008f7 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala @@ -32,33 +32,33 @@ class TypingResultSpec test("determine if can be subclass for typed object") { - typeMap("field1" -> Typed[String], "field2" -> Typed[Int]).canBeSubclassOf( + typeMap("field1" -> Typed[String], "field2" -> Typed[Int]).canBeImplicitlyConvertedTo( typeMap("field1" -> Typed[String]) ) shouldBe true - typeMap("field1" -> Typed[String]).canBeSubclassOf( + typeMap("field1" -> Typed[String]).canBeImplicitlyConvertedTo( typeMap("field1" -> Typed[String], "field2" -> Typed[Int]) ) shouldBe false - typeMap("field1" -> Typed[Int]).canBeSubclassOf( + typeMap("field1" -> Typed[Int]).canBeImplicitlyConvertedTo( typeMap("field1" -> Typed[String]) ) shouldBe false - typeMap("field1" -> Typed[Int]).canBeSubclassOf( + typeMap("field1" -> Typed[Int]).canBeImplicitlyConvertedTo( typeMap("field1" -> Typed[Number]) ) shouldBe true - typeMap("field1" -> list(typeMap("field2" -> Typed[String], "field3" -> Typed[Int]))).canBeSubclassOf( + typeMap("field1" -> list(typeMap("field2" -> Typed[String], "field3" -> Typed[Int]))).canBeImplicitlyConvertedTo( typeMap("field1" -> list(typeMap("field2" -> Typed[String]))) ) shouldBe true - typeMap("field1" -> list(typeMap("field2a" -> Typed[String], "field3" -> Typed[Int]))).canBeSubclassOf( + typeMap("field1" -> list(typeMap("field2a" -> Typed[String], "field3" -> Typed[Int]))).canBeImplicitlyConvertedTo( typeMap("field1" -> list(typeMap("field2" -> Typed[String]))) ) shouldBe false - typeMap("field1" -> Typed[String]).canBeSubclassOf(Typed[java.util.Map[_, _]]) shouldBe true + typeMap("field1" -> Typed[String]).canBeImplicitlyConvertedTo(Typed[java.util.Map[_, _]]) shouldBe true - Typed[java.util.Map[_, _]].canBeSubclassOf(typeMap("field1" -> Typed[String])) shouldBe false + Typed[java.util.Map[_, _]].canBeImplicitlyConvertedTo(typeMap("field1" -> Typed[String])) shouldBe false } test("extract Unknown value type when no super matching supertype found among all fields of Record") { @@ -76,72 +76,78 @@ class TypingResultSpec } test("determine if can be subclass for typed unions") { - Typed(Typed[String], Typed[Int]).canBeSubclassOf(Typed[Int]) shouldBe true - Typed[Int].canBeSubclassOf(Typed(Typed[String], Typed[Int])) shouldBe true + Typed(Typed[String], Typed[Int]).canBeImplicitlyConvertedTo(Typed[Int]) shouldBe true + Typed[Int].canBeImplicitlyConvertedTo(Typed(Typed[String], Typed[Int])) shouldBe true - Typed(Typed[String], Typed[Int]).canBeSubclassOf(Typed(Typed[Long], Typed[Int])) shouldBe true + Typed(Typed[String], Typed[Int]).canBeImplicitlyConvertedTo(Typed(Typed[Long], Typed[Int])) shouldBe true } test("determine if can be subclass for unknown") { - Unknown.canBeSubclassOf(Typed[Int]) shouldBe true - Typed[Int].canBeSubclassOf(Unknown) shouldBe true + Unknown.canBeImplicitlyConvertedTo(Typed[Int]) shouldBe true + Typed[Int].canBeImplicitlyConvertedTo(Unknown) shouldBe true - Unknown.canBeSubclassOf(Typed(Typed[String], Typed[Int])) shouldBe true - Typed(Typed[String], Typed[Int]).canBeSubclassOf(Unknown) shouldBe true + Unknown.canBeImplicitlyConvertedTo(Typed(Typed[String], Typed[Int])) shouldBe true + Typed(Typed[String], Typed[Int]).canBeImplicitlyConvertedTo(Unknown) shouldBe true - Unknown.canBeSubclassOf(typeMap("field1" -> Typed[String])) shouldBe true - typeMap("field1" -> Typed[String]).canBeSubclassOf(Unknown) shouldBe true + Unknown.canBeImplicitlyConvertedTo(typeMap("field1" -> Typed[String])) shouldBe true + typeMap("field1" -> Typed[String]).canBeImplicitlyConvertedTo(Unknown) shouldBe true } test("determine if can be subclass for class") { Typed .fromDetailedType[java.util.List[BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe true + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe true Typed .fromDetailedType[java.util.List[BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.List[Number]]) shouldBe true + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.List[Number]]) shouldBe true Typed .fromDetailedType[java.util.List[Number]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe false + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, Number]]) shouldBe true + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, Number]]) shouldBe true Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[Number, Number]]) shouldBe false + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[Number, Number]]) shouldBe false Typed .fromDetailedType[java.util.Map[Number, Number]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[Number, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[BigDecimal, Number]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[_, BigDecimal]]) shouldBe true + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[_, BigDecimal]]) shouldBe true Typed .fromDetailedType[java.util.Map[_, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true // For arrays it might be tricky - Typed.fromDetailedType[Array[BigDecimal]].canBeSubclassOf(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe true - Typed.fromDetailedType[Array[BigDecimal]].canBeSubclassOf(Typed.fromDetailedType[Array[Number]]) shouldBe true - Typed.fromDetailedType[Array[Number]].canBeSubclassOf(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe false + Typed + .fromDetailedType[Array[BigDecimal]] + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe true + Typed + .fromDetailedType[Array[BigDecimal]] + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[Array[Number]]) shouldBe true + Typed + .fromDetailedType[Array[Number]] + .canBeImplicitlyConvertedTo(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe false } test("determine if numbers can be converted") { - Typed[Int].canBeSubclassOf(Typed[Long]) shouldBe true - Typed[Long].canBeSubclassOf(Typed[Int]) shouldBe true - Typed[Long].canBeSubclassOf(Typed[Double]) shouldBe true - Typed[Double].canBeSubclassOf(Typed[Long]) shouldBe false - Typed[java.math.BigDecimal].canBeSubclassOf(Typed[Long]) shouldBe true - Typed[Long].canBeSubclassOf(Typed[java.math.BigDecimal]) shouldBe true + Typed[Int].canBeImplicitlyConvertedTo(Typed[Long]) shouldBe true + Typed[Long].canBeImplicitlyConvertedTo(Typed[Int]) shouldBe true + Typed[Long].canBeImplicitlyConvertedTo(Typed[Double]) shouldBe true + Typed[Double].canBeImplicitlyConvertedTo(Typed[Long]) shouldBe false + Typed[java.math.BigDecimal].canBeImplicitlyConvertedTo(Typed[Long]) shouldBe true + Typed[Long].canBeImplicitlyConvertedTo(Typed[java.math.BigDecimal]) shouldBe true } test("find common supertype for simple types") { @@ -297,22 +303,22 @@ class TypingResultSpec test("determine if can be subclass for tagged value") { Typed .tagged(Typed.typedClass[String], "tag1") - .canBeSubclassOf(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe true + .canBeImplicitlyConvertedTo(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe true Typed .tagged(Typed.typedClass[String], "tag1") - .canBeSubclassOf(Typed.tagged(Typed.typedClass[String], "tag2")) shouldBe false + .canBeImplicitlyConvertedTo(Typed.tagged(Typed.typedClass[String], "tag2")) shouldBe false Typed .tagged(Typed.typedClass[String], "tag1") - .canBeSubclassOf(Typed.tagged(Typed.typedClass[Integer], "tag1")) shouldBe false - Typed.tagged(Typed.typedClass[String], "tag1").canBeSubclassOf(Typed.typedClass[String]) shouldBe true - Typed.typedClass[String].canBeSubclassOf(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe false + .canBeImplicitlyConvertedTo(Typed.tagged(Typed.typedClass[Integer], "tag1")) shouldBe false + Typed.tagged(Typed.typedClass[String], "tag1").canBeImplicitlyConvertedTo(Typed.typedClass[String]) shouldBe true + Typed.typedClass[String].canBeImplicitlyConvertedTo(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe false } test("determine if can be subclass for null") { - TypedNull.canBeSubclassOf(Typed[Int]) shouldBe true - TypedNull.canBeSubclassOf(Typed.fromInstance(4)) shouldBe false - TypedNull.canBeSubclassOf(TypedNull) shouldBe true - Typed[String].canBeSubclassOf(TypedNull) shouldBe false + TypedNull.canBeImplicitlyConvertedTo(Typed[Int]) shouldBe true + TypedNull.canBeImplicitlyConvertedTo(Typed.fromInstance(4)) shouldBe false + TypedNull.canBeImplicitlyConvertedTo(TypedNull) shouldBe true + Typed[String].canBeImplicitlyConvertedTo(TypedNull) shouldBe false } test("should deeply extract typ parameters") { @@ -331,20 +337,20 @@ class TypingResultSpec } test("determine if can be subclass for object with value") { - Typed.fromInstance(45).canBeSubclassOf(Typed.typedClass[Long]) shouldBe true - Typed.fromInstance(29).canBeSubclassOf(Typed.typedClass[String]) shouldBe false - Typed.fromInstance(78).canBeSubclassOf(Typed.fromInstance(78)) shouldBe true - Typed.fromInstance(12).canBeSubclassOf(Typed.fromInstance(15)) shouldBe false - Typed.fromInstance(41).canBeSubclassOf(Typed.fromInstance("t")) shouldBe false - Typed.typedClass[String].canBeSubclassOf(Typed.fromInstance("t")) shouldBe true + Typed.fromInstance(45).canBeImplicitlyConvertedTo(Typed.typedClass[Long]) shouldBe true + Typed.fromInstance(29).canBeImplicitlyConvertedTo(Typed.typedClass[String]) shouldBe false + Typed.fromInstance(78).canBeImplicitlyConvertedTo(Typed.fromInstance(78)) shouldBe true + Typed.fromInstance(12).canBeImplicitlyConvertedTo(Typed.fromInstance(15)) shouldBe false + Typed.fromInstance(41).canBeImplicitlyConvertedTo(Typed.fromInstance("t")) shouldBe false + Typed.typedClass[String].canBeImplicitlyConvertedTo(Typed.fromInstance("t")) shouldBe true } test("determine if can be subclass for object with value - use conversion") { - Typed.fromInstance("2007-12-03").canBeSubclassOf(Typed.typedClass[LocalDate]) shouldBe true - Typed.fromInstance("2007-12-03T10:15:30").canBeSubclassOf(Typed.typedClass[LocalDateTime]) shouldBe true + Typed.fromInstance("2007-12-03").canBeImplicitlyConvertedTo(Typed.typedClass[LocalDate]) shouldBe true + Typed.fromInstance("2007-12-03T10:15:30").canBeImplicitlyConvertedTo(Typed.typedClass[LocalDateTime]) shouldBe true - Typed.fromInstance("2007-12-03-qwerty").canBeSubclassOf(Typed.typedClass[LocalDate]) shouldBe false - Typed.fromInstance("2007-12-03").canBeSubclassOf(Typed.typedClass[Currency]) shouldBe false + Typed.fromInstance("2007-12-03-qwerty").canBeImplicitlyConvertedTo(Typed.typedClass[LocalDate]) shouldBe false + Typed.fromInstance("2007-12-03").canBeImplicitlyConvertedTo(Typed.typedClass[Currency]) shouldBe false } test("determinate if can be superclass for objects with value") { @@ -443,7 +449,7 @@ class TypingResultSpec logger.trace(s"Checking: ${input.display}") withClue(s"Input: ${input.display};") { - input.canBeSubclassOf(input) shouldBe true + input.canBeImplicitlyConvertedTo(input) shouldBe true val superType = CommonSupertypeFinder.Default.commonSupertype(input, input) withClue(s"Supertype: ${superType.display};") { superType shouldEqual input @@ -457,11 +463,11 @@ class TypingResultSpec logger.trace(s"Checking: ${input.display}") withClue(s"Input: ${input.display};") { - input.canBeSubclassOf(input) shouldBe true + input.canBeImplicitlyConvertedTo(input) shouldBe true val superType = CommonSupertypeFinder.Default.commonSupertype(input, input) withClue(s"Supertype: ${superType.display};") { // We generate combinations of types co we can only check if input type is a subclass of super type - input.canBeSubclassOf(superType) + input.canBeImplicitlyConvertedTo(superType) } } } @@ -477,12 +483,12 @@ class TypingResultSpec logger.trace(s"Checking supertype of: ${first.display} and ${second.display}") withClue(s"Input: ${first.display}; ${second.display};") { - first.canBeSubclassOf(first) shouldBe true - second.canBeSubclassOf(second) shouldBe true + first.canBeImplicitlyConvertedTo(first) shouldBe true + second.canBeImplicitlyConvertedTo(second) shouldBe true val superType = CommonSupertypeFinder.Default.commonSupertype(first, second) withClue(s"Supertype: ${superType.display};") { - first.canBeSubclassOf(superType) - second.canBeSubclassOf(superType) + first.canBeImplicitlyConvertedTo(superType) + second.canBeImplicitlyConvertedTo(superType) } } } diff --git a/designer/client/src/http/HttpService.ts b/designer/client/src/http/HttpService.ts index 5d8565f5a6d..a046ba7dfc1 100644 --- a/designer/client/src/http/HttpService.ts +++ b/designer/client/src/http/HttpService.ts @@ -854,13 +854,7 @@ class HttpService { fetchAllProcessDefinitionDataDicts(processingType: ProcessingType, refClazzName: string, type = "TypedClass") { return api .post(`/processDefinitionData/${processingType}/dicts`, { - expectedType: { - value: { - type: type, - refClazzName, - params: [], - }, - }, + expectedType: { type: type, refClazzName, params: [] }, }) .catch((error) => Promise.reject( diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala index 456eebccc9f..d43ad4b89b9 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala @@ -56,13 +56,15 @@ class DictApiHttpService( case Some((_, dictionaries, classLoader)) => val decoder = new TypingResultDecoder(ClassUtils.forName(_, classLoader)).decodeTypingResults - decoder.decodeJson(dictListRequestDto.expectedType.value) match { + decoder.decodeJson(dictListRequestDto.expectedType) match { case Left(failure) => Future.successful(businessError(MalformedTypingResult(failure.getMessage()))) case Right(expectedType) => Future { success( dictionaries - .filter { case (id, definition) => definition.valueType(id).canBeSubclassOf(expectedType) } + .filter { case (id, definition) => + definition.valueType(id).canBeStrictSubclassOf(expectedType) + } .map { case (id, _) => DictDto(id, id) } .toList ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/DictApiEndpoints.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/DictApiEndpoints.scala index cfeed8df730..1a6fd43ae98 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/DictApiEndpoints.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/DictApiEndpoints.scala @@ -16,12 +16,14 @@ import pl.touk.nussknacker.ui.api.description.DictApiEndpoints.DictError.{ import pl.touk.nussknacker.ui.api.description.DictApiEndpoints.Dtos._ import sttp.model.StatusCode.{BadRequest, NotFound, Ok} import sttp.tapir._ -import sttp.tapir.json.circe.jsonBody +import sttp.tapir.json.circe._ +import sttp.tapir.Schema import scala.language.implicitConversions class DictApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpointDefinitions { + lazy val dictionaryEntryQueryEndpoint: SecuredEndpoint[(String, String, String), DictError, List[DictEntry], Any] = baseNuApiEndpoint .summary("Get list of dictionary entries matching the label pattern") @@ -81,13 +83,11 @@ class DictApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoin object DictApiEndpoints { - object Dtos { - @JsonCodec - case class TypingResultInJson(value: Json) + object Dtos { @JsonCodec - case class DictListRequestDto(expectedType: TypingResultInJson) + case class DictListRequestDto(expectedType: Json) @JsonCodec case class DictDto( @@ -95,9 +95,8 @@ object DictApiEndpoints { label: String // TODO: introduce separate labels for dictionaries, currently this is just equal to id ) - implicit lazy val typingResultInJsonSchema: Schema[TypingResultInJson] = TypingDtoSchemas.typingResult.as + implicit lazy val dictListRequestDtoSchema: Schema[DictListRequestDto] = Schema.derived[DictListRequestDto] implicit lazy val dictEntrySchema: Schema[DictEntry] = Schema.derived - implicit lazy val dictListRequestDtoSchema: Schema[DictListRequestDto] = Schema.derived implicit lazy val dictDtoSchema: Schema[DictDto] = Schema.derived } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala index 56f3a05611d..e3c3c6f1a62 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala @@ -7,7 +7,7 @@ import pl.touk.nussknacker.engine.api.definition.StringParameterEditor import pl.touk.nussknacker.engine.api.definition.Parameter import pl.touk.nussknacker.engine.api.graph.ScenarioGraph import pl.touk.nussknacker.engine.api.test.ScenarioTestData -import pl.touk.nussknacker.engine.api.typed.CanBeSubclassDeterminer +import pl.touk.nussknacker.engine.api.typed.ImplicitConversionDeterminer import pl.touk.nussknacker.engine.api.typed.typing.Typed import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.definition.test.{TestInfoProvider, TestingCapabilities} @@ -133,7 +133,7 @@ class ScenarioTestService( private def assignUserFriendlyEditor(uiSourceParameter: UISourceParameters): UISourceParameters = { val adaptedParameters = uiSourceParameter.parameters.map { uiParameter => - if (CanBeSubclassDeterminer.canBeSubclassOf(uiParameter.typ, Typed.apply(classOf[String])).isValid) { + if (ImplicitConversionDeterminer.canBeConvertedTo(uiParameter.typ, Typed.apply(classOf[String])).isValid) { uiParameter.copy(editor = StringParameterEditor) } else { uiParameter diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DictApiHttpServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DictApiHttpServiceSpec.scala index 457743945f4..3decd5d7d82 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DictApiHttpServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DictApiHttpServiceSpec.scala @@ -16,17 +16,37 @@ class DictApiHttpServiceSpec with RestAssuredVerboseLoggingIfValidationFails { "The endpoint for listing available dictionaries of expected type should" - { + + "return proper empty list for expected type Integer - check subclassing" in { + given() + .when() + .basicAuthAllPermUser() + .jsonBody("""{ + | "expectedType" : { + | "type" : "TypedClass", + | "refClazzName" : "java.lang.Integer", + | "params":[] + | } + |}""".stripMargin) + .post(s"$nuDesignerHttpAddress/api/processDefinitionData/${Streaming.stringify}/dicts") + .Then() + .statusCode(200) + .equalsJsonBody( + s"""[ + |]""".stripMargin + ) + + } + "return proper list for expected type String" in { given() .when() .basicAuthAllPermUser() .jsonBody("""{ | "expectedType" : { - | "value" : { | "type" : "TypedClass", | "refClazzName" : "java.lang.String", | "params" : [] - | } | } |}""".stripMargin) .post(s"$nuDesignerHttpAddress/api/processDefinitionData/${Streaming.stringify}/dicts") @@ -56,11 +76,33 @@ class DictApiHttpServiceSpec .basicAuthAllPermUser() .jsonBody("""{ | "expectedType" : { - | "value" : { | "type" : "TypedClass", | "refClazzName" : "java.lang.Long", | "params" : [] - | } + | } + |}""".stripMargin) + .post(s"$nuDesignerHttpAddress/api/processDefinitionData/${Streaming.stringify}/dicts") + .Then() + .statusCode(200) + .equalsJsonBody( + s"""[ + | { + | "id" : "long_dict", + | "label" : "long_dict" + | } + |]""".stripMargin + ) + } + + "return proper list for expected type BigDecimal" in { + given() + .when() + .basicAuthAllPermUser() + .jsonBody("""{ + | "expectedType" : { + | "type" : "TypedClass", + | "refClazzName" : "java.math.BigInteger", + | "params" : [] | } |}""".stripMargin) .post(s"$nuDesignerHttpAddress/api/processDefinitionData/${Streaming.stringify}/dicts") @@ -95,11 +137,9 @@ class DictApiHttpServiceSpec .basicAuthAllPermUser() .jsonBody("""{ | "expectedType" : { - | "value" : { | "type" : "TypedClass", | "refClazzName" : "java.lang.Long", | "params" : [] - | } | } |}""".stripMargin) .post(s"$nuDesignerHttpAddress/api/processDefinitionData/thisProcessingTypeDoesNotExist/dicts") 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 396f81c5fe6..3775754badd 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 @@ -1950,7 +1950,7 @@ class UIProcessValidatorSpec extends AnyFunSuite with Matchers with TableDrivenP ) val fragmentDefinition: CanonicalProcess = - createFragmentDefinition(fragmentId, List(FragmentParameter(ParameterName("P1"), FragmentClazzRef[Short]))) + createFragmentDefinition(fragmentId, List(FragmentParameter(ParameterName("P1"), FragmentClazzRef[Integer]))) val processWithFragment = createScenarioGraphWithFragmentParams(fragmentId, List(NodeParameter(ParameterName("P1"), "123".spel))) diff --git a/docs-internal/api/nu-designer-openapi.yaml b/docs-internal/api/nu-designer-openapi.yaml index 215692d8583..b3c4e8221f6 100644 --- a/docs-internal/api/nu-designer-openapi.yaml +++ b/docs-internal/api/nu-designer-openapi.yaml @@ -5080,16 +5080,7 @@ components: required: - expectedType properties: - expectedType: - oneOf: - - $ref: '#/components/schemas/TypedClass' - - $ref: '#/components/schemas/TypedDict' - - $ref: '#/components/schemas/TypedNull' - - $ref: '#/components/schemas/TypedObjectTypingResult' - - $ref: '#/components/schemas/TypedObjectWithValue' - - $ref: '#/components/schemas/TypedTaggedValue' - - $ref: '#/components/schemas/TypedUnion' - - $ref: '#/components/schemas/Unknown' + expectedType: {} DictParameterEditor: title: DictParameterEditor type: object diff --git a/engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/AggregatesSpec.scala b/engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/AggregatesSpec.scala index f4ad8ff12ee..12350236313 100644 --- a/engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/AggregatesSpec.scala +++ b/engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/AggregatesSpec.scala @@ -74,8 +74,8 @@ class AggregatesSpec extends AnyFunSuite with TableDrivenPropertyChecks with Mat private def shouldBeInstanceOf(obj: Any, typ: TypingResult): Unit = { val typeFromInstance = Typed.fromInstance(obj) - val canBeSubclassCase = typeFromInstance.canBeSubclassOf(typ) - val typedObjectCase = typ.isInstanceOf[TypedObjectTypingResult] && typeFromInstance.canBeSubclassOf( + val canBeSubclassCase = typeFromInstance.canBeImplicitlyConvertedTo(typ) + val typedObjectCase = typ.isInstanceOf[TypedObjectTypingResult] && typeFromInstance.canBeImplicitlyConvertedTo( typ.asInstanceOf[TypedObjectTypingResult].runtimeObjType ) (canBeSubclassCase || typedObjectCase) shouldBe true diff --git a/engine/flink/components/base-unbounded/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/aggregates.scala b/engine/flink/components/base-unbounded/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/aggregates.scala index bdb37f86bee..a9f883d1e6e 100644 --- a/engine/flink/components/base-unbounded/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/aggregates.scala +++ b/engine/flink/components/base-unbounded/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/aggregate/aggregates.scala @@ -190,7 +190,7 @@ object aggregates { override def result(finalAggregate: Aggregate): AnyRef = finalAggregate override def computeOutputType(input: typing.TypingResult): Validated[String, typing.TypingResult] = { - if (input.canBeSubclassOf(Typed[Boolean])) { + if (input.canBeImplicitlyConvertedTo(Typed[Boolean])) { Valid(Typed[Long]) } else { Invalid(s"Invalid aggregate type: ${input.display}, should be: ${Typed[Boolean].display}") @@ -239,7 +239,7 @@ object aggregates { override def computeOutputType(input: typing.TypingResult): Validated[String, typing.TypingResult] = { - if (!input.canBeSubclassOf(Typed[Number])) { + if (!input.canBeImplicitlyConvertedTo(Typed[Number])) { Invalid(s"Invalid aggregate type: ${input.display}, should be: ${Typed[Number].display}") } else { Valid(ForLargeFloatingNumbersOperation.promoteSingle(input)) @@ -353,7 +353,9 @@ object aggregates { ): Validated[String, TypedObjectTypingResult] = { input match { case TypedObjectTypingResult(inputFields, klass, _) - if inputFields.keySet == scalaFields.keySet && klass.canBeSubclassOf(Typed[java.util.Map[String, _]]) => + if inputFields.keySet == scalaFields.keySet && klass.canBeImplicitlyConvertedTo( + Typed[java.util.Map[String, _]] + ) => val validationRes = scalaFields .map { case (key, aggregator) => computeField(aggregator, inputFields(key)) @@ -437,7 +439,7 @@ object aggregates { trait MathAggregator { self: ReducingAggregator => override def computeOutputType(input: typing.TypingResult): Validated[String, typing.TypingResult] = { - if (input.canBeSubclassOf(Typed[Number])) { + if (input.canBeImplicitlyConvertedTo(Typed[Number])) { // In some cases type can be promoted to other class e.g. Byte is promoted to Int for sum Valid(promotionStrategy.promoteSingle(input)) } else { diff --git a/engine/flink/components/base/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/ForEachTransformer.scala b/engine/flink/components/base/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/ForEachTransformer.scala index f87e70e735d..951be76ce26 100644 --- a/engine/flink/components/base/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/ForEachTransformer.scala +++ b/engine/flink/components/base/src/main/scala/pl/touk/nussknacker/engine/flink/util/transformer/ForEachTransformer.scala @@ -45,7 +45,9 @@ object ForEachTransformer extends CustomStreamTransformer with Serializable { private def returnType(elements: LazyParameter[util.Collection[AnyRef]]): typing.TypingResult = elements.returnType match { case tc: SingleTypingResult - if tc.runtimeObjType.canBeSubclassOf(Typed[util.Collection[_]]) && tc.runtimeObjType.params.nonEmpty => + if tc.runtimeObjType.canBeImplicitlyConvertedTo( + Typed[util.Collection[_]] + ) && tc.runtimeObjType.params.nonEmpty => tc.runtimeObjType.params.head case _ => Unknown } diff --git a/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/sink/TableTypeOutputValidator.scala b/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/sink/TableTypeOutputValidator.scala index 42c2f4d560c..05f6b8cda9e 100644 --- a/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/sink/TableTypeOutputValidator.scala +++ b/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/sink/TableTypeOutputValidator.scala @@ -14,7 +14,7 @@ object TableTypeOutputValidator { val aligned = ToTableTypeSchemaBasedEncoder.alignTypingResult(actualType, expectedType) val expectedTypingResult = expectedType.toTypingResult - if (aligned.canBeSubclassOf(expectedTypingResult)) { + if (aligned.canBeImplicitlyConvertedTo(expectedTypingResult)) { Valid(()) } else { invalidNel( diff --git a/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/utils/ToTableTypeSchemaBasedEncoder.scala b/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/utils/ToTableTypeSchemaBasedEncoder.scala index d3e36185645..27ae00600c8 100644 --- a/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/utils/ToTableTypeSchemaBasedEncoder.scala +++ b/engine/flink/components/table/src/main/scala/pl/touk/nussknacker/engine/flink/table/utils/ToTableTypeSchemaBasedEncoder.scala @@ -26,7 +26,8 @@ object ToTableTypeSchemaBasedEncoder { case (null, _) => null // We don't know what is the precise of decimal so we have to assume that it will fit the target type to not block the user - case (number: Number, _) if Typed.typedClass(number.getClass).canBeSubclassOf(targetType.toTypingResult) => + case (number: Number, _) + if Typed.typedClass(number.getClass).canBeImplicitlyConvertedTo(targetType.toTypingResult) => NumberUtils .convertNumberToTargetClass[Number](number, targetType.getDefaultConversion.asInstanceOf[Class[Number]]) case (_, rowType: RowType) => @@ -82,7 +83,8 @@ object ToTableTypeSchemaBasedEncoder { targetType.toTypingResult // We don't know what is the precision of decimal so we have to assume that it will fit the target type to not block the user case (typ: SingleTypingResult, _) - if typ.canBeSubclassOf(Typed[Number]) && typ.canBeSubclassOf(targetType.toTypingResult) => + if typ + .canBeImplicitlyConvertedTo(Typed[Number]) && typ.canBeImplicitlyConvertedTo(targetType.toTypingResult) => targetType.toTypingResult case (recordType: TypedObjectTypingResult, rowType: RowType) if Set[Class[_]](javaMapClass, rowClass).contains(recordType.runtimeObjType.klass) => @@ -104,10 +106,10 @@ object ToTableTypeSchemaBasedEncoder { case ( TypedObjectTypingResult(_, TypedClass(`javaMapClass`, keyType :: valueType :: Nil), _), multisetType: MultisetType - ) if valueType.canBeSubclassOf(Typed[Int]) => + ) if valueType.canBeImplicitlyConvertedTo(Typed[Int]) => alignMultisetType(keyType, multisetType) case (TypedClass(`javaMapClass`, keyType :: valueType :: Nil), multisetType: MultisetType) - if valueType.canBeSubclassOf(Typed[Int]) => + if valueType.canBeImplicitlyConvertedTo(Typed[Int]) => alignMultisetType(keyType, multisetType) case (TypedClass(`arrayClass`, elementType :: Nil), arrayType: ArrayType) => Typed.genericTypeClass(arrayClass, List(alignTypingResult(elementType, arrayType.getElementType))) diff --git a/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/DocumentationFunctions.scala b/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/DocumentationFunctions.scala index d022901ffc8..bcbc358fc7c 100644 --- a/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/DocumentationFunctions.scala +++ b/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/DocumentationFunctions.scala @@ -62,7 +62,7 @@ object DocumentationFunctions { (left.withoutValue, right.withoutValue) match { case (`intType`, `intType`) => intType.validNel case (`doubleType`, `doubleType`) => doubleType.validNel - case (l, r) if List(l, r).forall(_.canBeSubclassOf(numberType)) => + case (l, r) if List(l, r).forall(_.canBeImplicitlyConvertedTo(numberType)) => OtherError(s"Addition of ${l.display} and ${r.display} is not supported").invalidNel case (`stringType`, `stringType`) => stringType.validNel case _ => ArgumentTypeError.invalidNel @@ -110,7 +110,7 @@ object DocumentationFunctions { case Some(v) => v.validNel case None => OtherError("No field with given name").invalidNel } - case TypedObjectTypingResult(_, _, _) :: x :: Nil if x.canBeSubclassOf(stringType) => + case TypedObjectTypingResult(_, _, _) :: x :: Nil if x.canBeImplicitlyConvertedTo(stringType) => OtherError("Expected string with known value").invalidNel case _ => ArgumentTypeError.invalidNel diff --git a/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/ExampleFunctions.scala b/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/ExampleFunctions.scala index e17be319fa6..723c0d2257b 100644 --- a/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/ExampleFunctions.scala +++ b/engine/flink/management/dev-model/src/main/scala/pl/touk/nussknacker/engine/management/sample/global/ExampleFunctions.scala @@ -147,7 +147,7 @@ object ExampleFunctions { override def computeResultType( arguments: List[TypingResult] ): ValidatedNel[GenericFunctionTypingError, TypingResult] = { - if (arguments.exists(!_.canBeSubclassOf(Typed[Number]))) return ArgumentTypeError.invalidNel + if (arguments.exists(!_.canBeImplicitlyConvertedTo(Typed[Number]))) return ArgumentTypeError.invalidNel arguments match { case t :: Nil => t.validNel case l :: r :: Nil => Typed.record(Map("left" -> l, "right" -> r)).validNel diff --git a/engine/lite/components/base/src/main/scala/pl/touk/nussknacker/engine/lite/components/ForEachTransformer.scala b/engine/lite/components/base/src/main/scala/pl/touk/nussknacker/engine/lite/components/ForEachTransformer.scala index cdceb363052..a2f82110e22 100644 --- a/engine/lite/components/base/src/main/scala/pl/touk/nussknacker/engine/lite/components/ForEachTransformer.scala +++ b/engine/lite/components/base/src/main/scala/pl/touk/nussknacker/engine/lite/components/ForEachTransformer.scala @@ -42,7 +42,9 @@ class ForEachTransformerComponent(elements: LazyParameter[java.util.Collection[A override def returnType: typing.TypingResult = { elements.returnType match { case tc: SingleTypingResult - if tc.runtimeObjType.canBeSubclassOf(Typed[java.util.Collection[_]]) && tc.runtimeObjType.params.nonEmpty => + if tc.runtimeObjType.canBeImplicitlyConvertedTo( + Typed[java.util.Collection[_]] + ) && tc.runtimeObjType.params.nonEmpty => tc.runtimeObjType.params.head case _ => Unknown } diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala index 92daaf69c35..01e37795330 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala @@ -154,7 +154,7 @@ object FragmentParameterValidator { val dictValueType = dictDefinition.valueType(dictId) - if (dictValueType.canBeSubclassOf(fragmentParameterTypingResult)) { + if (dictValueType.canBeImplicitlyConvertedTo(fragmentParameterTypingResult)) { Valid(()) } else { invalidNel( diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/ClassDefinitionExtractor.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/ClassDefinitionExtractor.scala index 263c406181a..d86ef54a602 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/ClassDefinitionExtractor.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/ClassDefinitionExtractor.scala @@ -141,7 +141,7 @@ class ClassDefinitionExtractor(settings: ClassExtractionSettings) extends LazyLo methodsForParams .find { case (_, method) => - methodsForParams.forall(mi => method.signature.result.canBeSubclassOf(mi._2.signature.result)) + methodsForParams.forall(mi => method.signature.result.canBeImplicitlyConvertedTo(mi._2.signature.result)) } .getOrElse(methodsForParams.minBy(_._2.signature.result.display)) } @@ -273,7 +273,7 @@ class ClassDefinitionExtractor(settings: ClassExtractionSettings) extends LazyLo ) reflectionBasedDefinition.result } - if (returnedResultType.canBeSubclassOf(returnedResultType)) { + if (returnedResultType.canBeImplicitlyConvertedTo(returnedResultType)) { returnedResultType } else { logger.warn( diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodDefinition.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodDefinition.scala index bad04d2eca9..452d5b64258 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodDefinition.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodDefinition.scala @@ -35,13 +35,15 @@ sealed trait MethodDefinition { // Allow pass array as List argument because of array to list auto conversion: // pl.touk.nussknacker.engine.spel.internal.ArrayToListConverter case (tc @ TypedClass(klass, _), Parameter(_, y)) if klass.isArray => - tc.canBeSubclassOf(y) || Typed.genericTypeClass[java.util.List[_]](tc.params).canBeSubclassOf(y) - case (x, Parameter(_, y)) => x.canBeSubclassOf(y) + tc.canBeImplicitlyConvertedTo(y) || Typed + .genericTypeClass[java.util.List[_]](tc.params) + .canBeImplicitlyConvertedTo(y) + case (x, Parameter(_, y)) => x.canBeImplicitlyConvertedTo(y) } val checkVarArgs = methodTypeInfo.varArg match { case Some(Parameter(_, t)) => - arguments.drop(methodTypeInfo.noVarArgs.length).forall(_.canBeSubclassOf(t)) + arguments.drop(methodTypeInfo.noVarArgs.length).forall(_.canBeImplicitlyConvertedTo(t)) case None => arguments.length == methodTypeInfo.noVarArgs.length } @@ -94,7 +96,7 @@ case class FunctionalMethodDefinition( val typeCalculated = typeFunction(methodInvocationTarget, arguments).leftMap(_.map(errorConverter.convert)) typeCalculated.map { calculated => - if (!typesFromStaticMethodInfo.exists(calculated.canBeSubclassOf)) { + if (!typesFromStaticMethodInfo.exists(calculated.canBeImplicitlyConvertedTo)) { val expectedTypesString = typesFromStaticMethodInfo.map(_.display).mkString("(", ", ", ")") val argumentsString = arguments.map(_.display).mkString("(", ", ", ")") throw new AssertionError( diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodTypeInfoSubclassChecker.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodTypeInfoSubclassChecker.scala index c6868bb4bb5..8a148911c10 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodTypeInfoSubclassChecker.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/clazz/MethodTypeInfoSubclassChecker.scala @@ -12,7 +12,7 @@ object MethodTypeInfoSubclassChecker { val MethodTypeInfo(superclassNoVarArg, superclassVarArgOption, superclassResult) = superclassInfo val validatedVarArgs = (subclassVarArgOption, superclassVarArgOption) match { - case (Some(sub), Some(sup)) if sub.refClazz.canBeSubclassOf(sup.refClazz) => ().validNel + case (Some(sub), Some(sup)) if sub.refClazz.canBeImplicitlyConvertedTo(sup.refClazz) => ().validNel case (Some(sub), Some(sup)) => NotSubclassVarArgument(sub.refClazz, sup.refClazz).invalidNel case (Some(_), None) => BadVarArg.invalidNel case (None, Some(_)) => ().validNel @@ -38,7 +38,7 @@ object MethodTypeInfoSubclassChecker { ) val validatedNoVarArgs = zippedParameters.zipWithIndex .map { - case ((sub, sup), _) if sub.refClazz.canBeSubclassOf(sup.refClazz) => ().validNel + case ((sub, sup), _) if sub.refClazz.canBeImplicitlyConvertedTo(sup.refClazz) => ().validNel case ((sub, sup), i) => NotSubclassArgument(i + 1, sub.refClazz, sup.refClazz).invalidNel } .sequence @@ -46,7 +46,7 @@ object MethodTypeInfoSubclassChecker { val validatedResult = Validated.condNel( - subclassResult.canBeSubclassOf(superclassResult), + subclassResult.canBeImplicitlyConvertedTo(superclassResult), (), NotSubclassResult(subclassResult, superclassResult) ) diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/language/dictWithLabel/DictKeyWithLabelExpressionParser.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/language/dictWithLabel/DictKeyWithLabelExpressionParser.scala index c95b3d41437..75ccd94f555 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/language/dictWithLabel/DictKeyWithLabelExpressionParser.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/language/dictWithLabel/DictKeyWithLabelExpressionParser.scala @@ -70,11 +70,11 @@ object DictKeyWithLabelExpressionParser extends ExpressionParser { override def language: Language = languageId override def evaluate[T](ctx: Context, globals: Map[String, Any]): T = { - if (expectedType.canBeSubclassOf(Typed[Long])) { + if (expectedType.canBeImplicitlyConvertedTo(Typed[Long])) { key.toLong.asInstanceOf[T] - } else if (expectedType.canBeSubclassOf(Typed[Boolean])) { + } else if (expectedType.canBeImplicitlyConvertedTo(Typed[Boolean])) { key.toBoolean.asInstanceOf[T] - } else if (expectedType.canBeSubclassOf(Typed[String])) { + } else if (expectedType.canBeImplicitlyConvertedTo(Typed[String])) { key.asInstanceOf[T] } else { throw new IllegalStateException( diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSuggester.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSuggester.scala index 333c1ff0a86..a63782e2b9f 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSuggester.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSuggester.scala @@ -411,9 +411,9 @@ class SpelExpressionSuggester( private def determineIterableElementTypingResult(parent: TypingResult): TypingResult = { parent match { - case tc: SingleTypingResult if tc.runtimeObjType.canBeSubclassOf(Typed[java.util.Collection[_]]) => + case tc: SingleTypingResult if tc.runtimeObjType.canBeImplicitlyConvertedTo(Typed[java.util.Collection[_]]) => tc.runtimeObjType.params.headOption.getOrElse(Unknown) - case tc: SingleTypingResult if tc.runtimeObjType.canBeSubclassOf(Typed[java.util.Map[_, _]]) => + case tc: SingleTypingResult if tc.runtimeObjType.canBeImplicitlyConvertedTo(Typed[java.util.Map[_, _]]) => Typed.record( Map( "key" -> tc.runtimeObjType.params.headOption.getOrElse(Unknown), diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionValidator.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionValidator.scala index ac49dff185c..6fc8764dafd 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionValidator.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/SpelExpressionValidator.scala @@ -18,7 +18,8 @@ class SpelExpressionValidator(typer: Typer) { val typedExpression = typer.typeExpression(expr, ctx) typedExpression.andThen { collected => collected.finalResult.typingResult match { - case a: TypingResult if a.canBeSubclassOf(expectedType) || expectedType == Typed[SpelExpressionRepr] => + case a: TypingResult + if a.canBeImplicitlyConvertedTo(expectedType) || expectedType == Typed[SpelExpressionRepr] => Valid(collected) case a: TypingResult => Invalid(NonEmptyList.of(ExpressionTypeError(expectedType, a))) diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala index 099e5c44183..1c7aa1abe60 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/Typer.scala @@ -146,8 +146,8 @@ private[spel] class Typer( def withChildrenOfType[Parts: universe.TypeTag](result: TypingResult) = { val w = valid(result) withTypedChildren { - case list if list.forall(_.canBeSubclassOf(Typed.fromDetailedType[Parts])) => w - case _ => w.tell(List(PartTypeError)) + case list if list.forall(_.canBeImplicitlyConvertedTo(Typed.fromDetailedType[Parts])) => w + case _ => w.tell(List(PartTypeError)) } } @@ -198,7 +198,7 @@ private[spel] class Typer( case (ref: PropertyOrFieldReference) :: Nil => typeFieldNameReferenceOnRecord(ref.getName, record) case _ => typeFieldNameReferenceOnRecord(indexString, record) } - case indexKey :: Nil if indexKey.canBeSubclassOf(Typed[String]) => + case indexKey :: Nil if indexKey.canBeImplicitlyConvertedTo(Typed[String]) => if (dynamicPropertyAccessAllowed) valid(Unknown) else invalid(DynamicPropertyAccessError) case _ :: Nil => indexer.children match { @@ -356,7 +356,8 @@ private[spel] class Typer( case e: OpMinus => withTypedChildren { - case left :: right :: Nil if left.canBeSubclassOf(Typed[Number]) && right.canBeSubclassOf(Typed[Number]) => + case left :: right :: Nil + if left.canBeImplicitlyConvertedTo(Typed[Number]) && right.canBeImplicitlyConvertedTo(Typed[Number]) => val fallback = NumberTypesPromotionStrategy.ForMathOperation.promote(left, right) operationOnTypesValue[Number, Number, Number](left, right, fallback)((n1, n2) => Valid(MathUtils.minus(n1, n2)) @@ -365,7 +366,7 @@ private[spel] class Typer( invalid(OperatorNonNumericError(e.getOperatorName, left)) case left :: right :: Nil => invalid(OperatorMismatchTypeError(e.getOperatorName, left, right)) - case left :: Nil if left.canBeSubclassOf(Typed[Number]) => + case left :: Nil if left.canBeImplicitlyConvertedTo(Typed[Number]) => val resultType = left.withoutValue val result = operationOnTypesValue[Number, Number](left)(MathUtils.negate).getOrElse(resultType) valid(result) @@ -395,18 +396,20 @@ private[spel] class Typer( withTypedChildren { case left :: right :: Nil if left == Unknown || right == Unknown => valid(Unknown) - case left :: right :: Nil if left.canBeSubclassOf(Typed[String]) || right.canBeSubclassOf(Typed[String]) => + case left :: right :: Nil + if left.canBeImplicitlyConvertedTo(Typed[String]) || right.canBeImplicitlyConvertedTo(Typed[String]) => operationOnTypesValue[Any, Any, String](left, right, Typed[String])((l, r) => Valid(l.toString + r.toString) ) - case left :: right :: Nil if left.canBeSubclassOf(Typed[Number]) && right.canBeSubclassOf(Typed[Number]) => + case left :: right :: Nil + if left.canBeImplicitlyConvertedTo(Typed[Number]) && right.canBeImplicitlyConvertedTo(Typed[Number]) => val fallback = NumberTypesPromotionStrategy.ForMathOperation.promote(left, right) operationOnTypesValue[Number, Number, Number](left, right, fallback)((n1, n2) => Valid(MathUtils.plus(n1, n2)) ) case left :: right :: Nil => invalid(OperatorMismatchTypeError(e.getOperatorName, left, right)) - case left :: Nil if left.canBeSubclassOf(Typed[Number]) => + case left :: Nil if left.canBeImplicitlyConvertedTo(Typed[Number]) => valid(left) case left :: Nil => invalid(OperatorNonNumericError(e.getOperatorName, left)) @@ -448,7 +451,7 @@ private[spel] class Typer( elementType <- extractIterativeType(iterateType) selectionType = resolveSelectionTypingResult(e, iterateType, elementType) result <- typeChildren(validationContext, node, current.pushOnStack(elementType)) { - case result :: Nil if result.canBeSubclassOf(Typed[Boolean]) => + case result :: Nil if result.canBeImplicitlyConvertedTo(Typed[Boolean]) => valid(selectionType) case other => invalid(IllegalSelectionTypeError(other), selectionType) @@ -459,7 +462,7 @@ private[spel] class Typer( case condition :: onTrue :: onFalse :: Nil => for { _ <- Option(condition) - .filter(_.canBeSubclassOf(Typed[Boolean])) + .filter(_.canBeImplicitlyConvertedTo(Typed[Boolean])) .map(valid) .getOrElse(invalid(TernaryOperatorNotBooleanError(condition))) } yield CommonSupertypeFinder.Default.commonSupertype(onTrue, onFalse) @@ -520,10 +523,10 @@ private[spel] class Typer( // as properly determining it would require evaluating the selection expression for each element (likely working on the AST) parentType match { case tc: SingleTypingResult - if tc.runtimeObjType.canBeSubclassOf(Typed[java.util.Collection[_]]) || + if tc.runtimeObjType.canBeImplicitlyConvertedTo(Typed[java.util.Collection[_]]) || tc.runtimeObjType.klass.isArray => tc.withoutValue - case tc: SingleTypingResult if tc.runtimeObjType.canBeSubclassOf(Typed[java.util.Map[_, _]]) => + case tc: SingleTypingResult if tc.runtimeObjType.canBeImplicitlyConvertedTo(Typed[java.util.Map[_, _]]) => Typed.record(Map.empty) case _ => parentType @@ -575,7 +578,8 @@ private[spel] class Typer( op: Option[(Number, Number) => Validated[ExpressionParseError, Any]] )(implicit numberPromotionStrategy: NumberTypesPromotionStrategy): TypingR[CollectedTypingResult] = { typeChildren(validationContext, node, current) { - case left :: right :: Nil if left.canBeSubclassOf(Typed[Number]) && right.canBeSubclassOf(Typed[Number]) => + case left :: right :: Nil + if left.canBeImplicitlyConvertedTo(Typed[Number]) && right.canBeImplicitlyConvertedTo(Typed[Number]) => val fallback = numberPromotionStrategy.promote(left, right) op .map(operationOnTypesValue[Number, Number, Any](left, right, fallback)(_)) @@ -594,7 +598,7 @@ private[spel] class Typer( current: TypingContext )(op: Number => Any): TypingR[CollectedTypingResult] = { typeChildren(validationContext, node, current) { - case left :: Nil if left.canBeSubclassOf(Typed[Number]) => + case left :: Nil if left.canBeImplicitlyConvertedTo(Typed[Number]) => val result = operationOnTypesValue[Number, Any](left)(op).getOrElse(left.withoutValue) valid(result) case left :: Nil => @@ -690,10 +694,10 @@ private[spel] class Typer( private def extractIterativeType(parent: TypingResult): TypingR[TypingResult] = parent match { case tc: SingleTypingResult - if tc.runtimeObjType.canBeSubclassOf(Typed[java.util.Collection[_]]) || + if tc.runtimeObjType.canBeImplicitlyConvertedTo(Typed[java.util.Collection[_]]) || tc.runtimeObjType.klass.isArray => valid(tc.runtimeObjType.params.headOption.getOrElse(Unknown)) - case tc: SingleTypingResult if tc.runtimeObjType.canBeSubclassOf(Typed[java.util.Map[_, _]]) => + case tc: SingleTypingResult if tc.runtimeObjType.canBeImplicitlyConvertedTo(Typed[java.util.Map[_, _]]) => valid( Typed.record( Map( diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/typer/MethodReferenceTyper.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/typer/MethodReferenceTyper.scala index ad0d4bf8ae8..9926b337e95 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/typer/MethodReferenceTyper.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/spel/typer/MethodReferenceTyper.scala @@ -62,7 +62,7 @@ class MethodReferenceTyper(classDefinitionSet: ClassDefinitionSet, methodExecuti )(implicit reference: MethodReference): Either[Option[ExpressionParseError], NonEmptyList[MethodDefinition]] = { def displayableType = clazzDefinitions.map(k => k.clazzName).map(_.display).toList.mkString(", ") - def isClass = clazzDefinitions.map(k => k.clazzName).exists(_.canBeSubclassOf(Typed[Class[_]])) + def isClass = clazzDefinitions.map(k => k.clazzName).exists(_.canBeImplicitlyConvertedTo(Typed[Class[_]])) val clazzMethods = if (reference.isStatic) clazzDefinitions.toList.flatMap(_.staticMethods.get(reference.methodName).toList.flatten) diff --git a/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/collection.scala b/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/collection.scala index 862381c03de..0858fe97465 100644 --- a/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/collection.scala +++ b/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/collection.scala @@ -356,7 +356,9 @@ object CollectionUtils { listType.copy(params = unknownMapType :: Nil) case _ if firstComponentType.withoutValue == secondComponentType.withoutValue => listType.copy(params = firstComponentType.withoutValue :: Nil) - case _ if firstComponentType.canBeSubclassOf(numberType) && secondComponentType.canBeSubclassOf(numberType) => + case _ + if firstComponentType.canBeImplicitlyConvertedTo(numberType) && secondComponentType + .canBeImplicitlyConvertedTo(numberType) => Typed.genericTypeClass(fClass, List(numberType)) case _ => listType.copy(params = Unknown :: Nil) } diff --git a/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/numeric.scala b/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/numeric.scala index 2233d31fd93..bc6d1d2a1f5 100644 --- a/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/numeric.scala +++ b/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/numeric.scala @@ -129,7 +129,7 @@ object NumericUtils { override def computeResultType( arguments: List[typing.TypingResult] ): ValidatedNel[GenericFunctionTypingError, typing.TypingResult] = { - if (arguments.head.canBeSubclassOf(Typed[Number])) arguments.head.withoutValue.validNel + if (arguments.head.canBeImplicitlyConvertedTo(Typed[Number])) arguments.head.withoutValue.validNel else Typed[Number].validNel } diff --git a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/encode/JsonSchemaOutputValidator.scala b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/encode/JsonSchemaOutputValidator.scala index 7d946008f7c..1219aacde4a 100644 --- a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/encode/JsonSchemaOutputValidator.scala +++ b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/encode/JsonSchemaOutputValidator.scala @@ -370,7 +370,7 @@ class JsonSchemaOutputValidator(validationMode: ValidationMode) extends LazyLogg case (TypedClass(_, Nil), TypedClass(_, Nil)) => invalid(typingResult, schema, rootSchema, path) case _ => condNel( - typingResult.canBeSubclassOf(schemaAsTypedResult), + typingResult.canBeImplicitlyConvertedTo(schemaAsTypedResult), (), OutputValidatorTypeError(path, typingResult, JsonSchemaExpected(schema, rootSchema)) ) diff --git a/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala b/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala index 6d5fc0b8f52..48292b5697e 100644 --- a/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala +++ b/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala @@ -152,9 +152,11 @@ class AvroSchemaOutputValidator(validationMode: ValidationMode) extends LazyLogg typingResult match { case _ @TypedClass(klass, key :: value :: Nil) if isMap(klass) => // Map keys are assumed to be strings: https://avro.apache.org/docs/current/spec.html#Maps - condNel(key.canBeSubclassOf(Typed.apply[java.lang.String]), (), typeError(typingResult, schema, path)).andThen( - _ => validateTypingResult(value, schema.getValueType, buildPath("*", path, useIndexer = true)) - ) + condNel( + key.canBeImplicitlyConvertedTo(Typed.apply[java.lang.String]), + (), + typeError(typingResult, schema, path) + ).andThen(_ => validateTypingResult(value, schema.getValueType, buildPath("*", path, useIndexer = true))) case map @ TypedClass(klass, _) if isMap(klass) => throw new IllegalArgumentException(s"Illegal typing Map: $map.") case _ @TypedObjectTypingResult(fields, TypedClass(klass, _), _) if isMap(klass) =>