packages feed

toml-reader-0.1.0.0: src/TOML/Error.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module TOML.Error (
  TOMLError (..),
  NormalizeError (..),
  DecodeContext,
  ContextItem (..),
  DecodeError (..),
  renderTOMLError,
) where

import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Text (Text)
import qualified Data.Text as Text

import TOML.Value (Table, Value (..), renderValue)

data TOMLError
  = ParseError Text
  | NormalizeError NormalizeError
  | DecodeError DecodeContext DecodeError
  deriving (Show, Eq)

data NormalizeError
  = -- | When a key is defined twice, e.g.
    --
    -- @
    -- name = 'First'
    -- name = 'Second'
    -- @
    DuplicateKeyError
      { _path :: NonEmpty Text
      , _existingValue :: Value
      , _valueToSet :: Value
      }
  | -- | When a section is defined twice, e.g.
    --
    -- @
    -- [foo]
    -- a = 1
    --
    -- [foo]
    -- b = 2
    -- @
    DuplicateSectionError
      { _sectionKey :: NonEmpty Text
      }
  | -- | When a key attempts to extend an invalid table
    --
    -- @
    -- a = {}
    -- [a.b]
    --
    -- b = {}
    -- b.a = 1
    --
    -- c.x.x = 1
    -- [c.a]
    -- @
    ExtendTableError
      { _path :: NonEmpty Text
      , _originalKey :: NonEmpty Text
      }
  | -- | When a section attempts to extend a table within an inline array
    --
    -- @
    -- a = [{ b = 1 }]
    -- [a.c]
    -- @
    ExtendTableInInlineArrayError
      { _path :: NonEmpty Text
      , _originalKey :: NonEmpty Text
      }
  | -- | When a key is already defined, but attempting to create an
    -- implicit array at the same key, e.g.
    --
    -- @
    -- list = [1, 2, 3]
    --
    -- [[list]]
    -- a = 1
    -- @
    ImplicitArrayForDefinedKeyError
      { _path :: NonEmpty Text
      , _existingValue :: Value
      , _tableSection :: Table
      }
  | -- | When a non-table value is already defined in a nested key, e.g.
    --
    -- @
    -- a.b = 1
    -- a.b.c.d = 2
    -- @
    NonTableInNestedKeyError
      { _path :: NonEmpty Text
      , _existingValue :: Value
      , _originalKey :: NonEmpty Text
      , _originalValue :: Value
      }
  | -- | When a non-table value is already defined in a nested implicit array, e.g.
    --
    -- @
    -- a.b = 1
    --
    -- [[a.b.c]]
    -- d = 2
    -- @
    NonTableInNestedImplicitArrayError
      { _path :: NonEmpty Text
      , _existingValue :: Value
      , _sectionKey :: NonEmpty Text
      , _tableSection :: Table
      }
  deriving (Show, Eq)

type DecodeContext = [ContextItem]

data ContextItem = Key Text | Index Int
  deriving (Show, Eq)

data DecodeError
  = MissingField
  | InvalidValue Text Value
  | TypeMismatch Value
  | OtherDecodeError Text
  deriving (Show, Eq)

renderTOMLError :: TOMLError -> Text
renderTOMLError = \case
  ParseError s -> s
  NormalizeError DuplicateKeyError{..} ->
    Text.unlines
      [ "Could not add value to path " <> showPath _path <> ":"
      , "  Existing value: " <> renderValue _existingValue
      , "  Value to set: " <> renderValue _valueToSet
      ]
  NormalizeError DuplicateSectionError{..} -> "Found duplicate section: " <> showPath _sectionKey
  NormalizeError ExtendTableError{..} ->
    Text.unlines
      [ "Invalid table key: " <> showPath _originalKey
      , "  Table already statically defined at " <> showPath _path
      ]
  NormalizeError ExtendTableInInlineArrayError{..} ->
    Text.unlines
      [ "Invalid table key: " <> showPath _originalKey
      , "  Table defined in inline array at " <> showPath _path
      ]
  NormalizeError ImplicitArrayForDefinedKeyError{..} ->
    Text.unlines
      [ "Could not create implicit array at path " <> showPath _path <> ":"
      , "  Existing value: " <> renderValue _existingValue
      , "  Array table section: " <> renderValue (Table _tableSection)
      ]
  NormalizeError NonTableInNestedKeyError{..} ->
    Text.unlines
      [ "Found non-Table at path " <> showPath _path <> " when defining nested key " <> showPath _originalKey <> ":"
      , "  Existing value: " <> renderValue _existingValue
      , "  Original value: " <> renderValue _originalValue
      ]
  NormalizeError NonTableInNestedImplicitArrayError{..} ->
    Text.unlines
      [ "Found non-Table at path " <> showPath _path <> " when initializing implicit array at path " <> showPath _sectionKey <> ":"
      , "  Existing value: " <> renderValue _existingValue
      , "  Array table section: " <> renderValue (Table _tableSection)
      ]
  DecodeError ctx e -> "Decode error at '" <> renderDecodeContext ctx <> "': " <> renderDecodeError e
  where
    showPath path = "\"" <> Text.intercalate "." (NonEmpty.toList path) <> "\""

    renderDecodeError = \case
      MissingField -> "Field does not exist"
      InvalidValue msg v -> "Invalid value: " <> msg <> ": " <> renderValue v
      TypeMismatch v -> "Type mismatch, got: " <> renderValue v
      OtherDecodeError msg -> msg

    renderDecodeContext = Text.concat . map renderContextItem
    renderContextItem = \case
      Key k -> "." <> k
      Index i -> "[" <> Text.pack (show i) <> "]"