Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit d6af13c

Browse files
committed
[CBR-26] Remove generic JSON serialization (resp. unserialization) code
We used to rely on this code solely for 'WalletError' instances. Though, we now declare those instances by hand using manual encoding. We might want to re-introduce generics is possible in the future. Note that we switch for a manual implementation because the current generics implementation relied on partial field accessors in the underlying types which we try to avoid. Another approach using generics without the need for partial field accessors would make everyone happier
1 parent 1e61367 commit d6af13c

File tree

2 files changed

+15
-162
lines changed

2 files changed

+15
-162
lines changed

wallet-new/cardano-sl-wallet-new.cabal

-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ library
183183
, http-client-tls
184184
, http-types
185185
, ixset-typed
186-
, json-sop
187186
, lens
188187
, log-warper
189188
, memory
+15-161
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,37 @@
11
{-# LANGUAGE PolyKinds #-}
22

33
module Cardano.Wallet.API.V1.Generic
4-
( gtoJsend
5-
, gparseJsend
6-
, gconsNames
4+
( gconsNames
75
, gconsName
86
) where
97

108
import Universum hiding (All, Generic)
119

12-
import Data.Aeson
13-
import Data.Aeson.Types (Parser)
1410
import Data.List ((!!))
1511
import Generics.SOP
16-
import Generics.SOP.JSON (JsonInfo (..), JsonOptions (..), Tag (..),
17-
defaultJsonOptions)
18-
19-
import Cardano.Wallet.API.Response.JSend (ResponseStatus (..))
20-
import Cardano.Wallet.Util (mkJsonKey)
21-
import Pos.Util.Util (aesonError)
22-
23-
import qualified Data.HashMap.Strict as HM
24-
import qualified Data.Vector as V
25-
26-
--
27-
-- Helper proxies
28-
--
29-
30-
pt :: Proxy ToJSON
31-
pt = Proxy
32-
33-
allpt :: Proxy (All ToJSON)
34-
allpt = Proxy
35-
36-
pf :: Proxy FromJSON
37-
pf = Proxy
38-
39-
allpf :: Proxy (All FromJSON)
40-
allpf = Proxy
4112

4213
--
43-
-- JSON encoding/decoding
14+
-- Misc
4415
--
4516

46-
-- | Generic method which makes JSON `Value` from a Haskell value in
47-
-- JSend format.
48-
gtoJsend
49-
:: forall a. (Generic a, HasDatatypeInfo a, All2 ToJSON (Code a))
50-
=> ResponseStatus -> a -> Value
51-
gtoJsend rs a =
52-
hcollapse $
53-
hcliftA2 allpt (gtoJsend' rs)
54-
(jsendInfo (Proxy :: Proxy a) jsendOptions)
55-
(unSOP $ from a)
56-
57-
-- | Our custom naming options
58-
jsendOptions :: JsonOptions
59-
jsendOptions = defaultJsonOptions
60-
{ jsonFieldName = const mkJsonKey
61-
}
62-
63-
-- | Slightly modified version compared to Generics.SOP.JSON, we also tag
64-
-- single-constructor (ADT with one constructor and newtype) because we
65-
-- rely on that information to wrap the corresponding json in a jsend payload.
66-
jsendInfo :: forall a. (HasDatatypeInfo a, SListI (Code a))
67-
=> Proxy a -> JsonOptions -> NP JsonInfo (Code a)
68-
jsendInfo pa opts =
69-
case datatypeInfo pa of
70-
Newtype _ t _ -> JsonOne (Tag $ jsonTagName opts t) :* Nil
71-
ADT _ n cs -> hliftA (jsonInfoFor opts n (Tag . jsonTagName opts)) cs
72-
73-
-- Extracted from Generics.SOP.JSON
74-
jsonInfoFor :: forall xs. JsonOptions -> DatatypeName -> (ConstructorName -> Tag) -> ConstructorInfo xs -> JsonInfo xs
75-
jsonInfoFor _ _ tag (Infix n _ _) = JsonMultiple (tag n)
76-
jsonInfoFor _ _ tag (Constructor n) =
77-
case shape :: Shape xs of
78-
ShapeNil -> JsonZero n
79-
ShapeCons ShapeNil -> JsonOne (tag n)
80-
_ -> JsonMultiple (tag n)
81-
jsonInfoFor opts d tag (Record n fields) =
82-
JsonRecord (tag n) (hliftA jfieldName fields)
83-
where
84-
jfieldName :: FieldInfo a -> K String a
85-
jfieldName (FieldInfo name) = K (jsonFieldName opts d name)
86-
87-
gtoJsend'
88-
:: All ToJSON xs
89-
=> ResponseStatus -> JsonInfo xs -> NP I xs -> K Value xs
90-
gtoJsend' rs (JsonZero n) Nil =
91-
jsendValue rs (Tag n) (Object mempty)
92-
gtoJsend' rs (JsonOne tag) (I a :* Nil) =
93-
jsendValue rs tag (toJSON a)
94-
gtoJsend' rs (JsonMultiple tag) cs =
95-
jsendValue rs tag . Array . V.fromList . hcollapse $
96-
hcliftA pt (K . toJSON . unI) cs
97-
gtoJsend' rs (JsonRecord tag fields) cs =
98-
jsendValue rs tag . Object . HM.fromList . hcollapse $
99-
hcliftA2 pt (\(K field) (I a) -> K (toText field, toJSON a)) fields cs
100-
101-
-- | Generic method which parses a Haskell value from given `Value`.
102-
gparseJsend
103-
:: forall a. (Generic a, HasDatatypeInfo a, All2 FromJSON (Code a))
104-
=> Value -> Parser a
105-
gparseJsend v = to <$> gparseJsend' v (jsendInfo (Proxy :: Proxy a) jsendOptions)
106-
107-
gparseJsend'
108-
:: forall (xss :: [[*]]). All2 FromJSON xss
109-
=> Value -> NP JsonInfo xss -> Parser (SOP I xss)
110-
gparseJsend' v infos = asum . hcollapse $
111-
hcliftA2 allpf (parseJsendConstructor v) infos
112-
(injections :: NP (Injection (NP I) xss) xss)
113-
114-
parseJsendConstructor
115-
:: forall (xss :: [[*]]) (xs :: [*]). All FromJSON xs
116-
=> Value -> JsonInfo xs -> Injection (NP I) xss xs -> K (Parser (SOP I xss)) xs
117-
parseJsendConstructor v info (Fn inj) = K $ do
118-
vals <- parseJsendValues info v
119-
return $ SOP $ unK (inj vals)
17+
-- | Get all constructors names available of an ADT
18+
gconsNames
19+
:: forall a. (HasDatatypeInfo a, SListI (Code a))
20+
=> Proxy a -> [Text]
21+
gconsNames =
22+
map toText . hcollapse . hliftA (K . constructorName) . gconsInfos
12023

121-
parseJsendValues
122-
:: forall (xs :: [*]). All FromJSON xs
123-
=> JsonInfo xs -> Value -> Parser (NP I xs)
124-
parseJsendValues (JsonZero n) =
125-
unJsendValue (Tag n) $
126-
const $ return Nil
127-
parseJsendValues (JsonOne tag) =
128-
unJsendValue tag $ \o -> do
129-
v <- parseJSON o
130-
return $ I v :* Nil
131-
parseJsendValues (JsonMultiple tag) =
132-
unJsendValue tag $
133-
withArray "Array" $ \arr ->
134-
case fromList (V.toList arr) of
135-
Nothing -> aesonError "Too few values!"
136-
Just vals ->
137-
let mkVal :: FromJSON a => K Value a -> Parser a
138-
mkVal = parseJSON . unK
139-
in hsequence $ hcliftA pf mkVal vals
140-
parseJsendValues (JsonRecord tag fields) =
141-
unJsendValue tag $
142-
withObject "Object" $ \o ->
143-
let getField :: FromJSON a => K String a -> Parser a
144-
getField (K name) = o .: toText name
145-
in hsequence $ hcliftA pf getField fields
14624

147-
-- | Helper function which makes a JSON value in JSend format
148-
-- from a constructor tag and object with constructor's arguments
149-
jsendValue :: ResponseStatus -> Tag -> Value -> K Value a
150-
jsendValue _ NoTag v = K v
151-
jsendValue rs (Tag t) v = K $ Object $
152-
HM.fromList [ ("message", String $ toText t)
153-
, ("diagnostic", v)
154-
, ("status", toJSON rs)
155-
]
25+
-- | Get the ADT constructor's name of the given value
26+
gconsName
27+
:: forall a. (Generic a, HasDatatypeInfo a)
28+
=> a -> Text
29+
gconsName a =
30+
gconsNames (Proxy @a) !! hindex (from a)
15631

157-
-- | Helper function to parse value in JSend format if desired constructor
158-
-- is known.
159-
unJsendValue :: Tag -> (Value -> Parser a) -> Value -> Parser a
160-
unJsendValue NoTag f = f
161-
unJsendValue (Tag n) f = withObject ("Expected JSend object with message `" <> n <> "`") $
162-
\o -> do
163-
msg <- o .: "message"
164-
guard $ n == msg
165-
val <- o .: "diagnostic"
166-
f val
16732

16833
--
169-
-- Misc
34+
-- INTERNALS
17035
--
17136

17237
gconsInfos
@@ -176,14 +41,3 @@ gconsInfos pa = case datatypeInfo pa of
17641
Newtype _ _ conInfo -> conInfo :* Nil
17742
ADT _ _ consInfo -> consInfo
17843

179-
gconsNames
180-
:: forall a. (HasDatatypeInfo a, SListI (Code a))
181-
=> Proxy a -> [Text]
182-
gconsNames =
183-
map toText . hcollapse . hliftA (K . constructorName) . gconsInfos
184-
185-
gconsName
186-
:: forall a. (Generic a, HasDatatypeInfo a)
187-
=> a -> Text
188-
gconsName a =
189-
gconsNames (Proxy @a) !! hindex (from a)

0 commit comments

Comments
 (0)