prosidyc-0.2.0.0: src/Prosidy/Compile/Strict.hs
{-|
Module : Prosidy.Compile.Strict
Description : Ensure that no unknown settings or properties are used.
Copyright : ©2020 James Alexander Feldman-Crough
License : MPL-2.0
Maintainer : alex@fldcr.com
-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TupleSections #-}
module Prosidy.Compile.Strict (strict) where
import Lens.Micro hiding ( strict )
import qualified Prosidy as P
import Prosidy.Compile.Core
import Prosidy.Compile.Error
import Control.Monad ( unless )
import Data.HashSet ( HashSet )
import qualified Data.HashSet as HS
import qualified Data.HashMap.Strict as HM
-- | Ensure that all properties and settings on a node are expected, and throw
-- an error when extraneous metadata is attached. This is extremely useful
-- for catching typos.
--
-- The matchers 'Prosidy.Compile.Match.blockTag',
-- 'Prosidy.Compile.Match.inlineTag', and 'Prosidy.Compile.Match.literalTag'
-- already match strictly: wrapping them in this combinator is unneccessary.
strict :: (Applicative f, P.HasMetadata i) => RuleT i e f a -> RuleT i e f a
strict r = r <* checked
where
checked = rule . Lift $ \item ->
let
Schema { schemaProperties, schemaSettings } = collectSchema r item
extraProperties =
HS.difference (item ^. P.properties . P._Set) schemaProperties
extraSettings = HS.difference
(item ^. P.settings . P._Assoc . to HM.keysSet)
schemaSettings
extras =
HS.map (PropertyKind, ) extraProperties
<> HS.map (SettingKind, ) extraSettings
in
pure $ unless (HS.null extras) (Left $ UnknownMetadata extras)
collectSchema :: RuleT i e f a -> i -> Schema
collectSchema rule = getSchema . interpretWith rule interpret
interpret :: Interpret e f Strict
interpret _ = \case
GetProperty _ name -> recordProperty name
GetSetting _ name _ -> recordSetting name
GetRequiredSetting name _ -> recordSetting name
_ -> doNothing
newtype Strict a = Strict (Schema -> Schema)
instance Functor Strict where
fmap _ = coerce
instance Applicative Strict where
pure _ = Strict id
Strict lhs <*> Strict rhs = Strict $ lhs <> rhs
data Schema = Schema
{ schemaProperties :: HashSet P.Key
, schemaSettings :: HashSet P.Key
}
instance Semigroup Schema where
Schema p1 s1 <> Schema p2 s2 = Schema (p1 <> p2) (s1 <> s2)
instance Monoid Schema where
mempty = Schema mempty mempty
coerce :: Strict a -> Strict b
coerce = \(Strict x) -> Strict x
getSchema :: Strict a -> Schema
getSchema (Strict x) = x mempty
doNothing :: Strict a
doNothing = Strict id
recordProperty :: P.Key -> Strict a
recordProperty k =
Strict $ \s -> s { schemaProperties = HS.insert k $ schemaProperties s }
recordSetting :: P.Key -> Strict a
recordSetting k =
Strict $ \s -> s { schemaSettings = HS.insert k $ schemaSettings s }