Skip to content

Commit 0b93bc1

Browse files
authored
[feat] Add ability to compare parameter set to an EasyPost object (#511)
- Add "Matches" function at base level of all parameter sets to test if a given set matches a provided EasyPostObject-based object - Add unit tests to confirm function can be implemented and works as expected
1 parent 870cd58 commit 0b93bc1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+169
-77
lines changed

EasyPost.Tests/ParametersTests/ParametersTest.cs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Collections.Generic;
22
using System.Threading.Tasks;
3+
using EasyPost._base;
34
using EasyPost.Models.API;
5+
using EasyPost.Parameters;
46
using EasyPost.Tests._Utilities;
57
using EasyPost.Tests._Utilities.Attributes;
68
using EasyPost.Utilities.Internal.Attributes;
@@ -135,7 +137,7 @@ public void TestRequiredAndOptionalParameterValidation()
135137
Assert.Throws<Exceptions.General.MissingParameterError>(() => parametersWithOnlyOptionalParameterSet.ToDictionary());
136138
}
137139

138-
private sealed class ParameterSetWithRequiredAndOptionalParameters : Parameters.BaseParameters
140+
private sealed class ParameterSetWithRequiredAndOptionalParameters : Parameters.BaseParameters<EasyPostObject>
139141
{
140142
[TopLevelRequestParameter(Necessity.Required, "test", "required")]
141143
public string? RequiredParameter { get; set; }
@@ -255,9 +257,9 @@ public async Task TestDisallowUsingParameterObjectDictionariesInDictionaryFuncti
255257
[Testing.Logic]
256258
public void TestParameterToDictionaryAccountsForNonPublicProperties()
257259
{
258-
ExampleParameters exampleParameters = new ExampleParameters();
260+
ExampleDecoratorParameters exampleDecoratorParameters = new ExampleDecoratorParameters();
259261

260-
Dictionary<string, object> dictionary = exampleParameters.ToDictionary();
262+
Dictionary<string, object> dictionary = exampleDecoratorParameters.ToDictionary();
261263

262264
// All decorated properties should be present in the dictionary, regardless of their access modifier
263265
Assert.True(dictionary.ContainsKey("decorated_public_property"));
@@ -270,11 +272,50 @@ public void TestParameterToDictionaryAccountsForNonPublicProperties()
270272
Assert.True(dictionary.Count == 4);
271273
}
272274

275+
/// <summary>
276+
/// This test proves that the .Matches() method will evaluate if a provided EasyPostObject matches the current parameter set, based on the defined match function.
277+
/// </summary>
278+
[Fact]
279+
[Testing.Logic]
280+
public void TestParameterMatchOverrideFunction()
281+
{
282+
ExampleMatchParametersEasyPostObject obj = new ExampleMatchParametersEasyPostObject
283+
{
284+
Prop1 = "prop1",
285+
// uses default match function at base level (returns false)
286+
// this can also be implemented on a per-parameter set basis
287+
// users can also override the match function to implement custom logic (see examples below)
288+
};
289+
290+
// The default match function should return false
291+
ExampleMatchParameters parameters = new ExampleMatchParameters
292+
{
293+
Prop1 = "prop1",
294+
};
295+
Assert.False(parameters.Matches(obj));
296+
297+
// The overridden match function should return true (because the Prop1 property matches)
298+
parameters = new ExampleMatchParameters
299+
{
300+
Prop1 = "prop1",
301+
MatchFunction = o => o.Prop1 == "prop1",
302+
};
303+
Assert.True(parameters.Matches(obj));
304+
305+
// The overridden match function should return false (because the Prop1 property does not match)
306+
parameters = new ExampleMatchParameters
307+
{
308+
Prop1 = "prop2",
309+
MatchFunction = o => o.Prop1 == "prop2",
310+
};
311+
Assert.False(parameters.Matches(obj));
312+
}
313+
273314
#endregion
274315
}
275316

276317
#pragma warning disable CA1852 // Can be sealed
277-
internal class ExampleParameters : Parameters.BaseParameters
318+
internal class ExampleDecoratorParameters : Parameters.BaseParameters<EasyPostObject>
278319
{
279320
// Default values set to guarantee any property won't be skipped for serialization due to a null value
280321

@@ -295,5 +336,16 @@ internal class ExampleParameters : Parameters.BaseParameters
295336

296337
private string? UndecoratedPrivateProperty { get; set; } = "undecorated_private";
297338
}
339+
340+
internal class ExampleMatchParametersEasyPostObject : EasyPostObject
341+
{
342+
public string? Prop1 { get; set; }
343+
}
344+
345+
internal class ExampleMatchParameters : Parameters.BaseParameters<ExampleMatchParametersEasyPostObject>
346+
{
347+
public string? Prop1 { get; set; }
348+
}
349+
298350
#pragma warning restore CA1852 // Can be sealed
299351
}

EasyPost/Models/API/Batch.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public class BatchCollection : PaginatedCollection<Batch>
112112
/// <returns>A TParameters-type parameters set.</returns>
113113
protected internal override TParameters BuildNextPageParameters<TParameters>(IEnumerable<Batch> entries, int? pageSize = null)
114114
{
115-
Parameters.Shipment.All parameters = Filters != null ? (Parameters.Shipment.All)Filters : new Parameters.Shipment.All();
115+
Parameters.Batch.All parameters = Filters != null ? (Parameters.Batch.All)Filters : new Parameters.Batch.All();
116116

117117
// TODO: Batches get returned in reverse order from everything else (oldest first instead of newest first), so this needs to be "after_id" instead of "before_id"
118118
parameters.AfterId = entries.Last().Id;

EasyPost/Models/API/Beta/CarrierMetadata.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using EasyPost._base;
34
using EasyPost.Utilities.Internal;
45
using Newtonsoft.Json;
56

@@ -10,7 +11,7 @@ namespace EasyPost.Models.API.Beta
1011
/// Class representing an <a href="https://www.easypost.com/docs/api#carriermetadata-object">EasyPost carrier metadata summary</a>.
1112
/// </summary>
1213
[Obsolete("This class is deprecated. Please use EasyPost.Models.API.CarrierMetadata instead. This class will be removed in a future version.", false)]
13-
public class CarrierMetadata
14+
public class CarrierMetadata : EasyPostObject
1415
{
1516
#region JSON Properties
1617

EasyPost/Models/API/CarrierMetadata.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using EasyPost._base;
23
using EasyPost.Utilities.Internal;
34
using Newtonsoft.Json;
45

@@ -8,7 +9,7 @@ namespace EasyPost.Models.API
89
/// <summary>
910
/// Class representing an <a href="https://www.easypost.com/docs/api#carriermetadata-object">EasyPost carrier metadata summary</a>.
1011
/// </summary>
11-
public class CarrierMetadata
12+
public class CarrierMetadata : EphemeralEasyPostObject
1213
{
1314
#region JSON Properties
1415

EasyPost/Models/Shared/PaginatedCollection.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Threading.Tasks;
4+
using EasyPost._base;
45
using EasyPost.Exceptions.General;
56
using Newtonsoft.Json;
67

@@ -21,7 +22,7 @@ public abstract class PaginatedCollection<TEntries> : _base.EasyPostObject where
2122
/// <summary>
2223
/// The filter parameters used to retrieve this collection.
2324
/// </summary>
24-
internal Parameters.BaseParameters? Filters { get; set; }
25+
internal Parameters.BaseParameters<TEntries>? Filters { get; set; }
2526

2627
/// <summary>
2728
/// Get the next page of a paginated collection.
@@ -33,7 +34,7 @@ public abstract class PaginatedCollection<TEntries> : _base.EasyPostObject where
3334
/// <typeparam name="TParameters">The type of <see cref="Parameters.BaseParameters"/> to construct for the API call.</typeparam>
3435
/// <returns>The next page of a paginated collection.</returns>
3536
/// <exception cref="EndOfPaginationError">Thrown if there is no next page to retrieve.</exception>
36-
internal async Task<TCollection> GetNextPage<TCollection, TParameters>(Func<TParameters, Task<TCollection>> apiCallFunction, List<TEntries>? currentEntries, int? pageSize = null) where TCollection : PaginatedCollection<TEntries> where TParameters : Parameters.BaseParameters
37+
internal async Task<TCollection> GetNextPage<TCollection, TParameters>(Func<TParameters, Task<TCollection>> apiCallFunction, List<TEntries>? currentEntries, int? pageSize = null) where TCollection : PaginatedCollection<TEntries> where TParameters : Parameters.BaseParameters<TEntries>
3738
{
3839
if (currentEntries == null || currentEntries.Count == 0)
3940
{
@@ -59,6 +60,6 @@ internal async Task<TCollection> GetNextPage<TCollection, TParameters>(Func<TPar
5960
/// <returns>A TParameters-type set of parameters to use for the subsequent API call.</returns>
6061
/// <exception cref="EndOfPaginationError">Thrown if there are no more items to retrieve for the paginated collection.</exception>
6162
// This method is abstract and must be implemented for each collection.
62-
protected internal abstract TParameters BuildNextPageParameters<TParameters>(IEnumerable<TEntries> entries, int? pageSize = null) where TParameters : Parameters.BaseParameters;
63+
protected internal abstract TParameters BuildNextPageParameters<TParameters>(IEnumerable<TEntries> entries, int? pageSize = null) where TParameters : Parameters.BaseParameters<TEntries>;
6364
}
6465
}

EasyPost/Parameters/Address/All.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace EasyPost.Parameters.Address
99
/// <a href="https://www.easypost.com/docs/api#retrieve-a-list-of-addresses">Parameters</a> for <see cref="EasyPost.Services.AddressService.All(All, System.Threading.CancellationToken)"/> API calls.
1010
/// </summary>
1111
[ExcludeFromCodeCoverage]
12-
public class All : BaseAllParameters
12+
public class All : BaseAllParameters<Models.API.Address>
1313
{
1414
#region Request Parameters
1515

EasyPost/Parameters/Address/Create.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace EasyPost.Parameters.Address
77
/// <a href="https://www.easypost.com/docs/api#create-and-verify-addresses">Parameters</a> for <see cref="EasyPost.Services.AddressService.Create(Create, System.Threading.CancellationToken)"/> API calls.
88
/// </summary>
99
[ExcludeFromCodeCoverage]
10-
public class Create : BaseParameters, IAddressParameter
10+
public class Create : BaseParameters<Models.API.Address>, IAddressParameter
1111
{
1212
#region Request Parameters
1313

EasyPost/Parameters/BaseAllParameters.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
using System;
22
using System.Collections.Generic;
3+
using EasyPost._base;
34

45
namespace EasyPost.Parameters;
56

67
/// <summary>
78
/// Base class for parameter sets used in `All` methods.
89
/// </summary>
9-
public abstract class BaseAllParameters : BaseParameters
10+
public abstract class BaseAllParameters<TMatchInputType> : BaseParameters<TMatchInputType> where TMatchInputType : EphemeralEasyPostObject
1011
{
1112
/// <summary>
12-
/// Construct a new <see cref="BaseAllParameters"/>-based instance from a <see cref="Dictionary{TKey,TValue}"/>.
13+
/// Construct a new <see cref="BaseAllParameters{TMatchInputType}"/>-based instance from a <see cref="Dictionary{TKey,TValue}"/>.
1314
/// </summary>
1415
/// <param name="dictionary">The dictionary to parse.</param>
1516
/// <returns>A BaseAllParameters-subtype object.</returns>
16-
public static BaseAllParameters FromDictionary(Dictionary<string, object>? dictionary)
17+
#pragma warning disable CA1000
18+
public static BaseAllParameters<TMatchInputType> FromDictionary(Dictionary<string, object>? dictionary)
19+
#pragma warning restore CA1000
1720
{
1821
throw new NotImplementedException();
1922
}

EasyPost/Parameters/BaseParameters.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace EasyPost.Parameters
1515
/// <summary>
1616
/// Base class for all parameters used in functions.
1717
/// </summary>
18-
public abstract class BaseParameters
18+
public abstract class BaseParameters<TMatchInputType> : IBaseParameters where TMatchInputType : EphemeralEasyPostObject
1919
{
2020
/*
2121
* NOTES:
@@ -31,10 +31,23 @@ public abstract class BaseParameters
3131
private Dictionary<string, object?> _parameterDictionary;
3232

3333
/// <summary>
34-
/// Initializes a new instance of the <see cref="BaseParameters"/> class for a new set of request parameters.
34+
/// A function to determine if a given object matches this parameter set.
35+
/// Defaults to always returning false, but can be overridden by child classes and end-users.
36+
/// </summary>
37+
public Func<TMatchInputType, bool> MatchFunction { get; set; } = _ => false;
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="BaseParameters{TMatchInputType}"/> class for a new set of request parameters.
3541
/// </summary>
3642
protected BaseParameters() => _parameterDictionary = new Dictionary<string, object?>();
3743

44+
/// <summary>
45+
/// Execute the match function on a given object.
46+
/// </summary>
47+
/// <param name="obj">The <see cref="EasyPostObject"/> to compare this parameter set against.</param>
48+
/// <returns>The result of the <see cref="MatchFunction"/></returns>
49+
public bool Matches(TMatchInputType obj) => MatchFunction(obj);
50+
3851
/// <summary>
3952
/// Convert this parameter object to a dictionary for an HTTP request.
4053
/// </summary>
@@ -91,7 +104,7 @@ public virtual Dictionary<string, object> ToDictionary()
91104
/// embedded.
92105
/// </param>
93106
/// <returns><see cref="Dictionary{TKey,TValue}" /> of parameters.</returns>
94-
protected virtual Dictionary<string, object> ToSubDictionary(Type parentParameterObjectType)
107+
public virtual Dictionary<string, object> ToSubDictionary(Type parentParameterObjectType)
95108
{
96109
// Construct the dictionary of all parameters
97110
PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Instance |
@@ -161,7 +174,7 @@ private void Add(RequestParameterAttribute requestParameterAttribute, object? va
161174
// If the given value is another base-Parameters object, serialize it as a sub-dictionary for the parent dictionary
162175
// This is because the JSON schema for a sub-object is different than the JSON schema for a top-level object
163176
// e.g. the schema for an address in the address create API call is different than the schema for an address in the shipment create API call
164-
case BaseParameters parameters:
177+
case IBaseParameters parameters: // TODO: if issues arise with this function, look at the type constraint on BaseParameters here
165178
return parameters.ToSubDictionary(GetType());
166179
// If the given value is a list, serialize each element of the list
167180
case IList list:

EasyPost/Parameters/Batch/AddShipments.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace EasyPost.Parameters.Batch
88
/// <a href="https://www.easypost.com/docs/api#add-shipments-to-a-batch">Parameters</a> for <see cref="EasyPost.Services.BatchService.AddShipments(string, AddShipments, System.Threading.CancellationToken)"/> API calls.
99
/// </summary>
1010
[ExcludeFromCodeCoverage]
11-
public class AddShipments : BaseParameters
11+
public class AddShipments : BaseParameters<Models.API.Batch>
1212
{
1313
#region Request Parameters
1414

0 commit comments

Comments
 (0)