Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partialy implement validation for valid key #307

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends
val text = valid match {
case Some(v) =>
v match {
case ValidationEq(expected) =>
s"must be equal to ${expression(expected, fullPortName, STYLE_EDGE_VALID)}"
case expected: ValidationEquals =>
s"must be equal to ${expression(expected.value, fullPortName, STYLE_EDGE_VALID)}"
case ValidationMin(min) =>
s"must be at least ${expression(min, fullPortName, STYLE_EDGE_VALID)}"
case ValidationMax(max) =>
Expand Down Expand Up @@ -517,12 +517,17 @@ object GraphvizClassCompiler extends LanguageCompilerStatic {

private def fixedBytes(blt: BytesLimitType, valid: Option[ValidationSpec]): Option[String] = {
valid match {
case Some(ValidationEq(Ast.expr.List(contents)))
if blt.size == Ast.expr.IntNum(contents.length) =>
Some(contents.map(_ match {
case Ast.expr.IntNum(byteVal) if byteVal >= 0x00 && byteVal <= 0xff => "%02X".format(byteVal)
case _ => return None
}).mkString(" "))
case Some(expected: ValidationEquals) => {
expected.value match {
case Ast.expr.List(contents) if blt.size == Ast.expr.IntNum(contents.length) => {
Some(contents.map(_ match {
case Ast.expr.IntNum(byteVal) if byteVal >= 0x00 && byteVal <= 0xff => "%02X".format(byteVal)
case _ => return None
}).mkString(" "))
}
case _ => None
}
}
case _ => None
}
}
Expand Down
19 changes: 13 additions & 6 deletions shared/src/main/scala/io/kaitai/struct/format/ValidationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@ import io.kaitai.struct.problems.KSYParseError

sealed trait ValidationSpec

case class ValidationEq(value: Ast.expr) extends ValidationSpec
sealed abstract class ValidationEquals(val value: Ast.expr) extends ValidationSpec
/** `valid: <...>` key */
case class Validation(_value: Ast.expr) extends ValidationEquals(_value)
/** `valid: eq: <...>` key */
case class ValidationEq(_value: Ast.expr) extends ValidationEquals(_value)
/** `contents: <...>` key */
case class ValidationContents(_value: Ast.expr) extends ValidationEquals(_value)

case class ValidationMin(min: Ast.expr) extends ValidationSpec
case class ValidationMax(max: Ast.expr) extends ValidationSpec
case class ValidationRange(min: Ast.expr, max: Ast.expr) extends ValidationSpec
case class ValidationAnyOf(values: List[Ast.expr]) extends ValidationSpec
case class ValidationInEnum() extends ValidationSpec
case class ValidationExpr(checkExpr: Ast.expr) extends ValidationSpec

object ValidationEq {
object ValidationEquals {
val LEGAL_KEYS = Set("eq")

def fromMap(src: Map[String, Any], path: List[String]): Option[ValidationEq] =
def fromMap(src: Map[String, Any], path: List[String]): Option[ValidationEquals] =
ParseUtils.getOptValueExpression(src, "eq", path).map { case eqExpr =>
ParseUtils.ensureLegalKeys(src, LEGAL_KEYS, path)
ValidationEq(eqExpr)
Expand Down Expand Up @@ -89,7 +96,7 @@ object ValidationExpr {

object ValidationSpec {
val LEGAL_KEYS =
ValidationEq.LEGAL_KEYS ++
ValidationEquals.LEGAL_KEYS ++
ValidationRange.LEGAL_KEYS ++
ValidationAnyOf.LEGAL_KEYS ++
ValidationInEnum.LEGAL_KEYS ++
Expand All @@ -113,10 +120,10 @@ object ValidationSpec {
}

def fromString(value: String, path: List[String]): ValidationSpec =
ValidationEq(Expressions.parse(value))
Validation(Expressions.parse(value))

def fromMap(src: Map[String, Any], path: List[String]): ValidationSpec = {
val opt1 = ValidationEq.fromMap(src, path)
val opt1 = ValidationEquals.fromMap(src, path)
if (opt1.nonEmpty)
return opt1.get
val opt2 = ValidationRange.fromMap(src, path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ trait ValidateOps extends ExceptionNames {
def attrValidate(attr: AttrLikeSpec, valid: ValidationSpec): Unit = {
val itemValue = Identifier.itemExpr(attr.id, attr.cond.repeat)
valid match {
case ValidationEq(expected) =>
attrValidateExprCompare(attr, Ast.cmpop.Eq, expected, ValidationNotEqualError(attr.dataType))
case expected: ValidationEquals =>
attrValidateExprCompare(attr, Ast.cmpop.Eq, expected.value, ValidationNotEqualError(attr.dataType))
case ValidationMin(min) =>
attrValidateExprCompare(attr, Ast.cmpop.GtE, min, ValidationLessThanError(attr.dataType))
case ValidationMax(max) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ class TypeValidator(specs: ClassSpecs) extends PrecompileStep {

val problemsDataType = validateDataType(attr.dataType, path)

List(problemsIf, problemsRepeat, problemsDataType).flatten
val problemsValid: Iterable[CompilationProblem] = attr.valid match {
case Some(valid) => validateValidClause(valid, attr.dataType, attr.path)
case None => None // all good
}

List(problemsIf, problemsRepeat, problemsDataType, problemsValid).flatten
}

def validateParseInstance(pis: ParseInstanceSpec): Iterable[CompilationProblem] = {
Expand All @@ -100,7 +105,12 @@ class TypeValidator(specs: ClassSpecs) extends PrecompileStep {
case None => None // all good
}

List(problemsAttr, problemsIo, problemsPos).flatten
val problemsValid: Iterable[CompilationProblem] = pis.valid match {
case Some(valid) => validateValidClause(valid, pis.dataType, pis.path)
case None => None // all good
}

List(problemsAttr, problemsIo, problemsPos, problemsValid).flatten
}

def validateValueInstance(vis: ValueInstanceSpec): Option[CompilationProblem] = {
Expand Down Expand Up @@ -204,6 +214,46 @@ class TypeValidator(specs: ClassSpecs) extends PrecompileStep {
}
}

/**
* @param valid The `valid` clause of Kaitai Struct attribute or parse instance definition
* @param attrType The type of attribute, used to check compatibility of valid expressions to that type
* @param path Path where `valid` key is defined
*/
private def validateValidClause(
valid: ValidationSpec,
attrType: DataType,
path: List[String]
): Iterable[CompilationProblem] = {
// TODO: This is not user-face message
val expected = attrType.toString
val validPath = path :+ "valid"
valid match {
case Validation(value) =>
checkAssertObject(value, attrType, expected, path, "valid")
case ValidationEq(value) =>
checkAssertObject(value, attrType, expected, validPath, "eq")
case ValidationContents(value) =>
checkAssertObject(value, attrType, expected, path, "contents")
case ValidationMin(min) =>
checkAssertObject(min, attrType, expected, validPath, "min")
case ValidationMax(max) =>
checkAssertObject(max, attrType, expected, validPath, "max")
case ValidationRange(min, max) => {
checkAssertObject(min, attrType, expected, validPath, "min") ++
checkAssertObject(max, attrType, expected, validPath, "max")
}
case ValidationAnyOf(values) => {
val itemPath = validPath :+ "any-of"
values.zipWithIndex.flatMap { case (value, i) =>
checkAssertObject(value, attrType, expected, itemPath, i.toString)
}
}
case ValidationInEnum() => List()
case ValidationExpr(checkExpr) =>
checkAssert[BooleanType](checkExpr, "boolean", validPath, "expr")
}
}

/**
* Checks that expression's type conforms to a given datatype, otherwise
* throw a human-readable exception, with some pointers that would help
Expand Down
Loading