Skip to content
Snippets Groups Projects
Unverified Commit 77d1f4ce authored by stuebinm's avatar stuebinm
Browse files

type check properties

/finally/ figured out that all properties just look like {name, value,
type} so now that's abstracted away and Properties.hs doesn't look like
javascript anymore
parent b17396b2
No related branches found
No related tags found
No related merge requests found
...@@ -23,8 +23,8 @@ data Level = Warning | Suggestion | Info | Forbidden | Error | Fatal ...@@ -23,8 +23,8 @@ data Level = Warning | Suggestion | Info | Forbidden | Error | Fatal
-- | a hint comes with an explanation (and a level) -- | a hint comes with an explanation (and a level)
data Hint = Hint data Hint = Hint
{ hintLevel :: Level { hintLevel :: Level
, hintMsg :: Text } , hintMsg :: Text
deriving (Generic, ToJSON) } deriving (Generic, ToJSON)
instance PrettyPrint Hint where instance PrettyPrint Hint where
prettyprint Hint { hintMsg, hintLevel } = prettyprint Hint { hintMsg, hintLevel } =
......
...@@ -6,25 +6,14 @@ module Properties (checkProperty) where ...@@ -6,25 +6,14 @@ module Properties (checkProperty) where
import Control.Monad (unless) import Control.Monad (unless)
import Control.Monad.Trans.Class (lift)
import Data.Aeson as Aeson (Value (String))
import Data.Map (Map, (!?))
import Data.Text (Text) import Data.Text (Text)
import Tiled2 (Layer (layerProperties)) import Tiled2 (Layer (layerProperties), Property, propertyName, propertyValue)
import Util (prettyprint) import Util (prettyprint)
import LintWriter (Hint, LintWriter, Level(..), hint, import LintWriter (LintWriter, complain, forbid, info,
assertWarn, complain, forbid, info, suggest, warn, Dep(..), require)
suggest, unwrapWarn, warn)
-- | values may be anything, and are not typechecked (for now), -- | the point of this module
-- since they may contain arbitrary json – our only guarantee
-- is that they are named, and therefore a map.
type Properties = Map Text Aeson.Value
-- | /technically/ the main function here
-- --
-- given a property, check if it is valid. It gets a reference -- given a property, check if it is valid. It gets a reference
-- to its own layer since sometimes the presense of one property -- to its own layer since sometimes the presense of one property
...@@ -33,28 +22,13 @@ type Properties = Map Text Aeson.Value ...@@ -33,28 +22,13 @@ type Properties = Map Text Aeson.Value
-- The tests in here are meant to comply with the informal spec -- The tests in here are meant to comply with the informal spec
-- at https://workadventu.re/map-building -- at https://workadventu.re/map-building
-- --
-- In practice, the actual specification of what is allowed is
-- handled in checkProperty', since apparently all possible layerProperties
-- are strings anyways, so this just extracts that string and then
-- calls that.
checkProperty :: Layer -> Properties -> LintWriter ()
checkProperty layer prop = do
tyObj <- lift $ getAttr prop "name"
ty <- lift $ case tyObj of
Aeson.String str -> Right str
_ -> Left (hint Suggestion "wtf")
checkProperty' layer prop ty
-- | The /real/ main thing.
--
-- I've attempted to build the LintWriter monad in a way -- I've attempted to build the LintWriter monad in a way
-- that should make this readable even to non-Haskellers -- that should make this readable even to non-Haskellers
checkProperty' :: Layer -> Properties -> Text -> LintWriter () -- TODO: also pass the value of this property directly
checkProperty' layer prop ty = case ty of checkProperty :: Layer -> Property -> LintWriter ()
checkProperty layer prop = case propName of
"jitsiRoom" -> do "jitsiRoom" -> do
propEqual prop "type" "string" info $ "found jitsi room: " <> prettyprint (propertyValue prop)
urlValue <- lift $ getAttr prop "value"
info $ "found jitsi room: " <> prettyprint urlValue
suggestPropertyValue "jitsiTrigger" "onaction" suggestPropertyValue "jitsiTrigger" "onaction"
"jitsiTrigger" -> "jitsiTrigger" ->
requireProperty "jitsiRoom" requireProperty "jitsiRoom"
...@@ -64,12 +38,12 @@ checkProperty' layer prop ty = case ty of ...@@ -64,12 +38,12 @@ checkProperty' layer prop ty = case ty of
"jitsiRoomAdminTag" -> isForbidden "jitsiRoomAdminTag" -> isForbidden
"playAudio" -> do "playAudio" -> do
-- TODO: check for url validity? -- TODO: check for url validity?
propEqual prop "type" "string" pure ()
"audioLoop" -> "audioLoop" ->
requireProperty "playAudio" requireProperty "playAudio"
"audioVolume" -> "audioVolume" ->
requireProperty "playAudio" requireProperty "playAudio"
"openWebsite" -> "openWebsite" -> do
suggestPropertyValue "openWebsiteTrigger" "onaction" suggestPropertyValue "openWebsiteTrigger" "onaction"
"openWebsiteTrigger" -> "openWebsiteTrigger" ->
requireProperty "openWebsite" requireProperty "openWebsite"
...@@ -79,13 +53,14 @@ checkProperty' layer prop ty = case ty of ...@@ -79,13 +53,14 @@ checkProperty' layer prop ty = case ty of
"startLayer" -> pure () "startLayer" -> pure ()
-- could also make this a "hard error" (i.e. Left), but then it -- could also make this a "hard error" (i.e. Left), but then it
-- stops checking other properties as checkLayer short-circuits. -- stops checking other properties as checkLayer short-circuits.
_ -> warn $ "unknown property type " <> prettyprint ty _ -> warn $ "unknown property type " <> prettyprint propName
where where
propName = propertyName prop
-- | require some property in this layer -- | require some property in this layer
requireProperty name = unless (hasProperty name layer) requireProperty name = unless (hasProperty name layer)
$ complain $ "property "<>prettyprint name<>" requires property "<>prettyprint ty $ complain $ "property "<>prettyprint name<>" requires property "<>prettyprint propName
-- | This property is forbidden and should not be used -- | This property is forbidden and should not be used
isForbidden = forbid $ "property " <> prettyprint ty <> " should not be used" isForbidden = forbid $ "property " <> prettyprint propName <> " should not be used"
-- TODO: check if the property has the correct value -- TODO: check if the property has the correct value
suggestPropertyValue :: Text -> Text -> LintWriter () suggestPropertyValue :: Text -> Text -> LintWriter ()
suggestPropertyValue name value = unless (hasProperty name layer) suggestPropertyValue name value = unless (hasProperty name layer)
...@@ -97,18 +72,5 @@ checkProperty' layer prop ty = case ty of ...@@ -97,18 +72,5 @@ checkProperty' layer prop ty = case ty of
-- | does this layer have the given property? -- | does this layer have the given property?
hasProperty :: Text -> Layer -> Bool hasProperty :: Text -> Layer -> Bool
hasProperty name = any hasProperty name = any
(\prop -> prop !? "name" == Just (Aeson.String name)) (\prop -> propertyName prop == name)
. layerProperties . layerProperties
-- | get an attribute from a map
getAttr :: Properties -> Text -> Either Hint Aeson.Value
getAttr props name = unwrapWarn msg $ props !? name
where msg = "field " <> name <> "does not exist"
-- | lint goal: the property with the given name has given value
propEqual :: Properties -> Text -> Aeson.Value -> LintWriter ()
propEqual props name value = do
value' <- lift $ getAttr props name
assertWarn ("field "<>name<>" has unexpected value "<>prettyprint value'
<>", should be "<>prettyprint value)
$ value' == value
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
-- cover some of the types and records that are available in the format. For -- cover some of the types and records that are available in the format. For
-- those you should read the TMX documentation at -- those you should read the TMX documentation at
-- http://doc.mapeditor.org/en/latest/reference/tmx-map-format/ -- http://doc.mapeditor.org/en/latest/reference/tmx-map-format/
{-# LANGUAGE NamedFieldPuns #-}
module Tiled2 where module Tiled2 where
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
...@@ -66,6 +67,29 @@ parseDefault :: FromJSON a => A.Object -> Text -> a -> Parser a ...@@ -66,6 +67,29 @@ parseDefault :: FromJSON a => A.Object -> Text -> a -> Parser a
parseDefault o s d = fromMaybe d <$> o .:? s parseDefault o s d = fromMaybe d <$> o .:? s
-- | workadventure custom property
data Property = Property { propertyName :: Text
--, propertyType :: Text (unnecessary since always string)
, propertyValue :: Text
} deriving (Eq, Generic, Show)
instance FromJSON Property where
parseJSON (A.Object o) = do
propertyType <- o .: "type"
if propertyType /= A.String "string"
then typeMismatch "type" "string"
else do
propertyName <- o .: "name"
propertyValue <- o .: "value"
pure $ Property { propertyName, propertyValue }
parseJSON invalid = typeMismatch "Property" invalid
instance ToJSON Property where
toJSON prop = object [ "type" .= A.String "string"
, "name" .= propertyName prop
, "value" .= propertyName prop
]
data Object = Object { objectId :: Int data Object = Object { objectId :: Int
-- ^ Incremental id - unique across all objects -- ^ Incremental id - unique across all objects
, objectWidth :: Double , objectWidth :: Double
...@@ -154,7 +178,7 @@ data Layer = Layer { layerWidth :: Double ...@@ -154,7 +178,7 @@ data Layer = Layer { layerWidth :: Double
-- ^ Array of GIDs. tilelayer only. -- ^ Array of GIDs. tilelayer only.
, layerObjects :: Maybe (Vector Object) , layerObjects :: Maybe (Vector Object)
-- ^ Array of Objects. objectgroup only. -- ^ Array of Objects. objectgroup only.
, layerProperties :: [Map Text Value] , layerProperties :: [Property]
-- ^ string key-value pairs. -- ^ string key-value pairs.
, layerOpacity :: Float , layerOpacity :: Float
-- ^ Value between 0 and 1 -- ^ Value between 0 and 1
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment