-
Notifications
You must be signed in to change notification settings - Fork 316
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
Offering
metadata
#2498
Offering
metadata
#2498
Conversation
Tests/BackendIntegrationTests/LoadShedderIntegrationTests.swift
Outdated
Show resolved
Hide resolved
Tests/UnitTests/Networking/Responses/Fixtures/OfferingsWithMetadata.json
Outdated
Show resolved
Hide resolved
6aaac9b
to
a3ef198
Compare
9c1cbfd
to
8006a72
Compare
Codecov Report
@@ Coverage Diff @@
## main #2498 +/- ##
=======================================
Coverage 87.85% 87.85%
=======================================
Files 199 199
Lines 13655 13664 +9
=======================================
+ Hits 11996 12005 +9
Misses 1659 1659
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is ready 👍🏻 Just need to fix those potential crashes in the test, I'll send a PR on Monday with a new method on Collection
so we can do that.
@@ -4,6 +4,9 @@ | |||
{ | |||
"description" : "standard set of packages", | |||
"identifier" : "default", | |||
"metadata" : { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing
@@ -48,10 +49,48 @@ class OfferingsDecodingTests: BaseHTTPResponseTest { | |||
} | |||
|
|||
func testDecodesSecondOffering() throws { | |||
let offering = try XCTUnwrap(self.response.offerings.last) | |||
let offering = try XCTUnwrap(self.response.offerings[1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will make the test crash. I'll help you on Monday, we'll need to define a new method that returns an Optional for an index.
} | ||
|
||
func testDecodesMetadataOffering() throws { | ||
let offering = try XCTUnwrap(self.response.offerings[2]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
expect(offering.metadata) == [:] | ||
expect(offering.packages).to(haveCount(1)) | ||
|
||
let package = offering.packages[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid the test crashing (instead of failing):
let package = offering.packages[0] | |
let package = try XCTUnwrap(offering.packages.onlyElement) |
] | ||
expect(offering.packages).to(haveCount(1)) | ||
|
||
let package = offering.packages[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto:
let package = offering.packages[0] | |
try XCTUnwrap(offering.packages.onlyElement) |
} | ||
|
||
func testDecodesNullMetadataOffering() throws { | ||
let offering = try XCTUnwrap(self.response.offerings[3]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
@NachoSoto I think I fixed all the safety things that could crash the tests! But let me know if you want me to do it a different way or move code around 😇 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is ready :) just one more suggested test.
private extension Collection { | ||
|
||
/// Returns the element at the specified index if it exists, otherwise nil. | ||
subscript (safe index: Index) -> Element? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool this is exactly what I had in mind 👍🏻
I can extract this an add unit tests for it in a separate PR since I think it's useful to have.
expect(offering.metadata) == [:] | ||
expect(offering.packages).to(haveCount(1)) | ||
|
||
let package = try XCTUnwrap(offering.packages[safe: 0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use first
here which is the same:
let package = try XCTUnwrap(offering.packages[safe: 0]) | |
let package = try XCTUnwrap(offering.packages.first) |
@@ -495,6 +495,7 @@ private extension OfferingsManagerTests { | |||
Offering( | |||
identifier: offering.identifier, | |||
serverDescription: offering.description, | |||
metadata: [:], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other tests only check OfferingsResponse
. Let's add some metadata here so you can check in another test in this file that they get copied correctly to Offering
(basically thatoffering.metadata.mapValues(\.asAny)
).
static let anyBackendOfferingsResponse: OfferingsResponse = .init( | ||
currentOfferingId: "base", | ||
offerings: [ | ||
.init(identifier: "base", | ||
description: "This is the base offering", | ||
packages: [ | ||
.init(identifier: "$rc_monthly", platformProductIdentifier: "monthly_freetrial") | ||
]) | ||
], | ||
metadata: _metadata) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit per our style guide:
metadata: _metadata) | |
metadata: Self.metadata) |
@@ -465,14 +466,27 @@ private extension OfferingsManagerTests { | |||
enum MockData { | |||
static let anyAppUserID = "" | |||
|
|||
@DefaultDecodable.EmptyDictionary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this shouldn't be here since this type isn't Decodable
.
@@ -153,6 +153,55 @@ class OfferingsTests: TestCase { | |||
expect(offerings.current) == offerings["offering_a"] | |||
} | |||
|
|||
func testOfferingsWithMetadataIsCreated() throws { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻
Sources/Purchasing/Offering.swift
Outdated
extension Offering { | ||
|
||
struct Metadata { | ||
let data: [String: Any] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, if we want to force developers to always have a default value, should we hide the raw data and provide a method to access values from the metadata that requires users to put a default value? Something like func getValue(key: String, defaultValue: Any)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, or maybe as an additional API 👍🏻
extension Offering {
func getMetadataValue<T>(for key: String, defaultValue: T) -> T {
return self.metadata.data[key] as? T ?? defaultValue
}
}
This would be Swift only, but I think it would be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we might want to have different accessors so people don't need to handle Any
. Things like getStringValue
, getIntValue
, getBoolValue
,... Not sure if we want to support different types though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not very Swifty.
For Obj-C the current API works well.
For Swift I'd go with my suggested generic API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want getMetadataValue()
on Offering
? Or should we make the Metadata
object public with a subscript thing?
Something like this on Metadata
... 🤷♂️
public subscript<T>(key: String, defaultValue defaultValue: T) -> T {
return self.data[key] as? T ?? defaultValue
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Metadata
is a struct
therefore it wouldn't be available to ObjC
, so it needs to be on Offering
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marking this as request changes since I want to look at the final version with the pending changes :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost ready! Just some nit-picks about the API.
Sources/Purchasing/Offering.swift
Outdated
extension Offering { | ||
|
||
/** | ||
Gets a value from `metadata` and falls back to a specified default value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe:
Gets a value from `metadata` and falls back to a specified default value. | |
- Returns: the `metadata` value associated to `key` for the expected type, | |
or `defaultValue` if not found, or it's not the expected type. |
Sources/Purchasing/Offering.swift
Outdated
/** | ||
Gets a value from `metadata` and falls back to a specified default value. | ||
*/ | ||
public func getMetadataValue<T>(for key: String, defaultValue: T) -> T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Being nit-picky since this is public
so we want to get it right from the beginning.
Maybe:
public func getMetadataValue<T>(for key: String, defaultValue: T) -> T { | |
public func getMetadataValue<T>(for key: String, default: T) -> T { |
Swift guidelines require avoiding that alliteration.
let metadataString: String = off.getMetadataValue(for: "", defaultValue: "") | ||
let metadataInt: Int = off.getMetadataValue(for: "", defaultValue: 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you were following the pattern here, but it's leftover from the first implementation which was kind of messy.
We should get rid of all these print
calls.
To avoid increasing the noise, I'd change these to:
let metadataString: String = off.getMetadataValue(for: "", defaultValue: "") | |
let metadataInt: Int = off.getMetadataValue(for: "", defaultValue: 0) | |
let _: String = off.getMetadataValue(for: "", defaultValue: "") | |
let _: Int = off.getMetadataValue(for: "", defaultValue: 0) |
let offeringA = try XCTUnwrap(offerings["offering_a"]) | ||
expect(offeringA.metadata.count) == metadata.count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I'd change this to haveCount
, otherwise the test is great 👍🏻
expect(offeringA.getMetadataValue(for: "int", defaultValue: 0)) == 5 | ||
expect(offeringA.getMetadataValue(for: "double", defaultValue: 0.0)) == 5.5 | ||
expect(offeringA.getMetadataValue(for: "boolean", defaultValue: false)) == true | ||
expect(offeringA.getMetadataValue(for: "string", defaultValue: "")) == "five" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add tests for not finding the key, and also having the wrong type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost ready! Just some nit-picks about the API.
@@ -153,6 +153,59 @@ class OfferingsTests: TestCase { | |||
expect(offerings.current) == offerings["offering_a"] | |||
} | |||
|
|||
func testOfferingsWithMetadataIsCreated() throws { | |||
@DefaultDecodable.EmptyDictionary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also reminder to remove this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is needed though 🤔 I couldn't get it to compile without that 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ship it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
- Returns: the `metadata` value associated to `key` for the expected type, | ||
or `defaultValue` if not found, or it's not the expected type. | ||
*/ | ||
public func getMetadataValue<T>(for key: String, default: T) -> T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this also support null default values? Like let metadataInt: Int? = offering.getMetadataValue(for: "key", default: nil)
. I think that could be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be let me write another test for that! 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, this works!
let optionalInt: Int? = offeringA.getMetadataValue(for: "optionalInt", default: nil)
expect(optionalInt).to(beNil())
Added `metadata` to `Offering` 😊 - Added `metadata` to `Offering` - Had to use a private internal `Metadata` struct to make Swift compiler happy - Updated unit tests - Updated backend integration tests - Updated load shedder tests
Motivation
Added
metadata
toOffering
😊Description
metadata
toOffering
Metadata
struct to make Swift compiler happy