-
Notifications
You must be signed in to change notification settings - Fork 572
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
AnyOf<>
generic class to handle polymorphic parameters
#1495
Conversation
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.
Wow, this is so cool!! I had no idea you could do this from user space, haha.
I'm slightly neutral on the change. I love the technical acumen, but I think the old Created
/ CreatedRange
style usage is quite a bit easier to understand. However, one you understand the trick to AnyOf
fields, I could see how they'd be pretty handy/fast. I'll leave the decision up to you!
One major inconvenience is that this only works for two types. In order to work with three types, we'd need to define another
AnyOf<T1, T2, T3>
overload, etc. Hopefully we don't have too many parameters that accept many different types!
That part seems okay to me. Some of the built-in C# classes even do that like Tuple
for example.
This looks really cool!
I'm on the opposite side where I think it's not good because the ones we (or at least I) wanted to actively clean up are the ones that are not as obvious and take multiple inputs like |
0a4247c
to
aa1ce55
Compare
Okay, I've updated the PR to make it production-ready:
@remi-stripe wdyt? Should we merge this? |
aa1ce55
to
bfb3da3
Compare
So the fact that it works is fairly magnificent, like really. Because we went from "Can we stop having to copy paste all of this and be inconsistent with naming" to "oh wow". So kudos here. It also makes the code (as a user) look so much cleaner, when I look at the test using Created and it just magically handles a string or a hash. Makes me re-excited that we even did this in the API in the first place. Now there are 2 things that "bother me". Mostly because they are clear unknowns for me:
I did not fully grasp that one. How does it work for External Account where it takes a string or one hash or one other hash? Same for Separately, Stripe as a whole is moving away (or trying to) from |
I think both the type's name and the XML comments do a pretty good job of making it clear that different types can be used. Here's what it looks like in VS Code (with the C# extension) when you hover over the And here's what it looks like in Visual Studio for Mac when you hover over the Presumably other C# IDEs can also display XML documentation in tooltips.
I think it's very unlikely we'd break anyone here, because right now options classes that use the same With this PR, serialization will work as users expect. The only potential issue happens if you try to deserialize an I think this is acceptable for now (though I'll add a test to showcase this specific edge-case and document it). Few users provide raw card details, and with any luck the intersection of such users with those who will serialize then deserialize options class will be empty. That said, if you disagree, here are some alternatives for handling this edge case:
|
AnyOf<>
generic class to handle polymorphic parameters
@ob-stripe Thanks for the screenshots, this feels perfect (though I never use IDEs so hard to say if it really is!) My main FUD is not on serialization but on deserialization. We have seen developers build their own JSON version of the options because it's easier to reason with for them and then want to pass it in the API. Here's an example in stripe-go where @brandur-stripe explained why we did not want this: stripe/stripe-go#654 I am fully in favour of the change, I think it makes it a lot cleaner and easier to understand than our "magic" around naming fields in a way that tries to make sense of it. |
assigning to @brandur-stripe for remaining thoughts. |
Thanks @remi-stripe! LGTM if OB wants to go forward with it. |
Thank you both!
Sure, my point was that it's unlikely anybody is deserializing options classes with polymorphic parameters right now, seeing as it's impossible to serialize them in the first place ;)
Thanks for sharing this! Brandur raises a very valid point about adding code that is not used by the library itself -- that's exactly the case of I think the main difference between stripe-go and stripe-dotnet on this particular issue is that stripe-dotnet is already using JSON annotations in options classes, as this was how Jayme originally designed them. Options classes without any polymorphic parameter (i.e. the vast majority) already support JSON serialization/deserialization. In light of this, I think it's fair to consider #648 as a bug rather than a feature request. Seeing as there are no objections, I will pull this in after giving it a little more polish (going to add some more comments and tests). |
20c520c
to
e051ba3
Compare
e051ba3
to
1c092b9
Compare
17121c3
to
82dfb26
Compare
1c092b9
to
411f9a5
Compare
29a4624
to
4a03db9
Compare
ab3a4aa
to
30eab20
Compare
8069bd3
to
7f5f789
Compare
30eab20
to
695c97a
Compare
5a3595f
to
130a982
Compare
7679b81
to
0c6ada6
Compare
There was an issue with the initial implementation. In order to decide which value to return in This works fine for reference types where the default value is I fixed this by explicitly storing which of the container values was set in the constructor, in an additional attribute This makes it possible to use @brandur-stripe @remi-stripe Please take a look at the second commit and let me know what you think. If you're fine with the approach I'll update the PR to cover all remaining polymorphic parameters of the form "timestamp or some magic values". Also cc @rattrayalex-stripe since we were chatting about this the other day and you seemed interested :) |
7ca274b
to
8cab5ae
Compare
Damn, I'm glad you caught this issue upfront. I'm curious if we should add some explicit tests to confirm this behaviour (the int 0 value) is not broken by mistake in the future. |
Nice catch Olivier. +1 Remi that some tests would be nice if possible. The fix's implementation looks pretty clean to me. |
You're a wizard, ob! ✨🚀 |
8cab5ae
to
7627deb
Compare
bf3d79f
to
aadfab1
Compare
7627deb
to
edb6cdd
Compare
aadfab1
to
6431266
Compare
edb6cdd
to
96d1340
Compare
6431266
to
10128f2
Compare
96d1340
to
598e93c
Compare
AnyOf<>
generic class to handle polymorphic parametersAnyOf<>
generic class to handle polymorphic parameters
* Better serialization * Remove unnecessary uses of Mapper * Standardize signature of OAuthTokenService.Deauthorize * New `FromJson` method * Modernize StripeConfiguration * Simplify Service request methods * Replace Parameter custom class with KeyValuePair<string, string> * Rewrite expandable field handling * Move base URLs out of resource services where possible * Refactor Client class * Minor fixes * Remove `Mapper` class * Simplify handling of Expand and ExtraParams * Minor improvements in EventUtility * More request encoding refactoring * Introduce new Request class to represent requests to Stripe's API * Revamp HTTP client requestor * Fixes to FileService and OAuthTokenService * Remove BaseOptionsExtensions * Remove ServiceExtensions, allow per-service clients * Add support for telemetry * Make `parent` on `OrderItem` expandable * Automatic request retries * Add missing attributes to StripeError * Remove parameters that are internal only today on PaymentIntent * Various minor cleanups * API key validation * Check validity of JSON in OK responses * Enforce that all properties have a Json attribute * Improved OAuth support (#1542) * Rename DuplicateChargeDocumentation to be more consistent with FileId (#1563) * AnyOf<> generic class to handle polymorphic parameters (#1495) * Add support for file_link_data (#1598) * Add support for passing application information (#1596) * Rename StripeConnectAcconutId to StripeAccount (#1603) * Update README (#1602) * Add wholesome test to check JSON names (#1609) * Remove System.Collections.Immutable dependency (#1615) * Raise ArgumentException on null or empty IDs (#1616) * Move default values for SystemNetHttpClient (#1623) * Remove StripeConfiguration.EnableTelemetry flag (#1622) * Refactor StripeClient setup in tests (#1631) * Set base URLs in StripeClient instead of StripeConfiguration (#1632) * Add support for setting API key and client ID in StripeClient (#1633) * Use StripeClient instance in tests (#1634) * Add support for setting MaxNetworkRetries and AppInfo in SystemNetHttpClient (#1635) * Make base URLs in StripeClient readonly (#1640) * Make client in services readonly (#1639) * Add AddRangeExpand method to BaseOptions (#1643) * Add options classes for Get/GetAsync methods (#1644) * Deprecate Expand properties on services (#1646) * Use constants instead of static strings (#1647) * Update README.md (#1648)
Here's a little experiment for better handling of polymorphic request parameters. The
AnyOf<T1, T2>
generic class uses implicit operators to seamlessly accept a value of either type.One major inconvenience is that this only works for two types. In order to work with three types, we'd need to define another
AnyOf<T1, T2, T3>
overload, etc. Hopefully we don't have too many parameters that accept many different types!cc @remi-stripe @brandur-stripe What do you think of this approach?