diff --git a/typed-json/src/main/scala/frawa/typedjson/eval/Verify.scala b/typed-json/src/main/scala/frawa/typedjson/eval/Verify.scala index 5ff4eb31..85fbd358 100644 --- a/typed-json/src/main/scala/frawa/typedjson/eval/Verify.scala +++ b/typed-json/src/main/scala/frawa/typedjson/eval/Verify.scala @@ -77,13 +77,13 @@ 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) @@ -91,15 +91,15 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[O]): 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 @@ -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 } } @@ -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 ) @@ -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))) @@ -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))) @@ -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( @@ -494,7 +501,7 @@ 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 @@ -502,7 +509,7 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[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]]], @@ -541,7 +548,7 @@ 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 @@ -549,7 +556,7 @@ class Verify[R[_], O](using TheResultMonad[R, O], OutputOps[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)))) @@ -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("?"))) } diff --git a/typed-json/src/main/scala/frawa/typedjson/output/BasicOutput.scala b/typed-json/src/main/scala/frawa/typedjson/output/BasicOutput.scala index dfa32e74..cf1c7bf9 100644 --- a/typed-json/src/main/scala/frawa/typedjson/output/BasicOutput.scala +++ b/typed-json/src/main/scala/frawa/typedjson/output/BasicOutput.scala @@ -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 @@ -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, @@ -82,7 +82,6 @@ object BasicOutput: BasicOutput( valid, annotations, - error = error.filterNot(_ => valid), errors = errors, instanceLocation = pointer ) @@ -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 diff --git a/typed-json/src/main/scala/frawa/typedjson/output/DetailedOutput.scala b/typed-json/src/main/scala/frawa/typedjson/output/DetailedOutput.scala index 300b7185..f2bc79ff 100644 --- a/typed-json/src/main/scala/frawa/typedjson/output/DetailedOutput.scala +++ b/typed-json/src/main/scala/frawa/typedjson/output/DetailedOutput.scala @@ -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 @@ -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 @@ -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 diff --git a/typed-json/src/main/scala/frawa/typedjson/output/FlagOutput.scala b/typed-json/src/main/scala/frawa/typedjson/output/FlagOutput.scala index edf202f9..a5d26243 100644 --- a/typed-json/src/main/scala/frawa/typedjson/output/FlagOutput.scala +++ b/typed-json/src/main/scala/frawa/typedjson/output/FlagOutput.scala @@ -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, @@ -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 diff --git a/typed-json/src/main/scala/frawa/typedjson/output/OutputOps.scala b/typed-json/src/main/scala/frawa/typedjson/output/OutputOps.scala index 8f4e878e..435a7cad 100644 --- a/typed-json/src/main/scala/frawa/typedjson/output/OutputOps.scala +++ b/typed-json/src/main/scala/frawa/typedjson/output/OutputOps.scala @@ -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 @@ -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 diff --git a/typed-json/src/main/scala/frawa/typedjson/output/SimpleOutput.scala b/typed-json/src/main/scala/frawa/typedjson/output/SimpleOutput.scala index 3cc7b7f9..7efb7f40 100644 --- a/typed-json/src/main/scala/frawa/typedjson/output/SimpleOutput.scala +++ b/typed-json/src/main/scala/frawa/typedjson/output/SimpleOutput.scala @@ -45,7 +45,7 @@ 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 @@ -53,7 +53,7 @@ object SimpleOutput: else Seq() SimpleOutput( valid, - error.map(WithPointer(_, pointer)).toSeq ++ os.flatMap(_.errors), + os.flatMap(_.errors), pointer, annotations ) @@ -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 diff --git a/typed-json/src/main/scala/frawa/typedjson/suggest/SuggestOutput.scala b/typed-json/src/main/scala/frawa/typedjson/suggest/SuggestOutput.scala index da15dc97..30262c5c 100644 --- a/typed-json/src/main/scala/frawa/typedjson/suggest/SuggestOutput.scala +++ b/typed-json/src/main/scala/frawa/typedjson/suggest/SuggestOutput.scala @@ -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) @@ -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))