{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -- | Tool for generating documentation-friendly examples module Data.API.Tools.Example ( Example(..) , exampleTool , samplesTool ) where import Data.API.TH import Data.API.Tools.Combinators import Data.API.Tools.Datatypes import Data.API.Types import Data.API.Utils import Control.Applicative import Data.Aeson import qualified Data.ByteString.Char8 as B import Data.Monoid import Data.Time import Language.Haskell.TH import Safe import Test.QuickCheck as QC import qualified Data.Text as T import Prelude -- | The Example class is used to generate a documentation-friendly -- example for each type in the model class Example a where -- | Generator for example values; defaults to 'arbitrary' if not -- specified example :: Gen a default example :: Arbitrary a => Gen a example = arbitrary instance Example a => Example (Maybe a) where example = oneof [return Nothing, Just <$> example] instance Example a => Example [a] where example = listOf example instance Example Int where example = arbitrarySizedBoundedIntegral `suchThat` (> 0) instance Example Bool where example = choose (False, True) instance Example T.Text where example = return "Mary had a little lamb" instance Example Binary where example = return $ Binary $ B.pack "lots of 1s and 0s" instance Example Value where example = return $ String "an example JSON value" instance Example UTCTime where example = return $ fromJustNote dg $ parseUTC_ "2013-06-09T15:52:30Z" where dg = "Data.API.Tools.Example-UTCTime" -- | Generate a list of (type name, sample generator) pairs -- corresponding to each type in the API, with samples encoded as -- JSON. This depends on the 'Example' instances generated by -- 'exampleTool'. It generates something like this: -- -- > samples :: [(String, Gen Value)] -- > samples = [("Foo", fmap toJSON (example :: Gen Foo)), ... ] samplesTool :: Name -> APITool samplesTool nm = simpleTool $ \ api -> simpleSigD nm [t| [(String, Gen Value)] |] (listE [ gen_sample nd | ThNode nd <- api ]) where gen_sample :: APINode -> ExpQ gen_sample an = [e| ($str, fmap toJSON (example :: Gen $(nodeT an))) |] where str = stringE $ T.unpack $ _TypeName $ anName an -- | Tool to generate 'Example' instances for types generated by -- 'datatypesTool'. This depends on 'quickCheckTool'. exampleTool :: APITool exampleTool = apiNodeTool $ apiSpecTool gen_sn_ex gen_sr_ex gen_su_ex gen_se_ex mempty -- | Generate an 'Example' instance for a newtype. If there is no -- filter, call 'example' on the underlying type; otherwise, use -- 'arbitrary'. Like 'Arbitrary', if a regular expression filter is -- applied the instance must be defined manually. gen_sn_ex :: Tool (APINode, SpecNewtype) gen_sn_ex = mkTool $ \ ts (an, sn) -> case snFilter sn of Just (FtrStrg _) -> return [] Just _ -> inst ts an [e| QC.arbitrary |] Nothing -> inst ts an [e| fmap $(nodeNewtypeConE ts an sn) example |] where inst ts an e = optionalInstanceD ts ''Example [nodeRepT an] [simpleD 'example e] -- | Generate an 'Example' instance for a record: -- -- > instance Example Foo where -- > example = sized $ \ x -> Foo <$> resize (x `div` 2) example <*> ... <*> resize (x `div` 2) example gen_sr_ex :: Tool (APINode, SpecRecord) gen_sr_ex = mkTool $ \ ts (an, sr) -> optionalInstanceD ts ''Example [nodeRepT an] [simpleD 'example (bdy an sr)] where bdy an sr = do x <- newName "x" appE (varE 'QC.sized) $ lamE [varP x] $ applicativeE (nodeConE an) $ replicate (length $ srFields sr) $ [e| QC.resize ($(varE x) `div` 2) example |] -- | Generate an 'Example' instance for a union: -- -- > instance Example Foo where -- > example = oneOf [ fmap Bar example, fmap Baz example ] gen_su_ex :: Tool (APINode, SpecUnion) gen_su_ex = mkTool $ \ ts (an, su) -> optionalInstanceD ts ''Example [nodeRepT an] [simpleD 'example (bdy an su)] where bdy an su | null (suFields su) = nodeConE an | otherwise = [e| oneof $(listE (alts an su)) |] alts an su = [ [e| fmap $(nodeAltConE an k) example |] | (k,_) <- suFields su ] -- | Generate an 'Example' instance for an enumeration, with no -- definition for the 'example' method, because we can inherit the -- behaviour of 'Arbitrary': -- -- > instance Example Foo gen_se_ex :: Tool (APINode, SpecEnum) gen_se_ex = mkTool $ \ ts (an, _) -> optionalInstanceD ts ''Example [nodeRepT an] []