compaREST-0.1.0.0: src/Data/OpenApi/Compare/Subtree.hs
{-# OPTIONS_GHC -Wno-orphans #-}
module Data.OpenApi.Compare.Subtree
( Steppable (..),
Step (..),
TraceRoot,
Trace,
Traced,
Traced',
pattern Traced,
traced,
retraced,
stepTraced,
Subtree (..),
checkCompatibility,
checkSubstructure,
CompatM (..),
CompatFormula',
SemanticCompatFormula,
ProdCons (..),
orientProdCons,
swapProdCons,
runCompatFormula,
issueAt,
anItem,
anIssue,
invertIssueOrientation,
invertIssueOrientationP,
embedFormula,
anyOfAt,
clarifyIssue,
structuralIssue,
-- * Structural helpers
structuralMaybe,
structuralMaybeWith,
structuralEq,
iohmStructural,
iohmStructuralWith,
structuralList,
-- * Reexports
(>>>),
(<<<),
extract,
ask,
local,
step,
Typeable,
)
where
import Control.Comonad.Env
import Control.Monad.Identity
import Control.Monad.State
import Data.Foldable
import Data.Functor.Compose
import Data.HList
import qualified Data.HashMap.Strict.InsOrd as IOHM
import Data.Hashable
import Data.Kind
import Data.OpenApi
import Data.OpenApi.Compare.Behavior
import Data.OpenApi.Compare.Formula
import Data.OpenApi.Compare.Memo
import Data.OpenApi.Compare.Paths
import qualified Data.OpenApi.Compare.PathsPrefixTree as P
import qualified Data.Set as S
import Data.Typeable
class
(Typeable Step, Typeable a, Typeable b, Ord (Step a b), Show (Step a b)) =>
Steppable (a :: Type) (b :: Type)
where
-- | How to get from an @a@ node to a @b@ node
data Step a b :: Type
data TraceRoot
instance Steppable TraceRoot OpenApi where
data Step TraceRoot OpenApi
= ClientSchema
| ServerSchema
deriving stock (Eq, Ord, Show)
type Trace = Paths Step TraceRoot
type instance AdditionalQuiverConstraints Step _ _ = ()
type Traced' a b = Env (Trace a) b
type Traced a = Traced' a a
pattern Traced :: Trace a -> b -> Traced' a b
pattern Traced t x = EnvT t (Identity x)
{-# COMPLETE Traced #-}
traced :: Trace a -> a -> Traced a
traced = env
retraced :: (Trace a -> Trace a') -> Traced' a b -> Traced' a' b
retraced f (Traced a b) = Traced (f a) b
stepTraced :: Steppable a a' => Step a a' -> Traced' a b -> Traced' a' b
stepTraced s = retraced (>>> step s)
data ProdCons a = ProdCons
{ producer :: a
, consumer :: a
}
deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)
orientProdCons :: Orientation -> ProdCons x -> ProdCons x
orientProdCons Forward x = x
orientProdCons Backward (ProdCons p c) = ProdCons c p
swapProdCons ::
SwapEnvRoles xs =>
(HList xs -> ProdCons x -> CompatFormula' q AnIssue r a) ->
(HList xs -> ProdCons x -> CompatFormula' q AnIssue r a)
swapProdCons f e (ProdCons p c) =
invertIssueOrientation $
f (swapEnvRoles e) (ProdCons c p)
{-# INLINE swapProdCons #-}
type family IsProdCons (x :: Type) :: Bool where
IsProdCons (ProdCons _) = 'True
IsProdCons _ = 'False
type SwapEnvElementRoles x = SwapEnvElementRoles' x (IsProdCons x)
class IsProdCons x ~ f => SwapEnvElementRoles' (x :: Type) f where
swapEnvElementRoles :: x -> x
instance SwapEnvElementRoles' (ProdCons x) 'True where
swapEnvElementRoles (ProdCons p c) = ProdCons c p
instance IsProdCons x ~ 'False => SwapEnvElementRoles' x 'False where
swapEnvElementRoles = id
class SwapEnvRoles xs where
swapEnvRoles :: HList xs -> HList xs
instance SwapEnvRoles '[] where
swapEnvRoles = id
instance (SwapEnvElementRoles x, SwapEnvRoles xs) => SwapEnvRoles (x ': xs) where
swapEnvRoles (HCons x xs) = HCons (swapEnvElementRoles x) (swapEnvRoles xs)
instance Applicative ProdCons where
pure x = ProdCons x x
ProdCons fp fc <*> ProdCons xp xc = ProdCons (fp xp) (fc xc)
newtype CompatM a = CompatM
{ unCompatM ::
StateT (MemoState VarRef) Identity a
}
deriving newtype
( Functor
, Applicative
, Monad
, MonadState (MemoState VarRef)
)
type CompatFormula' q f r = Compose CompatM (FormulaF q f r)
type SemanticCompatFormula = CompatFormula' Behave AnIssue 'APILevel
type StructuralCompatFormula = CompatFormula' VoidQuiver Proxy ()
data VoidQuiver a b
deriving stock instance Eq (VoidQuiver a b)
type instance AdditionalQuiverConstraints VoidQuiver _ _ = ()
deriving stock instance Ord (VoidQuiver a b)
deriving stock instance Show (VoidQuiver a b)
class (Typeable t, Issuable (SubtreeLevel t)) => Subtree (t :: Type) where
type CheckEnv t :: [Type]
type SubtreeLevel t :: BehaviorLevel
checkStructuralCompatibility ::
HList (CheckEnv t) ->
ProdCons (Traced t) ->
StructuralCompatFormula ()
checkSemanticCompatibility ::
HList (CheckEnv t) ->
Behavior (SubtreeLevel t) ->
ProdCons (Traced t) ->
SemanticCompatFormula ()
{-# WARNING checkStructuralCompatibility "You should not be calling this directly. Use 'checkSubstructure'" #-}
{-# WARNING checkSemanticCompatibility "You should not be calling this directly. Use 'checkCompatibility'" #-}
checkCompatibility ::
forall t xs.
(ReassembleHList xs (CheckEnv t), Subtree t) =>
Behavior (SubtreeLevel t) ->
HList xs ->
ProdCons (Traced t) ->
SemanticCompatFormula ()
checkCompatibility bhv e = memo bhv SemanticMemoKey $ \pc ->
case runCompatFormula $ checkSubstructure e pc of
Left _ -> checkSemanticCompatibility (reassemble e) bhv pc
Right () -> pure ()
{-# INLINE checkCompatibility #-}
checkSubstructure ::
(ReassembleHList xs (CheckEnv t), Subtree t) =>
HList xs ->
ProdCons (Traced t) ->
StructuralCompatFormula ()
checkSubstructure e = memo Root StructuralMemoKey $ checkStructuralCompatibility (reassemble e)
{-# INLINE checkSubstructure #-}
structuralMaybe ::
(Subtree a, ReassembleHList xs (CheckEnv a)) =>
HList xs ->
ProdCons (Maybe (Traced a)) ->
StructuralCompatFormula ()
structuralMaybe e = structuralMaybeWith (checkSubstructure e)
{-# INLINE structuralMaybe #-}
structuralMaybeWith ::
(ProdCons a -> StructuralCompatFormula ()) ->
ProdCons (Maybe a) ->
StructuralCompatFormula ()
structuralMaybeWith f (ProdCons (Just a) (Just b)) = f $ ProdCons a b
structuralMaybeWith _ (ProdCons Nothing Nothing) = pure ()
structuralMaybeWith _ _ = structuralIssue
{-# INLINE structuralMaybeWith #-}
structuralList ::
(Subtree a, ReassembleHList xs (CheckEnv a)) =>
HList xs ->
ProdCons [Traced a] ->
StructuralCompatFormula ()
structuralList _ (ProdCons [] []) = pure ()
structuralList e (ProdCons (a : aa) (b : bb)) = do
checkSubstructure e $ ProdCons a b
structuralList e $ ProdCons aa bb
pure ()
structuralList _ _ = structuralIssue
{-# INLINE structuralList #-}
structuralEq :: (Eq a, Comonad w) => ProdCons (w a) -> StructuralCompatFormula ()
structuralEq (ProdCons a b) = if extract a == extract b then pure () else structuralIssue
{-# INLINE structuralEq #-}
iohmStructural ::
(ReassembleHList (k ': xs) (CheckEnv v), Ord k, Subtree v, Hashable k, Typeable k, Show k) =>
HList xs ->
ProdCons (Traced (IOHM.InsOrdHashMap k v)) ->
StructuralCompatFormula ()
iohmStructural e =
iohmStructuralWith (\k -> checkSubstructure (k `HCons` e))
{-# INLINE iohmStructural #-}
instance (Typeable k, Typeable v, Ord k, Show k) => Steppable (IOHM.InsOrdHashMap k v) v where
data Step (IOHM.InsOrdHashMap k v) v = InsOrdHashMapKeyStep k
deriving stock (Eq, Ord, Show)
iohmStructuralWith ::
(Ord k, Hashable k, Typeable k, Typeable v, Show k) =>
(k -> ProdCons (Traced v) -> StructuralCompatFormula ()) ->
ProdCons (Traced (IOHM.InsOrdHashMap k v)) ->
StructuralCompatFormula ()
iohmStructuralWith f pc = do
let ProdCons pEKeys cEKeys = S.fromList . IOHM.keys . extract <$> pc
if pEKeys == cEKeys
then
for_
pEKeys
( \eKey ->
f eKey $ stepTraced (InsOrdHashMapKeyStep eKey) . fmap (IOHM.lookupDefault (error "impossible") eKey) <$> pc
)
else structuralIssue
{-# INLINE iohmStructuralWith #-}
runCompatFormula ::
CompatFormula' q f r a ->
Either (P.PathsPrefixTree q f r) a
runCompatFormula (Compose f) =
calculate . runIdentity . runMemo 0 . unCompatM $ f
{-# INLINE runCompatFormula #-}
embedFormula :: Paths q r l -> CompatFormula' q f l a -> CompatFormula' q f r a
embedFormula bhv (Compose x) = Compose $ mapErrors (P.embed bhv) <$> x
issueAt :: Issuable l => Paths q r l -> Issue l -> CompatFormula' q AnIssue r a
issueAt xs issue = Compose $ pure $ anError $ AnItem xs $ anIssue issue
{-# INLINE issueAt #-}
anIssue :: Issuable l => Issue l -> AnIssue l
anIssue = AnIssue Forward
{-# INLINE anIssue #-}
anItem :: AnItem q AnIssue r -> CompatFormula' q AnIssue r a
anItem = Compose . pure . anError
{-# INLINE anItem #-}
invertIssueOrientation :: CompatFormula' q AnIssue r a -> CompatFormula' q AnIssue r a
invertIssueOrientation (Compose x) =
Compose $ mapErrors invertIssueOrientationP <$> x
invertIssueOrientationP :: P.PathsPrefixTree q AnIssue r -> P.PathsPrefixTree q AnIssue r
invertIssueOrientationP = P.map (\(AnIssue ori i) -> AnIssue (toggleOrientation ori) i)
structuralIssue :: StructuralCompatFormula a
structuralIssue = Compose $ pure $ anError $ AnItem Root Proxy
anyOfAt ::
Issuable l =>
Paths q r l ->
Issue l ->
[CompatFormula' q AnIssue r a] ->
CompatFormula' q AnIssue r a
anyOfAt _ _ [x] = x
anyOfAt xs issue fs =
Compose $ (`eitherOf` AnItem xs (anIssue issue)) <$> traverse getCompose fs
-- | If the given formula contains any issues, add another issue on top. Otherwise succeed.
clarifyIssue ::
AnItem q AnIssue r ->
CompatFormula' q AnIssue r a ->
CompatFormula' q AnIssue r a
clarifyIssue item f =
Compose ((`eitherOf` item) <$> pure <$> getCompose f) *> f
fixpointKnot ::
MonadState (MemoState VarRef) m =>
KnotTier (FormulaF q f r ()) VarRef m
fixpointKnot =
KnotTier
{ onKnotFound = modifyMemoNonce succ
, onKnotUsed = \i -> pure $ variable i
, tieKnot = \i x -> pure $ maxFixpoint i x
}
memo ::
(Typeable (l :: k), Typeable q, Typeable f, Typeable k, Typeable a) =>
Paths q r l ->
MemoKey ->
(ProdCons (Traced a) -> CompatFormula' q f r ()) ->
(ProdCons (Traced a) -> CompatFormula' q f r ())
memo bhv k f pc = Compose $ do
formula' <-
memoWithKnot
fixpointKnot
( do
formula <- getCompose $ f pc
pure $ mapErrors (P.takeSubtree bhv) formula
)
(k, ask <$> pc)
pure $ mapErrors (P.embed bhv) formula'
data MemoKey = SemanticMemoKey | StructuralMemoKey
deriving stock (Eq, Ord)