{-# LANGUAGE OverloadedStrings, FlexibleInstances #-} -- | -- Module : Data.PHPSession.ImplicitConv.PHPTypeCoercion -- Copyright: (c) 2014 Edward Blake -- License: BSD-style -- Maintainer: Edward L. Blake -- Stability: experimental -- Portability: portable -- -- Functions for performing conversion from 'PHPSessionValue' objects to Haskell -- types while using a subset of the implicit PHP type coercion behaviour. Some -- of the differences from the implicit type conversion found in PHP are noted: -- -- * Conversions that are documented as undefined behaviour in the PHP manual -- will throw definite exceptions with these functions. -- -- * A significant difference from PHP's conversion rules is that @/NULL/@ -- cannot be directly converted to any data type except to 'Bool', otherwise -- @/NULL/@ has to be handled by using the type @'Maybe' a@ to capture -- nullable values. -- -- * Objects that implement Serializable are not convertible to any other type -- at all as their value systems are not directly interpretable in a meaningful -- manner. -- -- * Numbers can be converted to strings, but only strings that satisfy -- @reads str = [(value, \"\")]@ can be converted back to numbers. -- -- * Arrays and objects to string conversions which would normally be coerced -- to the simple strings \"Array\" and \"Object\" in PHP, are simply considered -- errors with these conversion functions. -- module Data.PHPSession.ImplicitConv.PHPTypeCoercion ( -- * Convert from 'PHPSessionValue' convFromPHPImplicit, convFromPHPImplicitSafe, -- * Type classes ConversionFromPHPImplicitValueOrMismatch(..) ) where import qualified Data.ByteString.Lazy.Char8 as LBS import qualified Data.ByteString.Char8 as BS import Data.PHPSession.Types import Data.PHPSession.ImplicitConv.ConvBool import Data.Int (Int32, Int64) import Data.List as L (foldl') import Data.PHPSession.Conv class ConversionFromPHPImplicitValueOrMismatch b where convFromPHPImplicitOM :: PHPSessionValue -> Either String b isScalarForArray v = case v of PHPSessionValueBool _ -> True PHPSessionValueFloat _ -> True PHPSessionValueInt _ -> True PHPSessionValueString _ -> True PHPSessionValueNull -> False PHPSessionValueObjectSerializeable _ _ -> False PHPSessionValueMisc _ _ -> False PHPSessionValueObject _ _ -> False PHPSessionValueArray _ -> False -- -- Refer to the following reference on array conversion at: -- -- instance ConversionFromPHPImplicitValueOrMismatch [(PHPSessionValue,PHPSessionValue)] where -- Not converted to array: -- * PHPSessionValueNull (differs from PHP: PHP creates empty array) -- * PHPSessionValueObjectSerializeable -- * PHPSessionValueMisc convFromPHPImplicitOM v | isScalarForArray v = arrayScalarAsValue v convFromPHPImplicitOM (PHPSessionValueObject _cls arr) = Right arr convFromPHPImplicitOM (PHPSessionValueArray arr) = Right arr convFromPHPImplicitOM v = mismatchError v "[(PHPSessionValue,PHPSessionValue)]" arrayScalarAsValue :: PHPSessionValue -> Either String [(PHPSessionValue,PHPSessionValue)] arrayScalarAsValue r = Right ([(PHPSessionValueInt 0, r)]) convArrayScalarLeftSide :: ConversionFromPHPImplicitValueOrMismatch a => PHPSessionValue -> Either String [(a,PHPSessionValue)] convArrayScalarLeftSide r = case convFromPHPImplicitOM (PHPSessionValueInt 0) of Left message -> Left message Right index -> Right ([(index, r)]) convArrayListLeftSide :: ConversionFromPHPImplicitValueOrMismatch a => [(PHPSessionValue,PHPSessionValue)] -> Either String [(a,PHPSessionValue)] convArrayListLeftSide vars = case L.foldl' ontoTuple (Right []) vars of Left str -> Left str Right lst -> Right $ reverse lst where ontoTuple (Left str) _ = Left str ontoTuple (Right lst) (l,r) = let l' = convFromPHPImplicitOM l in case l' of Left errl -> Left errl Right l'' -> Right ((l'',r) : lst) instance ConversionFromPHPImplicitValueOrMismatch b => ConversionFromPHPImplicitValueOrMismatch [(PHPSessionValue,b)] where -- Not converted to array: -- * PHPSessionValueNull (differs from PHP: PHP creates empty array) -- * PHPSessionValueObjectSerializeable -- * PHPSessionValueMisc convFromPHPImplicitOM v | isScalarForArray v = convArrayScalarRightSide v convFromPHPImplicitOM convFromPHPImplicitOM (PHPSessionValueObject _cls arr) = convArrayListRightSide arr convFromPHPImplicitOM convFromPHPImplicitOM (PHPSessionValueArray arr) = convArrayListRightSide arr convFromPHPImplicitOM convFromPHPImplicitOM v = mismatchError v "ConversionFromPHPValueOrMismatch b => ConversionFromPHPValueOrMismatch [(PHPSessionValue,b)]" -- convArrayScalarRightSide :: ConversionFromPHPImplicitValueOrMismatch b => PHPSessionValue -> Either String [(PHPSessionValue,b)] convArrayScalarRightSide r conv = case conv r of Left message -> Left message Right r' -> Right ([(PHPSessionValueInt 0, r')]) -- convArrayListRightSide :: ConversionFromPHPImplicitValueOrMismatch b => [(PHPSessionValue,PHPSessionValue)] -> Either String [(PHPSessionValue,b)] convArrayListRightSide vars conv = case L.foldl' ontoTuple (Right []) vars of Left str -> Left str Right lst -> Right $ reverse lst where ontoTuple (Left str) _ = Left str ontoTuple (Right lst) (l,r) = case conv r of Left errr -> Left errr Right r'' -> Right $ (l,r'') : lst instance (ConversionFromPHPImplicitValueOrMismatch a, ConversionFromPHPImplicitValueOrMismatch b) => ConversionFromPHPImplicitValueOrMismatch [(a,b)] where -- Not converted to array: -- * PHPSessionValueNull (differs from PHP: PHP creates empty array) -- * PHPSessionValueObjectSerializeable -- * PHPSessionValueMisc convFromPHPImplicitOM v | isScalarForArray v = convArrayScalarBothSides v convFromPHPImplicitOM convFromPHPImplicitOM (PHPSessionValueObject _cls arr) = convArrayListBothSides arr convFromPHPImplicitOM convFromPHPImplicitOM (PHPSessionValueArray arr) = convArrayListBothSides arr convFromPHPImplicitOM convFromPHPImplicitOM v = mismatchError v "(ConversionFromPHPValueOrMismatch a, ConversionFromPHPValueOrMismatch b) => ConversionFromPHPValueOrMismatch [(a,b)]" convArrayScalarBothSides :: ConversionFromPHPImplicitValueOrMismatch t1 => t -> (t -> Either String t2) -> Either String [(t1, t2)] convArrayScalarBothSides r conv = case convFromPHPImplicitOM (PHPSessionValueInt 0) of Left message -> Left message Right index -> case conv r of Left message' -> Left message' Right r' -> Right ([(index, r')]) -- convArrayListBothSides :: (ConversionFromPHPImplicitValueOrMismatch a, ConversionFromPHPImplicitValueOrMismatch b) => [(PHPSessionValue,PHPSessionValue)] -> Either String [(a,b)] convArrayListBothSides vars conv = case L.foldl' ontoTuple (Right []) vars of Left str -> Left str Right lst -> Right $ reverse lst where ontoTuple (Left str) _ = Left str ontoTuple (Right lst) (l,r) = case convFromPHPImplicitOM l of Left errl -> Left errl Right l'' -> case conv r of Left errr -> Left errr Right r'' -> Right ((l'',r'') : lst) {- -- Sequenceable: instance ConversionFromPHPImplicitValueOrMismatch [PHPSessionValue] where -- Not converted to array: -- * PHPSessionValueNull (differs from PHP: PHP creates empty array) -- * PHPSessionValueObjectSerializeable -- * PHPSessionValueMisc convFromPHPImplicitOM v | isScalarForArray v = Right [v] convFromPHPImplicitOM (PHPSessionValueObject _cls arr) = Right $ map (\(_,a) -> a) arr convFromPHPImplicitOM (PHPSessionValueArray arr) = Right $ map (\(_,a) -> a) arr convFromPHPImplicitOM v = mismatchError v "[PHPSessionValue]" -} -- Refer to Data.PHPSession.ImplicitConv.ConvBool for the boolean implicit -- conversion. -- instance ConversionFromPHPImplicitValueOrMismatch Bool where convFromPHPImplicitOM var = Right $ boolFromPHPLooseComparisonWithTrue var -- Refer to the following reference for floating point conversion rules at -- -- instance ConversionFromPHPImplicitValueOrMismatch Double where convFromPHPImplicitOM (PHPSessionValueFloat (Right var)) = (Right) var convFromPHPImplicitOM (PHPSessionValueFloat (Left var)) = (Right . fromIntegral) var convFromPHPImplicitOM (PHPSessionValueString str) = case reads str' of [(val,"")] -> Right val [] -> let v = PHPSessionValueString str in mismatchError v "Double" where str' = LBS.unpack str convFromPHPImplicitOM var = let intvar = convFromPHPImplicitOM var :: Either String Int in case intvar of Left message -> Left message Right intvar' -> (Right . fromIntegral) intvar' -- Refer to the following reference for integer conversion rules: -- -- instance ConversionFromPHPImplicitValueOrMismatch Int where -- Not converted to Int: -- * PHPSessionValueArray -- * PHPSessionValueObject -- * PHPSessionValueNull -- * PHPSessionValueObjectSerializeable -- * PHPSessionValueMisc convFromPHPImplicitOM (PHPSessionValueBool b) = Right $ if b then 1 else 0 convFromPHPImplicitOM (PHPSessionValueFloat lr) = Right $ case lr of Left i -> i Right f -> floor f convFromPHPImplicitOM (PHPSessionValueInt val) = Right $ fromIntegral val convFromPHPImplicitOM (PHPSessionValueString str) = case reads str' of [(val,"")] -> Right $ fromIntegral val [] -> case reads str' of [(valdbl,"")] -> Right $ floor valdbl [] -> let v = PHPSessionValueString str in mismatchError v "Int" where str' = LBS.unpack str convFromPHPImplicitOM v = mismatchError v "Int" instance ConversionFromPHPImplicitValueOrMismatch Int32 where convFromPHPImplicitOM var = let var' = convFromPHPImplicitOM var :: Either String Int in case var' of Left message -> Left message Right int -> Right $ fromIntegral int instance ConversionFromPHPImplicitValueOrMismatch Int64 where convFromPHPImplicitOM var = let var' = convFromPHPImplicitOM var :: Either String Int in case var' of Left message -> Left message Right int -> Right $ fromIntegral int instance ConversionFromPHPImplicitValueOrMismatch (PHPSessionClassName, [(PHPSessionValue,PHPSessionValue)]) where -- Not converted to Object: -- * PHPSessionValueObjectSerializeable -- * PHPSessionValueMisc convFromPHPImplicitOM (PHPSessionValueArray arr) = Right (phpStdClass,arr) convFromPHPImplicitOM (PHPSessionValueBool b) = Right (phpStdClass,[(phpScalarMember, PHPSessionValueBool b)]) convFromPHPImplicitOM (PHPSessionValueFloat a) = Right (phpStdClass,[(phpScalarMember, PHPSessionValueFloat a)]) convFromPHPImplicitOM (PHPSessionValueInt i) = Right (phpStdClass,[(phpScalarMember, PHPSessionValueInt i)]) convFromPHPImplicitOM (PHPSessionValueNull) = Right (phpStdClass,[]) convFromPHPImplicitOM (PHPSessionValueObject cls arr) = Right (cls,arr) convFromPHPImplicitOM (PHPSessionValueString a) = Right (phpStdClass,[(phpScalarMember, PHPSessionValueString a)]) convFromPHPImplicitOM v = mismatchError v "(PHPSessionClassName, [(PHPSessionValue,PHPSessionValue)])" phpStdClass = PHPSessionClassName "stdClass" phpScalarMember = PHPSessionValueString "scalar" instance ConversionFromPHPImplicitValueOrMismatch (PHPSessionClassName, LBS.ByteString) where -- Not converted to (and from) objects implementing Serializable: -- * PHPSessionValueArray -- * PHPSessionValueBool -- * PHPSessionValueFloat, -- * PHPSessionValueInt -- * PHPSessionValueNull -- * PHPSessionValueObject -- * PHPSessionValueString, -- * PHPSessionValueMisc convFromPHPImplicitOM (PHPSessionValueObjectSerializeable cls arr) = Right (cls,arr) convFromPHPImplicitOM v = mismatchError v "(PHPSessionClassName, LBS.ByteString)" -- -- instance ConversionFromPHPImplicitValueOrMismatch LBS.ByteString where -- Not converted to string: -- * PHPSessionValueArray -- * PHPSessionValueObject -- * PHPSessionValueObjectSerializeable -- * PHPSessionValueNull (differs from PHP: PHP creates empty string "") -- * PHPSessionValueMisc convFromPHPImplicitOM (PHPSessionValueBool b) = case b of True -> Right "1" False -> Right "" convFromPHPImplicitOM (PHPSessionValueFloat a) = case a of Left i -> (Right . LBS.pack . show) i Right f -> (Right . LBS.pack . show) f convFromPHPImplicitOM (PHPSessionValueInt i) = (Right . LBS.pack . show) i -- Unused: convFromPHPImplicitOM (PHPSessionValueNull) = Right "" convFromPHPImplicitOM (PHPSessionValueString var) = Right var convFromPHPImplicitOM v = mismatchError v "ByteString" instance ConversionFromPHPImplicitValueOrMismatch BS.ByteString where convFromPHPImplicitOM var = let var' = convFromPHPImplicitOM var :: Either String LBS.ByteString in case var' of Left message -> Left message Right str -> Right (BS.concat $ LBS.toChunks str) instance ConversionFromPHPImplicitValueOrMismatch a => ConversionFromPHPImplicitValueOrMismatch (Maybe a) where convFromPHPImplicitOM PHPSessionValueNull = Right Nothing convFromPHPImplicitOM v = case convFromPHPImplicitOM v of Left s -> Left s Right b' -> Right (Just b') mismatchError v totype = Left $ "Type mismatch converting from (" ++ show v ++ ") to " ++ totype -- | 'convFromPHPImplicit' and 'convFromPHPImplicitSafe' are functions that convert -- values stored as 'PHPSessionValue' into appropriate Haskell types depending on -- the desired type cast or inferred. Unlike the 'convFrom' and 'convFromSafe' -- functions provided in "Data.PHPSession.Conv", functions provided in this module -- perform type coercion based on a significant number of conversion rules to -- satisfy the type cast or inferred. -- -- The example @arrayOfPHPStrings@ definition given in the example documented in -- "Data.PHPSession.Conv" can be evaluated to @[(0,\"Hello\"),(1,\"World\")]@. -- -- >>> convFromPHPImplicit arrayOfPHPStrings :: [(Int,LBS.ByteString)] -- [(0,"Hello"),(1,"World")] -- -- However, if the desired type signature is changed: -- -- >>> convFromPHPImplicit arrayOfPHPStrings :: [(LBS.ByteString,LBS.ByteString)] -- [("0","Hello"),("1","World")] -- -- Where there is the possibility that the value being sought may be @/NULL/@, the -- type should be @('Maybe' a)@. -- convFromPHPImplicit :: ConversionFromPHPImplicitValueOrMismatch b => PHPSessionValue -> b convFromPHPImplicit var = case convFromPHPImplicitOM var of Left message -> error message Right var' -> var' -- | 'convFromPHPImplicitSafe' is a version of 'convFromPHPImplicit' that returns a -- 'Left' with an error message instead of throwing a run time exception. -- convFromPHPImplicitSafe :: ConversionFromPHPImplicitValueOrMismatch b => PHPSessionValue -> Either String b convFromPHPImplicitSafe var = convFromPHPImplicitOM var