Type validation for types generated from JSON (#530)

This commit is contained in:
Dima 2022-07-05 10:15:40 +03:00 committed by GitHub
parent 8cb0fec2b0
commit 198e339694
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 209 additions and 11 deletions

View File

@ -18,19 +18,32 @@ import scala.scalajs.js.JSON
object TypeValidator {
// Compare and validate type generated from JSON and type from Aqua file.
// Also, validation will be success if array or optional field will be missed in JSON type
def validateTypes(name: String, lt: Type, rtOp: Option[Type]): ValidatedNec[String, Unit] = {
rtOp match {
/**
* Compare and validate type from Aqua file and type generated from JSON.
* Also, the validation will succeed if the JSON type is missing an array or an optional field.
* The result will be incorrect if left and right are the same type.
* @param name field name
* @param aquaType a type from Aqua code
* @param jsonType a type generated from JSON
* @param fullOptionType save full optional part of Aqua and JSON field types to print clear error message if needed
* @return
*/
def validateTypes(
name: String,
aquaType: Type,
jsonType: Option[Type],
fullOptionType: Option[(Type, Type)] = None
): ValidatedNec[String, Unit] = {
jsonType match {
case None =>
lt match {
aquaType match {
case _: BoxType =>
validNec(())
case _ =>
invalidNec(s"Missing field '$name' in arguments")
}
case Some(rt) =>
(lt, rt) match {
(aquaType, rt) match {
case (l: ProductType, r: ProductType) =>
val ll = l.toList
val rl = r.toList
@ -41,8 +54,8 @@ object TypeValidator {
)
else
ll.zip(rl)
.map { case (lt, rt) =>
validateTypes(s"$name", lt, Some(rt))
.map { case (aquaType, rt) =>
validateTypes(s"$name", aquaType, Some(rt))
}
.sequence
.map(_ => ())
@ -53,15 +66,31 @@ object TypeValidator {
lsm.map { case (n, ltt) =>
validateTypes(s"$name.$n", ltt, rsm.get(n))
}.toList.sequence.map(_ => ())
case (l: OptionType, r) =>
// if we have ?[][]string and [][][]string it must throw an error
validateTypes(name, l.element, Some(r), Some((l, r)))
case (l: BoxType, r: BoxType) =>
validateTypes(name, l.element, Some(r.element))
validateTypes(name, l.element, Some(r.element), fullOptionType.orElse(Some(l, r)))
case (l: BoxType, r) =>
validateTypes(name, l.element, Some(r))
(l.element, fullOptionType) match {
case (_: BoxType, Some(td)) =>
// if we have ?[][]string and []string it must throw an error
invalidNec(
s"Type of the field '$name' is incorrect. Expected: '${td._1}' Actual: '${td._2}'"
)
case (ll: BoxType, None) =>
invalidNec(
s"Type of the field '$name' is incorrect. Expected: '$ll' Actual: '$r'"
)
case _ => validateTypes(name, l.element, Some(r), Some((l, r)))
}
case (l, r) =>
if (l >= r) validNec(())
else
val (li, ri) = fullOptionType.getOrElse((l, r))
invalidNec(
s"Type of the field '$name' is incorrect. Expected: '$l' Actual: '$r'"
s"Type of the field '$name' is incorrect. Expected: '$li' Actual: '$ri'"
)
}
}

View File

@ -0,0 +1,169 @@
package aqua
import aqua.json.TypeValidator
import aqua.types.{ArrayType, LiteralType, OptionType, ScalarType, StructType, Type}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import cats.data.{NonEmptyList, NonEmptyMap, ValidatedNec}
class TypeValidatorSpec extends AnyFlatSpec with Matchers {
val aquaType = StructType(
"some",
NonEmptyMap.of(
("field1", OptionType(ArrayType(ScalarType.u8))),
("field2", OptionType(ArrayType(OptionType(ScalarType.i32)))),
("field3", ArrayType(ArrayType(ArrayType(ScalarType.i64)))),
(
"field4",
OptionType(
StructType(
"some2",
NonEmptyMap.of(
("innerfield1", OptionType(ScalarType.u32)),
("innerfield2", ArrayType(ScalarType.i16))
)
)
)
)
)
)
private def validate(aquaType: Type, jsonType: Type) = {
TypeValidator.validateTypes("some", aquaType, Some(jsonType))
}
"type validator" should "return invalid result if check same type" in {
val res = validate(aquaType, aquaType)
res.isValid shouldBe false
}
"type validator" should "validate optional types properly" in {
val aquaOptionalArrArrType = OptionType(ArrayType(ArrayType(ScalarType.u8)))
val aquaOptionalArrType = OptionType(ArrayType(ScalarType.u8))
val aquaOptionalType = OptionType(ScalarType.u8)
val res1 = validate(aquaOptionalType, LiteralType.number)
res1.isValid shouldBe true
val res2 = validate(aquaOptionalArrType, ArrayType(LiteralType.number))
res2.isValid shouldBe true
val res3 = validate(aquaOptionalArrArrType, ArrayType(ArrayType(LiteralType.number)))
res3.isValid shouldBe true
val res1Invalid = validate(aquaOptionalType, ArrayType(LiteralType.number))
res1Invalid.isValid shouldBe false
}
"type validator" should "validate array types properly" in {
val aquaArrArrArrType = ArrayType(ArrayType(ArrayType(ScalarType.u8)))
val aquaArrArrType = ArrayType(ArrayType(ScalarType.u8))
val aquaArrType = ArrayType(ScalarType.u8)
val res1 = validate(aquaArrType, ArrayType(LiteralType.number))
res1.isValid shouldBe true
val res2 = validate(aquaArrArrType, ArrayType(ArrayType(LiteralType.number)))
res2.isValid shouldBe true
val res3 = validate(aquaArrArrArrType, ArrayType(ArrayType(ArrayType(LiteralType.number))))
res3.isValid shouldBe true
val res1invalid = validate(aquaArrType, LiteralType.number)
res1invalid.isValid shouldBe true
val res2invalid = validate(aquaArrArrType, ArrayType(LiteralType.number))
res2invalid.isValid shouldBe true
}
"type validator" should "validate options with arrays types properly" in {
val aquaOptArrOptArrType = OptionType(ArrayType(OptionType(ArrayType(ScalarType.u8))))
val res1 = validate(aquaOptArrOptArrType, ArrayType(ArrayType(LiteralType.number)))
res1.isValid shouldBe true
val res1invalid =
validate(aquaOptArrOptArrType, ArrayType(ArrayType(ArrayType(LiteralType.number))))
res1invalid.isValid shouldBe false
val res2invalid =
validate(aquaOptArrOptArrType, ArrayType(ArrayType(ArrayType(ArrayType(LiteralType.number)))))
res2invalid.isValid shouldBe false
}
"type validator" should "validate complex types properly" in {
val res1 = validate(
aquaType,
StructType(
"some",
NonEmptyMap.of(
("field1", ArrayType(LiteralType.number)),
("field2", ArrayType(LiteralType.number)),
("field3", ArrayType(ArrayType(ArrayType(LiteralType.number)))),
(
"field4",
StructType(
"some2",
NonEmptyMap.of(
("innerfield1", LiteralType.number),
("innerfield2", ArrayType(LiteralType.number))
)
)
)
)
)
)
res1.isValid shouldBe true
}
"type validator" should "return invalid if there is no field" in {
val structType = StructType(
"some",
NonEmptyMap.of(
("field1", ScalarType.u8),
("field2", ScalarType.string),
("field3", OptionType(ScalarType.string))
)
)
val res1invalid = validate(
structType,
StructType(
"some",
NonEmptyMap.of(
("field2", LiteralType.string)
)
)
)
res1invalid.isValid shouldBe false
val res2invalid = validate(
structType,
StructType(
"some",
NonEmptyMap.of(
("field1", ScalarType.u8)
)
)
)
res2invalid.isValid shouldBe false
val res1 = validate(
structType,
StructType(
"some",
NonEmptyMap.of(
("field1", LiteralType.number),
("field2", LiteralType.string)
)
)
)
res1.isValid shouldBe true
validate(structType, StructType(
"some",
NonEmptyMap.of(
("field1", ScalarType.u8),
("field2", ScalarType.string),
("field3", ScalarType.string)
)
)).isValid shouldBe true
}
}