mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
Type validation for types generated from JSON (#530)
This commit is contained in:
parent
8cb0fec2b0
commit
198e339694
@ -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'"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
169
cli/.js/src/test/scala/aqua/TypeValidatorSpec.scala
Normal file
169
cli/.js/src/test/scala/aqua/TypeValidatorSpec.scala
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user