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

Commit 21598f4

Browse files
committed
[CO-319] Make V1/Error part of V1/Types
To realize this, we had to extract JSONValidationFailed and MigrationFailed constructor from WalletError. They're now defined as constructor in different data-types (resp. JSONValidationError and MigrationError).
1 parent c797b3d commit 21598f4

File tree

14 files changed

+376
-363
lines changed

14 files changed

+376
-363
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ library
3939
Cardano.Wallet.API.V1
4040
Cardano.Wallet.API.V1.Accounts
4141
Cardano.Wallet.API.V1.Addresses
42-
Cardano.Wallet.API.V1.Errors
4342
Cardano.Wallet.API.V1.Generic
43+
Cardano.Wallet.API.V1.Errors
4444
Cardano.Wallet.API.V1.Handlers
4545
Cardano.Wallet.API.V1.Headers
4646
Cardano.Wallet.API.V1.Info

wallet-new/integration/TransactionSpecs.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ module TransactionSpecs (transactionSpecs) where
55

66
import Universum
77

8-
import Cardano.Wallet.API.V1.Errors hiding (describe)
98
import Cardano.Wallet.Client.Http
109
import Control.Lens
11-
import qualified Pos.Core as Core
1210
import Test.Hspec
1311

12+
import qualified Pos.Core as Core
13+
1414
import Util
1515

1616

wallet-new/integration/WalletSpecs.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ module WalletSpecs (walletSpecs) where
55

66
import Universum
77

8+
import Cardano.Wallet.API.V1.Types (WalletError (WalletAlreadyExists))
89
import Cardano.Wallet.Client.Http
9-
import Cardano.Wallet.API.V1.Errors (WalletError (WalletAlreadyExists))
1010
import Control.Lens
1111
import Test.Hspec
1212

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

+48-22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{-# LANGUAGE DeriveFunctor #-}
22
{-# LANGUAGE DeriveGeneric #-}
3+
{-# LANGUAGE LambdaCase #-}
34
{-# LANGUAGE OverloadedLists #-}
45
module Cardano.Wallet.API.Response (
56
Metadata (..)
67
, ResponseStatus(..)
78
, WalletResponse(..)
9+
, JSONValidationError(..)
810
-- * Generating responses for collections
911
, respondWith
1012
-- * Generating responses for single resources
@@ -13,33 +15,37 @@ module Cardano.Wallet.API.Response (
1315
) where
1416

1517
import Prelude
16-
import Universum (Buildable, decodeUtf8, toText, (<>))
18+
import Universum (Buildable, Text, decodeUtf8, toText, (<>))
1719

20+
import Cardano.Wallet.API.Indices (Indexable', IxSet')
21+
import Cardano.Wallet.API.Request (RequestParams (..))
22+
import Cardano.Wallet.API.Request.Filter (FilterOperations (..))
23+
import Cardano.Wallet.API.Request.Pagination (Page (..), PaginationMetadata (..),
24+
PaginationParams (..), PerPage (..))
25+
import Cardano.Wallet.API.Request.Sort (SortOperations (..))
26+
import Cardano.Wallet.API.Response.Filter.IxSet as FilterBackend
1827
import Cardano.Wallet.API.Response.JSend (ResponseStatus (..))
28+
import Cardano.Wallet.API.Response.Sort.IxSet as SortBackend
29+
import Cardano.Wallet.API.V1.Errors (ToServantError (..))
30+
import Cardano.Wallet.API.V1.Generic (gparseJsend, gtoJsend)
1931
import Control.Lens
2032
import Data.Aeson
2133
import Data.Aeson.Encode.Pretty (encodePretty)
2234
import Data.Aeson.TH
23-
import qualified Data.Char as Char
2435
import Data.Swagger as S
25-
import qualified Data.Text.Buildable
2636
import Data.Typeable
2737
import Formatting (bprint, build, (%))
38+
import Generics.SOP.TH (deriveGeneric)
2839
import GHC.Generics (Generic)
29-
import qualified Serokell.Aeson.Options as Serokell
40+
import Servant (err400)
3041
import Servant.API.ContentTypes (Accept (..), JSON, MimeRender (..), MimeUnrender (..),
3142
OctetStream)
3243
import Test.QuickCheck
3344

34-
import Cardano.Wallet.API.Indices (Indexable', IxSet')
35-
import Cardano.Wallet.API.Request (RequestParams (..))
36-
import Cardano.Wallet.API.Request.Filter (FilterOperations (..))
37-
import Cardano.Wallet.API.Request.Pagination (Page (..), PaginationMetadata (..),
38-
PaginationParams (..), PerPage (..))
39-
import Cardano.Wallet.API.Request.Sort (SortOperations (..))
40-
import Cardano.Wallet.API.Response.Filter.IxSet as FilterBackend
41-
import Cardano.Wallet.API.Response.Sort.IxSet as SortBackend
42-
import Cardano.Wallet.API.V1.Errors (WalletError (JSONValidationFailed))
45+
import qualified Data.Char as Char
46+
import qualified Data.Text.Buildable
47+
import qualified Serokell.Aeson.Options as Serokell
48+
4349

4450
-- | Extra information associated with an HTTP response.
4551
data Metadata = Metadata
@@ -136,7 +142,7 @@ respondWith :: (Monad m, Indexable' a)
136142
-> m (WalletResponse [a])
137143
respondWith RequestParams{..} fops sorts generator = do
138144
(theData, paginationMetadata) <- paginate rpPaginationParams . sortData sorts . applyFilters fops <$> generator
139-
return $ WalletResponse {
145+
return WalletResponse {
140146
wrData = theData
141147
, wrStatus = SuccessStatus
142148
, wrMeta = Metadata paginationMetadata
@@ -174,18 +180,38 @@ data ValidJSON deriving Typeable
174180

175181
instance FromJSON a => MimeUnrender ValidJSON a where
176182
mimeUnrender _ bs = case eitherDecode bs of
177-
Left err -> Left $ decodeUtf8 $ encodePretty (jsonValidationFailed err)
183+
Left err -> Left $ decodeUtf8 $ encodePretty (JSONValidationFailed $ toText err)
178184
Right v -> return v
179-
where
180-
-- NOTE Cheating a bit with type params here, ideally, we would like
181-
-- types we can render ToJSON, though we only use the JSONValidationFailed
182-
-- which doesn't rely on the type-parameters.
183-
jsonValidationFailed :: String -> WalletError () () ()
184-
jsonValidationFailed =
185-
JSONValidationFailed . toText
186185

187186
instance Accept ValidJSON where
188187
contentType _ = contentType (Proxy @ JSON)
189188

190189
instance ToJSON a => MimeRender ValidJSON a where
191190
mimeRender _ = mimeRender (Proxy @ JSON)
191+
192+
193+
--
194+
-- Error from parsing / validating JSON inputs
195+
--
196+
197+
newtype JSONValidationError
198+
= JSONValidationFailed Text
199+
deriving (Show, Eq)
200+
201+
deriveGeneric ''JSONValidationError
202+
203+
instance ToJSON JSONValidationError where
204+
toJSON = gtoJsend ErrorStatus
205+
206+
instance FromJSON JSONValidationError where
207+
parseJSON = gparseJsend
208+
209+
instance Buildable JSONValidationError where
210+
build = \case
211+
JSONValidationFailed _ ->
212+
bprint "Couldn't decode a JSON input."
213+
214+
instance ToServantError JSONValidationError where
215+
declareServantError = \case
216+
JSONValidationFailed _ ->
217+
err400
+18-193
Original file line numberDiff line numberDiff line change
@@ -1,202 +1,27 @@
1-
{-# LANGUAGE DeriveGeneric #-}
2-
{-# LANGUAGE LambdaCase #-}
3-
{-# LANGUAGE OverloadedStrings #-}
4-
{-# LANGUAGE TemplateHaskell #-}
5-
61
module Cardano.Wallet.API.V1.Errors where
72

83
import Universum
94

10-
import Data.Aeson
11-
import Generics.SOP.TH (deriveGeneric)
12-
import Servant
13-
import Test.QuickCheck (Arbitrary (arbitrary))
14-
import Test.QuickCheck.Gen (oneof)
15-
16-
import Cardano.Wallet.API.Response.JSend (ResponseStatus (ErrorStatus))
17-
import Cardano.Wallet.API.V1.Generic (gparseJsend, gtoJsend)
185
import Cardano.Wallet.API.V1.Headers (applicationJson)
6+
import Data.Aeson (ToJSON, encode)
7+
import Formatting (build, sformat)
8+
import Servant (ServantErr (..))
199

2010
import qualified Network.HTTP.Types as HTTP
2111

2212

23-
--
24-
-- Error handling
25-
--
26-
27-
-- | Type representing any error which might be thrown by wallet.
28-
--
29-
-- Errors are represented in JSON in the JSend format (<https://labs.omniti.com/labs/jsend>):
30-
-- ```
31-
-- {
32-
-- "status": "error"
33-
-- "message" : <constr_name>,
34-
-- "diagnostic" : <data>
35-
-- }
36-
-- ```
37-
-- where `<constr_name>` is a string containing name of error's constructor (e. g. `NotEnoughMoney`),
38-
-- and `<data>` is an object containing additional error data.
39-
-- Additional data contains constructor fields, field names are record field names without
40-
-- a `we` prefix, e. g. for `OutputIsRedeem` error "diagnostic" field will be the following:
41-
-- ```
42-
-- {
43-
-- "address" : <address>
44-
-- }
45-
-- ```
46-
--
47-
-- Additional data in constructor should be represented as record fields.
48-
-- Otherwise TemplateHaskell will raise an error.
49-
--
50-
-- If constructor does not have additional data (like in case of `WalletNotFound` error),
51-
-- then "diagnostic" field will be empty object.
52-
--
53-
-- TODO: change fields' types to actual Cardano core types, like `Coin` and `Address`
54-
data WalletError address syncProgress syncPercentage =
55-
NotEnoughMoney { weNeedMore :: !Int }
56-
| OutputIsRedeem { weAddress :: !address }
57-
| MigrationFailed { weDescription :: !Text }
58-
| JSONValidationFailed { weValidationError :: !Text }
59-
| UnknownError { weMsg :: !Text }
60-
| InvalidAddressFormat { weMsg :: !Text }
61-
| WalletNotFound
62-
-- FIXME(akegalj): https://iohk.myjetbrains.com/youtrack/issue/CSL-2496
63-
| WalletAlreadyExists
64-
| AddressNotFound
65-
| TxFailedToStabilize
66-
| TxRedemptionDepleted
67-
| TxSafeSignerNotFound { weAddress :: address }
68-
| MissingRequiredParams { requiredParams :: NonEmpty (Text, Text) }
69-
| WalletIsNotReadyToProcessPayments { weStillRestoring :: syncProgress }
70-
-- ^ The @Wallet@ where a @Payment@ is being originated is not fully
71-
-- synced (its 'WalletSyncState' indicates it's either syncing or
72-
-- restoring) and thus cannot accept new @Payment@ requests.
73-
| NodeIsStillSyncing { wenssStillSyncing :: syncPercentage }
74-
-- ^ The backend couldn't process the incoming request as the underlying
75-
-- node is still syncing with the blockchain.
76-
deriving (Show, Eq)
77-
78-
79-
--
80-
-- Instances for `WalletError`
81-
82-
-- deriveWalletErrorJSON ''WalletError
83-
deriveGeneric ''WalletError
84-
85-
instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON (WalletError a b c) where
86-
toJSON = gtoJsend ErrorStatus
87-
88-
instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON (WalletError a b c) where
89-
parseJSON = gparseJsend
90-
91-
instance (Typeable a, Show a, Typeable b, Show b, Typeable c, Show c) =>
92-
Exception (WalletError a b c)
93-
94-
instance (Arbitrary a, Arbitrary b, Arbitrary c) => Arbitrary (WalletError a b c) where
95-
arbitrary = oneof
96-
[ NotEnoughMoney <$> arbitrary
97-
, OutputIsRedeem <$> arbitrary
98-
, pure (MigrationFailed "Migration failed.")
99-
, pure (JSONValidationFailed "Expected String, found Null.")
100-
, pure (UnknownError "Unknown error.")
101-
, pure (InvalidAddressFormat "Invalid Base58 representation.")
102-
, pure WalletNotFound
103-
, pure WalletAlreadyExists
104-
, pure AddressNotFound
105-
, pure TxFailedToStabilize
106-
, pure TxRedemptionDepleted
107-
, TxSafeSignerNotFound <$> arbitrary
108-
, pure (MissingRequiredParams (("wallet_id", "walletId") :| []))
109-
, WalletIsNotReadyToProcessPayments <$> arbitrary
110-
, NodeIsStillSyncing <$> arbitrary
111-
]
112-
113-
114-
--
115-
-- Helpers
116-
--
117-
118-
-- | Give a short description of an error
119-
describe :: forall a b c. WalletError a b c -> String
120-
describe = \case
121-
NotEnoughMoney _ ->
122-
"Not enough available coins to proceed."
123-
OutputIsRedeem _ ->
124-
"One of the TX outputs is a redemption address."
125-
MigrationFailed _ ->
126-
"Error while migrating a legacy type into the current version."
127-
JSONValidationFailed _ ->
128-
"Couldn't decode a JSON input."
129-
UnknownError _ ->
130-
"Unexpected internal error."
131-
InvalidAddressFormat _ ->
132-
"Provided address format is not valid."
133-
WalletNotFound ->
134-
"Reference to an unexisting wallet was given."
135-
WalletAlreadyExists ->
136-
"Can't create or restore a wallet. The wallet already exists."
137-
AddressNotFound ->
138-
"Reference to an unexisting address was given."
139-
MissingRequiredParams _ ->
140-
"Missing required parameters in the request payload."
141-
WalletIsNotReadyToProcessPayments _ ->
142-
"This wallet is restoring, and it cannot send new transactions until restoration completes."
143-
NodeIsStillSyncing _ ->
144-
"The node is still syncing with the blockchain, and cannot process the request yet."
145-
TxRedemptionDepleted ->
146-
"The redemption address was already used."
147-
TxSafeSignerNotFound _ ->
148-
"The safe signer at the specified address was not found."
149-
TxFailedToStabilize ->
150-
"We were unable to find a set of inputs to satisfy this transaction."
151-
152-
153-
-- | Convert wallet errors to Servant errors
154-
toServantError
155-
:: forall a b c. (ToJSON a, ToJSON b, ToJSON c)
156-
=> WalletError a b c
157-
-> ServantErr
158-
toServantError err =
159-
mkServantErr $ case err of
160-
NotEnoughMoney{} ->
161-
err403
162-
OutputIsRedeem{} ->
163-
err403
164-
MigrationFailed{} ->
165-
err422
166-
JSONValidationFailed{} ->
167-
err400
168-
UnknownError{} ->
169-
err500
170-
WalletNotFound{} ->
171-
err404
172-
WalletAlreadyExists{} ->
173-
err403
174-
InvalidAddressFormat{} ->
175-
err401
176-
AddressNotFound{} ->
177-
err404
178-
MissingRequiredParams{} ->
179-
err400
180-
WalletIsNotReadyToProcessPayments{} ->
181-
err403
182-
NodeIsStillSyncing{} ->
183-
err412 -- Precondition failed
184-
TxFailedToStabilize{} ->
185-
err500
186-
TxRedemptionDepleted{} ->
187-
err400
188-
TxSafeSignerNotFound{} ->
189-
err400
190-
where
191-
mkServantErr serr@ServantErr{..} = serr
192-
{ errBody = encode err
193-
, errHeaders = applicationJson : errHeaders
194-
}
195-
196-
-- |
197-
toHttpStatus
198-
:: forall a b c. (ToJSON a, ToJSON b, ToJSON c)
199-
=> WalletError a b c
200-
-> HTTP.Status
201-
toHttpStatus err = HTTP.Status (errHTTPCode $ toServantError err)
202-
(encodeUtf8 $ describe err)
13+
class (ToJSON e) => ToServantError e where
14+
declareServantError :: e -> ServantErr
15+
toServantError :: e -> ServantErr
16+
toServantError err =
17+
mkServantErr (declareServantError err)
18+
where
19+
mkServantErr serr@ServantErr{..} = serr
20+
{ errBody = encode err
21+
, errHeaders = applicationJson : errHeaders
22+
}
23+
24+
class (ToServantError e, Buildable e) => ToHttpErrorStatus e where
25+
toHttpErrorStatus :: e -> HTTP.Status
26+
toHttpErrorStatus err =
27+
HTTP.Status (errHTTPCode $ toServantError err) (encodeUtf8 $ sformat build err)

0 commit comments

Comments
 (0)