packages feed

figment-0.2.0.0: src/Figment/Internal/Budget.hs

-- | A tiny state/error monad shared by every parser that builds a
-- t'Figment.Value.Value' from untrusted input: it enforces 'Limits' while the tree is
-- being built, so a hostile source is rejected before it is
-- materialised.
module Figment.Internal.Budget
  ( Budget
  , runBudget
  , spend
  , enterContainer
  , throwB
  , askLimits
  ) where

import Figment.Error
import Figment.Limits
import Figment.Metadata (Pos)
import Figment.Path (Path)

newtype Budget a = Budget {unBudget :: Limits -> Int -> Either ConfigError (a, Int)}

instance Functor Budget where
  fmap f (Budget g) = Budget $ \l n -> case g l n of
    Left e -> Left e
    Right (a, n') -> Right (f a, n')

instance Applicative Budget where
  pure a = Budget $ \_ n -> Right (a, n)
  Budget f <*> Budget g = Budget $ \l n -> case f l n of
    Left e -> Left e
    Right (h, n') -> case g l n' of
      Left e -> Left e
      Right (a, n'') -> Right (h a, n'')

instance Monad Budget where
  Budget g >>= k = Budget $ \l n -> case g l n of
    Left e -> Left e
    Right (a, n') -> unBudget (k a) l n'

runBudget :: Limits -> Budget a -> Either ConfigError a
runBudget l (Budget g) = fst <$> g l (maxNodes l)

askLimits :: Budget Limits
askLimits = Budget $ \l n -> Right (l, n)

throwB :: ConfigError -> Budget a
throwB e = Budget $ \_ _ -> Left e

-- | Account for @k@ values at the given location.
spend :: Int -> Path -> Maybe Pos -> Budget ()
spend k p pos = Budget $ \l n ->
  let n' = n - k
  in if n' < 0
       then Left (withPos pos (atPath p (configError (TooManyNodes (maxNodes l)))))
       else Right ((), n')

-- | Check that a container at nesting level @depth@ (root = 1) is
-- allowed.
enterContainer :: Int -> Path -> Maybe Pos -> Budget ()
enterContainer depth p pos = Budget $ \l n ->
  if depth > maxDepth l
    then Left (withPos pos (atPath p (configError (DepthExceeded (maxDepth l)))))
    else Right ((), n)