Skip to content
Draft
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
60 changes: 34 additions & 26 deletions typed-json/src/main/scala/frawa/typedjson/eval/Verify.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,29 +77,29 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):

private def funAll(fs: Seq[Fun[R[O]]]): Fun[R[O]] = value =>
val ros = fs.map(f => f(value))
FP.sequence(ros).map(os => ops.all(os, None, value.pointer))
FP.sequence(ros).map(os => ops.all(os, value.pointer))

private def funMap[A, B](fun: Fun[R[A]])(f: (A, Pointer) => B): Fun[R[B]] =
value => fun(value).map(a => f(a, value.pointer))

private def verifyAllOf(os: Seq[O], pointer: Pointer): O =
ops.all(os, None, pointer)
ops.all(os, pointer)

private def verifyNot(o: O, pointer: Pointer): O =
o.not(pointer)

private def verifyOneOf(os: Seq[O], pointer: Pointer): O =
val count = os.count(_.isValid)
val annotations = OutputOps.mergeAnnotations(os.filter(_.isValid).flatMap(_.getAnnotations()))
if count == 1 then ops.valid(pointer).withAnnotations(annotations)
else if count == 0 then ops.all(os, None, pointer) // TODO new error NoneOf?
else ops.invalid(NotOneOf(count), pointer)
if count == 1 then ops.valid(pointer).withAnnotations(annotations).isAggregating(os)
else if count == 0 then ops.all(os, pointer) // TODO new error NoneOf?
else ops.invalid(NotOneOf(count), pointer).isAggregating(os)

private def verifyAnyOf(os: Seq[O], pointer: Pointer): O =
val valid = os.exists(_.isValid)
val annotations = OutputOps.mergeAnnotations(os.filter(_.isValid).flatMap(_.getAnnotations()))
if valid then ops.valid(pointer).withAnnotations(annotations)
else ops.all(os, None, pointer) // TODO new error NoneOf?
if valid then ops.valid(pointer).withAnnotations(annotations).isAggregating(os)
else ops.all(os, pointer) // TODO new error NoneOf?

private def verifyNumberValue(
error: => ValidationError
Expand Down Expand Up @@ -201,8 +201,9 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
}
.map { ros =>
FP.sequence(ros).map { os =>
val o = ops.all(os, None, value.pointer)
if o.isValid then o.withAnnotation(EvaluatedIndices(Seq.range(0, os.size).toSet))
val o = ops.all(os, value.pointer)
if o.isValid then
o.withAnnotation(EvaluatedIndices(Seq.range(0, os.size).toSet)).isAggregating(os)
else o
}
}
Expand Down Expand Up @@ -243,7 +244,7 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
.map(_._2)
.toSet
val os = osWithProp.map(_._1)
val o = ops.all(os, None, value.pointer)
val o = ops.all(os, value.pointer)
if o.isValid then o.withAnnotation(EvaluatedProperties(validProperties))
else o
)
Expand Down Expand Up @@ -276,19 +277,25 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
funThen
.map(_(value))
.map(roThen =>
roThen.map { oThen => ops.all(Seq(condition, oThen), None, value.pointer) }
roThen.map { oThen => ops.all(Seq(condition, oThen), value.pointer) }
)
else
funElse
.map(_(value))
.map(roElse =>
roElse.map { oElse =>
ops.all(Seq(condition.not(value.pointer), oElse), None, value.pointer)
ops.all(Seq(condition.not(value.pointer), oElse), value.pointer)
}
)
thenOrElse.getOrElse(
monad.unit(ops.valid(value.pointer).withAnnotations(condition.getAnnotations()))
)
thenOrElse.getOrElse {
val os = Seq()
monad.unit(
ops
.valid(value.pointer)
.withAnnotations(condition.getAnnotations())
.isAggregating(os)
)
}
}
}
.getOrElse(monad.unit(ops.valid(value.pointer)))
Expand Down Expand Up @@ -340,7 +347,7 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
}.toSeq
// val validNames = os.filter(_._2.isValid).map(_._1).toSet
// TODO annotation EvaluatedProperties() with validNames
FP.sequence(ros.map(_._2)).map(os => ops.all(os, None, value.pointer))
FP.sequence(ros.map(_._2)).map(os => ops.all(os, value.pointer))
}
.getOrElse(monad.unit(ops.valid(value.pointer)))

Expand Down Expand Up @@ -426,7 +433,7 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
val required = vs.keySet.flatMap(schemas.get)
required.map(_(value)).toSeq
}
.map(ros => FP.sequence(ros).map(os => ops.all(os, None, value.pointer)))
.map(ros => FP.sequence(ros).map(os => ops.all(os, value.pointer)))
.getOrElse(monad.unit(ops.valid(value.pointer)))

def verifyContains(
Expand Down Expand Up @@ -494,15 +501,15 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
val rosRemaining = remainingIndexed.map(unevaluated)
val ros1 = ros ++ rosRemaining
FP.sequence(ros1)
.map(os => ops.all(os, None, value.pointer))
.map(os => ops.all(os, value.pointer))
.map { o =>
if o.isValid then o.withAnnotation(EvaluatedIndices(all))
else o
}
}
ro1
}
.getOrElse(FP.sequence(ros).map(os => ops.all(os, None, value.pointer)))
.getOrElse(FP.sequence(ros).map(os => ops.all(os, value.pointer)))

def verifyUnevaluatedProperties(
pushed: Seq[Fun[R[O]]],
Expand Down Expand Up @@ -541,15 +548,15 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
val rosRemaining = remainingValued.map(unevaluated)
val ros1 = ros ++ rosRemaining
FP.sequence(ros1)
.map(os => ops.all(os, None, value.pointer))
.map(os => ops.all(os, value.pointer))
.map { o =>
if o.isValid then o.withAnnotation(EvaluatedProperties(all))
else o
}
}
ro1
}
.getOrElse(FP.sequence(ros).map(os => ops.all(os, None, value.pointer)))
.getOrElse(FP.sequence(ros).map(os => ops.all(os, value.pointer)))

def verifyIgnored(keyword: String): Fun[R[O]] =
funUnit(value => ops.valid(value.pointer).withAnnotation(Ignored(Set(keyword))))
Expand All @@ -558,9 +565,10 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]):
fun(value).map { o =>
if o.isValid then o
else
ops.all(
Seq(o),
Some(AdditionalPropertyInvalid(value.pointer.targetField.getOrElse("?"))),
value.pointer
)
ops
.all(
Seq(o),
value.pointer
)
.withError(AdditionalPropertyInvalid(value.pointer.targetField.getOrElse("?")))
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object BasicOutput:
def invalid(error: ValidationError, pointer: Pointer): BasicOutput =
BasicOutput(false, error = Some(error), instanceLocation = pointer)

def all(os: Seq[BasicOutput], error: Option[ValidationError], pointer: Pointer): BasicOutput =
def all(os: Seq[BasicOutput], pointer: Pointer): BasicOutput =
val valid = os.forall(_.valid)
val annotations =
if valid then
Expand All @@ -69,7 +69,7 @@ object BasicOutput:
)
self +: o.errors
}
if errors.size == 1 && error.isEmpty then
if errors.size == 1 then
// TODO realy?
BasicOutput(
valid,
Expand All @@ -82,7 +82,6 @@ object BasicOutput:
BasicOutput(
valid,
annotations,
error = error.filterNot(_ => valid),
errors = errors,
instanceLocation = pointer
)
Expand All @@ -102,3 +101,7 @@ object BasicOutput:
// new RuntimeException().printStackTrace()
o
else o.copy(keywordLocation = Some(kl))
def withError(error: ValidationError): BasicOutput =
o.copy(error = Some(error))
def isAggregating(os: Seq[BasicOutput]): BasicOutput =
o
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ object DetailedOutput:
def invalid(error: ValidationError, pointer: Pointer): DetailedOutput =
DetailedOutput(false, error = Some(error), instanceLocation = pointer)

def all(
os: Seq[DetailedOutput],
error: Option[ValidationError],
pointer: Pointer
): DetailedOutput =
def all(os: Seq[DetailedOutput], pointer: Pointer): DetailedOutput =
val valid = os.forall(_.valid)
val annotations =
if valid then
Expand All @@ -58,13 +54,12 @@ object DetailedOutput:
val errors =
if valid then Seq()
else os.filterNot(_.valid)
if errors.size == 1 && error.isEmpty then
if errors.size == 1 then
// TODO really?
errors(0)
else
DetailedOutput(
valid,
error = error,
errors = errors,
instanceLocation = pointer,
annotations = annotations
Expand All @@ -85,3 +80,7 @@ object DetailedOutput:
// new RuntimeException().printStackTrace()
o
else o.copy(keywordLocation = Some(kl))
def withError(error: ValidationError): DetailedOutput =
o.copy(error = Some(error))
def isAggregating(os: Seq[DetailedOutput]): DetailedOutput =
o
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object FlagOutput:
def valid(pointer: Pointer): FlagOutput = FlagOutput(true, pointer)
def invalid(error: ValidationError, pointer: Pointer): FlagOutput = FlagOutput(false, pointer)

def all(os: Seq[FlagOutput], error: Option[ValidationError], pointer: Pointer): FlagOutput =
def all(os: Seq[FlagOutput], pointer: Pointer): FlagOutput =
FlagOutput(
os.forall(_.valid),
pointer,
Expand All @@ -51,3 +51,5 @@ object FlagOutput:
o.copy(annotations = o.annotations ++ annotations)
def getAnnotations(): Seq[Annotation] = o.annotations
def forKeyword(kl: KeywordLocation, k: Option[Keyword] = None): FlagOutput = o
def withError(error: ValidationError): FlagOutput = o
def isAggregating(os: Seq[FlagOutput]): FlagOutput = o
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ trait OutputOps[O]: // extends Monoid[O]:
def valid(pointer: Pointer): O
def invalid(error: ValidationError, pointer: Pointer): O

def all(os: Seq[O], error: Option[ValidationError], pointer: Pointer): O
def all(os: Seq[O], pointer: Pointer): O

extension (o: O)
def not(pointer: Pointer): O
Expand All @@ -42,6 +42,8 @@ trait OutputOps[O]: // extends Monoid[O]:
def withAnnotations(annotations: Seq[Annotation]): O
def getAnnotations(): Seq[Annotation]
def forKeyword(kl: KeywordLocation, k: Option[Keyword] = None): O
def withError(error: ValidationError): O
def isAggregating(os: Seq[O]): O

object OutputOps:
trait Annotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ object SimpleOutput:
def invalid(error: ValidationError, pointer: Pointer): SimpleOutput =
SimpleOutput(false, Seq(WithPointer(error, pointer)), pointer)

def all(os: Seq[SimpleOutput], error: Option[ValidationError], pointer: Pointer): SimpleOutput =
def all(os: Seq[SimpleOutput], pointer: Pointer): SimpleOutput =
val valid = os.forall(_.valid)
val annotations =
if valid then
OutputOps.mergeAnnotations(os.filter(_.pointer == pointer).flatMap(_.annotations))
else Seq()
SimpleOutput(
valid,
error.map(WithPointer(_, pointer)).toSeq ++ os.flatMap(_.errors),
os.flatMap(_.errors),
pointer,
annotations
)
Expand All @@ -72,3 +72,7 @@ object SimpleOutput:
o.copy(annotations = o.annotations ++ annotations)
def getAnnotations(): Seq[Annotation] = o.annotations
def forKeyword(kl: KeywordLocation, k: Option[Keyword] = None): SimpleOutput = o
def withError(error: ValidationError): SimpleOutput =
o.copy(errors = WithPointer(error, o.pointer) +: o.errors)
def isAggregating(os: Seq[SimpleOutput]): SimpleOutput =
o
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,19 @@ object SuggestOutput:
private val bops = summon[OutputOps[SimpleOutput]]
private val isAt = (pointer: Pointer) => at == pointer

// TODO avoid cheating via local mutation
import scala.collection.mutable
private val keywordsAt = mutable.ArrayBuffer.empty[Keyword]

def valid(pointer: Pointer): SuggestOutput =
SuggestOutput(bops.valid(pointer), keywordsAt.toSeq.distinct)
SuggestOutput(bops.valid(pointer))

def invalid(error: ValidationError, pointer: Pointer): SuggestOutput =
SuggestOutput(bops.invalid(error, pointer), keywordsAt.toSeq.distinct)
SuggestOutput(bops.invalid(error, pointer))

def all(
os: Seq[SuggestOutput],
error: Option[ValidationError],
pointer: Pointer
): SuggestOutput =
SuggestOutput(
bops.all(os.map(_.simple), error, pointer),
// os.flatMap(_.keywords)
keywordsAt.toSeq.distinct
bops.all(os.map(_.simple), pointer),
os.flatMap(_.keywords)
)

extension (o: SuggestOutput)
Expand All @@ -75,15 +69,15 @@ object SuggestOutput:
// TODO avoid restoring WithLocation
k match {
case _: WithLocation =>
keywordsAt.addOne(k)
// o.copy(keywords = o.keywords :+ k)
o
o.copy(keywords = o.keywords :+ k)
case _ =>
val k_ = WithLocation(k, kl)
keywordsAt.addOne(k_)
o
// o.copy(keywords = o.keywords :+ k_)
o.copy(keywords = o.keywords :+ k_)
}
}
.getOrElse(o)
else o
def withError(error: ValidationError): SuggestOutput =
o.copy(simple = o.simple.withError(error))
def isAggregating(os: Seq[SuggestOutput]): SuggestOutput =
o.copy(keywords = os.flatMap(_.keywords))