{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
-- | Helpers for associating example values with all the types we use in our
-- APIs. This allows us to write tests that will warn us when the encoding of
-- our types change, potentially in backwards-incompatible ways.
module Examples
( HasExamples (..),
Examples,
example,
render,
)
where
import qualified Data.Aeson
import qualified Data.Aeson.Encode.Pretty
import qualified Data.ByteString.Lazy
import qualified Data.List.NonEmpty as NonEmpty
import Data.Proxy (Proxy (Proxy))
import qualified Data.Text
import qualified Data.Text.Encoding
import qualified Dict
import qualified List
import NriPrelude
import Prelude ((<>))
import qualified Prelude
-- | Example values of a type.
newtype Examples = Examples (NonEmpty.NonEmpty Example)
data Example = Example
{ description :: Text,
encodedValue :: Text
}
deriving (Eq, Ord)
-- | Create an example for a type. Examples consists of a description and an
-- encoded value.
example :: Data.Aeson.ToJSON a => Text -> a -> Examples
example description x =
Example
{ description,
encodedValue =
Data.Aeson.Encode.Pretty.encodePretty x
|> Data.ByteString.Lazy.toStrict
|> Data.Text.Encoding.decodeUtf8
}
|> Prelude.pure
|> Examples
-- | A helper type class that provides us example values of particular types.
-- The `IsApi` typeclass below will demand we define an instance of this type
-- class for each type used in a request or response body.
class HasExamples t where
examples :: Proxy t -> Examples
instance Prelude.Semigroup Examples where
(Examples xs) <> (Examples ys) = Examples (xs <> ys)
-- | Render example values to a Text.
render :: Examples -> Text
render (Examples examples') =
NonEmpty.toList examples'
|> List.map renderExample
|> Data.Text.intercalate "\n\n"
renderExample :: Example -> Text
renderExample example' =
description example'
++ "\n"
++ encodedValue example'
instance (HasExamples a, HasExamples b) => HasExamples (a, b) where
examples _ = examples (Proxy :: Proxy a) ++ examples (Proxy :: Proxy b)
instance (HasExamples a, HasExamples b, HasExamples c) => HasExamples (a, b, c) where
examples _ =
examples (Proxy :: Proxy a)
++ examples (Proxy :: Proxy b)
++ examples (Proxy :: Proxy c)
instance (HasExamples a, HasExamples b) => HasExamples (Dict.Dict a b) where
examples _ = examples (Proxy :: Proxy a) ++ examples (Proxy :: Proxy b)
instance (HasExamples a) => HasExamples (Maybe a) where
examples _ = examples (Proxy :: Proxy a)
instance (HasExamples a) => HasExamples (a, List a) where
examples _ = examples (Proxy :: Proxy a)
instance (HasExamples a) => HasExamples (List a) where
examples _ = examples (Proxy :: Proxy a)
instance HasExamples Int where
examples _ = example "int" (1 :: Int)
instance HasExamples () where
examples _ = example "unit" ()