{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} module Types where import Control.Monad (when) import Data.Aeson import Data.Aeson.Types (Parser) import Data.Hashable (Hashable(..)) import Data.Maybe (isJust) #if MIN_VERSION_base(4,11,0) #else import Data.Monoid ((<>)) #endif import Data.Text as T import Data.Time (UTCTime) import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.UUID as UUID import Data.SafeJSON ---------------------------------------------------------- -- Versioned chain ---------------------------------------------------------- checkType :: String -> Object -> Parser () checkType t o = do typ <- o .: "type" when (typ /= t) $ fail $ "wrong type (" ++ t ++ ")" newtype NoVersion = NoVersion Int deriving (Eq, Show) instance SafeJSON NoVersion where version = noVersion instance FromJSON NoVersion where parseJSON = withObject "NoVersion" $ \o -> do checkType "test" o i <- o .: "int" return $ NoVersion i instance ToJSON NoVersion where toJSON (NoVersion i) = object [ "type" .= String "test" , "int" .= i ] newtype Version0 = Version0 Text deriving (Eq, Show) instance SafeJSON Version0 where kind = extended_base instance FromJSON Version0 where parseJSON = withObject "Version0" $ \o -> do checkType "test" o t <- o .: "text" return $ Version0 t instance ToJSON Version0 where toJSON (Version0 t) = object [ "type" .= String "test" , "text" .= t ] instance Migrate (Reverse Version0) where type MigrateFrom (Reverse Version0) = Version1 migrate (Version1 t) = Reverse $ Version0 t newtype Version1 = Version1 Text deriving (Eq, Show) instance SafeJSON Version1 where version = 1; kind = extended_extension instance FromJSON Version1 where parseJSON = withObject "Version1" $ \o -> do checkType "test" o t <- o .: "text" return $ Version1 t instance ToJSON Version1 where toJSON (Version1 t) = object [ "type" .= String "test" , "text" .= t ] instance Migrate Version1 where type MigrateFrom Version1 = NoVersion migrate (NoVersion i) = Version1 . pack . show $ i instance Migrate (Reverse Version1) where type MigrateFrom (Reverse Version1) = Version2 migrate (Version2 ts) = Reverse . Version1 $ intercalate ", " ts newtype Version2 = Version2 [Text] deriving (Eq, Show) instance SafeJSON Version2 where version = 2; kind = extension instance FromJSON Version2 where parseJSON = withObject "Version2" $ \o -> do checkType "test" o t <- o .: "texts" return $ Version2 t instance ToJSON Version2 where toJSON (Version2 ts) = object [ "type" .= String "test" , "texts" .= ts ] instance Migrate Version2 where type MigrateFrom Version2 = Version1 migrate (Version1 t) = Version2 [t] data Version3 = Version3 { v3texts :: [Text], v3Closed :: Bool } deriving (Eq, Show) instance SafeJSON Version3 where version = 3; kind = extended_extension instance FromJSON Version3 where parseJSON = withObject "Version3" $ \o -> do checkType "test" o ts <- o .: "texts" b <- o .: "closed" return $ Version3 ts b instance ToJSON Version3 where toJSON (Version3 ts b) = object [ "type" .= String "test" , "texts" .= ts , "closed" .= b ] instance Migrate Version3 where type MigrateFrom Version3 = Version2 migrate (Version2 ts) = Version3 ts False instance Migrate (Reverse Version3) where type MigrateFrom (Reverse Version3) = Version4 migrate (Version4 ts mTime) = Reverse $ Version3 ts $ isJust mTime data Version4 = Version4 { v4texts :: [Text], v4TimeClosed :: Maybe UTCTime } deriving (Eq, Show, Ord) instance SafeJSON Version4 where version = 4; kind = extension instance FromJSON Version4 where parseJSON = withObject "Version4" $ \o -> do checkType "test" o ts <- o .: "texts" c <- o .:? "closed" return $ Version4 ts c instance ToJSON Version4 where toJSON (Version4 ts c) = object $ [ "type" .= String "test" , "texts" .= ts ] ++ ["closed" .= x | Just x <- [c]] instance Migrate Version4 where type MigrateFrom Version4 = Version3 migrate (Version3 ts b) = Version4 ts time where time = if b then Just (posixSecondsToUTCTime 0) else Nothing ---------------------------------------------------------- -- Simple Version ---------------------------------------------------------- data SimpleVersion1 = SimpleVersion1 { s1UUID :: UUID, s1Name :: Text } deriving (Eq, Show) instance FromJSON SimpleVersion1 where parseJSON = withText "SimpleVersion1" $ \t -> let (ident,name) = T.span (/= ':') t in case UUID.fromText ident of Nothing -> fail "non-UUID prefix" Just uuid -> pure $ SimpleVersion1 uuid $ T.drop 1 name instance ToJSON SimpleVersion1 where toJSON (SimpleVersion1 uuid name) = String $ UUID.toText uuid <> ":" <> name instance SafeJSON SimpleVersion1 where version = 1 kind = extension instance Migrate SimpleVersion1 where type MigrateFrom SimpleVersion1 = UUID migrate uuid = SimpleVersion1 uuid "" data BadVersion = BadVersion Text deriving (Eq, Show) instance FromJSON BadVersion where parseJSON = withText "BadVersion" $ pure . BadVersion instance ToJSON BadVersion where toJSON (BadVersion t) = String t instance SafeJSON BadVersion where version = 8 -- Used for the HashSet migration test instance Hashable Version4 where hashWithSalt i = hashWithSalt i . encode