summaryrefslogtreecommitdiff
path: root/lib/Properties.hs
blob: 0b9a71f414b952eb95ec62c56b1d549546278f0a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Contains checks for custom properties of the map json
module Properties (checkProperty) where


import           Control.Monad             (unless, when)
import           Control.Monad.Trans.Class (lift)
import           Data.Aeson                as Aeson (Value (String))
import           Data.Map                  (Map, (!?))
import           Data.Text                 (Text)
import           Tiled2                    (Layer (layerProperties))
import           Util                      (quote, showAeson)

import           LintWriter                (Hint, LintWriter, Level(..), hint,
                                            assertWarn, complain, forbid, info,
                                            suggest, unwrapWarn, warn)

-- | values may be anything, and are not typechecked (for now),
-- 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
-- to its own layer since sometimes the presense of one property
-- implies the presence or absense of another.
--
-- The tests in here are meant to comply with the informal spec
-- at https://workadventu.re/map-building
--
-- In practice, the actual specifiaction 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
-- that should make this readable even to non-Haskellers
checkProperty' :: Layer -> Properties -> Text -> LintWriter ()
checkProperty' layer prop ty = case ty of
    "jitsiRoom" -> do
      propEqual prop "type" "string"
      urlValue <- lift $ getAttr prop "value"
      info $ "found jitsi room: " <> showAeson urlValue
      suggestPropertyValue "jitsiTrigger" "onaction"
    "jitsiTrigger" ->
      requireProperty "jitsiRoom"
    "jitsiUrl" -> isForbidden
    "jitsiConfig" -> isForbidden
    "jitsiClientConfig" -> isForbidden
    "jitsiRoomAdminTag" -> isForbidden
    "playAudio" -> do
      -- TODO: check for url validity?
      propEqual prop "type" "string"
    "audioLoop" ->
      requireProperty "playAudio"
    "audioVolume" ->
      requireProperty "playAudio"
    "openWebsite" ->
      suggestPropertyValue "openWebsiteTrigger" "onaction"
    "openWebsiteTrigger" ->
      requireProperty "openWebsite"
    "openWebsitePolicy" ->
      requireProperty "openWebsite"
    "exitUrl" -> pure ()
    "startLayer" -> pure ()
      -- could also make this a "hard error" (i.e. Left), but then it
      -- stops checking other properties as checkLayer short-circuits.
    _ -> warn $ "unknown property type " <> quote ty
    where
      -- | require some property in this layer
      requireProperty name = unless (hasProperty name layer)
        $ complain $ "property "<>quote name<>" requires property "<>quote ty
      -- | forbid some property in this layer
      forbidProperty name = when (hasProperty name layer)
        $ forbid $ "property " <> quote name <> " should not be used"
      -- | This property is forbidden and should not be used
      isForbidden = forbid $ "property " <> quote ty <> " should not be used"
      -- TODO: check if the property has the correct value
      suggestPropertyValue name value = unless (hasProperty name layer)
        $ suggest $ "set property " <> quote name <> " to " <> quote value




-- | does this layer have the given property?
hasProperty :: Text -> Layer -> Bool
hasProperty name = any
  (\prop -> prop !? "name" == Just (Aeson.String name))
  . 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 "<>showAeson value'
              <>", should be "<>showAeson value)
    $ value' == value