uri-templater-1.0.0: src/Network/URI/Template/Error.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{- |
Error types for URI template parsing and rendering.
This module provides structured error types that can be programmatically
inspected and pattern-matched, unlike the previous Doc AnsiStyle approach.
-}
module Network.URI.Template.Error (
-- * Parse Errors
ParseError (..),
displayParseError,
displayParseErrorSimple,
-- * Render Errors
RenderError (..),
displayRenderError,
) where
import Control.Exception (Exception)
import qualified Data.Text as T
import GHC.Generics (Generic)
-- | Errors that can occur when parsing a URI template.
data ParseError
= -- | The parser encountered an unexpected character at a specific position
UnexpectedCharacter
{ errorPosition :: !Int
-- ^ Position in the input string where the error occurred
, errorChar :: !Char
-- ^ The unexpected character
, errorContext :: !String
-- ^ Additional context about what was expected
}
| -- | The template expression was not properly terminated
UnterminatedExpression
{ errorPosition :: !Int
-- ^ Position where the expression started
}
| -- | An invalid modifier character was used
InvalidModifier
{ errorPosition :: !Int
-- ^ Position of the invalid modifier
, errorChar :: !Char
-- ^ The invalid modifier character
}
| -- | A variable name was malformed
MalformedVariable
{ errorPosition :: !Int
-- ^ Position of the malformed variable
, errorVariable :: !String
-- ^ The malformed variable name
}
| -- | An invalid max length specification
InvalidMaxLength
{ errorPosition :: !Int
-- ^ Position of the max length specification
, errorLengthSpec :: !String
-- ^ The invalid length specification
}
| -- | Generic parse error (fallback for non-specific errors)
GenericParseError
{ errorMessage :: !String
-- ^ Error message
}
deriving (Show, Eq, Generic)
instance Exception ParseError
-- | Display a parse error with full context, including source location
-- and surrounding text for better error messages.
displayParseError :: String -> ParseError -> T.Text
displayParseError source err = case err of
UnexpectedCharacter pos ch ctx ->
T.pack $
unlines
[ "Parse error at position " ++ show pos ++ ":"
, showSourceContext source pos
, "Unexpected character '" ++ [ch] ++ "'"
, ctx
]
UnterminatedExpression pos ->
T.pack $
unlines
[ "Parse error at position " ++ show pos ++ ":"
, showSourceContext source pos
, "Unterminated expression: expected '}'"
]
InvalidModifier pos ch ->
T.pack $
unlines
[ "Parse error at position " ++ show pos ++ ":"
, showSourceContext source pos
, "Invalid modifier character '" ++ [ch] ++ "'"
, "Valid modifiers are: + # . / ; ? & ="
]
MalformedVariable pos var ->
T.pack $
unlines
[ "Parse error at position " ++ show pos ++ ":"
, showSourceContext source pos
, "Malformed variable name: " ++ var
]
InvalidMaxLength pos spec ->
T.pack $
unlines
[ "Parse error at position " ++ show pos ++ ":"
, showSourceContext source pos
, "Invalid max length specification: " ++ spec
, "Expected a positive integer"
]
GenericParseError msg ->
T.pack $ "Parse error: " ++ msg
-- | Display a parse error in a simple format without source context.
displayParseErrorSimple :: ParseError -> T.Text
displayParseErrorSimple err = case err of
UnexpectedCharacter pos ch ctx ->
T.pack $ "Unexpected character '" ++ [ch] ++ "' at position " ++ show pos ++ ": " ++ ctx
UnterminatedExpression pos ->
T.pack $ "Unterminated expression at position " ++ show pos
InvalidModifier pos ch ->
T.pack $ "Invalid modifier '" ++ [ch] ++ "' at position " ++ show pos
MalformedVariable pos var ->
T.pack $ "Malformed variable '" ++ var ++ "' at position " ++ show pos
InvalidMaxLength pos spec ->
T.pack $ "Invalid max length '" ++ spec ++ "' at position " ++ show pos
GenericParseError msg ->
T.pack msg
-- | Show source context around an error position
showSourceContext :: String -> Int -> String
showSourceContext source pos =
let
contextSize = 40
start = max 0 (pos - contextSize)
end = min (length source) (pos + contextSize)
before = take (pos - start) $ drop start source
after = take (end - pos) $ drop pos source
caretPos = length before
in
unlines
[ " " ++ before ++ after
, " " ++ replicate caretPos ' ' ++ "^"
]
-- | Errors that can occur when rendering a URI template.
data RenderError
= -- | A required variable was not provided in the bindings
MissingVariable
{ renderVarName :: !T.Text
-- ^ The name of the missing variable
}
| -- | A variable had an incompatible value type for the template context
InvalidValueType
{ renderVarName :: !T.Text
-- ^ The name of the variable
, renderTypeError :: !String
-- ^ Description of the type mismatch
}
deriving (Show, Eq, Generic)
instance Exception RenderError
-- | Display a render error in a human-readable format.
displayRenderError :: RenderError -> T.Text
displayRenderError err = case err of
MissingVariable var ->
"Missing required variable: " <> var
InvalidValueType var typeErr ->
"Invalid value type for variable '" <> var <> "': " <> T.pack typeErr