packages feed

tadka-2.0.0.0: test/props/Phase8.hs

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

-- | Phase 8: the derive path and the manual path are two doors into the same
-- room. A @deriveDiagnostic@-generated instance and a hand-written instance for
-- a structurally identical type render byte-for-byte identically across all
-- three handlers.
module Phase8 (group) where

import qualified Data.Aeson                 as A
import           Data.Text                  (Text)
import qualified Data.Text.Lazy             as TL
import qualified Data.Text.Lazy.Encoding    as TLE
import           Prettyprinter              (LayoutOptions (..), PageWidth (Unbounded),
                                             layoutPretty, pretty)
import           Prettyprinter.Render.Text  (renderStrict)

import           Hedgehog                   (Group (..), Property, property, withTests, (===))

import           Tadka
import           Tadka.Internal             (buildContext)

rightOrErr :: Show a => Either a b -> b
rightOrErr = either (error . show) id

-- Derived via the macro (specMessage keeps the message value-dependent, matching
-- the manual twin exactly rather than dumping the whole record via `show`).
data DErr = DErr { dSrc :: NamedSource, dAt :: Span, dGot :: Text }
  deriving (Show)

deriveDiagnostic defaultSpec
  { specCode        = Just "tadka::E0007"
  , specHelp        = Just "try renaming it"
  , specUrl         = Just "https://example.org/errors/E0007"
  , specSourceField = Just 'dSrc
  , specLabelFields = [('dAt, "here")]
  , specMessage     = Just [| \e -> pretty ("undefined variable " <> dGot e) |]
  }
  ''DErr

-- Hand-written twin: identical fields, identical bodies (calling the same
-- shared functions a manual author would use).
data MErr = MErr { mSrc :: NamedSource, mAt :: Span, mGot :: Text }

instance Diagnostic MErr where
  message e = pretty ("undefined variable " <> mGot e)
  code _    = Just (rightOrErr (mkDiagnosticCode "tadka::E0007"))
  severity _ = SevError
  help _    = Just (pretty ("try renaming it" :: Text))
  url _     = Just (rightOrErr (mkUrl "https://example.org/errors/E0007"))
  context e = buildContext (mSrc e) [(mAt e, Just (pretty ("here" :: Text)))]

srcV :: NamedSource
srcV = rightOrErr (mkNamedSource "f.hs" "x = foo")

spanV :: Span
spanV = rightOrErr (mkSpan 4 3)   -- "foo"

dVal :: DErr
dVal = DErr srcV spanV "foo"

mVal :: MErr
mVal = MErr srcV spanV "foo"

gfx :: Diagnostic e => e -> Text
gfx e = case selectRenderer (withColorMode ColorNever (withUnicodeMode UnicodeAlways (withTarget TGraphical defaultConfig))) of
  SomeRenderer r@(Graphical _) -> renderStrict (layoutPretty (LayoutOptions Unbounded) (render r e))
  _                            -> ""

nar :: Diagnostic e => e -> Text
nar e = case selectRenderer (withTarget TNarratable defaultConfig) of
  SomeRenderer r@(Narratable _) -> render r e
  _                             -> ""

jsn :: Diagnostic e => e -> Text
jsn e = case selectRenderer (withTarget TJson defaultConfig) of
  SomeRenderer r@(Json _) -> TL.toStrict (TLE.decodeUtf8 (A.encode (render r e)))
  _                       -> ""

once :: (Text, Text) -> Property
once (a, b) = withTests 1 (property (a === b))

group :: Group
group = Group "Phase 8 - deriveDiagnostic (derived == manual)"
  [ ("graphical renders identically",  once (gfx dVal, gfx mVal))
  , ("narratable renders identically", once (nar dVal, nar mVal))
  , ("json renders identically",       once (jsn dVal, jsn mVal))
  ]