Files
simplexmq/src/Simplex/Messaging/Version.hs
Evgeny Poberezkin b27f126bab include server version range in transport handle (#1135)
* include server version range in transport handle

* xftp handshake

* remove coment

* simplify

* comments
2024-05-08 23:00:00 +01:00

163 lines
5.2 KiB
Haskell

{-# LANGUAGE ConstrainedClassMethods #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TypeFamilies #-}
module Simplex.Messaging.Version
( Version,
VersionRange (minVersion, maxVersion),
VersionScope,
pattern VersionRange,
VersionI (..),
VersionRangeI (..),
Compatible,
pattern Compatible,
mkVersionRange,
safeVersionRange,
versionToRange,
isCompatible,
isCompatibleRange,
proveCompatible,
compatibleVersion,
compatibleVRange,
compatibleVRange',
)
where
import Control.Applicative (optional)
import qualified Data.Aeson as J
import qualified Data.Aeson.Encoding as JE
import Data.Aeson.Types ((.:), (.=))
import qualified Data.Aeson.Types as JT
import qualified Data.Attoparsec.ByteString.Char8 as A
import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Version.Internal (Version (..))
pattern VersionRange :: Version v -> Version v -> VersionRange v
pattern VersionRange v1 v2 <- VRange v1 v2
{-# COMPLETE VersionRange #-}
data VersionRange v = VRange
{ minVersion :: Version v,
maxVersion :: Version v
}
deriving (Eq, Show)
instance J.FromJSON (VersionRange v) where
parseJSON (J.Object v) = do
minVersion <- v .: "minVersion"
maxVersion <- v .: "maxVersion"
pure VRange {minVersion, maxVersion}
parseJSON invalid =
JT.prependFailure "bad VersionRange, " (JT.typeMismatch "Object" invalid)
instance J.ToJSON (VersionRange v) where
toEncoding VRange {minVersion, maxVersion} = JE.pairs $ ("minVersion" .= minVersion) <> ("maxVersion" .= maxVersion)
toJSON VRange {minVersion, maxVersion} = J.object ["minVersion" .= minVersion, "maxVersion" .= maxVersion]
class VersionScope v
-- | construct valid version range, to be used in constants
mkVersionRange :: Version v -> Version v -> VersionRange v
mkVersionRange v1 v2
| v1 <= v2 = VRange v1 v2
| otherwise = error "invalid version range"
safeVersionRange :: Version v -> Version v -> Maybe (VersionRange v)
safeVersionRange v1 v2
| v1 <= v2 = Just $ VRange v1 v2
| otherwise = Nothing
versionToRange :: Version v -> VersionRange v
versionToRange v = VRange v v
instance VersionScope v => Encoding (VersionRange v) where
smpEncode (VRange v1 v2) = smpEncode (v1, v2)
smpP =
maybe (fail "invalid version range") pure
=<< safeVersionRange <$> smpP <*> smpP
instance VersionScope v => StrEncoding (VersionRange v) where
strEncode (VRange v1 v2)
| v1 == v2 = strEncode v1
| otherwise = strEncode v1 <> "-" <> strEncode v2
strP = do
v1 <- strP
v2 <- maybe (pure v1) (const strP) =<< optional (A.char '-')
maybe (fail "invalid version range") pure $ safeVersionRange v1 v2
class VersionScope v => VersionI v a | a -> v where
type VersionRangeT v a
version :: a -> Version v
toVersionRangeT :: a -> VersionRange v -> VersionRangeT v a
class VersionScope v => VersionRangeI v a | a -> v where
type VersionT v a
versionRange :: a -> VersionRange v
toVersionRange :: a -> VersionRange v -> a
toVersionT :: a -> Version v -> VersionT v a
instance VersionScope v => VersionI v (Version v) where
type VersionRangeT v (Version v) = VersionRange v
version = id
toVersionRangeT _ vr = vr
instance VersionScope v => VersionRangeI v (VersionRange v) where
type VersionT v (VersionRange v) = Version v
versionRange = id
toVersionRange _ vr = vr
toVersionT _ v = v
newtype Compatible a = Compatible_ a
pattern Compatible :: a -> Compatible a
pattern Compatible a <- Compatible_ a
{-# COMPLETE Compatible #-}
isCompatible :: VersionI v a => a -> VersionRange v -> Bool
isCompatible x (VRange v1 v2) = let v = version x in v1 <= v && v <= v2
isCompatibleRange :: VersionRangeI v a => a -> VersionRange v -> Bool
isCompatibleRange x (VRange min2 max2) = min1 <= max2 && min2 <= max1
where
VRange min1 max1 = versionRange x
proveCompatible :: VersionI v a => a -> VersionRange v -> Maybe (Compatible a)
proveCompatible x vr = x `mkCompatibleIf` (x `isCompatible` vr)
compatibleVersion :: VersionRangeI v a => a -> VersionRange v -> Maybe (Compatible (VersionT v a))
compatibleVersion x vr =
toVersionT x (min max1 max2) `mkCompatibleIf` isCompatibleRange x vr
where
max1 = maxVersion $ versionRange x
max2 = maxVersion vr
-- | intersection of version ranges
compatibleVRange :: VersionRangeI v a => a -> VersionRange v -> Maybe (Compatible a)
compatibleVRange x vr =
compatibleVRange_ x (max min1 min2) (min max1 max2)
where
VRange min1 max1 = versionRange x
VRange min2 max2 = vr
-- | version range capped by compatible version
compatibleVRange' :: VersionRangeI v a => a -> Version v -> Maybe (Compatible a)
compatibleVRange' x v
| v <= max1 = compatibleVRange_ x min1 v
| otherwise = Nothing
where
VRange min1 max1 = versionRange x
compatibleVRange_ :: VersionRangeI v a => a -> Version v -> Version v -> Maybe (Compatible a)
compatibleVRange_ x v1 v2 = Compatible_ . toVersionRange x <$> safeVersionRange v1 v2
mkCompatibleIf :: a -> Bool -> Maybe (Compatible a)
x `mkCompatibleIf` cond = if cond then Just $ Compatible_ x else Nothing