Commit Graph
6 Commits
Author SHA1 Message Date
NachoSoto 6d1cf97a41 ErrorResponse: drastically improved error messages, no more "unknown error"s (#2660)
## Description:

We've gotten several reports with error messages that aren't very
helpful, like #2390.

Turns out that whenever the API returned a non-successful response but
without an error code, we always produced _"unknown error"_.
This improves the behavior to default to showing the status code. One
example that was very obvious is setting up the SDK with an invalid API
key.

## Examples:

### `OfferingsManager.Error`:

- Before:
```
[Purchases] - ERROR: 🍎‼️ Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 0.)
Underlying error: The operation couldn’t be completed. (RevenueCat.NetworkError error 6.)
```

- After:
```
[Purchases] - ERROR: 🍎‼️ Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 0.)
Underlying error: Unknown error. API request failed with status code 401
```

### `BackendError`:
- Before:
```
[Purchases] - ERROR: 😿‼️ Unknown error.
```

- After:
```
[Purchases] - ERROR: 😿‼️ Unknown error. API request failed with status code 401
```

### Request debug logs:
- Before:
```
[Purchases] - DEBUG: ℹ️ API request failed: GET /v1/subscribers/$RCAnonymousID:988f9b85609b4a57af255750bec1fbb2/offerings: Unknown error.
```
- After:
```
[Purchases] - DEBUG: ℹ️ API request failed: GET /v1/subscribers/$RCAnonymousID:988f9b85609b4a57af255750bec1fbb2/offerings: Unknown error. API request failed with status code 401
```

### `CustomerInfoManager`:
- Before:
```
[Purchases] - WARN: ⚠️ Attempt to update CustomerInfo from network failed.
The operation couldn’t be completed. (RevenueCat.BackendError error 0.)
Underlying error: Unknown error
```

- After:
```
[Purchases] - WARN: ⚠️ Attempt to update CustomerInfo from network failed.
The operation couldn’t be completed. (RevenueCat.BackendError error 0.)
Underlying error: Unknown error. API request failed with status code 401
```
2023-06-16 09:19:16 -07:00
NachoSoto d4ea390970 Introduced type-safe PurchasesError errors and fixed some incorrect returned error types (#1879)
This is the last change from what was started with #1437. That PR introduced a few private error types, but for the errors returned in the public `APIs` we were still passing around `Error`s everywhere, which didn't provide any compile-time guarantees.

### Goals:
- Ensure that public APIs *only* return `ErrorCode`s wrapped with the additional `userInfo`, and not accidentally return other types like `SKError`, `BackendError`, `ErrorCode` directly, etc.
- Guarantee type-safety when passing errors around within our internal implementations instead of assuming certain thrown errors are of a particular type.

### Changes:
- Created `PurchasesError` for wrapping an `ErrorCode` with the additional `userInfo`.
- Changed returned errors from `Error` to `NSError`. This is a change in the API, but the new type is a more specific `Error` so it shouldn't lead to any changes in client apps.
- Because `PurchasesError` isn't implicitly convertible to `NSError`, this ensures that we don't return those values directly, and instead use `PurchasesError.asPublicError`.
- Changed `ErrorCodeConvertible` to a now more precise `PurchasesErrorConvertible`.

Thanks to this new type-safety this PR fixes at least 3 bugs where we were returning private errors instead of `ErrorCode`s.

### Testing:

Thanks to #1871 we know that the returned errors are still convertible to `ErrorCode` so users can `switch` over them.
2022-09-05 13:46:07 -07:00
NachoSoto 56246ae101 ErrorResponse: don't add attribute errors to message if empty (#1844)
Follow up to #1776. That PR added a lot of tests to cover error logging, and #1778 introduced a small error that wasn't covered: if attribute errors are empty, the `localizedDescription` could end up becoming `"[:]"`. This fixes that.

## Examples:

This was the underlying error returned (the output of `error.localizedDescription`):

#### Before:
> [:]
#### After:
> There was a credentials issue. Check the underlying error for more details.

And this is what was logged (notice that I also fixed `ErrorCode.invalidCredentialsError` to not log as an "Apple Error"):

#### Before:
> 🍎‼️There was a credentials issue. Check the underlying error for more details. [:]
#### After:
> 😿‼️ There was a credentials issue. Check the underlying error for more details.
2022-08-17 11:26:10 -07:00
NachoSoto c987f10266 Created TestCase superclass to allow re-generating snapshots (#1559)
## Motivation:

There is no way to run code statically like in Obj-C using `+ [NSObject initialize]`, so this ensures (through a new `SwiftLint` rule) that all test classes inherit this common test class instead of `XCTestCase` directly.
A future PR will make use of this new `CIRCLECI_TESTS_GENERATE_SNAPSHOTS` environment variable to allow regenerating snapshots in CI.

I've also moved the duplicated `CurrentTestCaseTracker` usages to a single point.
2022-05-10 12:40:40 -07:00
NachoSoto ea7230de38 NetworkError.errorResponse: added status code to userInfo (#1480)
This could help debug these errors.

Thanks to the error refactor this only had to be put in one place.
2022-04-12 15:14:15 -07:00
NachoSoto 23f63c5ef7 HTTPClient now handles response deserialization + Introduced NetworkError and BackendError (#1425)
Closes #695 and finishes [CF-195]. 
Depends on ~~#1431, #1432, #1433, #1437~~.

## Goals:

- [x] Handle all requests / response / deserialization errors in `HTTPClient`. Clients of `HTTPClient` will only have to handle the "happy" path: #1431
- [x] `HTTPClient` shouldn't return `Result.success` with failed responses, forcing clients to verify the response is actually a failure: https://github.com/RevenueCat/purchases-ios/pull/1431
- [x] Abstract error response deserialization / error creation: #1427
- [x] Abstract attribute error parsing: #1427 
- [x] Move response deserialization to `HTTPClient` based on a `ResponseType: Decodable` type, so the completion block will simply return a `Result<HTTPResponse<ResponseType>, Error>`
- [x] `ETagManager` should store response `Data` instead of a deserialized `[String: Any]`
- [x] Improved error types in `HTTPClient` to fix tests: #1437
- [x] Dealt with non-backwards compatible `ETagManager: #1438

## Changes:
- Replaced `HTTPResponse`'s body from `[String: Any]` to a generic `HTTPResponseBody`.
- Created `HTTPResponseBody` to abstract `Decodable` and provide some default implementations for types like `Data,` `[String: Any]` (for backwards compatibility to types that aren't `Decodable` yet), and `Decodable` itself.
- New `HTTPResponse.Result` typealias (`Result<HTTPResponse<HTTPResponseBody>, Error>`) used everywhere. This will allow replacing `Error` with a more specific `Error` so we can forward known typed errors, and make sure that we don't end up with the wrong error type, or with a very complex error hierarchy and the details buried in `underlyingError`.
- Each layer (only a few for now) has its own error type: `NetworkError`, `BackendError`, `OfferingsManager.Error`.
- `HTTPClient` for example has to produce `NetworkError`, operations produce `BackendError`
- The parent `BackendError` can have specific errors like `.missingAppUserID`, but also be simply a child error `case networkError(NetworkError)`
- All of these conform to a `ErrorCodeConvertible`, so there is a single point of code that converts from simple and readable errors (like `BackendError.emptySubscriberAttributes`, `.unexpectedBackendResponse(.loginResponseDecoding)`) into errors with all the context using `ErrorUtils`
- Converted `DNSError` into `NetworkError.dnsError`. Its functionality remains unchanged.
- Removed `Backend.RCSuccessfullySyncedKey` and `ErrorDetails.finishableKey` in favor of tested properties on `NetworkError`

### Leftovers

There's a few things I've decided to not finish for now:
- [ ] `CustomerInfo` still does't conform to `Decodable` (manual deserialization is still supported by the current system): #1496
- `SubscriberAttributeDict` could also be `Codable`
- [x] Replace `OfferingsFactory` with `Decodable`: #1435

[CF-195]: https://revenuecats.atlassian.net/browse/CF-195?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-04-12 15:11:22 -07:00