1
1
module Data.Codec.Argonaut.Sum
2
- ( Tag (..)
2
+ ( enumSum
3
3
, taggedSum
4
4
) where
5
5
@@ -8,52 +8,73 @@ import Prelude
8
8
import Control.Monad.Reader (ReaderT (..))
9
9
import Control.Monad.Writer (Writer , writer )
10
10
import Data.Argonaut.Core as J
11
+ import Data.Bifunctor (lmap )
11
12
import Data.Codec (GCodec (..), decode , encode )
12
- import Data.Codec.Argonaut (JsonCodec , JsonDecodeError , jobject , json , prop , string )
13
- import Data.Either as E
14
- import Data.Newtype ( class Newtype )
13
+ import Data.Codec.Argonaut (JsonCodec , JsonDecodeError (..) , jobject , json , prop , string )
14
+ import Data.Either ( Either (..))
15
+ import Data.Maybe ( Maybe (..), maybe )
15
16
import Data.Profunctor.Star (Star (..))
16
17
import Data.StrMap as SM
17
18
import Data.StrMap.ST as SMST
18
19
import Data.Tuple (Tuple (..))
19
20
20
- -- | A tag value for a case in a sum type.
21
- newtype Tag = Tag String
22
-
23
- derive newtype instance eqTag ∷ Eq Tag
24
- derive newtype instance ordTag ∷ Ord Tag
25
- derive instance newtypeTag ∷ Newtype Tag _
21
+ -- | A helper for defining JSON codecs for "enum" sum types, where every
22
+ -- | constructor is nullary, and the type will be encoded as a string.
23
+ enumSum
24
+ ∷ ∀ a
25
+ . (a → String )
26
+ → (String → Maybe a )
27
+ → JsonCodec a
28
+ enumSum printTag parseTag = GCodec dec enc
29
+ where
30
+ dec ∷ ReaderT J.Json (Either JsonDecodeError ) a
31
+ dec = ReaderT \j → do
32
+ value ← decode string j
33
+ case parseTag value of
34
+ Just a → Right a
35
+ Nothing → Left (UnexpectedValue j)
36
+ enc ∷ Star (Writer J.Json ) a a
37
+ enc = Star \a → writer $ Tuple a (encode string (printTag a))
26
38
27
- -- | A helper for defining JSON codecs for sum types.
39
+ -- | A helper for defining JSON codecs for sum types. To ensure exhaustivity
40
+ -- | there needs to be a mapping to and from a tag type for the type to be
41
+ -- | encoded.
28
42
-- |
29
- -- | - The first function attempts to decode a case, using the specified tag.
30
- -- | - The second function encodes a case, returning an appropriate tag and
31
- -- | encoded value.
43
+ -- | - The first argument is the name of the type being decoded, for error
44
+ -- | message purposes.
45
+ -- | - The second argument maps a tag value to a string to use in the encoding.
46
+ -- | - The second argument maps a string back to a tag value during decoding.
47
+ -- | - The third argument returns either a constant value or a decoder function
48
+ -- | based on a tag value.
49
+ -- | - The fourth argument returns a tag value and optional encoded value to
50
+ -- | store for a constructor of the sum.
32
51
taggedSum
33
- ∷ ∀ a
34
- . (Tag → J.Json → E.Either JsonDecodeError a )
35
- → (a → Tuple Tag J.Json )
52
+ ∷ ∀ tag a
53
+ . String
54
+ → (tag → String )
55
+ → (String → Maybe tag )
56
+ → (tag → Either a (J.Json → Either JsonDecodeError a ))
57
+ → (a → Tuple tag (Maybe J.Json ))
36
58
→ JsonCodec a
37
- taggedSum f g = GCodec (decodeCase f) (encodeCase g)
38
-
39
- decodeCase
40
- ∷ ∀ a
41
- . (Tag → J.Json → E.Either JsonDecodeError a )
42
- → ReaderT J.Json (E.Either JsonDecodeError ) a
43
- decodeCase f = ReaderT \j → do
44
- obj ← decode jobject j
45
- tag ← decode (prop " tag" string) obj
46
- value ← decode (prop " value" json) obj
47
- f (Tag tag) value
48
-
49
- encodeCase
50
- ∷ ∀ a
51
- . (a → Tuple Tag J.Json )
52
- → Star (Writer J.Json ) a a
53
- encodeCase f = Star case _ of
54
- a | Tuple (Tag tag) value ← f a →
55
- writer $ Tuple a $ encode jobject $
56
- SM .pureST do
57
- obj ← SMST .new
58
- _ ← SMST .poke obj " tag" (encode string tag)
59
- SMST .poke obj " value" value
59
+ taggedSum name printTag parseTag f g = GCodec decodeCase encodeCase
60
+ where
61
+ decodeCase ∷ ReaderT J.Json (Either JsonDecodeError ) a
62
+ decodeCase = ReaderT \j → lmap (Named name) do
63
+ obj ← decode jobject j
64
+ tag ← decode (prop " tag" string) obj
65
+ case parseTag tag of
66
+ Nothing → Left (AtKey " tag" (UnexpectedValue (J .fromString tag)))
67
+ Just t →
68
+ case f t of
69
+ Left a → pure a
70
+ Right decoder → do
71
+ value ← decode (prop " value" json) obj
72
+ lmap (AtKey " value" ) (decoder value)
73
+ encodeCase ∷ Star (Writer J.Json ) a a
74
+ encodeCase = Star case _ of
75
+ a | Tuple tag value ← g a →
76
+ writer $ Tuple a $ encode jobject $
77
+ SM .pureST do
78
+ obj ← SMST .new
79
+ _ ← SMST .poke obj " tag" (encode string (printTag tag))
80
+ maybe (pure obj) (SMST .poke obj " value" ) value
0 commit comments