### Motivation:
Initially I wanted to make `SubscriberAttribute.asDictionary` work using `Codable`. However, this isn't the right refactor because we need the actual raw types to be encoded inside `UserDefaults`.
At least this refactor provides several improvements:
### Changes:
- `SubscriberAttribute` now has value semantics
- ⚠️ `SubscriberAttribute.isSynced` used to be mutable, with reference semantics, which was dangerous
- `SubscriberAttribute: Equatable` implementation is now automatic
- Cleaned up `SubscriberAttribute.asDictionary` implementation, extracting keys into an `enum`
- Moved `Dictionary` -> `SubscriberAttribute` creation to the type itself
- ⚠️ `SubscriberAttribute.init(dictionary:)` returns an `Optional` instead of using force-unwrap, avoiding potential crashes
- Added more serialization / deserialization tests
- Removed `SubscriberAttributesMarshaller` and moved method into `SubscriberAttribute` extension
- Moved `SubscriberAttributeDict` into `SubscriberAttribute.Dictionary`
Extract out various dependencies that are required to instantiate the `Backend` object.
Refactor to assist with the next few PRs that extract out other pieces
See [CSDK-82]
Resolves [CSDK-155]
The new response is now represented by `GetIntroEligibilityResponse`, and has dedicated tests (`GetIntroEligibilityDecodingTests`) using a JSON fixture.
The operation becomes much simpler now, plus I've done a lot of code removing that was redundant.
The actual response handling is now 3 lines:
```swift
let result: [String: IntroEligibility] = Set(productIdentifiers)
.dictionaryWithValues { eligibilities?[$0] ?? .unknown }
.mapValues(IntroEligibility.init)
```
All the tests in `BackendGetIntroEligibilityTests` pass with no changes.
## Changes:
- Added `PostOfferResponse`
- Added `PostOfferDecodingTests` tests
- Using the automatic deserialization by `HTTPClient` by simply changing the callback to `HTTPResponse<PostOfferResponse>.Result`
- All tests in `BackendPostOfferForSigningTests` (which are very exhaustive) pass with no changes!
* Resolves [CF-443]
* Adds `configureWithConfiguration` to `RCPurchasesAPI.m`
* Create Configuration object and API to configure Purchases
* Update API doc for new Purchases API
* Add example code to Configuration documentation
* Adds deprecations for various Purchases.configure methods
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
Follow up to #1429. For #695.
This was duplicated in all operations.
`HTTPClient` now only forwards `Result.success` if the request actually succeeded,
leaving each `NetworkOperation` to just parse the body (which will become part of `HTTPClient` too).
Other changes:
- Simplified logic for when to retry requests in `HTTPClient` by using a new `Result.asOptionalResult`
- Removed several response handlers that don't need to do anything anymore
- Removed several tests that were basically duplicated:
- The `finishable` checks are already tested as part of `ErrorResponse`.
- All the checks for the error details are also part of `ErrorResponse`.
- No longer need to have a test for each type of failed response since that's part of `HTTPClient` tests now.
Follow up to #1427.
The new way to create errors is like this:
```swift
ErrorResponse
.from(response)
.asBackendError(with: statusCode)
```
## Other changes:
- Removed `UserInfoAttributeParser`
- Simplified `JSONDecoder.decode` implementation by simply relying on `defaultJsonDecoder`
- Added support for "wrapped" error responses. This was previously handled by `UserInfoAttributeParser`.
- Removed all duplicated code parsing error responses
- The new `ErrorResponse` introduced in #1427 correctly decodes the format sent by the backend. Note that some tests had the wrong format, and `UserInfoAttributeParser` was parsing that wrong as well.
An upcoming PR will actually move these calls to `ErrorResponse` to `HTTPClient`.
This is duplicated in most of the `NetworkOperation`s. This new shared implementation will be used by `HTTPClient`.
For #695.
See #1429 for how this is used.
For #695.
The biggest change is that this code at the beginning of `HTTPClient.handle` is now gone:
```swift
var statusCode: HTTPStatusCode = .networkConnectTimeoutError
var jsonObject: [String: Any]?
var httpResponse: HTTPResponse? = HTTPResponse(statusCode: statusCode, jsonObject: jsonObject)
var receivedJSONError: Error?
```
It was hard to trace which paths modified these default values. The new implementation has a mostly linear path (part of it abstracted in smaller methods now) and doesn’t have mutable values.
This is the new `handle` implementation:
```swift
func handle(urlResponse: URLResponse?,
request: Request,
urlRequest: URLRequest,
data: Data?,
error networkError: Error?) {
let (response, shouldRetry) = self.parse(
urlResponse: urlResponse,
request: request,
urlRequest: urlRequest,
data: data,
error: networkError
)
if shouldRetry {
Logger.debug(Strings.network.retrying_request(httpMethod: request.method.httpMethod,
path: request.path))
self.state.modify {
$0.queuedRequests.insert(request.retriedRequest(), at: 0)
}
} else {
request.completionHandler?(response)
}
self.beginNextRequest()
}
```
## All Changes:
- `HTTPClient.perform` no longer has a bunch of mutable variables with forking paths that make it hard to follow. Most of the response handling is now in a new `parse` method. This will simplify the task of continuing to refactor response deserialization.
- `HTTPClient`’s completion abstracts response into `HTTPResponse`
- `UserInfoAttributeParser` is now an `enum`
- `ETagManager` no longer takes an `Error`. The only thing it did with it is to return the default response.
- `HTTPResponse.jsonObject` is no longer `Optional`
- Moved `defaultJsonEncoder` / `defaultJsonDecoder` to `JSONDecoder+Extensions`. They used by `HTTPClient` and others soon.