cats.data.Validated.Invalid Scala Examples

The following examples show how to use cats.data.Validated.Invalid. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.
Example 1
Source File: SimulatePlanAppIT.scala    From Scala-Programming-Projects   with MIT License 5 votes vote down vote up
package retcalc

import cats.data.Validated.{Invalid, Valid}
import org.scalactic.TypeCheckedTripleEquals
import org.scalatest.{Matchers, WordSpec}

class SimulatePlanAppIT extends WordSpec with Matchers with TypeCheckedTripleEquals {
  "SimulatePlanApp.strMain" should {
    "simulate a retirement plan using market returns" in {
      val actualResult = SimulatePlanApp.strMain(
        Array("1952.09,2017.09", "25", "40", "3000", "2000", "10000"))

      val expectedResult =
        s"""
           |Capital after 25 years of savings:    468925
           |Capital after 40 years in retirement: 2958842
           |""".stripMargin
      actualResult should ===(Valid(expectedResult))
    }

    "return an error when the period exceeds the returns bounds" in {
      val actualResult = SimulatePlanApp.strMain(
        Array("1952.09,2017.09", "25", "60", "3000", "2000", "10000"))
      val expectedResult = "Cannot get the return for month 780. Accepted range: 0 to 779"
      actualResult should ===(Invalid(expectedResult))
    }

    "return an usage example when the number of arguments is incorrect" in {
      val result = SimulatePlanApp.strMain(
        Array("1952.09:2017.09", "25.0", "60", "3'000", "2000.0"))
      result should ===(Invalid(
        """Usage:
          |simulatePlan from,until nbOfYearsSaving nbOfYearsRetired netIncome currentExpenses initialCapital
          |
          |Example:
          |simulatePlan 1952.09,2017.09 25 40 3000 2000 10000
          |""".stripMargin))
    }

    "return several errors when several arguments are invalid" in {
      val result = SimulatePlanApp.strMain(
        Array("1952.09:2017.09", "25.0", "60", "3'000", "2000.0", "10000"))
      result should ===(Invalid(
        """Invalid format for fromUntil. Expected: from,until, actual: 1952.09:2017.09
          |Invalid number for nbOfYearsSaving: 25.0
          |Invalid number for netIncome: 3'000
          |Invalid number for currentExpenses: 2000.0""".stripMargin))
    }
  }
} 
Example 2
Source File: fileValidation.scala    From sbt-org-policies   with Apache License 2.0 5 votes vote down vote up
package sbtorgpolicies.settings

import cats.data.Validated.{Invalid, Valid}
import sbt.Keys._
import sbt._
import sbtorgpolicies.exceptions.ValidationException
import sbtorgpolicies.rules._
import sbtorgpolicies.OrgPoliciesKeys._
import sbtorgpolicies.templates.FileType

trait fileValidation extends ValidationFunctions {

  val fileValidation = new FileValidation

  val orgFileValidationTasks = Seq(
    orgValidateFiles := Def.task {
      val baseDirFile: File = (baseDirectory in LocalRootProject).value
      onlyRootUnitTask(baseDirectory.value, baseDirFile, streams.value.log) {
        val files: List[FileType] = orgEnforcedFilesSetting.value
        val validations: List[Validation] = files.flatMap {
          case FileType(true, _, _, _, path, _, _, list) =>
            List(
              mkValidation(
                (baseDirFile / path).getAbsolutePath,
                if (list.isEmpty) List(emptyValidation)
                else list
              )
            )
          case _ =>
            Nil
        }
        validationFilesTask(validations, streams.value.log)
      }
    }.value
  )

  private[this] def validationFilesTask(list: List[Validation], log: Logger): Unit =
    list foreach (validationFileTask(_, log))

  private[this] def validationFileTask(validation: Validation, log: Logger): Unit = {

    def errorHandler(description: String, errorList: List[ValidationException]): Unit = {
      val errorMessage =
        s"""$description
           |${errorList map (e => s" - ${e.message}") mkString "\n"}
         """.stripMargin
      if (validation.policyLevel == PolicyWarning) log.warn(errorMessage)
      else {
        throw ValidationException(errorMessage)
      }
    }

    fileValidation.validateFile(
      validation.validationRule.inputPath,
      validation.validationRule.validationList: _*
    ) match {
      case Valid(_) =>
        log.info(s"File ${validation.validationRule.inputPath} was validated successfully")
      case Invalid(errors) =>
        errorHandler(
          s"Some errors where found while validating ${validation.validationRule.inputPath}:",
          errors.toList
        )
    }

  }

} 
Example 3
Source File: HealthCheckRoutes.scala    From healthchecks   with MIT License 5 votes vote down vote up

package com.github.everpeace.healthchecks.route

import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.PathDirectives
import akka.http.scaladsl.server.{PathMatchers, Route}
import cats.data.Validated.{Invalid, Valid}
import com.github.everpeace.healthchecks.{HealthCheck, HealthCheckResult}
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
import io.circe.JsonObject
import io.circe.generic.JsonCodec
import io.circe.generic.auto._

import scala.collection.convert.DecorateAsScala
import scala.concurrent.{ExecutionContext, Future}

object HealthCheckRoutes extends DecorateAsScala {

  @JsonCodec case class HealthCheckResultJson(
      name: String,
      severity: String,
      status: String,
      messages: List[String])

  @JsonCodec case class ResponseJson(status: String, check_results: List[HealthCheckResultJson])

  private def status(s: Boolean) = if (s) "healthy" else "unhealthy"

  private def statusCode(s: Boolean) = if (s) OK else ServiceUnavailable

  private def toResultJson(check: HealthCheck, result: HealthCheckResult) =
    HealthCheckResultJson(
      check.name,
      check.severity.toString,
      status(result.isValid),
      result match {
        case Valid(_)        => List()
        case Invalid(errors) => errors.toList
      }
    )

  def health(
      checks: HealthCheck*
    )(implicit
      ec: ExecutionContext
    ): Route = health("health", checks.toList)

  def health(
      path: String,
      checks: List[HealthCheck]
    )(implicit
      ec: ExecutionContext
    ): Route = {
    require(checks.nonEmpty, "checks must not empty.")
    require(
      checks.map(_.name).toSet.size == checks.length,
      s"HealthCheck name should be unique (given HealthCheck names = [${checks.map(_.name).mkString(",")}])."
    )
    val rootSlashRemoved =
      if (path.startsWith("/")) path.substring(1) else path
    PathDirectives.path(PathMatchers.separateOnSlashes(rootSlashRemoved)) {
      parameter("full" ? false) { full =>
        get {
          def isHealthy(checkAndResults: List[(HealthCheck, HealthCheckResult)]) =
            checkAndResults.forall(cr => cr._2.isValid || (!cr._1.severity.isFatal))
          val checkAndResultsFuture = Future.traverse(checks) { c =>
            c.run().map(c -> _)
          }
          if (full) {
            complete {
              checkAndResultsFuture.map { checkAndResults =>
                val healthy = isHealthy(checkAndResults)
                statusCode(healthy) -> ResponseJson(
                  status(healthy),
                  checkAndResults.map {
                    case (check, result) => toResultJson(check, result)
                  }
                )
              }
            }
          } else {
            complete {
              checkAndResultsFuture.map { checkAndResults =>
                statusCode(isHealthy(checkAndResults)) -> JsonObject.empty
              }
            }
          }
        }
      }
    }
  }
} 
Example 4
Source File: ValidationExtension.scala    From franklin   with Apache License 2.0 5 votes vote down vote up
package com.azavea.franklin.extensions.validation

import cats.data.NonEmptyList
import cats.data.Validated.{Invalid, Valid}
import cats.kernel.Semigroup
import com.azavea.stac4s.extensions.ItemAssetExtension
import com.azavea.stac4s.extensions.{ExtensionResult, ItemExtension, LinkExtension}
import eu.timepit.refined.types.string.NonEmptyString
import io.circe._
import io.circe.refined._
import io.circe.syntax._

final case class ValidationExtension(
    attemptedExtensions: NonEmptyList[NonEmptyString],
    errors: List[NonEmptyString]
)

object ValidationExtension {

  implicit val decValidationExtension: Decoder[ValidationExtension] = Decoder.forProduct2(
    "validation:attemptedExtensions",
    "validation:errors"
  )((extensions: NonEmptyList[NonEmptyString], errors: List[NonEmptyString]) =>
    ValidationExtension(extensions, errors)
  )

  implicit val encValidationExtension: Encoder.AsObject[ValidationExtension] = Encoder
    .AsObject[Map[String, Json]]
    .contramapObject((validationExtensionFields: ValidationExtension) =>
      Map(
        "validation:attemptedExtensions" -> validationExtensionFields.attemptedExtensions.asJson,
        "validation:errors"              -> validationExtensionFields.errors.asJson
      )
    )

  implicit val validationExtensionItemExtension: ItemExtension[ValidationExtension] =
    ItemExtension.instance

  implicit val validationExtensionLinkExtension: LinkExtension[ValidationExtension] =
    LinkExtension.instance

  implicit val validationExtensionAssetExtension: ItemAssetExtension[ValidationExtension] =
    ItemAssetExtension.instance

  implicit val semigroupValidationExtension: Semigroup[ValidationExtension] =
    new Semigroup[ValidationExtension] {

      def combine(x: ValidationExtension, y: ValidationExtension): ValidationExtension = {
        ValidationExtension(
          x.attemptedExtensions.concat(y.attemptedExtensions.toList),
          x.errors ++ y.errors
        )
      }
    }

  def success(name: NonEmptyString) = ValidationExtension(
    NonEmptyList.of(name),
    Nil
  )

  def failure(name: NonEmptyString, errors: List[DecodingFailure]) =
    ValidationExtension(NonEmptyList.of(name), errors map { (err: DecodingFailure) =>
      NonEmptyString.from(CursorOp.opsToPath(err.history))
    } collect {
      case Right(v) => v
    })

  def fromResult[T](result: ExtensionResult[T], name: NonEmptyString) = result match {
    case Invalid(errs) =>
      failure(name, errs collect {
        case e: DecodingFailure => e
      })
    case Valid(_) =>
      success(name)
  }
} 
Example 5
Source File: ConfigReaderInstances.scala    From DataQuality   with GNU Lesser General Public License v3.0 5 votes vote down vote up
package com.agilelab.dataquality.common.instances

import cats.data.Validated.{Invalid, Valid}
import cats.data.{NonEmptyList, ValidatedNel}
import cats.implicits._
import com.agilelab.dataquality.common.enumerations.DBTypes
import com.agilelab.dataquality.common.models.DatabaseCommon
import com.agilelab.dataquality.common.parsers.ConfigReader
import com.agilelab.dataquality.common.parsers.DQConfig.AllErrorsOr
import com.typesafe.config.Config

import scala.util.{Failure, Success, Try}

object ConfigReaderInstances {

  private def parseString(str: String)(implicit conf: Config): AllErrorsOr[String] = {
    Try(conf.getString(str)) match {
      case Success(v) => Valid(v)
      case Failure(_) => Invalid(NonEmptyList.one(s"Field $str is missing"))
    }
  }

  private def parseStringEnumerated(str: String, enum: Set[String])(implicit conf: Config): AllErrorsOr[String] = {
    Try(conf.getString(str)) match {
      case Success(v) if enum.contains(v) => Valid(v)
      case Success(v) => Invalid(NonEmptyList.one(s"Unsupported value of $str: $v"))
      case Failure(_) => Invalid(NonEmptyList.one(s"Field $str is missing"))
    }
  }

  implicit val databaseReader: ConfigReader[DatabaseCommon] =
    new ConfigReader[DatabaseCommon] {
      def read(conf: Config, enums: Set[String]*): AllErrorsOr[DatabaseCommon] = {
        val id: AllErrorsOr[String] = parseString("id")(conf)
        val subtype: AllErrorsOr[String] = parseStringEnumerated("subtype", DBTypes.names)(conf)

        Try(conf.getConfig("config")) match {
          case Success(innerConf) =>
            val host: AllErrorsOr[String] = parseString("host")(innerConf)

            val port: AllErrorsOr[Option[Int]] = Valid(Try(innerConf.getString("port").toInt).toOption)
            val service: AllErrorsOr[Option[String]] = Valid(Try(innerConf.getString("service")).toOption)
            val user: AllErrorsOr[Option[String]] = Valid(Try(innerConf.getString("user")).toOption)
            val password: AllErrorsOr[Option[String]] = Valid(Try(innerConf.getString("password")).toOption)
            val schema: AllErrorsOr[Option[String]] = Valid(Try(innerConf.getString("schema")).toOption)

            (id, subtype, host, port, service, user, password, schema).mapN(DatabaseCommon.apply _)
          case Failure(_) => Invalid(NonEmptyList.one("Inner config is missing"))
        }
      }
    }

//  implicit val sourceReader: ConfigReader[SourceCommon] =
//    new ConfigReader[SourceCommon] {
//      override def read(value: Config): AllErrorsOr[SourceCommon] = ???
//    }
} 
Example 6
Source File: BatchChangeInterfaces.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.api.domain.batch

import cats.data.Validated.{Invalid, Valid}
import cats.data._
import cats.effect._
import cats.implicits._
import vinyldns.core.domain.DomainValidationError

object BatchChangeInterfaces {

  type SingleValidation[A] = ValidatedNel[DomainValidationError, A]
  type ValidatedBatch[A] = List[ValidatedNel[DomainValidationError, A]]
  type BatchResult[A] = EitherT[IO, BatchChangeErrorResponse, A]

  implicit class IOBatchResultImprovements[A](theIo: IO[A]) {
    def toBatchResult: BatchResult[A] = EitherT.liftF(theIo)
  }

  implicit class IOEitherBatchResultImprovements[A](theIo: IO[Either[_, A]]) {
    def toBatchResult: BatchResult[A] = EitherT {
      theIo.map {
        case Right(r) => Right(r)
        case Left(err: BatchChangeErrorResponse) => Left(err)
        case Left(x) =>
          Left(UnknownConversionError(s"Cannot convert item to BatchResponse: $x"))
      }
    }
  }

  implicit class EitherBatchResultImprovements[A](eth: Either[BatchChangeErrorResponse, A]) {
    def toBatchResult: BatchResult[A] = EitherT.fromEither[IO](eth)
  }

  implicit class BatchResultImprovements[A](a: A) {
    def toRightBatchResult: BatchResult[A] = EitherT.rightT[IO, BatchChangeErrorResponse](a)
  }

  implicit class BatchResultErrorImprovements[A](err: BatchChangeErrorResponse) {
    def toLeftBatchResult: BatchResult[A] = EitherT.leftT[IO, A](err)
  }

  implicit class ValidatedBatchImprovements[A](batch: ValidatedBatch[A]) {
    def mapValid[B](fn: A => ValidatedNel[DomainValidationError, B]): ValidatedBatch[B] =
      // gets rid of the map then flatmap thing we have to do when dealing with Seq[ValidatedNel[]]
      batch.map {
        case Valid(item) => fn(item)
        case Invalid(errList) => errList.invalid
      }

    def getValid: List[A] = batch.collect {
      case Valid(input) => input
    }

    def getInvalid: List[DomainValidationError] =
      batch
        .collect {
          case Invalid(input) => input
        }
        .flatMap(_.toList)
  }

  implicit class SingleValidationImprovements[A](validation: SingleValidation[A]) {
    def asUnit: SingleValidation[Unit] =
      validation.map(_ => ())
  }

  implicit class IOCollectionImprovements[A](value: List[IO[A]]) {
    // Pulls out the successful IO from the list; drops IO failures
    def collectSuccesses(): IO[List[A]] = {
      val asSuccessfulOpt: List[IO[Option[A]]] = value.map { f =>
        f.attempt.map {
          case Right(a) => Some(a)
          case _ => None
        }
      }

      asSuccessfulOpt.sequence[IO, Option[A]].map { lst =>
        lst.collect {
          case Some(rs) => rs
        }
      }
    }
  }
} 
Example 7
Source File: ResultHelpers.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.api

import cats.data.Validated.{Invalid, Valid}
import cats.data.ValidatedNel
import cats.effect._
import cats.implicits._
import cats.scalatest.ValidatedMatchers
import org.scalatest.matchers.should.Matchers
import org.scalatest.propspec.AnyPropSpec

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import scala.reflect.ClassTag

final case class TimeoutException(message: String) extends Throwable(message)

trait ResultHelpers {
  private implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)
  private implicit val cs: ContextShift[IO] =
    IO.contextShift(scala.concurrent.ExecutionContext.global)

  def await[T](f: => IO[_], duration: FiniteDuration = 1.second): T =
    awaitResultOf[T](f.map(_.asInstanceOf[T]).attempt, duration).toOption.get

  // Waits for the future to complete, then returns the value as an Either[Throwable, T]
  def awaitResultOf[T](
      f: => IO[Either[Throwable, T]],
      duration: FiniteDuration = 1.second
  ): Either[Throwable, T] = {

    val timeOut = IO.sleep(duration) *> IO(
      TimeoutException("Timed out waiting for result").asInstanceOf[Throwable]
    )

    IO.race(timeOut, f.handleError(e => Left(e))).unsafeRunSync() match {
      case Left(e) => Left(e)
      case Right(ok) => ok
    }
  }

  // Assumes that the result of the future operation will be successful, this will fail on a left disjunction
  def rightResultOf[T](f: => IO[Either[Throwable, T]], duration: FiniteDuration = 1.second): T =
    awaitResultOf[T](f, duration) match {
      case Right(result) => result
      case Left(error) => throw error
    }

  // Assumes that the result of the future operation will fail, this will error on a right disjunction
  def leftResultOf[T](
      f: => IO[Either[Throwable, T]],
      duration: FiniteDuration = 1.second
  ): Throwable = awaitResultOf(f, duration).swap.toOption.get

  def leftValue[T](t: Either[Throwable, T]): Throwable = t.swap.toOption.get

  def rightValue[T](t: Either[Throwable, T]): T = t.toOption.get
}

object ValidationTestImprovements extends AnyPropSpec with Matchers with ValidatedMatchers {

  implicit class ValidatedNelTestImprovements[DomainValidationError, A](
      value: ValidatedNel[DomainValidationError, A]
  ) {

    def failures: List[DomainValidationError] = value match {
      case Invalid(e) => e.toList
      case Valid(_) =>
        fail("should have no failures!") // Will (correctly) cause expected failures to fail upon succeeding
    }

    def failWith[EE <: DomainValidationError](implicit tag: ClassTag[EE]): Unit =
      value.failures.map(_ shouldBe an[EE])
  }
} 
Example 8
Source File: ValidatedMatchersSpec.scala    From cats-scalatest   with Apache License 2.0 5 votes vote down vote up
package cats.scalatest

import cats.data.Validated.{Invalid, Valid}
import cats.data.{NonEmptyList, ValidatedNel}

class ValidatedMatchersSpec extends TestBase with ValidatedMatchers {
  "ValidatedMatchers" should {
    val simpleFailureNel: ValidatedNel[String, Nothing] = Invalid(NonEmptyList.of(thisRecord, thisTobacconist))

    "Match one specific element in an Invalid NEL" in {
      simpleFailureNel should haveInvalid(thisRecord)
    }

    "Match multiple specific elements in an Invalid NEL" in {
      simpleFailureNel should (haveInvalid(thisRecord).and(haveInvalid(thisTobacconist)))
    }

    "Match a specific element of a single Invalid" in {
      Invalid(thisRecord) should beInvalid(thisRecord)
    }

    "Test whether a Validated instance is a Invalid w/o specific element value" in {
      Invalid(thisTobacconist) should be(invalid)
    }

    "By negating 'invalid', test whether a Validated instance is a Valid" in {
      Valid(hovercraft) should not be (invalid)
    }

    "Test whether a Validated instance is a Valid" in {
      Valid(hovercraft) should be(valid)
    }

    "By negating 'valid', test whether a Validated instance is an invalid" in {
      Invalid(thisTobacconist) should not be (valid)
    }

    "Match a specific element of a single Valid" in {
      Valid(hovercraft) should beValid(hovercraft)
    }

    "Match one specific type in an Invalid NEL" in {
      simpleFailureNel should haveAnInvalid[String]

      val nel: ValidatedNel[String, Nothing] = Invalid(NonEmptyList.of("test"))
      nel should haveAnInvalid[String]
      nel should haveAnInvalid[Any]
      nel shouldNot haveAnInvalid[Int]

      val nel2: ValidatedNel[Nothing, Unit] = Valid(())
      nel2 shouldNot haveAnInvalid[String]
      nel2 shouldNot haveAnInvalid[Unit]
    }
  }
} 
Example 9
Source File: GraphQLSuperMicroService.scala    From cornichon   with Apache License 2.0 5 votes vote down vote up
package com.github.agourlay.cornichon.framework.examples.superHeroes.server

import cats.data.Validated
import cats.data.Validated.{ Invalid, Valid }
import sangria.macros.derive._
import sangria.schema.Schema
import sangria.schema._
import sangria.marshalling.circe._
import io.circe.generic.auto._

class GraphQLSuperMicroService(sm: SuperMicroService) {

  def publisherByName(sessionId: String, name: String): Option[Publisher] =
    unpack(sm.publisherByName(sessionId, name))

  def superheroByName(sessionId: String, name: String, protectIdentity: Boolean = false): Option[SuperHero] =
    unpack(sm.superheroByName(sessionId, name, protectIdentity))

  def updateSuperhero(sessionId: String, s: SuperHero): Option[SuperHero] =
    unpack(sm.updateSuperhero(sessionId, s))

  private def unpack[A](v: Validated[ApiError, A]): Option[A] =
    v match {
      case Valid(p) => Some(p)
      case Invalid(e) => e match {
        case SessionNotFound(_)   => None
        case PublisherNotFound(_) => None
        case SuperHeroNotFound(_) => None
        case _                    => throw new RuntimeException(e.msg)
      }
    }
}

object GraphQlSchema {

  implicit val PublisherType = deriveObjectType[Unit, Publisher](
    ObjectTypeDescription("A comics publisher.")
  )

  implicit val SuperHeroType = deriveObjectType[Unit, SuperHero](
    ObjectTypeDescription("A superhero.")
  )

  implicit val PublisherInputType = deriveInputObjectType[Publisher](
    InputObjectTypeName("PublisherInput")
  )

  implicit val SuperHeroInputType = deriveInputObjectType[SuperHero](
    InputObjectTypeName("SuperHeroInput")
  )

  val QueryType = deriveObjectType[Unit, GraphQLSuperMicroService](
    ObjectTypeName("Root"),
    ObjectTypeDescription("Gateway to awesomeness."),
    IncludeMethods("publisherByName", "superheroByName")
  )

  val MutationType = deriveObjectType[Unit, GraphQLSuperMicroService](
    ObjectTypeName("RootMut"),
    ObjectTypeDescription("Gateway to mutation awesomeness!"),
    IncludeMethods("updateSuperhero")
  )

  val SuperHeroesSchema = Schema(QueryType, Some(MutationType))
} 
Example 10
Source File: AssertStep.scala    From cornichon   with Apache License 2.0 5 votes vote down vote up
package com.github.agourlay.cornichon.steps.regular.assertStep

import cats.data._
import cats.data.Validated.Invalid
import cats.syntax.either._
import com.github.agourlay.cornichon.core.ScenarioRunner._
import com.github.agourlay.cornichon.core._
import monix.eval.Task

import scala.concurrent.duration.Duration

case class AssertStep(title: String, action: ScenarioContext => Assertion, show: Boolean = true) extends LogValueStep[Done] {

  def setTitle(newTitle: String): Step = copy(title = newTitle)

  override def runLogValueStep(runState: RunState): Task[Either[NonEmptyList[CornichonError], Done]] =
    Task.delay {
      val assertion = action(runState.scenarioContext)
      assertion.validated match {
        case Invalid(e) => e.asLeft
        case _          => Done.rightDone
      }
    }

  override def onError(errors: NonEmptyList[CornichonError], runState: RunState, executionTime: Duration): (LogInstruction, FailedStep) =
    errorsToFailureStep(this, runState.depth, errors, Some(executionTime))

  override def logOnSuccess(result: Done, runState: RunState, executionTime: Duration): LogInstruction =
    successLog(title, runState.depth, show, executionTime)
} 
Example 11
Source File: InvokeScriptTxValidator.scala    From Waves   with MIT License 5 votes vote down vote up
package com.wavesplatform.transaction.validation.impl

import cats.data.NonEmptyList
import cats.data.Validated.{Invalid, Valid}
import cats.implicits._
import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL
import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader}
import com.wavesplatform.protobuf.transaction.PBTransactions
import com.wavesplatform.transaction.TxValidationError.{GenericError, NonPositiveAmount, TooBigArray}
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.transaction.validation.{TxValidator, ValidatedNV, ValidatedV}
import com.wavesplatform.utils._

import scala.util.Try

object InvokeScriptTxValidator extends TxValidator[InvokeScriptTransaction] {
  override def validate(tx: InvokeScriptTransaction): ValidatedV[InvokeScriptTransaction] = {
    import tx._

    def checkAmounts(payments: Seq[Payment]): ValidatedNV = {
      val invalid = payments.filter(_.amount <= 0)
      if (invalid.nonEmpty)
        Invalid(NonEmptyList.fromListUnsafe(invalid.toList).map(p => NonPositiveAmount(p.amount, p.assetId.fold("Waves")(_.toString))))
      else Valid(())
    }

    def checkLength =
      if (tx.isProtobufVersion)
        PBTransactions
          .toPBInvokeScriptData(tx.dAppAddressOrAlias, tx.funcCallOpt, tx.payments)
          .toByteArray
          .length <= ContractLimits.MaxInvokeScriptSizeInBytes
      else tx.bytes().length <= ContractLimits.MaxInvokeScriptSizeInBytes

    val callableNameSize =
      funcCallOpt match {
        case Some(FUNCTION_CALL(FunctionHeader.User(internalName, _), _)) => internalName.utf8Bytes.length
        case _ => 0
      }

    V.seq(tx)(
      V.addressChainId(dAppAddressOrAlias, chainId),
      V.cond(
        funcCallOpt.isEmpty || funcCallOpt.get.args.size <= ContractLimits.MaxInvokeScriptArgs,
        GenericError(s"InvokeScript can't have more than ${ContractLimits.MaxInvokeScriptArgs} arguments")
      ),
      V.cond(
        callableNameSize <= ContractLimits.MaxDeclarationNameInBytes,
        GenericError(s"Callable function name size = $callableNameSize bytes must be less than ${ContractLimits.MaxDeclarationNameInBytes}")
      ),
      checkAmounts(payments),
      V.fee(fee),
      Try(checkLength)
        .toEither
        .leftMap(err => GenericError(err.getMessage))
        .filterOrElse(identity, TooBigArray)
        .toValidatedNel
        .map(_ => ())
    )
  }
}