{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | -- Module : Pinch.Internal.Generic -- Copyright : (c) Abhinav Gupta 2015 -- License : BSD3 -- -- Maintainer : Abhinav Gupta -- Stability : experimental -- -- Implements support for automatically deriving Pinchable instances for types -- that implement @Generic@ and follow a specific pattern. -- module Pinch.Internal.Generic ( Field(..) , getField , putField , field , Enumeration(..) , enum , Void(..) ) where import Control.Applicative import Control.DeepSeq (NFData) import Data.Proxy (Proxy (..)) import Data.Typeable (Typeable) import GHC.Generics import GHC.TypeLits import qualified Data.HashMap.Strict as HM import Pinch.Internal.Pinchable import Pinch.Internal.TType import Pinch.Internal.Value (Value (..)) -- | Implemented by TType tags whose values know how to combine. class Combinable t where combine :: Value t -> Value t -> Value t instance Combinable TStruct where combine (VStruct as) (VStruct bs) = VStruct $ as `HM.union` bs instance {-# OVERLAPPABLE #-} GPinchable a => GPinchable (M1 i c a) where type GTag (M1 i c a) = GTag a gPinch = gPinch . unM1 gUnpinch = fmap M1 . gUnpinch -- Adds the name of the data type to the error message. instance (Datatype d, GPinchable a) => GPinchable (D1 d a) where type GTag (D1 d a) = GTag a gPinch = gPinch . unM1 gUnpinch v = parserCatch (gUnpinch v) (\msg -> fail $ "Failed to read '" ++ name ++ "': " ++ msg) (return . M1) where name = datatypeName (undefined :: D1 d a b) instance ( GPinchable a , GPinchable b , GTag a ~ GTag b , Combinable (GTag a) ) => GPinchable (a :*: b) where type GTag (a :*: b) = GTag a gPinch (a :*: b) = gPinch a `combine` gPinch b gUnpinch m = (:*:) <$> gUnpinch m <*> gUnpinch m instance ( GPinchable a , GPinchable b , GTag a ~ GTag b ) => GPinchable (a :+: b) where type GTag (a :+: b) = GTag a gPinch (L1 a) = gPinch a gPinch (R1 b) = gPinch b gUnpinch m = L1 <$> gUnpinch m <|> R1 <$> gUnpinch m ------------------------------------------------------------------------------ -- | Fields of data types that represent structs, unions, and exceptions -- should be wrapped inside 'Field' and tagged with the field identifier. -- -- > data Foo = Foo (Field 1 Text) (Field 2 (Maybe Int32)) deriving Generic -- > instance Pinchable Foo -- -- > data A = A (Field 1 Int32) | B (Field 2 Text) deriving Generic -- > instance Pinchable Foo -- -- Fields which hold @Maybe@ values are treated as optional. All fields values -- must be 'Pinchable' to automatically derive a @Pinchable@ instance for the -- new data type. newtype Field (n :: Nat) a = Field a deriving (Bounded, Eq, Enum, Foldable, Functor, Generic, Semigroup, Monoid, NFData, Ord, Show, Traversable, Typeable) -- | Gets the current value of a field. -- -- > let Foo a' _ = {- ... -} -- > a = getField a' getField :: Field n a -> a getField (Field a) = a -- | Puts a value inside a field. -- -- > Foo (putField "Hello") (putField (Just 42)) putField :: a -> Field n a putField = Field -- | A lens on @Field@ wrappers for use with the lens library. -- -- > person & name . field .~ "new value" -- field :: Functor f => (a -> f b) -> Field n a -> f (Field n b) field f (Field a) = Field <$> f a instance {-# OVERLAPPABLE #-} (Pinchable a, KnownNat n) => GPinchable (K1 i (Field n a)) where type GTag (K1 i (Field n a)) = TStruct gPinch (K1 (Field a)) = struct [n .= a] where n = fromIntegral $ natVal (Proxy :: Proxy n) gUnpinch m = K1 . Field <$> m .: n where n = fromIntegral $ natVal (Proxy :: Proxy n) instance (Pinchable a, KnownNat n) => GPinchable (K1 i (Field n (Maybe a))) where type GTag (K1 i (Field n (Maybe a))) = TStruct gPinch (K1 (Field a)) = struct [n ?= a] where n = fromIntegral $ natVal (Proxy :: Proxy n) gUnpinch m = K1 . Field <$> m .:? n where n = fromIntegral $ natVal (Proxy :: Proxy n) ------------------------------------------------------------------------------ -- | Data types that represent Thrift enums must have one constructor for each -- enum item accepting an 'Enumeration' object tagged with the corresponding -- enum value. -- -- > data Role = RoleUser (Enumeration 1) | RoleAdmin (Enumeration 2) -- > deriving Generic -- > instance Pinchable Role data Enumeration (n :: Nat) = Enumeration deriving (Eq, Generic, Ord, Show, Typeable) instance NFData (Enumeration n) -- | Convenience function to construct 'Enumeration' objects. -- -- > let role = RoleUser enum enum :: Enumeration n enum = Enumeration instance KnownNat n => GPinchable (K1 i (Enumeration n)) where type GTag (K1 i (Enumeration n)) = TEnum gPinch (K1 Enumeration) = VInt32 . fromIntegral $ natVal (Proxy :: Proxy n) gUnpinch (VInt32 i) | i == val = return (K1 Enumeration) | otherwise = fail $ "Couldn't match enum value " ++ show i where val = fromIntegral $ natVal (Proxy :: Proxy n) ------------------------------------------------------------------------------ -- | Represents a @void@ result for methods. -- -- This should be used as an element in a response union along with 'Field' -- tags. -- -- For a method, -- -- > void setValue(..) throws -- > (1: ValueAlreadyExists alreadyExists, -- > 2: InternalError internalError) -- -- Something similar to the following can be used. -- -- > data SetValueResponse -- > = SetValueAlreadyExists (Field 1 ValueAlreadyExists) -- > | SetValueInternalError (Field 2 InternalError) -- > | SetValueSuccess Void -- > deriving (Generic) -- > -- > instance Pinchable SetValueResponse data Void = Void deriving (Eq, Generic, Ord, Show, Typeable) instance GPinchable (K1 i Void) where type GTag (K1 i Void) = TStruct gPinch (K1 Void) = struct [] -- If the map isn't empty, there's probably an exception in there. gUnpinch (VStruct m) | HM.null m = return $ K1 Void gUnpinch x = fail $ "Failed to read response. Expected void, got: " ++ show x