All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cron4s.validation.NodeValidator.scala Maven / Gradle / Ivy

There is a newer version: 0.6.1
Show newest version
/*
 * Copyright 2017 Antonio Alonso Dominguez
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cron4s.validation

import cats.data._
import cats.implicits._

import cron4s.{CronField, CronUnit, InvalidField}
import cron4s.expr._
import cron4s.base.Enumerated
import cron4s.syntax.field._

/**
  * Created by alonsodomin on 18/12/2016.
  */
sealed trait NodeValidator[A] {

  def validate(node: A): List[InvalidField]

}

object NodeValidator extends NodeValidatorInstances {

  @inline def apply[A](implicit validator: NodeValidator[A]): NodeValidator[A] = validator

  def alwaysValid[A]: NodeValidator[A] = new NodeValidator[A] {
    def validate(node: A): List[InvalidField] = List.empty
  }

}

private[validation] trait NodeValidatorInstances extends LowPriorityNodeValidatorInstances {

  implicit def eachValidator[F <: CronField]: NodeValidator[EachNode[F]] =
    NodeValidator.alwaysValid[EachNode[F]]

  implicit def anyValidator[F <: CronField]: NodeValidator[AnyNode[F]] =
    NodeValidator.alwaysValid[AnyNode[F]]

  implicit def constValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[ConstNode[F]] = new NodeValidator[ConstNode[F]] {

      def validate(node: ConstNode[F]): List[InvalidField] = {
        if (node.value < node.unit.min || node.value > node.unit.max) {
          List(InvalidField(
            node.unit.field,
            s"Value ${node.value} is out of bounds for field: ${node.unit.field}"
          ))
        } else List.empty
      }

    }

  implicit def betweenValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[BetweenNode[F]] = new NodeValidator[BetweenNode[F]] {
      val subValidator = NodeValidator[ConstNode[F]]

      def validate(node: BetweenNode[F]): List[InvalidField] = {
        val baseErrors = List(
          subValidator.validate(node.begin),
          subValidator.validate(node.end)
        ).flatten

        if (node.begin.value >= node.end.value) {
          val error = InvalidField(
            node.unit.field,
            s"${node.begin.value} should be less than ${node.end.value}"
          )
          error :: baseErrors
        } else {
          baseErrors
        }
      }
  }

  implicit def severalValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[SeveralNode[F]] = new NodeValidator[SeveralNode[F]] {
      val elemValidator = NodeValidator[EnumerableNode[F]]

      def implicationErrorMsg(that: EnumerableNode[F], impliedBy: EnumerableNode[F]): String =
        s"Value '${that.show}' is implied by '${impliedBy.show}'"

      def checkImplication(curr: EnumerableNode[F]): State[List[EnumerableNode[F]], List[List[InvalidField]]] = {
        lazy val currField = curr.unit.field

        def impliedByError(elem: EnumerableNode[F]): List[InvalidField] =
          if (curr.impliedBy(elem)) List(InvalidField(currField, implicationErrorMsg(curr, elem)))
          else Nil

        def impliesError(elem: EnumerableNode[F]): List[InvalidField] =
          if (curr.implies(elem)) List(InvalidField(currField, implicationErrorMsg(elem, curr)))
          else Nil

        State { seen =>
          val errors = seen.foldMap { elem =>
            impliesError(elem) ++ impliedByError(elem)
          }
          (curr :: seen) -> List(errors)
        }
      }

      def validate(node: SeveralNode[F]): List[InvalidField] = {
        val validation = node.values.foldMapM { elem =>
          val elemErrors = elemValidator.validate(elem)
          // If subexpressions in the elements are not valid, then
          // do not check for element implication
          if (elemErrors.isEmpty) checkImplication(elem)
          else State.pure[List[EnumerableNode[F]], List[List[InvalidField]]](List(elemErrors))
        }
        validation.map(_.flatten).runEmptyA.value
      }
    }

  implicit def everyValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[EveryNode[F]] = new NodeValidator[EveryNode[F]] {
      def validate(node: EveryNode[F]): List[InvalidField] = {
        lazy val baseErrors = NodeValidator[DivisibleNode[F]].validate(node.base)
        val evenlyDivided = (node.base.range.size % node.freq) == 0
        if (!evenlyDivided) {
          InvalidField(
            node.unit.field,
            s"Step '${node.freq}' does not evenly divide the value '${node.base.show}'"
          ) :: baseErrors
        } else baseErrors
      }
    }

}

private[validation] trait LowPriorityNodeValidatorInstances {

  implicit def enumerableNodeValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[EnumerableNode[F]] = new NodeValidator[EnumerableNode[F]] {
      def validate(node: EnumerableNode[F]): List[InvalidField] =
        node.raw.fold(ops.validate)
    }

  implicit def divisibleNodeValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[DivisibleNode[F]] = new NodeValidator[DivisibleNode[F]] {
      def validate(node: DivisibleNode[F]): List[InvalidField] =
        node.raw.fold(ops.validate)
    }

  implicit def fieldNodeValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[FieldNode[F]] = new NodeValidator[FieldNode[F]] {
      def validate(node: FieldNode[F]): List[InvalidField] =
        node.raw.fold(ops.validate)
    }

  implicit def fieldNodeWithAnyValidator[F <: CronField](
      implicit
      ev: Enumerated[CronUnit[F]]
    ): NodeValidator[FieldNodeWithAny[F]] = new NodeValidator[FieldNodeWithAny[F]] {
    def validate(node: FieldNodeWithAny[F]): List[InvalidField] =
      node.raw.fold(ops.validate)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy