/
Combinators.purs
136 lines (119 loc) · 4.05 KB
/
Combinators.purs
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
module Data.Argonaut.Decode.Combinators
( getField
, getFieldDeprecated
, getFieldOptional
, getFieldOptionalDeprecated
, getFieldOptional'
, defaultField
, defaultFieldDeprecated
, (.:)
, (.?)
, (.:!)
, (.:?)
, (.??)
, (.!=)
, (.?=)
) where
import Prelude
import Data.Argonaut.Core (Json, isNull)
import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
import Data.Bifunctor (lmap)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..), fromMaybe, maybe)
import Foreign.Object as FO
import Prim.TypeError (class Warn, Text)
-- | Attempt to get the value for a given key on an `Object Json`.
-- |
-- | Use this accessor if the key and value *must* be present in your object.
-- | If the key and value are optional, use `getFieldOptional'` (`.:?`) instead.
getField :: forall a. DecodeJson a => FO.Object Json -> String -> Either String a
getField o s =
maybe
(Left $ "Expected field " <> show s)
(elaborateFailure s <<< decodeJson)
(FO.lookup s o)
infix 7 getField as .:
getFieldDeprecated
:: forall a. Warn ( Text "`.?` is deprecated, use `.:` instead" )
=> DecodeJson a
=> FO.Object Json
-> String
-> Either String a
getFieldDeprecated = getField
infix 7 getFieldDeprecated as .?
-- | Attempt to get the value for a given key on an `Object Json`.
-- |
-- | The result will be `Right Nothing` if the key and value are not present,
-- | or if the key is present and the value is `null`.
-- |
-- | Use this accessor if the key and value are optional in your object.
-- | If the key and value are mandatory, use `getField` (`.:`) instead.
getFieldOptional' :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
getFieldOptional' o s =
maybe
(pure Nothing)
decode
(FO.lookup s o)
where
decode json =
if isNull json
then pure Nothing
else Just <$> decodeJson json
infix 7 getFieldOptional' as .:?
-- | Attempt to get the value for a given key on an `Object Json`.
-- |
-- | The result will be `Right Nothing` if the key and value are not present,
-- | but will fail if the key is present but the value cannot be converted to the right type.
-- |
-- | This function will treat `null` as a value and attempt to decode it into your desired type.
-- | If you would like to treat `null` values the same as absent values, use
-- | `getFieldOptional` (`.:?`) instead.
getFieldOptional :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
getFieldOptional o s =
maybe
(pure Nothing)
decode
(FO.lookup s o)
where
decode json = Just <$> (elaborateFailure s <<< decodeJson) json
infix 7 getFieldOptional as .:!
getFieldOptionalDeprecated
:: forall a. Warn ( Text "`.??` is deprecated, use `.:!` or `.:?` instead" )
=> DecodeJson a
=> FO.Object Json
-> String
-> Either String (Maybe a)
getFieldOptionalDeprecated = getFieldOptional
infix 7 getFieldOptionalDeprecated as .??
-- | Helper for use in combination with `.:?` to provide default values for optional
-- | `Object Json` fields.
-- |
-- | Example usage:
-- | ```purescript
-- | newtype MyType = MyType
-- | { foo :: String
-- | , bar :: Maybe Int
-- | , baz :: Boolean
-- | }
-- |
-- | instance decodeJsonMyType :: DecodeJson MyType where
-- | decodeJson json = do
-- | x <- decodeJson json
-- | foo <- x .: "foo" -- mandatory field
-- | bar <- x .:? "bar" -- optional field
-- | baz <- x .:? "baz" .!= false -- optional field with default value of `false`
-- | pure $ MyType { foo, bar, baz }
-- | ```
defaultField :: forall a. Either String (Maybe a) -> a -> Either String a
defaultField parser default = fromMaybe default <$> parser
infix 6 defaultField as .!=
defaultFieldDeprecated
:: forall a. Warn ( Text "`.?=` is deprecated, use `.!=` instead" )
=> Either String (Maybe a) -> a -> Either String a
defaultFieldDeprecated = defaultField
infix 6 defaultFieldDeprecated as .?=
elaborateFailure :: ∀ a. String -> Either String a -> Either String a
elaborateFailure s e =
lmap msg e
where
msg m = "Failed to decode key '" <> s <> "': " <> m