{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Custom fpco conversion of Haskell values to a JSON Fay value. module FP.API.Convert (encodeFpco ,decodeFpco ,validFileName ,validateFilePath) where import Control.Monad import Data.Aeson import Data.Aeson.Types (parseEither) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BS import Data.Data import Data.Generics.Aliases import qualified Data.Text as Text import Fay.Convert (encodeFay, decodeFay) import qualified FP.API as API import FP.API.ModuleName import Prelude encodeFpco :: GenericQ Value encodeFpco = encodeFay $ \f -> (f `extQ` (int :: Integer -> Value)) where int = Number . fromIntegral {- $ \f -> f `extQ` bytestring `extQ` int64 where obj con = Object . Map.fromList . (("instance", String con):) wrapper con field val = obj con [(field, val)] bytestring = wrapper "ByteString" "slot1" . String . decodeUtf8 . B64.encode int64 :: Int64 -> Value int64 = wrapper "Int64" "slot1" . String . Text.pack . show -} decodeFpco :: Data a => Value -> Either String a decodeFpco = decodeFay $ \value r -> r `extR` parseInteger value `extR` (validateEncFileName =<< normalDecode value) `extR` (either (Left . Text.unpack) Right . checkModuleName =<< normalDecode value) normalDecode :: Data a => Value -> Either String a normalDecode = decodeFay (\_ r' -> r') {- $ \value r -> r `extR` bytestring value `extR` int64 value where wrapper con field (Object mp) | Map.lookup "instance" mp == Just (String con) , Just val <- Map.lookup field mp = Right val wrapper con field val = Left $ "Couldn't find expected " ++ Text.unpack con ++ " constructor with " ++ Text.unpack field ++ " field. Instead, got\n" ++ show val bytestring = B64.decode <=< fmap encodeUtf8 . expectText <=< wrapper "ByteString" "slot1" int64 :: Value -> Either String Int64 int64 = expectRead <=< wrapper "Int64" "slot1" expectText :: Value -> Either String Text expectText (String txt) = Right txt expectText val = Left $ "Expected string, but got " ++ show val expectRead :: forall a. (Read a, Typeable a) => Value -> Either String a expectRead val = maybe (Left err) Right . readMay . Text.unpack =<< expectText val where err = "Couldn't parse " ++ show (typeOf (undefined :: a)) ++ ", got: " ++ show val -} validateEncFileName :: API.EncFileName -> Either String API.EncFileName validateEncFileName = fmap API.encFileNameFromByteString . validateFilePath . API.unFileName . API.unEncFileName -- | Cannonicalizes and validates filepaths. See #1643. validateFilePath :: ByteString -> Either String ByteString validateFilePath path = case filter (not . BS.null) $ BS.splitWith (`elem` ['\\', '/']) $ BS.filter (/= '\NUL') path of [] -> Left "Invalid filepath: Empty" components -> Right $ BS.intercalate "/" components validFileName :: ByteString -> Either String API.FileName validFileName = fmap API.FileName . validateFilePath -- | -- Utilities copied from Fay.Convert -- | Parse an int. parseInteger :: Value -> Either String Integer parseInteger = parseEither parseJSON