From 28ae697a27107bc35ac9c93c20a169704869b61e Mon Sep 17 00:00:00 2001 From: Olivier Bellone Date: Fri, 22 Feb 2019 00:12:18 +0100 Subject: [PATCH] Enforce that all properties have a Json attribute --- .../Entities/EphemeralKeys/EphemeralKey.cs | 18 +++--- src/Stripe.net/Entities/Orders/Order.cs | 1 + src/Stripe.net/Entities/Orders/OrderReturn.cs | 1 + src/Stripe.net/Entities/Topups/Topup.cs | 1 + src/Stripe.net/Entities/_base/StripeEntity.cs | 1 + .../Customers/CustomerCreateOptions.cs | 2 + .../UpcomingInvoiceListLineItemsOptions.cs | 3 + .../Invoices/UpcomingInvoiceOptions.cs | 3 + .../Services/Plans/PlanTierOptions.cs | 3 +- .../SubscriptionSharedOptions.cs | 2 + .../SubscriptionUpdateOptions.cs | 3 +- .../Wholesome/PropertiesHaveJsonAttributes.cs | 62 +++++++++++++++++++ .../Wholesome/UseListsInsteadOfArrays.cs | 3 +- src/StripeTests/Wholesome/WholesomeTest.cs | 10 +-- 14 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 src/StripeTests/Wholesome/PropertiesHaveJsonAttributes.cs diff --git a/src/Stripe.net/Entities/EphemeralKeys/EphemeralKey.cs b/src/Stripe.net/Entities/EphemeralKeys/EphemeralKey.cs index a798052311..d7d6c5a30e 100644 --- a/src/Stripe.net/Entities/EphemeralKeys/EphemeralKey.cs +++ b/src/Stripe.net/Entities/EphemeralKeys/EphemeralKey.cs @@ -16,15 +16,6 @@ public class EphemeralKey : StripeEntity, IHasId, IHasObject [JsonProperty("associated_objects")] public List AssociatedObjects { get; set; } - // RawJson is the raw JSON data of the response that was used to initialize this - // ephemeral key. When working with mobile clients that might only understand - // one version of the API you should prefer to send this value back to them so - // that they'll be able to decode an object that's current according to their version. - public string RawJson - { - get { return this.StripeResponse?.Content; } - } - [JsonProperty("created")] [JsonConverter(typeof(DateTimeConverter))] public DateTime Created { get; set; } @@ -41,5 +32,14 @@ public string RawJson [JsonProperty("livemode")] public bool Livemode { get; set; } + + /// + /// is the raw JSON data of the response that was used to initialize + /// this ephemeral key. When working with mobile clients that might only understand + /// one version of the API you should prefer to send this value back to them so + /// that they'll be able to decode an object that's current according to their version. + /// + [JsonIgnore] + public string RawJson => this.StripeResponse?.Content; } } diff --git a/src/Stripe.net/Entities/Orders/Order.cs b/src/Stripe.net/Entities/Orders/Order.cs index f96e59bf1f..2c6113cd15 100644 --- a/src/Stripe.net/Entities/Orders/Order.cs +++ b/src/Stripe.net/Entities/Orders/Order.cs @@ -66,6 +66,7 @@ public class Order : StripeEntity, IHasId, IHasMetadata, IHasObject /// /// The customer used for the order. /// + [JsonIgnore] public string CustomerId { get; set; } [JsonIgnore] diff --git a/src/Stripe.net/Entities/Orders/OrderReturn.cs b/src/Stripe.net/Entities/Orders/OrderReturn.cs index a2b162190e..7e6e309082 100644 --- a/src/Stripe.net/Entities/Orders/OrderReturn.cs +++ b/src/Stripe.net/Entities/Orders/OrderReturn.cs @@ -61,6 +61,7 @@ public class OrderReturn : StripeEntity, IHasId, IHasObject /// The ID of the refund issued for this return. /// Expandable /// + [JsonIgnore] public string RefundId { get; set; } [JsonIgnore] diff --git a/src/Stripe.net/Entities/Topups/Topup.cs b/src/Stripe.net/Entities/Topups/Topup.cs index d6862e7c01..df6625187f 100644 --- a/src/Stripe.net/Entities/Topups/Topup.cs +++ b/src/Stripe.net/Entities/Topups/Topup.cs @@ -24,6 +24,7 @@ public class Topup : StripeEntity, IHasId, IHasMetadata, IHasObject, IBal /// /// ID of the balance transaction that describes the impact of this Top-up on your account balance (not including refunds or disputes). /// + [JsonIgnore] public string BalanceTransactionId { get; set; } [JsonIgnore] diff --git a/src/Stripe.net/Entities/_base/StripeEntity.cs b/src/Stripe.net/Entities/_base/StripeEntity.cs index ab80067180..a1e400ee9d 100644 --- a/src/Stripe.net/Entities/_base/StripeEntity.cs +++ b/src/Stripe.net/Entities/_base/StripeEntity.cs @@ -5,6 +5,7 @@ namespace Stripe using System.Runtime.CompilerServices; using Newtonsoft.Json; + [JsonObject(MemberSerialization.OptIn)] public abstract class StripeEntity : IStripeEntity { [JsonIgnore] diff --git a/src/Stripe.net/Services/Customers/CustomerCreateOptions.cs b/src/Stripe.net/Services/Customers/CustomerCreateOptions.cs index dd7c187fb0..8209324f59 100644 --- a/src/Stripe.net/Services/Customers/CustomerCreateOptions.cs +++ b/src/Stripe.net/Services/Customers/CustomerCreateOptions.cs @@ -51,8 +51,10 @@ public class CustomerCreateOptions : BaseOptions #region Trial End + [JsonIgnore] public DateTime? TrialEnd { get; set; } + [JsonIgnore] public bool EndTrialNow { get; set; } [JsonProperty("trial_end")] diff --git a/src/Stripe.net/Services/Invoices/UpcomingInvoiceListLineItemsOptions.cs b/src/Stripe.net/Services/Invoices/UpcomingInvoiceListLineItemsOptions.cs index 3c93137ad1..7f04c1ce9f 100644 --- a/src/Stripe.net/Services/Invoices/UpcomingInvoiceListLineItemsOptions.cs +++ b/src/Stripe.net/Services/Invoices/UpcomingInvoiceListLineItemsOptions.cs @@ -38,10 +38,13 @@ public class UpcomingInvoiceListLineItemsOptions : ListOptions /// month or year intervals, the day of the month for subsequent invoices.For /// existing subscriptions, the value can only be set to now or unchanged. /// + [JsonIgnore] public DateTime? SubscriptionBillingCycleAnchor { get; set; } + [JsonIgnore] public bool SubscriptionBillingCycleAnchorNow { get; set; } + [JsonIgnore] public bool SubscriptionBillingCycleAnchorUnchanged { get; set; } [JsonProperty("subscription_billing_cycle_anchor")] diff --git a/src/Stripe.net/Services/Invoices/UpcomingInvoiceOptions.cs b/src/Stripe.net/Services/Invoices/UpcomingInvoiceOptions.cs index 6f8c20901e..0f8a4edb90 100644 --- a/src/Stripe.net/Services/Invoices/UpcomingInvoiceOptions.cs +++ b/src/Stripe.net/Services/Invoices/UpcomingInvoiceOptions.cs @@ -38,10 +38,13 @@ public class UpcomingInvoiceOptions : BaseOptions /// month or year intervals, the day of the month for subsequent invoices.For /// existing subscriptions, the value can only be set to now or unchanged. /// + [JsonIgnore] public DateTime? SubscriptionBillingCycleAnchor { get; set; } + [JsonIgnore] public bool SubscriptionBillingCycleAnchorNow { get; set; } + [JsonIgnore] public bool SubscriptionBillingCycleAnchorUnchanged { get; set; } [JsonProperty("subscription_billing_cycle_anchor")] diff --git a/src/Stripe.net/Services/Plans/PlanTierOptions.cs b/src/Stripe.net/Services/Plans/PlanTierOptions.cs index c8a2c21828..63c01f7598 100644 --- a/src/Stripe.net/Services/Plans/PlanTierOptions.cs +++ b/src/Stripe.net/Services/Plans/PlanTierOptions.cs @@ -1,7 +1,5 @@ namespace Stripe { - using System; - using System.Reflection; using Newtonsoft.Json; public class PlanTierOptions : INestedOptions @@ -13,6 +11,7 @@ public class PlanTierOptions : INestedOptions public long? UnitAmount { get; set; } #region UpTo + [JsonIgnore] public UpToOption UpTo { get; set; } [JsonProperty("up_to")] diff --git a/src/Stripe.net/Services/Subscriptions/SubscriptionSharedOptions.cs b/src/Stripe.net/Services/Subscriptions/SubscriptionSharedOptions.cs index f91715c19c..a76aa1bfb4 100644 --- a/src/Stripe.net/Services/Subscriptions/SubscriptionSharedOptions.cs +++ b/src/Stripe.net/Services/Subscriptions/SubscriptionSharedOptions.cs @@ -76,8 +76,10 @@ public abstract class SubscriptionSharedOptions : BaseOptions /// /// Date representing the end of the trial period the customer will get before being charged for the first time. Set to true to end the customer’s trial immediately. /// + [JsonIgnore] public DateTime? TrialEnd { get; set; } + [JsonIgnore] public bool EndTrialNow { get; set; } [JsonProperty("trial_end")] diff --git a/src/Stripe.net/Services/Subscriptions/SubscriptionUpdateOptions.cs b/src/Stripe.net/Services/Subscriptions/SubscriptionUpdateOptions.cs index e86e2dd6e1..25a46abe08 100644 --- a/src/Stripe.net/Services/Subscriptions/SubscriptionUpdateOptions.cs +++ b/src/Stripe.net/Services/Subscriptions/SubscriptionUpdateOptions.cs @@ -3,13 +3,14 @@ namespace Stripe using System; using System.Collections.Generic; using Newtonsoft.Json; - using Stripe.Infrastructure; public class SubscriptionUpdateOptions : SubscriptionSharedOptions { #region BillingCycleAnchor + [JsonIgnore] public bool BillingCycleAnchorNow { get; set; } + [JsonIgnore] public bool BillingCycleAnchorUnchanged { get; set; } [JsonProperty("billing_cycle_anchor")] diff --git a/src/StripeTests/Wholesome/PropertiesHaveJsonAttributes.cs b/src/StripeTests/Wholesome/PropertiesHaveJsonAttributes.cs new file mode 100644 index 0000000000..b1dd6842ba --- /dev/null +++ b/src/StripeTests/Wholesome/PropertiesHaveJsonAttributes.cs @@ -0,0 +1,62 @@ +#if NETCOREAPP +namespace StripeTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Newtonsoft.Json; + using Stripe; + using Xunit; + + /// + /// This wholesome test ensures that all properties in Stripe entities and options classes are + /// annotated with either or + /// . + /// + public class PropertiesHaveJsonAttributes : WholesomeTest + { + private const string AssertionMessage = + "Found at least one property lacking a Json*Attribute. Please add either a " + + "[JsonProperty(\"name\")] or a [JsonIgnore] attribute."; + + [Fact] + public void Check() + { + List results = new List(); + + // Get all classes that derive from StripeEntity or implement INestedOptions + var stripeClasses = GetSubclassesOf(typeof(StripeEntity)); + stripeClasses.AddRange(GetClassesWithInterface(typeof(INestedOptions))); + + foreach (var stripeClass in stripeClasses) + { + foreach (var property in stripeClass.GetProperties()) + { + var hasJsonAttribute = false; + + foreach (var attribute in property.GetCustomAttributes()) + { + if (attribute.GetType() == typeof(JsonPropertyAttribute) + || attribute.GetType() == typeof(JsonIgnoreAttribute) + || attribute.GetType() == typeof(JsonExtensionDataAttribute)) + { + hasJsonAttribute = true; + break; + } + } + + if (hasJsonAttribute) + { + continue; + } + + results.Add($"{stripeClass.Name}.{property.Name}"); + } + } + + AssertEmpty(results, AssertionMessage); + } + } +} +#endif diff --git a/src/StripeTests/Wholesome/UseListsInsteadOfArrays.cs b/src/StripeTests/Wholesome/UseListsInsteadOfArrays.cs index 485acfc2d6..1975a20534 100644 --- a/src/StripeTests/Wholesome/UseListsInsteadOfArrays.cs +++ b/src/StripeTests/Wholesome/UseListsInsteadOfArrays.cs @@ -3,7 +3,6 @@ namespace StripeTests { using System; using System.Collections.Generic; - using System.Linq; using System.Reflection; using Newtonsoft.Json; using Stripe; @@ -25,7 +24,7 @@ public void Check() // Get all classes that derive from StripeEntity or implement INestedOptions var stripeClasses = GetSubclassesOf(typeof(StripeEntity)); - stripeClasses.Concat(GetClassesWithInterface(typeof(INestedOptions))); + stripeClasses.AddRange(GetClassesWithInterface(typeof(INestedOptions))); foreach (Type stripeClass in stripeClasses) { diff --git a/src/StripeTests/Wholesome/WholesomeTest.cs b/src/StripeTests/Wholesome/WholesomeTest.cs index 7dcd5e8d98..44b148a981 100644 --- a/src/StripeTests/Wholesome/WholesomeTest.cs +++ b/src/StripeTests/Wholesome/WholesomeTest.cs @@ -40,23 +40,25 @@ protected static void AssertEmpty(List results, string message) /// /// Returns the list of classes that are subclasses of the provided type. /// - protected static IEnumerable GetSubclassesOf(Type parentClass) + protected static List GetSubclassesOf(Type parentClass) { var assembly = parentClass.GetTypeInfo().Assembly; return assembly.DefinedTypes .Where(t => t.IsClass && t.IsSubclassOf(parentClass)) - .Select(t => t.AsType()); + .Select(t => t.AsType()) + .ToList(); } /// /// Returns the list of classes that implement the provided interface. /// - protected static IEnumerable GetClassesWithInterface(Type implementedInterface) + protected static List GetClassesWithInterface(Type implementedInterface) { var assembly = implementedInterface.GetTypeInfo().Assembly; return assembly.DefinedTypes .Where(t => t.IsClass && t.ImplementedInterfaces.Contains(implementedInterface)) - .Select(t => t.AsType()); + .Select(t => t.AsType()) + .ToList(); } } }