{-# LANGUAGE DeriveFunctor #-}

module Data.Validator.Failure where

import           Import
-- Hiding is for GHCs before 7.10:
import           Prelude            hiding (concat, sequence)

import qualified Data.Aeson.Pointer as AP

-- | Validators shouldn't know more about the schema they're going to
-- be used with than necessary. If a validator throws errors using the
-- error sum type of a particular schema, then it can't be used with
-- other schemas later that have different error sum types (at least not
-- without writing partial functions).
--
-- Because of this we make 'Fail' a higher order type, so each validator
-- can return a sum type describing only the failures that can occur in that
-- validator (or '()' if that validator can only fail in one way).
--
-- It's the job of a schema's validate function to unify the errors produced
-- by the validators it uses into a single error sum type for that schema.
-- The schema's validate function will return a 'Fail' with
-- that sum type as its type argument.
--
-- The slightly weird naming ('Fail' and 'Failure') is so that we can define
-- a 'type Failure = Fail SchemaErrorType' for each of our schemas, and
-- export it along with 'Fail(..)'. This way the users of the library only
-- use 'Failure', not 'Fail'.
data Fail err = Failure
    { _failureValidatorsCalled :: !err
      -- ^ E.g. @Items UniqueItems@ during draft 4 validation.
    , _failureFinalValidator   :: !Value
      -- ^ The value of the validator that raised the error (e.g. the value of
      -- @"uniqueItems"@ in the above example.
    , _failureOffendingPointer :: !AP.Pointer
      -- ^ A pointer to the part of the data that caused invalidation.
    , _failureOffendingData    :: !Value
      -- ^ The part of the data that caused invalidation. Usually this is
      -- identical to the result of resolving '_invalidOffendingPointer'
      -- against the starting data, but not always (e.g. in the case of
      -- 'additionalItems' where '_invalidOffendingData' will be the items
      -- in the array that were not allowed, instead of the entire array).
    } deriving (Eq, Show, Functor)

prependToPath :: AP.Token -> Fail a -> Fail a
prependToPath tok failure =
    let old = _failureOffendingPointer failure
    in failure { _failureOffendingPointer = AP.Pointer [tok] <> old }