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

Commit 2813d97

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 88651af commit 2813d97

File tree

14 files changed

+381
-359
lines changed

14 files changed

+381
-359
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

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

66
import Universum hiding (log)
77

8-
import Cardano.Wallet.API.V1.Errors hiding (describe)
98
import Cardano.Wallet.Client.Http
9+
import Control.Concurrent (threadDelay)
1010
import Control.Lens
11-
import qualified Pos.Core as Core
1211
import Test.Hspec
13-
14-
import Control.Concurrent (threadDelay)
1512
import Text.Show.Pretty (ppShow)
13+
14+
import qualified Pos.Core as Core
15+
1616
import Util
1717

1818
{-# ANN module ("HLint: ignore Reduce duplication" :: Text) #-}

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

+50-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,40 @@ 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 Exception JSONValidationError
210+
211+
instance Buildable JSONValidationError where
212+
build = \case
213+
JSONValidationFailed _ ->
214+
bprint "Couldn't decode a JSON input."
215+
216+
instance ToServantError JSONValidationError where
217+
declareServantError = \case
218+
JSONValidationFailed _ ->
219+
err400
+18-196
Original file line numberDiff line numberDiff line change
@@ -1,205 +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)
19-
import Cardano.Wallet.API.V1.Types (SyncPercentage, SyncProgress (..), V1 (..), WalletId,
20-
exampleWalletId, mkEstimatedCompletionTime,
21-
mkSyncPercentage, mkSyncThroughput)
6+
import Data.Aeson (ToJSON, encode)
7+
import Formatting (build, sformat)
8+
import Servant (ServantErr (..))
229

2310
import qualified Network.HTTP.Types as HTTP
2411

2512

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