* Small cleanup
* add `LocalTransactionDetailsStorage`
* Add models for local transaction metadata caching. Reset PurchasesOrchestrator
* Complete `Codable`, lossless implementation of `LocalTransactionMetadata`
* Rename `LocalTransactionMetadataCache` to `LocalTransactionMetadataStore`
* Comment
* lint
* Remove unnecessary import
* Remove unused method
* Replace `Storefront` with `storeCountry` in both `ProductRequestData` and `PurchasedTransactionData`
This removes the need of a `StorefrontEncodedWrapper` object
* Disambiguate `asDictionary()` method
Now that `SubscriberAttribute` is `Codable`, `Encodable`'s `asDictionary()` and `SubscriberAttribute`'s `asDictionary()` were clashing
* Undo `Codable` conformance of `PresentedOfferingContext` and `TargetingContext`
* lint
* `LocalTransactionMetadata` now refers to a single transaction. `LocalTransactionMetadataStore` now stores one file per transaction and does not use an in-memory cache
* Temporarily remove `LocalTransactionMetadata` and `LocalTransactionMetadataStore` for easier PR review
* Undo `Codable` conformance of `EncodedAppleReceipt`
Not needed for now
This is a simple example, but on a larger JSON it becomes easier for us
to debug decoding the entire JSON rather than seeing only the part that
failed:
```
[codable] DEBUG: ℹ️ Encountered error when decoding JSON: {"e": "e3"}
[codable] ERROR: 😿‼️ Couldn't decode data from json, it was corrupted: Context(codingPath: [CodingKeys(stringValue: "e", intValue: nil)], debugDescription: "Cannot initialize E from invalid String value e3", underlyingError: nil)
```
See also #1658.
This updates the behavior of `CustomerInfoResponse` decoding so that having invalid content inside of one of the structures fails more loudly instead of getting ignored.
For that, `@DefaultValue` no longer ignores errors, and instead `@IgnoreDecodeErrors` must be used.
For some of the values in `CustomerInfoResponse` this makes sense (like `Store`), but not for things like subscriptions where we would be losing data.
The method doesn't actually throw, so calling `throw decoder.throwValueNotFoundError()` was confusing. Now the code reads `throw decoder.valueNotFoundError()`.
This allows having properties in an `Encodable` type that won't be encoded.
This will be used to storing the `rawData` when decoding our types (see #1496).
A future PR will make use of these: #1540.
The purpose of this is to be able to define `Decodable` types with default values without needing to implement the whole `init(from decoder: Decoder)` from scratch. If any one property has a default value, that's the only way to provide it without these.
For example, `EntitlementInfo.ProductData` implements this:
```swift
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.isSandbox = try container.decodeIfPresent(Bool.self, forKey: .isSandbox) ?? false
self.originalPurchaseDate = try container.decodeIfPresent(Date.self, forKey: .originalPurchaseDate)
self.expiresDate = try container.decodeIfPresent(Date.self, forKey: .expiresDate)
self.unsubscribeDetectedAt = try container.decodeIfPresent(Date.self, forKey: .unsubscribeDetectedAt)
self.billingIssuesDetectedAt = try container.decodeIfPresent(Date.self, forKey: .billingIssuesDetectedAt)
self.periodType = container.decode(PeriodType.self, forKey: .periodType, defaultValue: .normal)
self.store = container.decode(Store.self, forKey: .store, defaultValue: .unknownStore)
self.ownershipType = container.decode(PurchaseOwnershipType.self,
forKey: .ownershipType,
defaultValue: .purchased)
}
```
With these new types, that whole implementation is unnecessary and can be data-driven instead, which is more maintanable and less error-prone:
```swift
struct ProductData: Decodable {
@DefaultValue<PeriodType> var periodType: PeriodType
var originalPurchaseDate: Date?
var expiresDate: Date?
@DefaultValue<Store> var store: Store
@DefaultDecodable.False var isSandbox: Bool
var unsubscribeDetectedAt: Date?
var billingIssuesDetectedAt: Date?
@DefaultValue<PurchaseOwnershipType> var ownershipType: PurchaseOwnershipType
}
```