{-| Module : WebApi.Param License : BSD3 Stability : experimental Param serialization and deserialization. 'ToParam' and 'EncodeParam' are responsible for serialization part. 'EncodeParam' converts the value into a wire format. 'ToParam' is responsible for creating (nested) key value pairs, which can be then used to deserialize to original type. For example @ encodeParam 5 == "5" data Foo = Foo { foo :: Int } deriving (Show, Eq, Generic) data Bar = Bar { bar :: Foo } deriving (Show, Eq, Generic) instance ToParam Foo 'FormParam instance ToParam Bar 'FormParam toParam (Proxy :: Proxy 'FormParam) "" (Bar (Foo 5)) == [("bar.foo","5")] @ Deserialization works analogously, 'FromParam' and 'DecodeParam' are counterparts to 'ToParam' and 'EncodeParam' respectively. Generic instances are provided for all of them. This means that the user only need to derive Generic in their type, and provide instance with an empty body. Note that for headers 'FromHeader' and 'ToHeader' is being used in place of 'FromParam' and 'ToParam'. Nesting is not supported for headers. -} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} module WebApi.Param ( -- * Serialization ToParam (..) , EncodeParam (..) , ToHeader (..) , SerializedData , toQueryParam , toFormParam , toFileParam , toPathParam , toCookie , toNonNestedParam -- * Deserialization , FromParam (..) , DecodeParam (..) , FromHeader (..) , Validation (..) , ParamErr (..) , ParamErrToApiErr (..) , DeSerializedData , fromQueryParam , fromFormParam , fromFileParam , fromCookie , lookupParam , fromNonNestedParam -- * Wrappers , Field (..) , JsonOf (..) , OptValue (..) , FileInfo (..) , NonNested (..) -- * Helpers , ParamK (..) , filePath , nest ) where import Blaze.ByteString.Builder (toByteString) import Blaze.ByteString.Builder.Char.Utf8 (fromChar) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as A import Data.ByteString as SB hiding (index, isPrefixOf) import qualified Data.ByteString as SB (isPrefixOf) import Data.ByteString.Builder (byteString, char7, toLazyByteString) import Data.ByteString.Char8 as ASCII (pack, readInteger, split, unpack) import Data.ByteString.Lazy (toStrict) import qualified Data.ByteString.Lex.Fractional as LexF import Data.ByteString.Lex.Integral import Data.CaseInsensitive as CI import Data.Foldable as Fold (foldl') import Data.Int import qualified Data.List as L (find) import Data.Monoid ((<>)) import Data.Proxy import qualified Data.Text as T (Text, pack, uncons) import Data.Text.Encoding (decodeUtf8', encodeUtf8) import Data.Time.Calendar (Day) import Data.Time.Clock (UTCTime) import Data.Time.Format (FormatTime, defaultTimeLocale, formatTime, parseTimeM) import Data.Trie as Trie import Data.Typeable import Data.Vector (Vector) import qualified Data.Vector as V import Data.Word import GHC.Generics import GHC.TypeLits import Network.HTTP.Types import Network.HTTP.Types as Http (Header, QueryItem) import qualified Network.Wai.Parse as Wai (FileInfo (..)) -- | A type for holding a file. newtype FileInfo = FileInfo { fileInfo :: Wai.FileInfo FilePath } deriving (Eq, Show) -- | Obtain the file path from 'FileInfo'. filePath :: FileInfo -> FilePath filePath = Wai.fileContent . fileInfo -- | (Kind) Describes the various types of Param. data ParamK = QueryParam | FormParam | FileParam | PathParam | Cookie -- | Use this type if a key is required but the value is optional. newtype OptValue a = OptValue { toMaybe :: Maybe a} deriving (Show, Read, Eq, Ord) -- | Serializing 'JsonOf' will produce a JSON representation of the value contained within. This is useful if params has to be sent as JSON. newtype JsonOf a = JsonOf {getValue :: a} deriving (Show, Read, Eq, Ord) data Unit = Unit deriving (Show, Eq) instance ToJSON a => ToJSON (JsonOf a) where toJSON (JsonOf a) = toJSON a instance FromJSON a => FromJSON (JsonOf a) where parseJSON jval = JsonOf `fmap` parseJSON jval -- | Define result of serialization of a type of kind 'ParamK'. type family SerializedData (par :: ParamK) where SerializedData 'QueryParam = Http.QueryItem SerializedData 'FormParam = (ByteString, ByteString) SerializedData 'FileParam = (ByteString, Wai.FileInfo FilePath) SerializedData 'PathParam = ByteString SerializedData 'Cookie = (ByteString, ByteString) -- | Define result of deserialization of a type of kind 'ParamK'. type family DeSerializedData (par :: ParamK) where DeSerializedData 'QueryParam = Maybe ByteString DeSerializedData 'FormParam = ByteString DeSerializedData 'FileParam = Wai.FileInfo FilePath DeSerializedData 'Cookie = ByteString -- | Datatype representing the parsed result of params. newtype Validation e a = Validation { getValidation :: Either e a } deriving (Eq, Functor, Show) instance Monoid e => Applicative (Validation e) where pure = Validation . Right Validation a <*> Validation b = Validation $ case a of Right va -> fmap va b Left ea -> either (Left . mappend ea) (const $ Left ea) b -- | Serialize a type into query params. toQueryParam :: (ToParam a 'QueryParam) => a -> Query toQueryParam = toParam (Proxy :: Proxy 'QueryParam) "" -- | Serialize a type into form params. toFormParam :: (ToParam a 'FormParam) => a -> [(ByteString, ByteString)] toFormParam = toParam (Proxy :: Proxy 'FormParam) "" -- | Serialize a type into file params. toFileParam :: (ToParam a 'FileParam) => a -> [(ByteString, Wai.FileInfo FilePath)] toFileParam = toParam (Proxy :: Proxy 'FileParam) "" -- | Serialize a type into path params. toPathParam :: (ToParam a 'PathParam) => a -> [ByteString] toPathParam = toParam (Proxy :: Proxy 'PathParam) "" -- | Serialize a type into cookie. toCookie :: (ToParam a 'Cookie) => a -> [(ByteString, ByteString)] toCookie = toParam (Proxy :: Proxy 'Cookie) "" -- | (Try to) Deserialize a type from query params. fromQueryParam :: (FromParam a 'QueryParam) => Query -> Validation [ParamErr] a fromQueryParam par = fromParam (Proxy :: Proxy 'QueryParam) "" $ Trie.fromList par -- | (Try to) Deserialize a type from form params. fromFormParam :: (FromParam a 'FormParam) => [(ByteString, ByteString)] -> Validation [ParamErr] a fromFormParam par = fromParam (Proxy :: Proxy 'FormParam) "" $ Trie.fromList par -- | (Try to) Deserialize a type from file params. fromFileParam :: (FromParam a 'FileParam) => [(ByteString, Wai.FileInfo FilePath)] -> Validation [ParamErr] a fromFileParam par = fromParam (Proxy :: Proxy 'FileParam) "" $ Trie.fromList par -- | (Try to) Deserialize a type from cookie. fromCookie :: (FromParam a 'Cookie) => [(ByteString, ByteString)] -> Validation [ParamErr] a fromCookie par = fromParam (Proxy :: Proxy 'Cookie) "" $ Trie.fromList par -- | Serialize a type to a given type of kind 'ParamK'. class ToParam a (parK :: ParamK) where toParam :: Proxy (parK :: ParamK) -> ByteString -> a -> [SerializedData parK] default toParam :: (Generic a, GToParam (Rep a) parK) => Proxy (parK :: ParamK) -> ByteString -> a -> [SerializedData parK] toParam pt pfx = gtoParam pt pfx (ParamAcc 0 False) ParamSettings . from -- | (Try to) Deserialize a type from a given type of kind 'ParamK'. class FromParam a (parK :: ParamK) where fromParam :: Proxy (parK :: ParamK) -> ByteString -> Trie (DeSerializedData parK) -> Validation [ParamErr] a default fromParam :: (Generic a, GFromParam (Rep a) parK) => Proxy (parK :: ParamK) -> ByteString -> Trie (DeSerializedData parK) -> Validation [ParamErr] a fromParam pt pfx = (fmap to) . gfromParam pt pfx (ParamAcc 0 False) ParamSettings -- | Serialize a type to 'ByteString'. class EncodeParam (t :: *) where encodeParam :: t -> ByteString default encodeParam :: (Generic t, GHttpParam (Rep t)) => t -> ByteString encodeParam = gEncodeParam . from -- | (Try to) Deserialize a type from 'ByteString'. class DecodeParam (t :: *) where decodeParam :: ByteString -> Maybe t default decodeParam :: (Generic t, GHttpParam (Rep t)) => ByteString -> Maybe t decodeParam = (fmap to) . gDecodeParam instance EncodeParam ByteString where encodeParam = id instance DecodeParam ByteString where decodeParam = Just instance EncodeParam Int where encodeParam i = ASCII.pack $ show i instance DecodeParam Int where decodeParam str = case readSigned readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Int8 where encodeParam i = ASCII.pack $ show i instance DecodeParam Int8 where decodeParam str = case readSigned readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Int16 where encodeParam i = ASCII.pack $ show i instance DecodeParam Int16 where decodeParam str = case readSigned readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Int32 where encodeParam i = ASCII.pack $ show i instance DecodeParam Int32 where decodeParam str = case readSigned readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Int64 where encodeParam i = ASCII.pack $ show i instance DecodeParam Int64 where decodeParam str = case readSigned readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Word where encodeParam i = ASCII.pack $ show i instance DecodeParam Word where decodeParam str = case readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Word8 where encodeParam i = ASCII.pack $ show i instance DecodeParam Word8 where decodeParam str = case readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Word16 where encodeParam i = ASCII.pack $ show i instance DecodeParam Word16 where decodeParam str = case readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Word32 where encodeParam i = ASCII.pack $ show i instance DecodeParam Word32 where decodeParam str = case readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Word64 where encodeParam i = ASCII.pack $ show i instance DecodeParam Word64 where decodeParam str = case readDecimal str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Float where encodeParam d = ASCII.pack $ show d instance DecodeParam Float where decodeParam str = case readSigned LexF.readExponential str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Double where encodeParam d = ASCII.pack $ show d instance DecodeParam Double where decodeParam str = case readSigned LexF.readExponential str of Just (v, "") -> Just v _ -> Nothing instance EncodeParam Char where encodeParam = toByteString . fromChar instance DecodeParam Char where decodeParam str = case decodeUtf8' str of Right txt -> fmap fst (T.uncons txt) Left _ -> Nothing instance EncodeParam T.Text where encodeParam = encodeUtf8 instance DecodeParam T.Text where decodeParam str = case decodeUtf8' str of Right txt -> Just txt Left _ -> Nothing instance EncodeParam Day where encodeParam day = ASCII.pack $ show day instance DecodeParam Day where decodeParam str = case reads $ ASCII.unpack str of [(a,"")] -> Just a _ -> Nothing instance EncodeParam UTCTime where encodeParam t = ASCII.pack $ formatTime defaultTimeLocale format t where format = "%FT%T." ++ formatSubseconds t ++ "Z" instance DecodeParam UTCTime where decodeParam str = case parseTimeM True defaultTimeLocale "%FT%T%QZ" (ASCII.unpack str) of Just d -> Just d _ -> Nothing formatSubseconds :: (FormatTime t) => t -> String formatSubseconds = formatTime defaultTimeLocale "%q" instance EncodeParam Unit where encodeParam _ = "()" instance DecodeParam Unit where decodeParam str = case str of "()" -> Just Unit _ -> Nothing instance (EncodeParam a, EncodeParam b) => EncodeParam (a,b) where encodeParam (a,b) = toStrict $ toLazyByteString $ byteString (encodeParam a) <> char7 ',' <> byteString (encodeParam b) instance (DecodeParam a, DecodeParam b) => DecodeParam (a,b) where decodeParam str = case ASCII.split ',' str of [str1, str2] -> (,) <$> decodeParam str1 <*> decodeParam str2 _ -> Nothing instance EncodeParam Bool where encodeParam i = ASCII.pack $ show i instance DecodeParam Bool where decodeParam str | str == "True" = Just True decodeParam str | str == "False" = Just False | otherwise = Nothing instance EncodeParam Integer where encodeParam i = ASCII.pack $ show i instance DecodeParam Integer where decodeParam str = case ASCII.readInteger str of Just (i, "") -> Just i _ -> Nothing instance (ToJSON a) => EncodeParam (JsonOf a) where encodeParam (JsonOf a) = toStrict $ A.encode a instance (FromJSON a) => DecodeParam (JsonOf a) where decodeParam str = A.decodeStrict' str class GHttpParam f where gEncodeParam :: f a -> ByteString gDecodeParam :: ByteString -> Maybe (f a) instance (GHttpParam f) => GHttpParam (D1 c f) where gEncodeParam (M1 c) = gEncodeParam c gDecodeParam str = M1 <$> (gDecodeParam str) instance (GHttpParam f, GHttpParam g) => GHttpParam (f :+: g) where gEncodeParam (L1 l) = gEncodeParam l gEncodeParam (R1 r) = gEncodeParam r gDecodeParam str = case L1 <$> gDecodeParam str of l1@(Just _) -> l1 _ -> R1 <$> gDecodeParam str instance (GHttpParam f, Constructor c) => GHttpParam (C1 c f) where gEncodeParam con@(M1 c) = const (ASCII.pack $ conName con) $ gEncodeParam c gDecodeParam str = if (ASCII.pack $ conName (undefined :: (C1 c f) a)) == str then M1 <$> gDecodeParam str else Nothing instance GHttpParam U1 where gEncodeParam U1 = error "Panic! Unreacheable code @ GHttpParam U1" gDecodeParam _ = Just U1 -- | Use this type if for serialization \/ deserialization nesting is not required. The type contained within most likely requires 'EncodeParam' \/ 'DecodeParam'. newtype NonNested a = NonNested { getNonNestedParam :: a } deriving (Show, Eq, Read) -- | Serialize a type without nesting. toNonNestedParam :: (ToParam (NonNested a) parK) => Proxy (parK :: ParamK) -> ByteString -> a -> [SerializedData parK] toNonNestedParam par pfx a = toParam par pfx (NonNested a) -- | (Try to) Deserialize a type without nesting. fromNonNestedParam :: (FromParam (NonNested a) parK) => Proxy (parK :: ParamK) -> ByteString -> Trie (DeSerializedData parK) -> Validation [ParamErr] a fromNonNestedParam par pfx kvs = getNonNestedParam <$> fromParam par pfx kvs instance (EncodeParam a) => ToParam (NonNested a) 'QueryParam where toParam _ pfx (NonNested val) = [(pfx, Just $ encodeParam val)] instance (EncodeParam a) => ToParam (NonNested a) 'FormParam where toParam _ pfx (NonNested val) = [(pfx, encodeParam val)] instance (EncodeParam a) => ToParam (NonNested a) 'Cookie where toParam _ pfx (NonNested val) = [(pfx, encodeParam val)] instance (DecodeParam a, Typeable a) => FromParam (NonNested a) 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right $ NonNested v _ -> Validation $ Left [ParseErr key $ T.pack $ "Unable to cast to " ++ (show $ typeOf (Proxy :: Proxy a))] _ -> Validation $ Left [NotFound key] instance (DecodeParam a, Typeable a) => FromParam (NonNested a) 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right $ NonNested v _ -> Validation $ Left [ParseErr key $ T.pack $ "Unable to cast to " ++ (show $ typeOf (Proxy :: Proxy a))] _ -> Validation $ Left [NotFound key] instance (DecodeParam a, Typeable a) => FromParam (NonNested a) 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right $ NonNested v _ -> Validation $ Left [ParseErr key $ T.pack $ "Unable to cast to " ++ (show $ typeOf (Proxy :: Proxy a))] _ -> Validation $ Left [NotFound key] instance ToParam () parK where toParam _ _ _ = [] instance ToHeader () where toHeader _ = [] instance ToParam Unit 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Unit 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Unit 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Int 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int8 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Int8 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int8 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int16 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Int16 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int16 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int32 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Int32 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int32 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int64 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Int64 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Int64 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Word 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word8 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Word8 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word8 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word16 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Word16 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word16 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word32 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Word32 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word32 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word64 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Word64 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Word64 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Integer 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Integer 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Integer 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Bool 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Bool 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Bool 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Double 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Double 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Double 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Float 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Float 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Float 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Char 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Char 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Char 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam T.Text 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam T.Text 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam T.Text 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam ByteString 'QueryParam where toParam _ pfx val = [(pfx, Just $ val)] instance ToParam ByteString 'FormParam where toParam _ pfx val = [(pfx, val)] instance ToParam ByteString 'Cookie where toParam _ pfx val = [(pfx, val)] instance ToParam Day 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam Day 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam Day 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam UTCTime 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance ToParam UTCTime 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam UTCTime 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance (EncodeParam a) => ToParam (OptValue a) 'QueryParam where toParam _ pfx (OptValue (Just val)) = [(pfx, Just $ encodeParam val)] toParam _ pfx (OptValue Nothing) = [(pfx, Nothing)] instance (EncodeParam a) => ToParam (OptValue a) 'FormParam where toParam _ pfx (OptValue (Just val)) = [(pfx, encodeParam val)] toParam _ _ (OptValue Nothing) = [] instance (EncodeParam a) => ToParam (OptValue a) 'Cookie where toParam _ pfx (OptValue (Just val)) = [(pfx, encodeParam val)] toParam _ _ (OptValue Nothing) = [] instance (ToJSON a) => ToParam (JsonOf a) 'QueryParam where toParam _ pfx val = [(pfx, Just $ encodeParam val)] instance (ToJSON a) => ToParam (JsonOf a) 'FormParam where toParam _ pfx val = [(pfx, encodeParam val)] instance (ToJSON a) => ToParam (JsonOf a) 'Cookie where toParam _ pfx val = [(pfx, encodeParam val)] instance ToParam a par => ToParam (Maybe a) par where toParam pt pfx (Just val) = toParam pt pfx val toParam _ _ Nothing = [] instance (ToParam a par, ToParam b par) => ToParam (Either a b) par where toParam pt pfx (Left e) = toParam pt (pfx `nest` "Left") e toParam pt pfx (Right v) = toParam pt (pfx `nest` "Right") v instance ToParam a par => ToParam [a] par where toParam pt pfx vals = Prelude.concatMap (\(ix, v) -> toParam pt (pfx `nest` (ASCII.pack $ show ix)) v) $ Prelude.zip [(0 :: Word)..] vals instance ToParam a par => ToParam (Vector a) par where toParam pt pfx vals = toParam pt pfx (V.toList vals) instance FromParam () parK where fromParam _ _ _ = pure () instance FromHeader () where fromHeader _ = pure () instance FromParam Unit 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to NullaryConstructor"] _ -> Validation $ Left [NotFound key] instance FromParam Unit 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to NullaryConstructor"] _ -> Validation $ Left [NotFound key] instance FromParam Unit 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to NullaryConstructor"] _ -> Validation $ Left [NotFound key] instance FromParam Bool 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Bool"] _ -> Validation $ Left [NotFound key] instance FromParam Bool 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Bool"] _ -> Validation $ Left [NotFound key] instance FromParam Bool 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Bool"] _ -> Validation $ Left [NotFound key] instance FromParam Char 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Char"] _ -> Validation $ Left [NotFound key] instance FromParam Char 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Char"] _ -> Validation $ Left [NotFound key] instance FromParam Char 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Char"] _ -> Validation $ Left [NotFound key] instance FromParam UTCTime 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to UTCTime (ISO-8601)"] _ -> Validation $ Left [NotFound key] instance FromParam UTCTime 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to UTCTime (ISO-8601)"] _ -> Validation $ Left [NotFound key] instance FromParam UTCTime 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to UTCTime (ISO-8601)"] _ -> Validation $ Left [NotFound key] instance FromParam Int 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int"] _ -> Validation $ Left [NotFound key] instance FromParam Int 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int"] _ -> Validation $ Left [NotFound key] instance FromParam Int 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int"] _ -> Validation $ Left [NotFound key] instance FromParam Int8 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int8"] _ -> Validation $ Left [NotFound key] instance FromParam Int8 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int8"] _ -> Validation $ Left [NotFound key] instance FromParam Int8 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int8"] _ -> Validation $ Left [NotFound key] instance FromParam Int16 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int16"] _ -> Validation $ Left [NotFound key] instance FromParam Int16 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int16"] _ -> Validation $ Left [NotFound key] instance FromParam Int16 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int16"] _ -> Validation $ Left [NotFound key] instance FromParam Int32 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int32"] _ -> Validation $ Left [NotFound key] instance FromParam Int32 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int32"] _ -> Validation $ Left [NotFound key] instance FromParam Int32 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int32"] _ -> Validation $ Left [NotFound key] instance FromParam Int64 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int64"] _ -> Validation $ Left [NotFound key] instance FromParam Int64 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int64"] _ -> Validation $ Left [NotFound key] instance FromParam Int64 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int64"] _ -> Validation $ Left [NotFound key] instance FromParam Integer 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int64"] _ -> Validation $ Left [NotFound key] instance FromParam Integer 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int64"] _ -> Validation $ Left [NotFound key] instance FromParam Integer 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Int64"] _ -> Validation $ Left [NotFound key] instance FromParam Word 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word"] _ -> Validation $ Left [NotFound key] instance FromParam Word 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word"] _ -> Validation $ Left [NotFound key] instance FromParam Word 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word"] _ -> Validation $ Left [NotFound key] instance FromParam Word8 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word8"] _ -> Validation $ Left [NotFound key] instance FromParam Word8 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word8"] _ -> Validation $ Left [NotFound key] instance FromParam Word8 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word8"] _ -> Validation $ Left [NotFound key] instance FromParam Word16 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word16"] _ -> Validation $ Left [NotFound key] instance FromParam Word16 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word16"] _ -> Validation $ Left [NotFound key] instance FromParam Word16 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word16"] _ -> Validation $ Left [NotFound key] instance FromParam Word32 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word32"] _ -> Validation $ Left [NotFound key] instance FromParam Word32 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word32"] _ -> Validation $ Left [NotFound key] instance FromParam Word32 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word32"] _ -> Validation $ Left [NotFound key] instance FromParam Word64 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word64"] _ -> Validation $ Left [NotFound key] instance FromParam Word64 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word64"] _ -> Validation $ Left [NotFound key] instance FromParam Word64 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Word64"] _ -> Validation $ Left [NotFound key] instance FromParam Double 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Double"] _ -> Validation $ Left [NotFound key] instance FromParam Double 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Double"] _ -> Validation $ Left [NotFound key] instance FromParam Double 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Double"] _ -> Validation $ Left [NotFound key] instance FromParam Float 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Float"] _ -> Validation $ Left [NotFound key] instance FromParam Float 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Float"] _ -> Validation $ Left [NotFound key] instance FromParam Float 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Float"] _ -> Validation $ Left [NotFound key] instance FromParam ByteString 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to ByteString"] _ -> Validation $ Left [NotFound key] instance FromParam ByteString 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to ByteString"] _ -> Validation $ Left [NotFound key] instance FromParam ByteString 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to ByteString"] _ -> Validation $ Left [NotFound key] instance FromParam a par => FromParam (Maybe a) par where fromParam pt key kvs = case Trie.null kvs' of True -> Validation $ Right Nothing False -> case (fromParam pt key kvs' :: Validation [ParamErr] a) of Validation (Right val) -> Validation $ Right $ Just val Validation (Left errs) -> Validation $ Left errs where kvs' = submap key kvs instance (FromParam a par, FromParam b par) => FromParam (Either a b) par where fromParam pt key kvs = case Trie.null kvsL of True -> case Trie.null kvsR of True -> Validation $ Left [ParseErr key "Unable to cast to Either"] False -> Right <$> fromParam pt keyR kvsR False -> Left <$> fromParam pt keyL kvsL where kvsL = submap keyL kvs kvsR = submap keyR kvs keyL = (key `nest` "Left") keyR = (key `nest` "Right") instance FromParam T.Text 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Text"] _ -> Validation $ Left [NotFound key] instance FromParam T.Text 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Text"] _ -> Validation $ Left [NotFound key] instance FromParam T.Text 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Text"] _ -> Validation $ Left [NotFound key] instance FromParam Day 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Day"] _ -> Validation $ Left [NotFound key] instance FromParam Day 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Day"] _ -> Validation $ Left [NotFound key] instance FromParam Day 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right v _ -> Validation $ Left [ParseErr key "Unable to cast to Day"] _ -> Validation $ Left [NotFound key] instance (FromParam a par) => FromParam [a] par where fromParam pt key kvs = case Trie.null kvs' of True -> Validation $ Right [] False -> let pars = Prelude.map (\(nkey, kv) -> fromParam pt nkey kv :: Validation [ParamErr] a) kvitems in Prelude.reverse <$> Fold.foldl' accRes (Validation $ Right []) pars where kvs' = submap key kvs kvitems = Prelude.takeWhile (not . Prelude.null . snd) (Prelude.map (\ix -> let ixkey = key `nest` (ASCII.pack $ show ix) in (ixkey, submap ixkey kvs')) [(0 :: Word) .. 2000]) accRes acc elemt = case (acc, elemt) of (Validation (Right as), Validation (Right e)) -> Validation $ Right (e:as) (Validation (Left as), Validation (Right _)) -> Validation $ Left as (Validation (Right _), Validation (Left es)) -> Validation $ Left es (Validation (Left as), Validation (Left es)) -> Validation $ Left (es ++ as) instance (FromParam a par) => FromParam (Vector a) par where fromParam pt key kvs = case fromParam pt key kvs of Validation (Right v) -> Validation $ Right (V.fromList v) Validation (Left err) -> Validation (Left err) instance (DecodeParam a) => FromParam (OptValue a) 'QueryParam where fromParam pt key kvs = case lookupParam pt key kvs of Just (Just par) -> case decodeParam par of Just v -> Validation $ Right $ OptValue $ Just v _ -> Validation $ Left [ParseErr key "Unable to cast to OptValue"] Just Nothing -> Validation $ Right $ OptValue Nothing _ -> Validation $ Left [NotFound key] instance (DecodeParam a) => FromParam (OptValue a) 'FormParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right $ OptValue $ Just v _ -> Validation $ Left [ParseErr key "Unable to cast to OptValue"] _ -> Validation $ Left [NotFound key] instance (DecodeParam a) => FromParam (OptValue a) 'Cookie where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> case decodeParam par of Just v -> Validation $ Right $ OptValue $ Just v _ -> Validation $ Left [ParseErr key "Unable to cast to OptValue"] _ -> Validation $ Left [NotFound key] instance ToParam FileInfo 'FileParam where toParam _ key (FileInfo val) = [(key, val)] instance FromParam FileInfo 'FileParam where fromParam pt key kvs = case lookupParam pt key kvs of Just par -> Validation $ Right (FileInfo par) Nothing -> Validation $ Left [NotFound key] instance ToParam ByteString 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Int 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Int8 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Int16 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Int32 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Int64 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Word 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Word8 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Word16 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Word32 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Word64 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Float 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Double 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Char 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam T.Text 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Day 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam UTCTime 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Bool 'PathParam where toParam _ _ v = [encodeParam v] instance ToParam Integer 'PathParam where toParam _ _ v = [encodeParam v] instance (ToJSON a) => ToParam (JsonOf a) 'PathParam where toParam _ _ v = [encodeParam v] instance ( EncodeParam a , EncodeParam b ) => ToParam (a, b) 'PathParam where toParam _ _ (a, b) = [encodeParam a, encodeParam b] instance ( EncodeParam a , EncodeParam b , EncodeParam c ) => ToParam (a, b, c) 'PathParam where toParam _ _ (a, b, c) = [ encodeParam a , encodeParam b , encodeParam c ] instance ( EncodeParam a , EncodeParam b , EncodeParam c , EncodeParam d ) => ToParam (a, b, c, d) 'PathParam where toParam _ _ (a, b, c, d) = [ encodeParam a , encodeParam b , encodeParam c , encodeParam d ] instance ( EncodeParam a , EncodeParam b , EncodeParam c , EncodeParam d , EncodeParam e ) => ToParam (a, b, c, d, e) 'PathParam where toParam _ _ (a, b, c, d, e) = [ encodeParam a , encodeParam b , encodeParam c , encodeParam d , encodeParam e ] instance ( EncodeParam a , EncodeParam b , EncodeParam c , EncodeParam d , EncodeParam e , EncodeParam f ) => ToParam (a, b, c, d, e, f) 'PathParam where toParam _ _ (a, b, c, d, e, f) = [ encodeParam a , encodeParam b , encodeParam c , encodeParam d , encodeParam e , encodeParam f ] instance ( EncodeParam a , EncodeParam b , EncodeParam c , EncodeParam d , EncodeParam e , EncodeParam f , EncodeParam g , EncodeParam h ) => ToParam (a, b, c, d, e, f, g, h) 'PathParam where toParam _ _ (a, b, c, d, e, f, g, h) = [ encodeParam a , encodeParam b , encodeParam c , encodeParam d , encodeParam e , encodeParam f , encodeParam g , encodeParam h ] instance ( EncodeParam a , EncodeParam b , EncodeParam c , EncodeParam d , EncodeParam e , EncodeParam f , EncodeParam g , EncodeParam h , EncodeParam i ) => ToParam (a, b, c, d, e, f, g, h, i) 'PathParam where toParam _ _ (a, b, c, d, e, f, g, h, i) = [ encodeParam a , encodeParam b , encodeParam c , encodeParam d , encodeParam e , encodeParam f , encodeParam g , encodeParam h , encodeParam i ] instance ( EncodeParam a , EncodeParam b , EncodeParam c , EncodeParam d , EncodeParam e , EncodeParam f , EncodeParam g , EncodeParam h , EncodeParam i , EncodeParam j ) => ToParam (a, b, c, d, e, f, g, h, i, j) 'PathParam where toParam _ _ (a, b, c, d, e, f, g, h, i, j) = [ encodeParam a , encodeParam b , encodeParam c , encodeParam d , encodeParam e , encodeParam f , encodeParam g , encodeParam h , encodeParam i , encodeParam j ] -- | Errors that occured during deserialization. data ParamErr = NotFound ByteString -- ^ The key was not found. | ParseErr ByteString T.Text -- ^ A parse error occured while deserializing the type. deriving (Show, Eq) utf8DecodeError :: String -> String -> a utf8DecodeError src msg = error $ "Error decoding Bytes into UTF8 string at: " ++ src ++ " Message: " ++ msg instance ToJSON ParamErr where toJSON (NotFound bs) = case decodeUtf8' bs of Left ex -> utf8DecodeError "ToJSON ParamErr" (show ex) Right bs' -> A.object ["NotFound" A..= bs'] toJSON (ParseErr bs msg) = case decodeUtf8' bs of Left ex -> utf8DecodeError "ToJSON ParamErr" (show ex) Right bs' -> A.object ["ParseErr" A..= [bs', msg]] -- | Convert the 'ParamErr' that occured during deserialization into 'ApiErr' type which can then be put in 'Response'. class ParamErrToApiErr apiErr where toApiErr :: [ParamErr] -> apiErr instance ParamErrToApiErr () where toApiErr = const () instance ParamErrToApiErr T.Text where toApiErr errs = T.pack (show errs) instance ParamErrToApiErr A.Value where toApiErr errs = toJSON errs -- | Nest the key with a prefix. -- -- > nest "pfx" "key" == "pfx.key" -- > nest "" "key" == "key" nest :: ByteString -> ByteString -> ByteString nest s1 s2 | SB.null s1 = s2 | otherwise = SB.concat [s1, ".", s2] -- | Lookup a value from the 'Trie' using the given key. lookupParam :: Proxy (parK :: ParamK) -> ByteString -> Trie (DeSerializedData parK) -> Maybe (DeSerializedData parK) lookupParam _ key kvs = Trie.lookup key kvs data ParamAcc = ParamAcc { index :: Int, isSum :: Bool } deriving (Show, Eq) data ParamSettings = ParamSettings deriving (Show, Eq) -- | Used to alias the field name while serailizing FromParam/ToParam instances -- -- > data Foo = Foo { foobar :: Field "foo_bar" Int} -- fieldname would be aliased to foo_bar instead of foobar newtype Field (s :: Symbol) a = Field { unField :: a } instance (ToParam a parK) => ToParam (Field s a) parK where toParam pt pfx = toParam pt pfx . unField instance (FromParam a parK) => FromParam (Field s a) parK where fromParam pt key kvs = Field <$> fromParam pt key kvs type family IsField a where IsField (Field s a) = 'True IsField a = 'False class FieldModifier a (b :: Bool) where fieldMod :: Proxy a -> Proxy b -> (ByteString -> ByteString) instance (KnownSymbol s) => FieldModifier (Field s a) 'True where fieldMod _ _ = const $ ASCII.pack (symbolVal (Proxy :: Proxy s)) instance FieldModifier a 'False where fieldMod _ _ = id -- | Serialize a type to the header params class ToHeader a where toHeader :: a -> [Http.Header] default toHeader :: (Generic a, GToHeader (Rep a)) => a -> [Http.Header] toHeader = gtoHeader "" (ParamAcc 0 False) ParamSettings . from -- | (Try to) Deserialize a type from the header params class FromHeader a where fromHeader :: [Http.Header] -> Validation [ParamErr] a default fromHeader :: (Generic a, GFromHeader (Rep a)) => [Http.Header] -> Validation [ParamErr] a fromHeader = (fmap to) . gfromHeader "" (ParamAcc 0 False) ParamSettings class GToHeader f where gtoHeader :: ByteString -> ParamAcc -> ParamSettings -> f a -> [Http.Header] instance (GToHeader f, GToHeader g) => GToHeader (f :+: g) where gtoHeader pfx pa psett (L1 x) = gtoHeader pfx (pa { isSum = True }) psett x gtoHeader pfx pa psett (R1 y) = gtoHeader pfx (pa { isSum = True }) psett y instance (GToHeader f, GToHeader g) => GToHeader (f :*: g) where gtoHeader pfx pa psett (x :*: y) = gtoHeader pfx pa psett x ++ gtoHeader pfx (pa { index = index pa + 1 }) psett y instance (EncodeParam c) => GToHeader (K1 i c) where gtoHeader pfx _ _ (K1 x) = [(mk pfx, encodeParam x)] instance (GToHeader f, Constructor t) => GToHeader (M1 C t f) where gtoHeader pfx pa psett con@(M1 x) = case isSum pa of True -> gtoHeader (pfx `nest` ASCII.pack (conName con)) (pa { index = 0 }) psett x False -> gtoHeader pfx (pa { index = 0 }) psett x instance (GToHeader f) => GToHeader (M1 D t f) where gtoHeader pfx pa psett (M1 x) = gtoHeader pfx pa psett x instance (GToHeader f, Selector t) => GToHeader (M1 S t f) where gtoHeader pfx pa psett m@(M1 x) = let fldN = ASCII.pack (selName m) in case fldN of "" -> gtoHeader (pfx `nest` numberedFld pa) pa psett x _ -> gtoHeader (pfx `nest` fldN) pa psett x instance GToHeader U1 where gtoHeader pfx _ _ _ = [(mk pfx, encodeParam Unit)] class GFromHeader f where gfromHeader :: ByteString -> ParamAcc -> ParamSettings -> [Http.Header] -> Validation [ParamErr] (f a) instance (GFromHeader f, GFromHeader g) => GFromHeader (f :*: g) where gfromHeader pfx pa psett kvs = (:*:) <$> gfromHeader pfx pa psett kvs <*> gfromHeader pfx (pa { index = index pa + 1 }) psett kvs instance (GFromHeader f, GFromHeader g) => GFromHeader (f :+: g) where gfromHeader pfx pa psett kvs = case L1 <$> gfromHeader pfx (pa { isSum = True }) psett kvs of l1@(Validation (Right _)) -> l1 Validation (Left []) -> R1 <$> gfromHeader pfx (pa { isSum = True }) psett kvs l1 -> l1 instance (GFromHeader f, Constructor t) => GFromHeader (M1 C t f) where gfromHeader pfx pa psett kvs = let conN = ASCII.pack (conName (undefined :: (M1 C t f) a)) in case isSum pa of True -> case isMemberH (pfx `nest` conN) kvs of True -> M1 <$> gfromHeader (pfx `nest` conN) pa psett kvs False -> Validation $ Left [] False -> M1 <$> gfromHeader pfx pa psett kvs instance (GFromHeader f, Datatype t) => GFromHeader (M1 D t f) where gfromHeader pfx pa psett kvs = case M1 <$> gfromHeader pfx pa psett kvs of Validation (Left []) -> Validation (Left [ParseErr pfx ("Unable to cast to SumType: " <> dtN)]) v -> v where dtN = T.pack $ datatypeName (undefined :: (M1 D t f) a) instance (GFromHeader f, Selector t) => GFromHeader (M1 S t f) where gfromHeader pfx pa psett kvs = let fldN = (ASCII.pack $ (selName (undefined :: (M1 S t f) a))) in case fldN of "" -> M1 <$> gfromHeader (pfx `nest` numberedFld pa) pa psett kvs _ -> M1 <$> gfromHeader (pfx `nest` fldN) pa psett kvs instance (DecodeParam c) => GFromHeader (K1 i c) where gfromHeader key _ _ kvs = case lookupH key kvs of Just v -> case decodeParam v of Just v' -> Validation (Right $ K1 v') -- K1 <$> fromParam pt pfx kvs Nothing -> Validation $ Left [ParseErr key "Unable to cast to "] _ -> Validation $ Left [NotFound key] instance GFromHeader U1 where gfromHeader key _ _ kvs = case lookupH key kvs of Just v -> case (decodeParam v :: Maybe Unit) of Just _ -> Validation (Right U1) Nothing -> Validation $ Left [ParseErr key "Unable to cast to "] _ -> Validation $ Left [NotFound key] class GFromParam f (parK :: ParamK) where gfromParam :: Proxy (parK :: ParamK) -> ByteString -> ParamAcc -> ParamSettings -> Trie (DeSerializedData parK) -> Validation [ParamErr] (f a) instance (GFromParam f parK, GFromParam g parK) => GFromParam (f :*: g) parK where gfromParam pt pfx pa psett kvs = (:*:) <$> gfromParam pt pfx pa psett kvs <*> gfromParam pt pfx (pa { index = index pa + 1 }) psett kvs instance (GFromParam f parK, GFromParam g parK) => GFromParam (f :+: g) parK where gfromParam pt pfx pa psett kvs = case L1 <$> gfromParam pt pfx (pa { isSum = True }) psett kvs of l1@(Validation (Right _)) -> l1 Validation (Left []) -> R1 <$> gfromParam pt pfx (pa { isSum = True }) psett kvs l1 -> l1 instance (GFromParam f parK, Constructor t) => GFromParam (M1 C t f) parK where gfromParam pt pfx pa psett kvs = let conN = ASCII.pack (conName (undefined :: (M1 C t f) a)) in case isSum pa of True -> case Trie.null $ submap (pfx `nest` conN) kvs of False -> M1 <$> gfromParam pt (pfx `nest` conN) pa psett kvs True -> Validation $ Left [] False -> M1 <$> gfromParam pt pfx pa psett kvs instance (GFromParam f parK, Datatype t) => GFromParam (M1 D t f) parK where gfromParam pt pfx pa psett kvs = case M1 <$> gfromParam pt pfx pa psett kvs of Validation (Left []) -> Validation (Left [ParseErr pfx ("Unable to cast to SumType: " <> dtN)]) v -> v where dtN = T.pack $ datatypeName (undefined :: (M1 D t f) a) instance (GFromParam f parK, Selector t, f ~ (K1 i c), FieldModifier c (IsField c)) => GFromParam (M1 S t f) parK where gfromParam pt pfx pa psett kvs = let fldN = (ASCII.pack $ (selName (undefined :: (M1 S t f) a))) modSelName = fieldMod (Proxy :: Proxy c) (Proxy :: Proxy (IsField c)) in case fldN of "" -> M1 <$> gfromParam pt (pfx `nest` numberedFld pa) pa psett (submap pfx kvs) _ -> M1 <$> gfromParam pt (pfx `nest` (modSelName fldN)) pa psett (submap pfx kvs) instance (FromParam c parK) => GFromParam (K1 i c) parK where gfromParam pt pfx _ _ kvs = K1 <$> fromParam pt pfx kvs instance (FromParam Unit parK) => GFromParam U1 parK where gfromParam pt key _ _ kvs = const U1 <$> (fromParam pt key kvs :: Validation [ParamErr] Unit) class GToParam f (parK :: ParamK) where gtoParam :: Proxy (parK :: ParamK) -> ByteString -> ParamAcc -> ParamSettings -> f a -> [SerializedData parK] instance (GToParam f parK, GToParam g parK) => GToParam (f :*: g) parK where gtoParam pt pfx pa psett (x :*: y) = gtoParam pt pfx pa psett x ++ gtoParam pt pfx (pa { index = index pa + 1 }) psett y instance (GToParam f parK, GToParam g parK) => GToParam (f :+: g) parK where gtoParam pt pfx pa psett(L1 x) = gtoParam pt pfx (pa { isSum = True }) psett x gtoParam pt pfx pa psett (R1 y) = gtoParam pt pfx (pa { isSum = True }) psett y instance (ToParam c parK) => GToParam (K1 i c) parK where gtoParam pt pfx _ _ (K1 x) = toParam pt pfx x instance (GToParam f parK, Constructor t) => GToParam (M1 C t f) parK where gtoParam pt pfx pa psett con@(M1 x) = case isSum pa of True -> gtoParam pt (pfx `nest` ASCII.pack (conName con)) (pa { index = 0 }) psett x False -> gtoParam pt pfx (pa { index = 0 }) psett x instance (GToParam f parK) => GToParam (M1 D t f) parK where gtoParam pt pfx pa psett (M1 x) = gtoParam pt pfx pa psett x instance (GToParam f parK, Selector t, f ~ (K1 i c), FieldModifier c (IsField c)) => GToParam (M1 S t f) parK where gtoParam pt pfx pa psett m@(M1 x) = let fldN = ASCII.pack (selName m) modSelName = fieldMod (Proxy :: Proxy c) (Proxy :: Proxy (IsField c)) in case fldN of "" -> gtoParam pt (pfx `nest` numberedFld pa) pa psett x _ -> gtoParam pt (pfx `nest` (modSelName fldN)) pa psett x instance (ToParam Unit parK) => GToParam U1 parK where gtoParam pt pfx _ _ _ = toParam pt pfx Unit numberedFld :: ParamAcc -> ByteString numberedFld pa = ASCII.pack $ show (index pa) isMemberH :: ByteString -> [Header] -> Bool isMemberH k = maybe False (const True) . lookupH' isPrefixOf k lookupH :: ByteString -> [Header] -> Maybe ByteString lookupH = lookupH' (==) lookupH' :: (CI ByteString -> CI ByteString -> Bool) -> ByteString -> [Header] -> Maybe ByteString lookupH' f k = fmap snd . L.find ((f $ mk k) . fst) isPrefixOf :: CI ByteString -> CI ByteString -> Bool isPrefixOf n h = foldedCase n `SB.isPrefixOf` foldedCase h