Skip to content

Commit

Permalink
[feat] Add ability to compare parameter set to an EasyPost object (#511)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
nwithan8 authored Aug 30, 2023
1 parent 870cd58 commit 0b93bc1
Show file tree
Hide file tree
Showing 64 changed files with 169 additions and 77 deletions.
60 changes: 56 additions & 4 deletions EasyPost.Tests/ParametersTests/ParametersTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyPost._base;
using EasyPost.Models.API;
using EasyPost.Parameters;
using EasyPost.Tests._Utilities;
using EasyPost.Tests._Utilities.Attributes;
using EasyPost.Utilities.Internal.Attributes;
Expand Down Expand Up @@ -135,7 +137,7 @@ public void TestRequiredAndOptionalParameterValidation()
Assert.Throws<Exceptions.General.MissingParameterError>(() => parametersWithOnlyOptionalParameterSet.ToDictionary());
}

private sealed class ParameterSetWithRequiredAndOptionalParameters : Parameters.BaseParameters
private sealed class ParameterSetWithRequiredAndOptionalParameters : Parameters.BaseParameters<EasyPostObject>
{
[TopLevelRequestParameter(Necessity.Required, "test", "required")]
public string? RequiredParameter { get; set; }
Expand Down Expand Up @@ -255,9 +257,9 @@ public async Task TestDisallowUsingParameterObjectDictionariesInDictionaryFuncti
[Testing.Logic]
public void TestParameterToDictionaryAccountsForNonPublicProperties()
{
ExampleParameters exampleParameters = new ExampleParameters();
ExampleDecoratorParameters exampleDecoratorParameters = new ExampleDecoratorParameters();

Dictionary<string, object> dictionary = exampleParameters.ToDictionary();
Dictionary<string, object> dictionary = exampleDecoratorParameters.ToDictionary();

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

/// <summary>
/// This test proves that the .Matches() method will evaluate if a provided EasyPostObject matches the current parameter set, based on the defined match function.
/// </summary>
[Fact]
[Testing.Logic]
public void TestParameterMatchOverrideFunction()
{
ExampleMatchParametersEasyPostObject obj = new ExampleMatchParametersEasyPostObject
{
Prop1 = "prop1",
// uses default match function at base level (returns false)
// this can also be implemented on a per-parameter set basis
// users can also override the match function to implement custom logic (see examples below)
};

// The default match function should return false
ExampleMatchParameters parameters = new ExampleMatchParameters
{
Prop1 = "prop1",
};
Assert.False(parameters.Matches(obj));

// The overridden match function should return true (because the Prop1 property matches)
parameters = new ExampleMatchParameters
{
Prop1 = "prop1",
MatchFunction = o => o.Prop1 == "prop1",
};
Assert.True(parameters.Matches(obj));

// The overridden match function should return false (because the Prop1 property does not match)
parameters = new ExampleMatchParameters
{
Prop1 = "prop2",
MatchFunction = o => o.Prop1 == "prop2",
};
Assert.False(parameters.Matches(obj));
}

#endregion
}

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

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

private string? UndecoratedPrivateProperty { get; set; } = "undecorated_private";
}

internal class ExampleMatchParametersEasyPostObject : EasyPostObject
{
public string? Prop1 { get; set; }
}

internal class ExampleMatchParameters : Parameters.BaseParameters<ExampleMatchParametersEasyPostObject>
{
public string? Prop1 { get; set; }
}

#pragma warning restore CA1852 // Can be sealed
}
2 changes: 1 addition & 1 deletion EasyPost/Models/API/Batch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public class BatchCollection : PaginatedCollection<Batch>
/// <returns>A TParameters-type parameters set.</returns>
protected internal override TParameters BuildNextPageParameters<TParameters>(IEnumerable<Batch> entries, int? pageSize = null)
{
Parameters.Shipment.All parameters = Filters != null ? (Parameters.Shipment.All)Filters : new Parameters.Shipment.All();
Parameters.Batch.All parameters = Filters != null ? (Parameters.Batch.All)Filters : new Parameters.Batch.All();

// 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"
parameters.AfterId = entries.Last().Id;
Expand Down
3 changes: 2 additions & 1 deletion EasyPost/Models/API/Beta/CarrierMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using EasyPost._base;
using EasyPost.Utilities.Internal;
using Newtonsoft.Json;

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

Expand Down
3 changes: 2 additions & 1 deletion EasyPost/Models/API/CarrierMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using EasyPost._base;
using EasyPost.Utilities.Internal;
using Newtonsoft.Json;

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

Expand Down
7 changes: 4 additions & 3 deletions EasyPost/Models/Shared/PaginatedCollection.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyPost._base;
using EasyPost.Exceptions.General;
using Newtonsoft.Json;

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

/// <summary>
/// Get the next page of a paginated collection.
Expand All @@ -33,7 +34,7 @@ public abstract class PaginatedCollection<TEntries> : _base.EasyPostObject where
/// <typeparam name="TParameters">The type of <see cref="Parameters.BaseParameters"/> to construct for the API call.</typeparam>
/// <returns>The next page of a paginated collection.</returns>
/// <exception cref="EndOfPaginationError">Thrown if there is no next page to retrieve.</exception>
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
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>
{
if (currentEntries == null || currentEntries.Count == 0)
{
Expand All @@ -59,6 +60,6 @@ internal async Task<TCollection> GetNextPage<TCollection, TParameters>(Func<TPar
/// <returns>A TParameters-type set of parameters to use for the subsequent API call.</returns>
/// <exception cref="EndOfPaginationError">Thrown if there are no more items to retrieve for the paginated collection.</exception>
// This method is abstract and must be implemented for each collection.
protected internal abstract TParameters BuildNextPageParameters<TParameters>(IEnumerable<TEntries> entries, int? pageSize = null) where TParameters : Parameters.BaseParameters;
protected internal abstract TParameters BuildNextPageParameters<TParameters>(IEnumerable<TEntries> entries, int? pageSize = null) where TParameters : Parameters.BaseParameters<TEntries>;
}
}
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Address/All.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace EasyPost.Parameters.Address
/// <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.
/// </summary>
[ExcludeFromCodeCoverage]
public class All : BaseAllParameters
public class All : BaseAllParameters<Models.API.Address>
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Address/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace EasyPost.Parameters.Address
/// <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.
/// </summary>
[ExcludeFromCodeCoverage]
public class Create : BaseParameters, IAddressParameter
public class Create : BaseParameters<Models.API.Address>, IAddressParameter
{
#region Request Parameters

Expand Down
9 changes: 6 additions & 3 deletions EasyPost/Parameters/BaseAllParameters.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
using System;
using System.Collections.Generic;
using EasyPost._base;

namespace EasyPost.Parameters;

/// <summary>
/// Base class for parameter sets used in `All` methods.
/// </summary>
public abstract class BaseAllParameters : BaseParameters
public abstract class BaseAllParameters<TMatchInputType> : BaseParameters<TMatchInputType> where TMatchInputType : EphemeralEasyPostObject
{
/// <summary>
/// Construct a new <see cref="BaseAllParameters"/>-based instance from a <see cref="Dictionary{TKey,TValue}"/>.
/// Construct a new <see cref="BaseAllParameters{TMatchInputType}"/>-based instance from a <see cref="Dictionary{TKey,TValue}"/>.
/// </summary>
/// <param name="dictionary">The dictionary to parse.</param>
/// <returns>A BaseAllParameters-subtype object.</returns>
public static BaseAllParameters FromDictionary(Dictionary<string, object>? dictionary)
#pragma warning disable CA1000
public static BaseAllParameters<TMatchInputType> FromDictionary(Dictionary<string, object>? dictionary)
#pragma warning restore CA1000
{
throw new NotImplementedException();
}
Expand Down
21 changes: 17 additions & 4 deletions EasyPost/Parameters/BaseParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace EasyPost.Parameters
/// <summary>
/// Base class for all parameters used in functions.
/// </summary>
public abstract class BaseParameters
public abstract class BaseParameters<TMatchInputType> : IBaseParameters where TMatchInputType : EphemeralEasyPostObject
{
/*
* NOTES:
Expand All @@ -31,10 +31,23 @@ public abstract class BaseParameters
private Dictionary<string, object?> _parameterDictionary;

/// <summary>
/// Initializes a new instance of the <see cref="BaseParameters"/> class for a new set of request parameters.
/// A function to determine if a given object matches this parameter set.
/// Defaults to always returning false, but can be overridden by child classes and end-users.
/// </summary>
public Func<TMatchInputType, bool> MatchFunction { get; set; } = _ => false;

/// <summary>
/// Initializes a new instance of the <see cref="BaseParameters{TMatchInputType}"/> class for a new set of request parameters.
/// </summary>
protected BaseParameters() => _parameterDictionary = new Dictionary<string, object?>();

/// <summary>
/// Execute the match function on a given object.
/// </summary>
/// <param name="obj">The <see cref="EasyPostObject"/> to compare this parameter set against.</param>
/// <returns>The result of the <see cref="MatchFunction"/></returns>
public bool Matches(TMatchInputType obj) => MatchFunction(obj);

/// <summary>
/// Convert this parameter object to a dictionary for an HTTP request.
/// </summary>
Expand Down Expand Up @@ -91,7 +104,7 @@ public virtual Dictionary<string, object> ToDictionary()
/// embedded.
/// </param>
/// <returns><see cref="Dictionary{TKey,TValue}" /> of parameters.</returns>
protected virtual Dictionary<string, object> ToSubDictionary(Type parentParameterObjectType)
public virtual Dictionary<string, object> ToSubDictionary(Type parentParameterObjectType)
{
// Construct the dictionary of all parameters
PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Instance |
Expand Down Expand Up @@ -161,7 +174,7 @@ private void Add(RequestParameterAttribute requestParameterAttribute, object? va
// If the given value is another base-Parameters object, serialize it as a sub-dictionary for the parent dictionary
// This is because the JSON schema for a sub-object is different than the JSON schema for a top-level object
// 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
case BaseParameters parameters:
case IBaseParameters parameters: // TODO: if issues arise with this function, look at the type constraint on BaseParameters here
return parameters.ToSubDictionary(GetType());
// If the given value is a list, serialize each element of the list
case IList list:
Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Batch/AddShipments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace EasyPost.Parameters.Batch
/// <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.
/// </summary>
[ExcludeFromCodeCoverage]
public class AddShipments : BaseParameters
public class AddShipments : BaseParameters<Models.API.Batch>
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Batch/All.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace EasyPost.Parameters.Batch
/// <a href="https://www.easypost.com/docs/api#list-all-batches">Parameters</a> for <see cref="EasyPost.Services.BatchService.All(All, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class All : BaseAllParameters
public class All : BaseAllParameters<Models.API.Batch>
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Batch/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace EasyPost.Parameters.Batch
/// <a href="https://www.easypost.com/docs/api#create-a-batch">Parameters</a> for <see cref="EasyPost.Services.BatchService.Create(Create, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class Create : BaseParameters, IBatchParameter
public class Create : BaseParameters<Models.API.Batch>, IBatchParameter
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Batch/GenerateLabel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace EasyPost.Parameters.Batch
/// <a href="https://www.easypost.com/docs/api#batch-labels">Parameters</a> for <see cref="EasyPost.Services.BatchService.GenerateLabel(string, GenerateLabel, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class GenerateLabel : BaseParameters
public class GenerateLabel : BaseParameters<Models.API.Batch>
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Batch/GenerateScanForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace EasyPost.Parameters.Batch
/// <a href="https://www.easypost.com/docs/api#manifesting-scan-form">Parameters</a> for <see cref="EasyPost.Services.BatchService.GenerateScanForm(string, GenerateScanForm, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class GenerateScanForm : BaseParameters
public class GenerateScanForm : BaseParameters<Models.API.Batch>
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Batch/RemoveShipments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace EasyPost.Parameters.Batch
/// <a href="https://www.easypost.com/docs/api#remove-shipments-from-a-batch">Parameters</a> for <see cref="EasyPost.Services.BatchService.RemoveShipments(string, RemoveShipments, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class RemoveShipments : BaseParameters
public class RemoveShipments : BaseParameters<Models.API.Batch>
{
#region Request Parameters

Expand Down
4 changes: 2 additions & 2 deletions EasyPost/Parameters/Beta/CarrierMetadata/Retrieve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace EasyPost.Parameters.Beta.CarrierMetadata
/// </summary>
[ExcludeFromCodeCoverage]
[Obsolete("This class is deprecated. Please use EasyPost.Parameters.CarrierMetadata.Retrieve instead. This class will be removed in a future version.", false)]
public class Retrieve : BaseParameters
public class Retrieve : BaseParameters<Models.API.Beta.CarrierMetadata>
{
#region Request Parameters

Expand All @@ -28,7 +28,7 @@ public class Retrieve : BaseParameters
#endregion

/// <summary>
/// Override the default <see cref="BaseParameters.ToDictionary"/> method to handle the unique serialization requirements for this parameter set.
/// Override the default <see cref="BaseParameters{TMatchInputType}.ToDictionary"/> method to handle the unique serialization requirements for this parameter set.
/// </summary>
/// <returns>A <see cref="Dictionary{TKey,TValue}"/>.</returns>
public override Dictionary<string, object> ToDictionary()
Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/Beta/Rate/Retrieve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace EasyPost.Parameters.Beta.Rate
/// <a href="https://www.easypost.com/docs/api#retrieve-rates-for-a-shipment">Parameters</a> for <see cref="EasyPost.Services.Beta.RateService.RetrieveStatelessRates(Retrieve, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class Retrieve : BaseParameters
public class Retrieve : BaseParameters<Models.API.Beta.StatelessRate>
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/CarrierAccount/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace EasyPost.Parameters.CarrierAccount
/// <a href="https://www.easypost.com/docs/api#create-a-carrier-account">Parameters</a> for <see cref="EasyPost.Services.CarrierAccountService.Create(Create, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class Create : BaseParameters, ICarrierAccountParameter
public class Create : BaseParameters<Models.API.CarrierAccount>, ICarrierAccountParameter
{
#region Request Parameters

Expand Down
2 changes: 1 addition & 1 deletion EasyPost/Parameters/CarrierAccount/Update.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace EasyPost.Parameters.CarrierAccount
/// <a href="https://www.easypost.com/docs/api#update-a-carrieraccount">Parameters</a> for <see cref="EasyPost.Services.CarrierAccountService.Update(string, Update, System.Threading.CancellationToken)"/> API calls.
/// </summary>
[ExcludeFromCodeCoverage]
public class Update : BaseParameters
public class Update : BaseParameters<Models.API.CarrierAccount>
{
#region Request Parameters

Expand Down
Loading

0 comments on commit 0b93bc1

Please sign in to comment.