packages feed

aws-eventbridge-cron-0.1.0.0: src/AWS/EventBridge/Years.hs

{-# LANGUAGE OverloadedStrings #-}

module AWS.EventBridge.Years where

import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Data.Void (Void)
import Text.Megaparsec (Parsec, choice, eof, errorBundlePretty, parse, try)
import Text.Megaparsec.Char (char)
import Text.Megaparsec.Char.Lexer (decimal)

type Parser = Parsec Void Text

data YearsExprT
  = AllYears
  | AtYear Int
  | RangeYear Int Int
  | StepYear Int Int
  | UnionYear YearsExprT YearsExprT
  deriving (Eq, Show)

-- AWS EventBridge supports calendar years 1970..2199 inclusive.
isValidYear :: Int -> Bool
isValidYear y = 1970 <= y && y <= 2199

evaluateYearT :: YearsExprT -> Either String [Int]
evaluateYearT expr = fmap S.toAscList (go expr >>= ensureBounds . S.fromList)
  where
    ensureBounds xs =
      if all isValidYear xs
        then Right xs
        else Left "year out of bounds: expected 1970..2199"
    go AllYears = Right [1970..2199]
    go (AtYear y)
      | isValidYear y = Right [y]
      | otherwise     = Left ("invalid year: " ++ show y ++ " (expected 1970..2199)")
    go (RangeYear a b)
      | isValidYear a && isValidYear b && a <= b = Right [a..b]
      | not (isValidYear a) = Left ("invalid range start: " ++ show a ++ " (expected 1970..2199)")
      | not (isValidYear b) = Left ("invalid range end: " ++ show b ++ " (expected 1970..2199)")
      | otherwise           = Left ("invalid range: start " ++ show a ++ " > end " ++ show b)
    go (StepYear start step)
      | not (isValidYear start) = Left ("invalid step start: " ++ show start ++ " (expected 1970..2199)")
      | step >= 1 = Right (takeWhile (<= 2199) [start, start + step ..])
      | otherwise = Left ("invalid step: " ++ show step ++ " (expected >= 1)")
    go (UnionYear a b) = (++) <$> go a <*> go b

parseYears :: Parser YearsExprT
parseYears = choice
  [ try parseUnionYear
  , try parseStepYear
  , try parseRangeYear
  , try parseAtYear
  , parseAllYear
  ]

parseAllYear :: Parser YearsExprT
parseAllYear = char '*' >> pure AllYears

parseAtYear :: Parser YearsExprT
parseAtYear = AtYear <$> decimal

parseRangeYear :: Parser YearsExprT
parseRangeYear = do
  start <- decimal
  _ <- char '-'
  RangeYear start <$> decimal

parseStepYear :: Parser YearsExprT
parseStepYear = do
  start <- decimal
  _ <- char '/'
  StepYear start <$> decimal

parseUnionYear :: Parser YearsExprT
parseUnionYear = do
  s <- choice [try parseStepYear, try parseRangeYear, try parseAtYear, parseAllYear]
  _ <- char ','
  e <- choice [try parseUnionYear, try parseStepYear, try parseRangeYear, try parseAtYear, parseAllYear]
  eof
  pure (UnionYear s e)

parseYearsText :: Text -> Either String YearsExprT
parseYearsText input =
  case parse parseYears "years" (T.strip input) of
    Left err -> Left (errorBundlePretty err)
    Right expr -> Right expr