{-# LANGUAGE CPP #-} {-# 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 #-} #if __GLASGOW_HASKELL__ < 709 {-# LANGUAGE OverlappingInstances #-} #define OVERLAP #else #define OVERLAP {-# OVERLAPPABLE #-} #endif -- | -- 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 #if __GLASGOW_HASKELL__ < 709 import Data.Foldable (Foldable) import Data.Traversable (Traversable) #endif import Data.Semigroup 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 OVERLAP 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 OVERLAP (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) gUnpinch x = fail $ "Failed to read enum. Got " ++ show x ------------------------------------------------------------------------------ -- | 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