From b4ff15dbf0ffbfaa28721f54a6cf5eab3fe3afa5 Mon Sep 17 00:00:00 2001 From: Nate Harris Date: Fri, 15 Nov 2024 16:23:10 -0700 Subject: [PATCH 1/4] - Account for enum comparison during inter-dependency checks on Parameter sets --- .../ParametersTests/ParametersTest.cs | 67 +++++++++++++++++++ .../Attributes/RequestParameterAttribute.cs | 35 ++++++---- EasyPost/Utilities/Internal/Enum.cs | 11 +-- EasyPost/Utilities/Internal/Objects.cs | 20 ++++++ 4 files changed, 114 insertions(+), 19 deletions(-) diff --git a/EasyPost.Tests/ParametersTests/ParametersTest.cs b/EasyPost.Tests/ParametersTests/ParametersTest.cs index 4d66213f2..1be7cf44d 100644 --- a/EasyPost.Tests/ParametersTests/ParametersTest.cs +++ b/EasyPost.Tests/ParametersTests/ParametersTest.cs @@ -6,6 +6,7 @@ using EasyPost.Models.API; using EasyPost.Tests._Utilities; using EasyPost.Tests._Utilities.Attributes; +using EasyPost.Utilities.Internal; using EasyPost.Utilities.Internal.Attributes; using Xunit; @@ -577,6 +578,50 @@ public void TestDependentNestedParameters() } } + [Fact] + [Testing.Custom] + public async Task TestEnumUsageInInterdependentParameterEnforcement() + { + // Should pass because Param1 and Param2 are set correctly + ParameterSetWithInterdependentEnums parameters = new() + { + Param1 = ParameterEnum.Value1, + Param2 = "value2" + }; + + try + { + parameters.ToDictionary(); + } catch (Exceptions.General.InvalidParameterPairError) + { + Assert.Fail("Should not throw exception if both Param1 and Param2 are set correctly."); + } + + // Should fail because Param1 is set to Value1, but Param2 is not set to "value2" + parameters = new ParameterSetWithInterdependentEnums + { + Param1 = ParameterEnum.Value1, + Param2 = "value3" + }; + + Assert.Throws(() => parameters.ToDictionary()); + + // Should pass because Param1 is not set to a value that enforces a restriction on Param2 + parameters = new ParameterSetWithInterdependentEnums + { + Param1 = ParameterEnum.Value2, + Param2 = "value3" + }; + + try + { + parameters.ToDictionary(); + } catch (Exceptions.General.InvalidParameterPairError) + { + Assert.Fail("Should not throw exception if Param1 is not set to a value that enforces a restriction on Param2."); + } + } + /// /// This test proves that we can reuse the Addresses.Create parameter object, /// with its serialization logic adapting to whether it is a top-level parameter object @@ -889,6 +934,28 @@ internal sealed class ParameterSetWithMixedDependentPresenceAndValueBasedTopLeve public string? BParam { get; set; } } + internal sealed class ParameterEnum : ValueEnum + { + public static readonly ParameterEnum Value1 = new(1, "value1"); + + public static readonly ParameterEnum Value2 = new(2, "value2"); + + private ParameterEnum(int id, object value) + : base(id, value) + { + } + } + + internal sealed class ParameterSetWithInterdependentEnums : Parameters.BaseParameters + { + [TopLevelRequestParameter(Necessity.Optional, "param1")] + [TopLevelRequestParameterDependents(IndependentStatus.IfValue, "value1", DependentStatus.MustBeValue, dependentValue: "value2", "Param2")] + public ParameterEnum? Param1 { get; set; } + + [TopLevelRequestParameter(Necessity.Optional, "param2")] + public string? Param2 { get; set; } + } + #pragma warning restore CA1852 // Can be sealed #endregion diff --git a/EasyPost/Utilities/Internal/Attributes/RequestParameterAttribute.cs b/EasyPost/Utilities/Internal/Attributes/RequestParameterAttribute.cs index c4d4d7b40..60a1ee9a6 100644 --- a/EasyPost/Utilities/Internal/Attributes/RequestParameterAttribute.cs +++ b/EasyPost/Utilities/Internal/Attributes/RequestParameterAttribute.cs @@ -150,9 +150,9 @@ protected RequestParameterDependentsAttribute(IndependentStatus independentStatu /// Initializes a new instance of the class. /// /// The set status of the independent property. - /// The value of the independent property. + /// The value of the independent property. If enforcing a custom , provide the underlying value. /// The set status of the dependent properties. - /// The value of the dependent properties. + /// The value of the dependent properties. If enforcing a custom , provide the underlying value. /// The names of the dependent properties. protected RequestParameterDependentsAttribute(IndependentStatus independentStatus, object independentValue, DependentStatus dependentStatus, object dependentValue, params string[] dependentProperties) { @@ -168,7 +168,7 @@ protected RequestParameterDependentsAttribute(IndependentStatus independentStatu /// /// The set status of the independent property. /// The set status of the dependent properties. - /// The value of the dependent properties. + /// The value of the dependent properties. If enforcing a custom , provide the underlying value. /// The names of the dependent properties. protected RequestParameterDependentsAttribute(IndependentStatus independentStatus, DependentStatus dependentStatus, object dependentValue, params string[] dependentProperties) { @@ -182,7 +182,7 @@ protected RequestParameterDependentsAttribute(IndependentStatus independentStatu /// Initializes a new instance of the class. /// /// The set status of the independent property. - /// The value of the independent property. + /// The value of the independent property. If enforcing a custom , provide the underlying value. /// The set status of the dependent properties. /// The names of the dependent properties. protected RequestParameterDependentsAttribute(IndependentStatus independentStatus, object independentValue, DependentStatus dependentStatus, params string[] dependentProperties) @@ -197,7 +197,7 @@ protected RequestParameterDependentsAttribute(IndependentStatus independentStatu /// Check that the expected value state of the property is met. /// /// Optional, the value of the independent property. - /// The value of the dependent property. + /// The value of the dependent property. Do not pass in ; instead, pass in the underlying value. /// True if the dependent property meets the dependency condition, false otherwise. private bool DependencyConditionPasses(object? propertyValue, object? dependentPropertyValue) { @@ -228,10 +228,17 @@ private bool DependencyConditionPasses(object? propertyValue, object? dependentP /// Check that all dependent properties are compliant with the dependency conditions. /// /// The object containing the dependent properties. - /// The value of the independent property. + /// The value of the independent property. A will be converted to its underlying value. /// A tuple containing a boolean indicating whether the dependency is met, and a string containing the name of the first dependent property that does not meet the dependency conditions. public Tuple DependentsAreCompliant(object obj, object? propertyValue) { + // Convert any value enums to their underlying values (this cannot work with non-value Enums, but those can't be passed to attributes, so it is safe to ignore) + if (propertyValue != null && Objects.IsValueEnum(propertyValue)) + { + ValueEnum enumValue = (ValueEnum)propertyValue; + propertyValue = enumValue.Value; + } + // No need to check dependent IfSet properties if the property is not set if (propertyValue == null && IndependentStatus == IndependentStatus.IfSet) { @@ -304,9 +311,9 @@ public TopLevelRequestParameterDependentsAttribute(IndependentStatus independent /// Initializes a new instance of the class. /// /// The set status of the independent property. - /// The value of the independent property. + /// The value of the independent property. If enforcing a custom , provide the underlying value. /// The set status of the dependent properties. - /// The value of the dependent properties. + /// The value of the dependent properties. If enforcing a custom , provide the underlying value. /// The names of the dependent properties. public TopLevelRequestParameterDependentsAttribute(IndependentStatus independentStatus, object independentValue, DependentStatus dependentStatus, object dependentValue, params string[] dependentProperties) : base(independentStatus, independentValue, dependentStatus, dependentValue, dependentProperties) @@ -318,7 +325,7 @@ public TopLevelRequestParameterDependentsAttribute(IndependentStatus independent /// /// The set status of the independent property. /// The set status of the dependent properties. - /// The value of the dependent properties. + /// The value of the dependent properties. If enforcing a custom , provide the underlying value. /// The names of the dependent properties. public TopLevelRequestParameterDependentsAttribute(IndependentStatus independentStatus, DependentStatus dependentStatus, object dependentValue, params string[] dependentProperties) : base(independentStatus, dependentStatus, dependentValue, dependentProperties) @@ -329,7 +336,7 @@ public TopLevelRequestParameterDependentsAttribute(IndependentStatus independent /// Initializes a new instance of the class. /// /// The set status of the independent property. - /// The value of the independent property. + /// The value of the independent property. If enforcing a custom , provide the underlying value. /// The set status of the dependent properties. /// The names of the dependent properties. public TopLevelRequestParameterDependentsAttribute(IndependentStatus independentStatus, object independentValue, DependentStatus dependentStatus, params string[] dependentProperties) @@ -392,9 +399,9 @@ public NestedRequestParameterDependentsAttribute(IndependentStatus independentSt /// Initializes a new instance of the class. /// /// The set status of the independent property. - /// The value of the independent property. + /// The value of the independent property. If enforcing a custom , provide the underlying value. /// The set status of the dependent properties. - /// The value of the dependent properties. + /// The value of the dependent properties. If enforcing a custom , provide the underlying value. /// The names of the dependent properties. public NestedRequestParameterDependentsAttribute(IndependentStatus independentStatus, object independentValue, DependentStatus dependentStatus, object dependentValue, params string[] dependentProperties) : base(independentStatus, independentValue, dependentStatus, dependentValue, dependentProperties) @@ -406,7 +413,7 @@ public NestedRequestParameterDependentsAttribute(IndependentStatus independentSt /// /// The set status of the independent property. /// The set status of the dependent properties. - /// The value of the dependent properties. + /// The value of the dependent properties. If enforcing a custom , provide the underlying value. /// The names of the dependent properties. public NestedRequestParameterDependentsAttribute(IndependentStatus independentStatus, DependentStatus dependentStatus, object dependentValue, params string[] dependentProperties) : base(independentStatus, dependentStatus, dependentValue, dependentProperties) @@ -417,7 +424,7 @@ public NestedRequestParameterDependentsAttribute(IndependentStatus independentSt /// Initializes a new instance of the class. /// /// The set status of the independent property. - /// The value of the independent property. + /// The value of the independent property. If enforcing a custom , provide the underlying value. /// The set status of the dependent properties. /// The names of the dependent properties. public NestedRequestParameterDependentsAttribute(IndependentStatus independentStatus, object independentValue, DependentStatus dependentStatus, params string[] dependentProperties) diff --git a/EasyPost/Utilities/Internal/Enum.cs b/EasyPost/Utilities/Internal/Enum.cs index de3204df1..c5ccb235b 100644 --- a/EasyPost/Utilities/Internal/Enum.cs +++ b/EasyPost/Utilities/Internal/Enum.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -109,11 +110,11 @@ public override bool Equals(object? obj) public static IEnumerable GetAll() where T : IEnum => - typeof(T).GetFields(BindingFlags.Public | - BindingFlags.Static | - BindingFlags.DeclaredOnly) - .Select(f => f.GetValue(null)) - .Cast(); + typeof(T).GetFields(BindingFlags.Public | + BindingFlags.Static | + BindingFlags.DeclaredOnly) + .Select(f => f.GetValue(null)) + .Cast(); /// /// Compare two objects. diff --git a/EasyPost/Utilities/Internal/Objects.cs b/EasyPost/Utilities/Internal/Objects.cs index 21c9c881d..aa64086ab 100644 --- a/EasyPost/Utilities/Internal/Objects.cs +++ b/EasyPost/Utilities/Internal/Objects.cs @@ -32,5 +32,25 @@ public static bool IsPrimitive(object? obj) { return obj is string or ValueType or null; } + + /// + /// Check if an object is an or derived from . + /// + /// The object to evaluate. + /// true if the object is an or derived from , false otherwise. + public static bool IsEnum(object? obj) + { + return obj is Enum; + } + + /// + /// Check if an object is a or derived from . + /// + /// The object to evaluate. + /// true if the object is a or derived from , false otherwise. + public static bool IsValueEnum(object? obj) + { + return obj is ValueEnum; + } } } From a57c83d3c43702d73069b7f1b594a17b7d194670 Mon Sep 17 00:00:00 2001 From: Nate Harris Date: Fri, 15 Nov 2024 16:27:13 -0700 Subject: [PATCH 2/4] - Add missing `CheckDeliveryAddress` parameter to claims creation parameters - Add missing `ACH` payment option for claims --- EasyPost.Tests/Fixture.cs | 1 + EasyPost/Models/API/ClaimPaymentMethod.cs | 6 ++++++ EasyPost/Parameters/Claim/Create.cs | 9 ++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/EasyPost.Tests/Fixture.cs b/EasyPost.Tests/Fixture.cs index f3b909228..d397e8135 100644 --- a/EasyPost.Tests/Fixture.cs +++ b/EasyPost.Tests/Fixture.cs @@ -298,6 +298,7 @@ internal static ParameterSets.Claim.Create Create(Dictionary? fi ContactEmail = fixture.GetOrNull("contact_email"), PaymentMethod = fixture.GetOrNullEnum("payment_method"), RecipientName = fixture.GetOrNull("recipient_name"), + CheckDeliveryAddress = fixture.GetOrNull("check_delivery_address"), }; } diff --git a/EasyPost/Models/API/ClaimPaymentMethod.cs b/EasyPost/Models/API/ClaimPaymentMethod.cs index 53a8b63a9..dd94f1fc0 100644 --- a/EasyPost/Models/API/ClaimPaymentMethod.cs +++ b/EasyPost/Models/API/ClaimPaymentMethod.cs @@ -17,6 +17,12 @@ public class ClaimPaymentMethod : ValueEnum /// public static readonly ClaimPaymentMethod EasyPostWallet = new(2, "easypost_wallet"); + /// + /// An enum representing paying a claim reimbursement via a bank transfer. + /// + // ReSharper disable once InconsistentNaming + public static readonly ClaimPaymentMethod ACH = new(3, "ach"); + /// /// Initializes a new instance of the class. /// diff --git a/EasyPost/Parameters/Claim/Create.cs b/EasyPost/Parameters/Claim/Create.cs index 8a1f5b8ca..98b3c68b3 100644 --- a/EasyPost/Parameters/Claim/Create.cs +++ b/EasyPost/Parameters/Claim/Create.cs @@ -71,11 +71,18 @@ public class Create : BaseParameters, IClaimParameter public string? ContactEmail { get; set; } /// - /// The for the claim reimbursement. + /// The for the claim reimbursement. If set to , the must be provided. /// [TopLevelRequestParameter(Necessity.Optional, "payment_method")] + [TopLevelRequestParameterDependents(IndependentStatus.IfValue, "mailed_check", DependentStatus.MustBeSet, "CheckDeliveryAddress")] public ClaimPaymentMethod? PaymentMethod { get; set; } + /// + /// The destination address for a reimbursement check. Required if the is . + /// + [TopLevelRequestParameter(Necessity.Optional, "check_delivery_address")] + public string? CheckDeliveryAddress { get; set; } + #endregion } } From 87034f93ef45051f4ef9a34c7b3fe128c8a4d016 Mon Sep 17 00:00:00 2001 From: Nate Harris Date: Fri, 15 Nov 2024 16:37:09 -0700 Subject: [PATCH 3/4] - Linting --- EasyPost.Tests/ParametersTests/ParametersTest.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/EasyPost.Tests/ParametersTests/ParametersTest.cs b/EasyPost.Tests/ParametersTests/ParametersTest.cs index 1be7cf44d..65dabb6cc 100644 --- a/EasyPost.Tests/ParametersTests/ParametersTest.cs +++ b/EasyPost.Tests/ParametersTests/ParametersTest.cs @@ -592,7 +592,8 @@ public async Task TestEnumUsageInInterdependentParameterEnforcement() try { parameters.ToDictionary(); - } catch (Exceptions.General.InvalidParameterPairError) + } + catch (Exceptions.General.InvalidParameterPairError) { Assert.Fail("Should not throw exception if both Param1 and Param2 are set correctly."); } @@ -616,7 +617,8 @@ public async Task TestEnumUsageInInterdependentParameterEnforcement() try { parameters.ToDictionary(); - } catch (Exceptions.General.InvalidParameterPairError) + } + catch (Exceptions.General.InvalidParameterPairError) { Assert.Fail("Should not throw exception if Param1 is not set to a value that enforces a restriction on Param2."); } From 5e79442ba73b6f6f9b5d415a2997afbedba0964a Mon Sep 17 00:00:00 2001 From: Nate Harris Date: Mon, 18 Nov 2024 12:33:33 -0700 Subject: [PATCH 4/4] - Linting --- EasyPost/Utilities/Cryptography.cs | 2 ++ EasyPost/Utilities/Internal/Enum.cs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/EasyPost/Utilities/Cryptography.cs b/EasyPost/Utilities/Cryptography.cs index 4b45519f6..13a84b0c3 100644 --- a/EasyPost/Utilities/Cryptography.cs +++ b/EasyPost/Utilities/Cryptography.cs @@ -46,6 +46,7 @@ public static byte[] AsByteArray(this string str, Encoding? encoding = null) /// /// Byte array to convert to hex string. /// Hex string equivalent of input byte array. +#pragma warning disable CA1859 // Use byte[] instead of IReadOnlyList private static string AsHexString(this IReadOnlyList bytes) { // Fastest safe way to convert a byte array to hex string, @@ -61,6 +62,7 @@ private static string AsHexString(this IReadOnlyList bytes) return new string(result).ToLowerInvariant(); } +#pragma warning restore CA1859 // Use byte[] instead of IReadOnlyList /// /// Convert a string to a hex string using a specific encoding. diff --git a/EasyPost/Utilities/Internal/Enum.cs b/EasyPost/Utilities/Internal/Enum.cs index c5ccb235b..4aff4556f 100644 --- a/EasyPost/Utilities/Internal/Enum.cs +++ b/EasyPost/Utilities/Internal/Enum.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq;