1
1
module Data.Argonaut.Decode.Generic.Rep (
2
2
class DecodeRep ,
3
3
class DecodeRepArgs ,
4
- class DecodeRepFields ,
4
+ class DecodeRepRowList ,
5
5
class DecodeLiteral ,
6
6
decodeRep ,
7
7
decodeRepArgs ,
8
- decodeRepFields ,
8
+ decodeRepRowList ,
9
9
genericDecodeJson ,
10
10
decodeLiteralSum ,
11
11
decodeLiteralSumWithTransform ,
@@ -21,10 +21,16 @@ import Data.Array (uncons)
21
21
import Data.Bifunctor (lmap )
22
22
import Data.Either (Either (..))
23
23
import Data.Generic.Rep as Rep
24
- import Data.Maybe (Maybe , maybe )
25
- import Data.StrMap as SM
24
+ import Data.Maybe (Maybe (..), maybe )
26
25
import Data.Symbol (class IsSymbol , SProxy (..), reflectSymbol )
26
+ import Foreign.Object as FO
27
27
import Partial.Unsafe (unsafeCrashWith )
28
+ import Prim.Row as Row
29
+ import Prim.RowList (class RowToList , Cons , Nil , kind RowList )
30
+ import Prim.TypeError (class Fail , Text )
31
+ import Record.Builder (Builder )
32
+ import Record.Builder as Builder
33
+ import Type.Data.RowList (RLProxy (..))
28
34
29
35
class DecodeRep r where
30
36
decodeRep :: Json -> Either String r
@@ -40,11 +46,11 @@ instance decodeRepConstructor :: (IsSymbol name, DecodeRepArgs a) => DecodeRep (
40
46
let name = reflectSymbol (SProxy :: SProxy name )
41
47
let decodingErr msg = " When decoding a " <> name <> " : " <> msg
42
48
jObj <- mFail (decodingErr " expected an object" ) (toObject j)
43
- jTag <- mFail (decodingErr " 'tag' property is missing" ) (SM .lookup " tag" jObj)
49
+ jTag <- mFail (decodingErr " 'tag' property is missing" ) (FO .lookup " tag" jObj)
44
50
tag <- mFail (decodingErr " 'tag' property is not a string" ) (toString jTag)
45
51
when (tag /= name) $
46
52
Left $ decodingErr " 'tag' property has an incorrect value"
47
- jValues <- mFail (decodingErr " 'values' property is missing" ) (SM .lookup " values" jObj)
53
+ jValues <- mFail (decodingErr " 'values' property is missing" ) (FO .lookup " values" jObj)
48
54
values <- mFail (decodingErr " 'values' property is not an array" ) (toArray jValues)
49
55
{init, rest} <- lmap decodingErr $ decodeRepArgs values
50
56
when (rest /= [] ) $
@@ -63,28 +69,68 @@ instance decodeRepArgsProduct :: (DecodeRepArgs a, DecodeRepArgs b) => DecodeRep
63
69
{init: b, rest: js''} <- decodeRepArgs js'
64
70
pure {init: Rep.Product a b, rest: js''}
65
71
66
- instance decodeRepArgsArgument :: (DecodeJson a ) => DecodeRepArgs (Rep.Argument a ) where
72
+ instance decodeRepRecordArgument ::
73
+ ( RowToList row rl
74
+ , DecodeRepRowList rl () row
75
+ ) => DecodeRepArgs (Rep.Argument (Record row )) where
67
76
decodeRepArgs js = do
68
- {head, tail} <- mFail " too few values were present" (uncons js)
69
- {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head
70
-
71
- instance decodeRepArgsRec :: (DecodeRepFields fields ) => DecodeRepArgs (Rep.Rec fields ) where
77
+ {head, tail} <- mFail " to few values were present" (uncons js)
78
+ obj <- mFail " no json object" (toObject head)
79
+ steps <- decodeRepRowList rlp obj
80
+ let arg = Rep.Argument $ Builder .build steps {}
81
+ pure {init: arg, rest: tail}
82
+ where
83
+ rlp :: RLProxy rl
84
+ rlp = RLProxy
85
+
86
+ else instance decodeRepArgsArgument :: (DecodeJson a ) => DecodeRepArgs (Rep.Argument a ) where
72
87
decodeRepArgs js = do
73
88
{head, tail} <- mFail " too few values were present" (uncons js)
74
- jObj <- mFail " record is not encoded as an object" (toObject head)
75
- {init: _, rest: tail} <<< Rep.Rec <$> decodeRepFields jObj
76
-
77
- class DecodeRepFields r where
78
- decodeRepFields :: SM.StrMap Json -> Either String r
89
+ {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head
79
90
80
- instance decodeRepFieldsProduct :: (DecodeRepFields a , DecodeRepFields b ) => DecodeRepFields (Rep.Product a b ) where
81
- decodeRepFields js = Rep.Product <$> decodeRepFields js <*> decodeRepFields js
82
91
83
- instance decodeRepFieldsField :: (IsSymbol field , DecodeJson a ) => DecodeRepFields (Rep.Field field a ) where
84
- decodeRepFields js = do
85
- let name = reflectSymbol (SProxy :: SProxy field )
86
- value <- mFail (" the field '" <> name <> " ' is not present" ) (SM .lookup name js)
87
- Rep.Field <$> decodeJson value
92
+ -- | a `DecodeRepRowList` represents a relation between a `RowList` and a record you
93
+ -- | can build from it by deserializing it's fields from a JSON `Object`
94
+ -- |
95
+ -- | this one is strictly internal to help out `decodeRepRecordArgument` handling records
96
+ -- |
97
+ -- | a `RowList` on the type level is very similar to a *cons-list* on the value level
98
+ -- | so the two instances handle all possible `RowList`s
99
+ -- |
100
+ -- | the idea is to use `Builder` to convert a `RowList` into a record at the type-level
101
+ -- | and have `decodeRepRowList` as witness on the value level that will try to decode
102
+ -- | JSON in to the resulting record value
103
+ -- |
104
+ -- | `from` and `to` are two helper types - using these `decodeRepRowListCons` can
105
+ -- | recursively create `Builder`-steps and make sure that every *symbol* in `rl`
106
+ -- | can only occur once (the fields in the records must be unique)
107
+ -- | (see `Row.Lacks`)
108
+ class DecodeRepRowList (rl :: RowList ) (from :: #Type ) (to :: #Type ) | rl -> from to where
109
+ decodeRepRowList :: forall g . g rl -> FO.Object Json -> Either String (Builder (Record from ) (Record to ))
110
+
111
+ instance decodeRepRowListNil :: DecodeRepRowList Nil () () where
112
+ decodeRepRowList _ _ = pure identity
113
+
114
+ instance decodeRepRowListCons ::
115
+ ( DecodeJson ty
116
+ , IsSymbol name
117
+ , DecodeRepRowList tail from from'
118
+ , Row.Lacks name from'
119
+ , Row.Cons name ty from' to
120
+ ) => DecodeRepRowList (Cons name ty tail ) from to where
121
+ decodeRepRowList _ obj = do
122
+ value :: ty <- (error $ FO .lookup name obj) >>= decodeJson
123
+ rest <- decodeRepRowList tailp obj
124
+ let
125
+ first :: Builder (Record from' ) (Record to )
126
+ first = Builder .insert namep value
127
+ pure $ first <<< rest
128
+ where
129
+ namep = SProxy :: SProxy name
130
+ tailp = RLProxy :: RLProxy tail
131
+ name = reflectSymbol namep
132
+ error Nothing = Left (" error while decoding field " <> name)
133
+ error (Just a) = Right a
88
134
89
135
-- | Decode `Json` representation of a value which has a `Generic` type.
90
136
genericDecodeJson :: forall a r . Rep.Generic a r => DecodeRep r => Json -> Either String a
@@ -95,7 +141,7 @@ mFail msg = maybe (Left msg) Right
95
141
96
142
-- | A function for decoding `Generic` sum types using string literal representations
97
143
decodeLiteralSum :: forall a r . Rep.Generic a r => DecodeLiteral r => Json -> Either String a
98
- decodeLiteralSum = decodeLiteralSumWithTransform id
144
+ decodeLiteralSum = decodeLiteralSumWithTransform identity
99
145
100
146
-- | A function for decoding `Generic` sum types using string literal representations
101
147
-- | Takes a function for transforming the tag name in encoding
@@ -117,7 +163,9 @@ instance decodeLiteralConstructor :: (IsSymbol name) => DecodeLiteral (Rep.Const
117
163
Left $ decodingErr " string literal " <> tag <> " had an incorrect value."
118
164
pure $ Rep.Constructor (Rep.NoArguments )
119
165
120
- type FailMessage = " " " `decodeLiteralSum` can only be used with sum types, where all of the constructors are nullary. This is because a string literal cannot be encoded into a product type." " "
166
+
167
+ type FailMessage =
168
+ Text " `decodeLiteralSum` can only be used with sum types, where all of the constructors are nullary. This is because a string literal cannot be encoded into a product type."
121
169
122
170
instance decodeLiteralConstructorCannotTakeProduct
123
171
:: Fail FailMessage
0 commit comments