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

Commit 808f00b

Browse files
committed
[CO-319] Correctly format (jsend) newtype errors
This is rather ugly and could probably be achieved nicely with a better understanding of the Generics.SOP library. As far as I could tell, there's no easy way to retrieve 'Tag' for single constructor (cf: 'For a datatype with a single constructor we do not need to tag values with their constructor; but for a datatype with multiple constructors we do. ')
1 parent fd801a7 commit 808f00b

File tree

4 files changed

+54
-19
lines changed

4 files changed

+54
-19
lines changed

wallet-new/src/Cardano/Wallet/API/Response.hs

+5
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ instance FromJSON JSONValidationError where
208208

209209
instance Exception JSONValidationError
210210

211+
instance Arbitrary JSONValidationError where
212+
arbitrary = oneof
213+
[ pure $ JSONValidationFailed "JSON validation failed."
214+
]
215+
211216
instance Buildable JSONValidationError where
212217
build = \case
213218
JSONValidationFailed _ ->

wallet-new/src/Cardano/Wallet/API/V1/Generic.hs

+37-16
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import Data.Aeson.Types (Parser)
1414
import qualified Data.HashMap.Strict as HM
1515
import qualified Data.Vector as V
1616
import Generics.SOP
17-
import Generics.SOP.JSON (JsonInfo (..), JsonOptions (..), Tag (..), defaultJsonOptions,
18-
jsonInfo)
17+
import Generics.SOP.JSON (JsonInfo (..), JsonOptions (..), Tag (..), defaultJsonOptions)
1918

2019
import Cardano.Wallet.API.Response.JSend (ResponseStatus (..))
2120
import Cardano.Wallet.Util (mkJsonKey)
@@ -42,24 +41,46 @@ allpf = Proxy
4241
-- JSON encoding/decoding
4342
--
4443

45-
-- | Returns `JsonInfo` for type (from `json-sop` package)
46-
-- for representing a type in a JSend format.
47-
jsendInfo
48-
:: forall a. (HasDatatypeInfo a, SListI (Code a))
49-
=> Proxy a -> NP JsonInfo (Code a)
50-
jsendInfo pa = jsonInfo pa $ defaultJsonOptions
51-
{ jsonFieldName = const mkJsonKey
52-
}
53-
5444
-- | Generic method which makes JSON `Value` from a Haskell value in
5545
-- JSend format.
5646
gtoJsend
5747
:: forall a. (Generic a, HasDatatypeInfo a, All2 ToJSON (Code a))
5848
=> ResponseStatus -> a -> Value
59-
gtoJsend rs a = hcollapse $
60-
hcliftA2 allpt (gtoJsend' rs)
61-
(jsendInfo (Proxy :: Proxy a))
62-
(unSOP $ from a)
49+
gtoJsend rs a =
50+
hcollapse $
51+
hcliftA2 allpt (gtoJsend' rs)
52+
(jsendInfo (Proxy :: Proxy a) jsendOptions)
53+
(unSOP $ from a)
54+
55+
-- | Our custom naming options
56+
jsendOptions :: JsonOptions
57+
jsendOptions = defaultJsonOptions
58+
{ jsonFieldName = const mkJsonKey
59+
}
60+
61+
-- | Slightly modified version compared to Generics.SOP.JSON, we also tag
62+
-- single-constructor (ADT with one constructor and newtype) because we
63+
-- rely on that information to wrap the corresponding json in a jsend payload.
64+
jsendInfo :: forall a. (HasDatatypeInfo a, SListI (Code a))
65+
=> Proxy a -> JsonOptions -> NP JsonInfo (Code a)
66+
jsendInfo pa opts =
67+
case datatypeInfo pa of
68+
Newtype _ t _ -> JsonOne (Tag $ jsonTagName opts t) :* Nil
69+
ADT _ n cs -> hliftA (jsonInfoFor opts n (Tag . jsonTagName opts)) cs
70+
71+
-- Extracted from Generics.SOP.JSON
72+
jsonInfoFor :: forall xs. JsonOptions -> DatatypeName -> (ConstructorName -> Tag) -> ConstructorInfo xs -> JsonInfo xs
73+
jsonInfoFor _ _ tag (Infix n _ _) = JsonMultiple (tag n)
74+
jsonInfoFor _ _ tag (Constructor n) =
75+
case shape :: Shape xs of
76+
ShapeNil -> JsonZero n
77+
ShapeCons ShapeNil -> JsonOne (tag n)
78+
_ -> JsonMultiple (tag n)
79+
jsonInfoFor opts d tag (Record n fields) =
80+
JsonRecord (tag n) (hliftA jfieldName fields)
81+
where
82+
jfieldName :: FieldInfo a -> K String a
83+
jfieldName (FieldInfo name) = K (jsonFieldName opts d name)
6384

6485
gtoJsend'
6586
:: All ToJSON xs
@@ -79,7 +100,7 @@ gtoJsend' rs (JsonRecord tag fields) cs =
79100
gparseJsend
80101
:: forall a. (Generic a, HasDatatypeInfo a, All2 FromJSON (Code a))
81102
=> Value -> Parser a
82-
gparseJsend v = to <$> gparseJsend' v (jsendInfo (Proxy :: Proxy a))
103+
gparseJsend v = to <$> gparseJsend' v (jsendInfo (Proxy :: Proxy a) jsendOptions)
83104

84105
gparseJsend'
85106
:: forall (xss :: [[*]]). All2 FromJSON xss

wallet-new/src/Cardano/Wallet/API/V1/Migration/Types.hs

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import Pos.Crypto (decodeHash)
2727
import Pos.Wallet.Web.ClientTypes.Instances ()
2828
import Pos.Wallet.Web.Tracking.Sync (calculateEstimatedRemainingTime)
2929
import Servant (err422)
30+
import Test.QuickCheck (Arbitrary (..))
31+
import Test.QuickCheck.Gen (oneof)
3032

3133
import qualified Cardano.Wallet.API.V1.Types as V1
3234
import qualified Control.Lens as Lens
@@ -347,6 +349,11 @@ instance FromJSON MigrationError where
347349

348350
instance Exception MigrationError
349351

352+
instance Arbitrary MigrationError where
353+
arbitrary = oneof
354+
[ pure $ MigrationFailed "Migration failed."
355+
]
356+
350357
instance Buildable MigrationError where
351358
build = \case
352359
MigrationFailed _ ->

wallet-new/test/MarshallingSpec.hs

+5-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import qualified Pos.Core as Core
2424

2525
import Cardano.Wallet.API.Indices
2626
import Cardano.Wallet.API.Request.Pagination (Page, PerPage)
27-
import Cardano.Wallet.API.V1.Errors (WalletError)
28-
import Cardano.Wallet.API.V1.Migration.Types (Migrate (..))
27+
import Cardano.Wallet.API.Response (JSONValidationError)
28+
import Cardano.Wallet.API.V1.Migration.Types (Migrate (..), MigrationError)
2929
import Cardano.Wallet.API.V1.Types
3030
import Cardano.Wallet.Orphans ()
3131
import qualified Cardano.Wallet.Util as Util
@@ -53,6 +53,8 @@ spec = parallel $ describe "Marshalling & Unmarshalling" $ do
5353
aesonRoundtripProp @TransactionType Proxy
5454
aesonRoundtripProp @TransactionStatus Proxy
5555
aesonRoundtripProp @WalletError Proxy
56+
aesonRoundtripProp @JSONValidationError Proxy
57+
aesonRoundtripProp @MigrationError Proxy
5658
aesonRoundtripProp @WalletId Proxy
5759
aesonRoundtripProp @Wallet Proxy
5860
aesonRoundtripProp @SlotDuration Proxy
@@ -145,7 +147,7 @@ migrateRoundtrip :: (Arbitrary from, Migrate from to, Migrate to from, Eq from,
145147
migrateRoundtrip (_ :: proxy from) (_ :: proxy to) = forAll arbitrary $ \(arbitraryFrom :: from) -> do
146148
(eitherMigrate =<< migrateTo arbitraryFrom) === Right arbitraryFrom
147149
where
148-
migrateTo x = eitherMigrate x :: Either WalletError to
150+
migrateTo x = eitherMigrate x :: Either MigrationError to
149151

150152
migrateRoundtripProp
151153
:: (Arbitrary from, Migrate from to, Migrate to from, Eq from, Show from, Typeable from, Typeable to)

0 commit comments

Comments
 (0)