Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Machine-readable shared wallet API errors. #3693

Merged
merged 10 commits into from
Jan 17, 2023

Conversation

jonathanknowles
Copy link
Member

@jonathanknowles jonathanknowles commented Jan 17, 2023

Issue Number

Follow-on from review of #3676.

Summary

This PR removes an interpolated string equality check from our API integration test suite, and replaces it an equality check that uses decodeErrorInfo in conjunction with an API error record:

lib/wallet/integration/src/Test/Integration/Scenario/API/Shared/Wallets.hs
 
-     expectErrorMessage (errMsg403NoSuchCosigner (toText Payment) 7) rPatch5
+     decodeErrorInfo rPatch5 `shouldBe` SharedWalletNoSuchCosigner
+         ApiErrorSharedWalletNoSuchCosigner
+             { cosignerIndex = ApiCosignerIndex 7
+             , credentialType = ApiCredentialType Payment
+             }
lib/wallet/integration/src/Test/Integration/Framework/TestData.hs
 
- errMsg403NoSuchCosigner :: Text -> Int -> String
- errMsg403NoSuchCosigner cred cosigner = unwords
-     [ "It looks like you've tried to add a cosigner key to a"
-     , "shared wallet's"
-     , unpack cred
-     , "template for a non-existing cosigner index:"
-     , show cosigner
-     ]

The above definition of errMsg403NoCosigner (now removed) was essentially a duplicate of an identical interpolated string in Http.Server.Error:

https://github.com/input-output-hk/cardano-wallet/blob/1263e6ed8c486f31ed75219b59a8e5fc6af577de/lib/wallet/api/http/Cardano/Wallet/Api/Http/Server/Error.hs#L801-L807

Advantages

  1. If we make any breaking changes to the API error type, the compiler will warn us to update any areas of the test suite that use this type.
  2. Someone browsing the integration tests can easily jump to the definition of the error type, or to the definitions of any of the constructors used.
  3. We no longer need to manually keep two copies of an interpolated string function in sync with one another.

Method

To achieve this, we introduce the following structured error type:

data ApiErrorSharedWalletNoSuchCosigner = ApiErrorSharedWalletNoSuchCosigner
    { cosignerIndex
        :: !ApiCosignerIndex
    , credentialType
        :: !ApiCredentialType
    }
    deriving (Data, Eq, Generic, Show, Typeable)
    deriving (FromJSON, ToJSON)
        via DefaultRecord ApiErrorSharedWalletNoSuchCosigner
    deriving anyclass NFData

And add it as a parameter to the relevant constructor of ApiErrorInfo:

lib/wallet/api/http/Cardano/Wallet/Api/Types/Error.hs

  data ApiErrorInfo
      = ...
      | ...
-     | SharedWalletNoSuchCosigner
+     | SharedWalletNoSuchCosigner !ApiErrorSharedWalletNoSuchCosigner
      | ...

When errors of this type are encoded as JSON, this results in objects of the following format:

{ "code": "shared_wallet_no_such_cosigner"
, "info":
  { "cosigner_index": 182
  , "credential_type": "delegation"
  }
, "message": "It looks like you've..."
}

Future Work

This PR serves as a demonstration of this technique, and only converts one particular error type. We could use this method to remove all duplicate interpolated error messages from our API integration test suite.

@jonathanknowles jonathanknowles force-pushed the jonathanknowles/shared-wallet-error-types branch from 71aaf99 to a72e41f Compare January 17, 2023 07:47
@jonathanknowles jonathanknowles changed the title Add detail to SharedWalletNoSuchCosigner API error. Add JSON info record to SharedWalletNoSuchCosigner API error. Jan 17, 2023
@jonathanknowles jonathanknowles changed the title Add JSON info record to SharedWalletNoSuchCosigner API error. Make SharedWalletNoSuchCosigner API error machine readable. Jan 17, 2023
@jonathanknowles jonathanknowles changed the title Make SharedWalletNoSuchCosigner API error machine readable. Make API error SharedWalletNoSuchCosigner machine readable. Jan 17, 2023
@jonathanknowles jonathanknowles changed the title Make API error SharedWalletNoSuchCosigner machine readable. Machine-readable SharedWalletNoSuchCosigner API error. Jan 17, 2023
@jonathanknowles jonathanknowles force-pushed the jonathanknowles/shared-wallet-error-types branch from 97a692a to 77961b2 Compare January 17, 2023 08:05
Strictly speaking, this is completely unrelated to the purpose of this
PR, but since this PR will touch the `ApiErrorInfo` type, it makes sense
to fix this while we're here.
Though the `QuickCheck` library provides a handy `arbitraryBoundedEnum`
function, it currently doesn't provide a `shrinkBoundedEnum`
counterpart. Users have asked for a function like this here:

nick8325/quickcheck#343

This commit adds our own version, for now. Perhaps, later on, we can
submit a PR to the `QuickCheck` repository to add this function, and
delete our own version.
Later on, this type will be used within the `ApiErrorInfo` type, so it
has to provide an instance of `Data`.

Since the underlying `CredentialType` type does not provide an instance
of `Data`, we add that too.
This commit parameterises the `SharedWalletNoSuchCosigner` constructor
with a value of type `ApiErrorSharedWalletNoSuchCosigner`.

In addition, we update the OpenAPI specification to be consistent with
the new definition.

This makes it easy for clients (such as our API integration tests) to
inspect the following values programmatically:

    - cosigner_index
    - credential_type
…sts.

This commit replaces a string equality check (using an interpolated
string) with an equality check on a concrete error type.
This interpolated string is a duplicate of a similar interpolated string
in `Http.Server.Error`.
This is not strictly necessary, since we already have a golden file for
`ApiErrorSharedWalletNoSuchCosigner.json`, but it allows us to
demonstrate the format of a complete error object:

```json
{ "code": "shared_wallet_no_such_cosigner"
, "info":
  { "cosigner_index": 182
  , "credential_type": "delegation"
  }
, "message": "It looks like you've..."
}
```
@jonathanknowles jonathanknowles force-pushed the jonathanknowles/shared-wallet-error-types branch from 77961b2 to d1c0f19 Compare January 17, 2023 08:11
@jonathanknowles jonathanknowles changed the title Machine-readable SharedWalletNoSuchCosigner API error. Machine-readable shared wallet API errors. Jan 17, 2023
@jonathanknowles jonathanknowles changed the title Machine-readable shared wallet API errors. Machine-readable shared wallet API errors. Jan 17, 2023
Copy link
Contributor

@piotr-iohk piotr-iohk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jonathanknowles jonathanknowles self-assigned this Jan 17, 2023
Copy link
Contributor

@paweljakubas paweljakubas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beautiful change!

@jonathanknowles
Copy link
Member Author

bors r+

iohk-bors bot added a commit that referenced this pull request Jan 17, 2023
3693: Machine-readable shared wallet API errors. r=jonathanknowles a=jonathanknowles

## Issue Number

Follow-on from review of #3676.

## Summary

This PR removes an **interpolated string equality check** from our API integration test suite, and replaces it an equality check that uses **`decodeErrorInfo`** in conjunction with an **API error record**:

```patch
lib/wallet/integration/src/Test/Integration/Scenario/API/Shared/Wallets.hs
 
-     expectErrorMessage (errMsg403NoSuchCosigner (toText Payment) 7) rPatch5
+     decodeErrorInfo rPatch5 `shouldBe` SharedWalletNoSuchCosigner
+         ApiErrorSharedWalletNoSuchCosigner
+             { cosignerIndex = ApiCosignerIndex 7
+             , credentialType = ApiCredentialType Payment
+             }
```
```patch
lib/wallet/integration/src/Test/Integration/Framework/TestData.hs
 
- errMsg403NoSuchCosigner :: Text -> Int -> String
- errMsg403NoSuchCosigner cred cosigner = unwords
-     [ "It looks like you've tried to add a cosigner key to a"
-     , "shared wallet's"
-     , unpack cred
-     , "template for a non-existing cosigner index:"
-     , show cosigner
-     ]
```
The above definition of `errMsg403NoCosigner` (now removed) was essentially a **duplicate** of an identical interpolated string in `Http.Server.Error`:

https://github.com/input-output-hk/cardano-wallet/blob/1263e6ed8c486f31ed75219b59a8e5fc6af577de/lib/wallet/api/http/Cardano/Wallet/Api/Http/Server/Error.hs#L801-L807

## Advantages

1. If we make any breaking changes to the API error type, the compiler will warn us to update the test suite.
2. Someone browsing the integration tests can easily jump to the definition of the error type, or to the definitions of any of the constructors used.
3. We no longer need to manually keep two copies of an interpolated string function in sync with one another.

## Method

To achieve this, we introduce the following structured error type:
```haskell
data ApiErrorSharedWalletNoSuchCosigner = ApiErrorSharedWalletNoSuchCosigner
    { cosignerIndex
        :: !ApiCosignerIndex
    , credentialType
        :: !ApiCredentialType
    }
    deriving (Data, Eq, Generic, Show, Typeable)
    deriving (FromJSON, ToJSON)
        via DefaultRecord ApiErrorSharedWalletNoSuchCosigner
    deriving anyclass NFData
```
And add it as a parameter to the relevant constructor of ApiErrorInfo:
```patch
lib/wallet/api/http/Cardano/Wallet/Api/Types/Error.hs

  data ApiErrorInfo
      = ...
      | ...
-     | SharedWalletNoSuchCosigner
+     | SharedWalletNoSuchCosigner !ApiErrorSharedWalletNoSuchCosigner
      | ...
```
When errors of this type are encoded as JSON, this results in objects of the following format:
```json
{ "code": "shared_wallet_no_such_cosigner"
, "info":
  { "cosigner_index": 182
  , "credential_type": "delegation"
  }
, "message": "It looks like you've..."
}
```

## Future Work

This PR serves as a demonstration of this technique, and only converts one particular error type. We could use this method to remove all duplicate interpolated error messages from our API integration test suite.

Co-authored-by: Jonathan Knowles <jonathan.knowles@iohk.io>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jan 17, 2023

Build failed:

Errors (not caused by this PR):

cardano-wallet-test-integration>   integration/src/Test/Integration/Scenario/API/Shelley/Migrations.hs:526:22:
cardano-wallet-test-integration>   1) API Specifications, SHELLEY_MIGRATIONS, SHELLEY_CREATE_MIGRATION_PLAN_08 - Can create a partial migration plan for a wallet with a large number of freerider UTxO entries, but with not quite enough non-freerider entries to enable the entire UTxO set to be migrated.
cardano-wallet-test-integration>        While verifying value:
cardano-wallet-test-integration>          ( Status
cardano-wallet-test-integration>              { statusCode = 200
cardano-wallet-test-integration>              , statusMessage = "OK"
cardano-wallet-test-integration>              }
cardano-wallet-test-integration>          , Right
cardano-wallet-test-integration>              ( ApiWallet
cardano-wallet-test-integration>                  { id = ApiT
cardano-wallet-test-integration>                      ( WalletId
cardano-wallet-test-integration>                          { getWalletId = 00260361ae3b275e15a053b60822c225e85e945f }
cardano-wallet-test-integration>                      )
cardano-wallet-test-integration>                  , addressPoolGap = ApiT
cardano-wallet-test-integration>                      ( AddressPoolGap
cardano-wallet-test-integration>                          { getAddressPoolGap = 20 }
cardano-wallet-test-integration>                      )
cardano-wallet-test-integration>                  , balance = ApiWalletBalance
cardano-wallet-test-integration>                      { available = Quantity 246250000
cardano-wallet-test-integration>                      , total = Quantity 246250000
cardano-wallet-test-integration>                      , reward = Quantity 0
cardano-wallet-test-integration>                      }
cardano-wallet-test-integration>                  , assets = ApiWalletAssetsBalance
cardano-wallet-test-integration>                      { available = ApiT
cardano-wallet-test-integration>                          ( TokenMap
cardano-wallet-test-integration>                              ( fromList
cardano-wallet-test-integration>                                  [
cardano-wallet-test-integration>                                      ( UnsafeTokenPolicyId
cardano-wallet-test-integration>                                          ( Hash "OðIX\K0pV9f7\xfT'Ô¢óX\x8bÎAFÕz\x93ÇÓ" )
cardano-wallet-test-integration>                                      , NonEmptyMap
cardano-wallet-test-integration>                                          { least =
cardano-wallet-test-integration>                                              ( UnsafeTokenName "00000000000000000SeaHorse1"
cardano-wallet-test-integration>                                              , TokenQuantity 5
cardano-wallet-test-integration>                                              )
cardano-wallet-test-integration>                                          , rest = fromList
cardano-wallet-test-integration>                                              [
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse10"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse11"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse12"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse13"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse14"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse15"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse16"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse17"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse18"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse19"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse2"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse20"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse3"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse4"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse5"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse6"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse7"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse8"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse9"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ]
cardano-wallet-test-integration>                                          }
cardano-wallet-test-integration>                                      )
cardano-wallet-test-integration>                                  ]
cardano-wallet-test-integration>                              )
cardano-wallet-test-integration>                          )
cardano-wallet-test-integration>                      , total = ApiT
cardano-wallet-test-integration>                          ( TokenMap
cardano-wallet-test-integration>                              ( fromList
cardano-wallet-test-integration>                                  [
cardano-wallet-test-integration>                                      ( UnsafeTokenPolicyId
cardano-wallet-test-integration>                                          ( Hash "OðIX\K0pV9f7\xfT'Ô¢óX\x8bÎAFÕz\x93ÇÓ" )
cardano-wallet-test-integration>                                      , NonEmptyMap
cardano-wallet-test-integration>                                          { least =
cardano-wallet-test-integration>                                              ( UnsafeTokenName "00000000000000000SeaHorse1"
cardano-wallet-test-integration>                                              , TokenQuantity 5
cardano-wallet-test-integration>                                              )
cardano-wallet-test-integration>                                          , rest = fromList
cardano-wallet-test-integration>                                              [
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse10"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse11"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse12"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse13"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse14"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse15"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse16"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse17"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse18"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse19"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse2"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse20"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse3"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse4"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse5"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse6"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse7"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse8"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ,
cardano-wallet-test-integration>                                                  ( UnsafeTokenName "00000000000000000SeaHorse9"
cardano-wallet-test-integration>                                                  , TokenQuantity 5
cardano-wallet-test-integration>                                                  )
cardano-wallet-test-integration>                                              ]
cardano-wallet-test-integration>                                          }
cardano-wallet-test-integration>                                      )
cardano-wallet-test-integration>                                  ]
cardano-wallet-test-integration>                              )
cardano-wallet-test-integration>                          )
cardano-wallet-test-integration>                      }
cardano-wallet-test-integration>                  , delegation = ApiWalletDelegation
cardano-wallet-test-integration>                      { active = ApiWalletDelegationNext
cardano-wallet-test-integration>                          { status = NotDelegating
cardano-wallet-test-integration>                          , target = Nothing
cardano-wallet-test-integration>                          , changesAt = Nothing
cardano-wallet-test-integration>                          }
cardano-wallet-test-integration>                      , next = []
cardano-wallet-test-integration>                      }
cardano-wallet-test-integration>                  , name = ApiT
cardano-wallet-test-integration>                      ( WalletName
cardano-wallet-test-integration>                          { getWalletName = "Empty Wallet" }
cardano-wallet-test-integration>                      )
cardano-wallet-test-integration>                  , passphrase = Just
cardano-wallet-test-integration>                      ( ApiWalletPassphraseInfo 2023-01-17 09:57:15.635407741 UTC )
cardano-wallet-test-integration>                  , state = ApiT Ready
cardano-wallet-test-integration>                  , tip = ApiBlockReference
cardano-wallet-test-integration>                      { absoluteSlotNumber = ApiT
cardano-wallet-test-integration>                          ( SlotNo 1139 )
cardano-wallet-test-integration>                      , slotId = ApiSlotId
cardano-wallet-test-integration>                          { epochNumber = ApiT
cardano-wallet-test-integration>                              ( EpochNo
cardano-wallet-test-integration>                                  { unEpochNo = 7 }
cardano-wallet-test-integration>                              )
cardano-wallet-test-integration>                          , slotNumber = ApiT
cardano-wallet-test-integration>                              ( SlotInEpoch
cardano-wallet-test-integration>                                  { unSlotInEpoch = 19 }
cardano-wallet-test-integration>                              )
cardano-wallet-test-integration>                          }
cardano-wallet-test-integration>                      , time = 2023-01-17 09:57:37.8 UTC
cardano-wallet-test-integration>                      , block = ApiBlockInfo
cardano-wallet-test-integration>                          ( Quantity 447 )
cardano-wallet-test-integration>                      }
cardano-wallet-test-integration>                  }
cardano-wallet-test-integration>              )
cardano-wallet-test-integration>          )
cardano-wallet-test-integration>        expected: 275500000
cardano-wallet-test-integration>         but got: 246250000
cardano-wallet-test-integration>   To rerun use: --match "/API Specifications/SHELLEY_MIGRATIONS/SHELLEY_CREATE_MIGRATION_PLAN_08 - Can create a partial migration plan for a wallet with a large number of freerider UTxO entries, but with not quite enough non-freerider entries to enable the entire UTxO set to be migrated./"
cardano-wallet-test-integration>   integration/src/Test/Integration/Scenario/API/Shelley/TransactionsNew.hs:2201:65:
cardano-wallet-test-integration>   2) API Specifications, NEW_SHELLEY_TRANSACTIONS, Plutus scenarios, withdrawal
cardano-wallet-test-integration>        uncaught exception: ProcessHasExited
cardano-wallet-test-integration>        ProcessHasExited "cardano-cli failed: Command failed: transaction submit  Error: Error while submitting tx: ShelleyTxValidationError ShelleyBasedEraAlonzo (ApplyTxError [DelegsFailure (DelplFailure (DelegFailure (StakeKeyAlreadyRegisteredDELEG (ScriptHashObj (ScriptHash \"1b8374d0b294a4a3399ff25f06a0707b16214f1e9b67cf89ba2454a6\")))))])\n" (ExitFailure 1)
cardano-wallet-test-integration>   To rerun use: --match "/API Specifications/NEW_SHELLEY_TRANSACTIONS/Plutus scenarios/withdrawal/"
cardano-wallet-test-integration>   integration/src/Test/Integration/Framework/DSL.hs:2555:7:
cardano-wallet-test-integration>   3) API Specifications, NEW_SHELLEY_TRANSACTIONS, TRANS_NEW_CREATE_10e - Minting assets with timelocks successful as validity interval is inside time interval of a script
cardano-wallet-test-integration>        expected a successful response but got an error: ClientError (Object (fromList [("code",String "created_invalid_transaction"),("message",String "The submitted transaction was rejected by the local node. Here's an error message that may help with debugging: HardForkApplyTxErrFromEra S (S (S (S (Z (WrapApplyTxErr {unwrapApplyTxErr = ApplyTxError [UtxowFailure (WrappedShelleyEraFailure (UtxoFailure (OutsideValidityIntervalUTxO (ValidityInterval {invalidBefore = SJust (SlotNo 0), invalidHereafter = SJust (SlotNo 4179)}) (SlotNo 4179))))]})))))")]))
cardano-wallet-test-integration>        While verifying value:
cardano-wallet-test-integration>          ( Status
cardano-wallet-test-integration>              { statusCode = 500
cardano-wallet-test-integration>              , statusMessage = "Internal Server Error"
cardano-wallet-test-integration>              }
cardano-wallet-test-integration>          , Left
cardano-wallet-test-integration>              ( ClientError
cardano-wallet-test-integration>                  ( Object
cardano-wallet-test-integration>                      ( fromList
cardano-wallet-test-integration>                          [
cardano-wallet-test-integration>                              ( "code"
cardano-wallet-test-integration>                              , String "created_invalid_transaction"
cardano-wallet-test-integration>                              )
cardano-wallet-test-integration>                          ,
cardano-wallet-test-integration>                              ( "message"
cardano-wallet-test-integration>                              , String "The submitted transaction was rejected by the local node. Here's an error message that may help with debugging: HardForkApplyTxErrFromEra S (S (S (S (Z (WrapApplyTxErr {unwrapApplyTxErr = ApplyTxError [UtxowFailure (WrappedShelleyEraFailure (UtxoFailure (OutsideValidityIntervalUTxO (ValidityInterval {invalidBefore = SJust (SlotNo 0), invalidHereafter = SJust (SlotNo 4179)}) (SlotNo 4179))))]})))))"
cardano-wallet-test-integration>                              )
cardano-wallet-test-integration>                          ]
cardano-wallet-test-integration>                      )
cardano-wallet-test-integration>                  )
cardano-wallet-test-integration>              )
cardano-wallet-test-integration>          )
cardano-wallet-test-integration>   To rerun use: --match "/API Specifications/NEW_SHELLEY_TRANSACTIONS/TRANS_NEW_CREATE_10e - Minting assets with timelocks successful as validity interval is inside time interval of a script/"

@jonathanknowles
Copy link
Member Author

bors r+

toEnum <$> filter (>= minBoundInt) (shrinkIntegral $ fromEnum a)
where
minBoundInt :: Int
minBoundInt = fromEnum (minBound @a)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later on I turned this into a PR on QuickCheck itself:
nick8325/quickcheck#350

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jan 17, 2023

Build succeeded:

@iohk-bors iohk-bors bot merged commit 76fdaf9 into master Jan 17, 2023
@iohk-bors iohk-bors bot deleted the jonathanknowles/shared-wallet-error-types branch January 17, 2023 11:58
WilliamKingNoel-Bot pushed a commit that referenced this pull request Jan 17, 2023
…jonathanknowles a=jonathanknowles ## Issue Number Follow-on from review of #3676.
 
 ## Summary
 
 This PR removes an **interpolated string equality check** from our API integration test suite, and replaces it an equality check that uses **`decodeErrorInfo`** in conjunction with an **API error record**:
 
 ```patch
 lib/wallet/integration/src/Test/Integration/Scenario/API/Shared/Wallets.hs
 
 - expectErrorMessage (errMsg403NoSuchCosigner (toText Payment) 7) rPatch5
 + decodeErrorInfo rPatch5 `shouldBe` SharedWalletNoSuchCosigner
 + ApiErrorSharedWalletNoSuchCosigner
 + { cosignerIndex = ApiCosignerIndex 7
 + , credentialType = ApiCredentialType Payment
 + }
 ```
 ```patch
 lib/wallet/integration/src/Test/Integration/Framework/TestData.hs
 
 - errMsg403NoSuchCosigner :: Text -> Int -> String
 - errMsg403NoSuchCosigner cred cosigner = unwords
 - [ "It looks like you've tried to add a cosigner key to a"
 - , "shared wallet's"
 - , unpack cred
 - , "template for a non-existing cosigner index:"
 - , show cosigner
 - ]
 ```
 The above definition of `errMsg403NoCosigner` (now removed) was essentially a **duplicate** of an identical interpolated string in `Http.Server.Error`:
 
 https://github.com/input-output-hk/cardano-wallet/blob/1263e6ed8c486f31ed75219b59a8e5fc6af577de/lib/wallet/api/http/Cardano/Wallet/Api/Http/Server/Error.hs#L801-L807
 
 ## Advantages
 
 1. If we make any breaking changes to the API error type, the compiler will warn us to update the test suite.
 2. Someone browsing the integration tests can easily jump to the definition of the error type, or to the definitions of any of the constructors used.
 3. We no longer need to manually keep two copies of an interpolated string function in sync with one another.
 
 ## Method
 
 To achieve this, we introduce the following structured error type:
 ```haskell
 data ApiErrorSharedWalletNoSuchCosigner = ApiErrorSharedWalletNoSuchCosigner
 { cosignerIndex
 :: !ApiCosignerIndex
 , credentialType
 :: !ApiCredentialType
 }
 deriving (Data, Eq, Generic, Show, Typeable)
 deriving (FromJSON, ToJSON)
 via DefaultRecord ApiErrorSharedWalletNoSuchCosigner
 deriving anyclass NFData
 ```
 And add it as a parameter to the relevant constructor of ApiErrorInfo:
 ```patch
 lib/wallet/api/http/Cardano/Wallet/Api/Types/Error.hs
 
 data ApiErrorInfo
 = ...
 | ...
 - | SharedWalletNoSuchCosigner
 + | SharedWalletNoSuchCosigner !ApiErrorSharedWalletNoSuchCosigner
 | ...
 ```
 When errors of this type are encoded as JSON, this results in objects of the following format:
 ```json
 { "code": "shared_wallet_no_such_cosigner"
 , "info":
 { "cosigner_index": 182
 , "credential_type": "delegation"
 }
 , "message": "It looks like you've..."
 }
 ```
 
 ## Future Work
 
 This PR serves as a demonstration of this technique, and only converts one particular error type. We could use this method to remove all duplicate interpolated error messages from our API integration test suite. Co-authored-by: Jonathan Knowles <jonathan.knowles@iohk.io> Source commit: 76fdaf9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants