From 98b631f247c3e1f88f12104e0e579c88dbf659d3 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:15:11 -0700 Subject: [PATCH 1/4] chore: remove unecessary dispose logic, instantiate services inline --- CHANGELOG.md | 1 - EasyPost.Tests/ClientTest.cs | 7 - EasyPost.Tests/Fixture.cs | 16 +++ .../HttpTests/ClientConfigurationTest.cs | 1 - EasyPost.Tests/HttpTests/RequestTest.cs | 1 - EasyPost.Tests/ServicesTests/ServiceTest.cs | 1 - .../ServicesTests/TrackerServiceTest.cs | 20 --- .../WithParameters/TrackerServiceTest.cs | 22 +++ EasyPost.Tests/_Utilities/UnitTest.cs | 34 +---- .../retrieve_batch.json | 26 ++-- EasyPost/BetaClient.cs | 18 --- EasyPost/Client.cs | 134 ++++-------------- EasyPost/ClientConfiguration.cs | 38 +---- EasyPost/Http/Request.cs | 37 +---- EasyPost/Parameters/Tracker/Batch.cs | 80 +++++++++++ EasyPost/Services/ReferralCustomerService.cs | 4 - EasyPost/Services/TrackerService.cs | 4 +- EasyPost/_base/EasyPostClient.cs | 50 +------ EasyPost/_base/EasyPostService.cs | 35 +---- 19 files changed, 165 insertions(+), 364 deletions(-) rename EasyPost.Tests/cassettes/net/{tracker_service => tracker_service_with_parameters}/retrieve_batch.json (91%) create mode 100644 EasyPost/Parameters/Tracker/Batch.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index c31ec7294..a5b167ed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,6 @@ - Adds support for `UspsShipAccount` - Adds `Tracker.RetrieveBatch` function - Adds `VerifyCarrier` address param -- Disposes of Luma service after use ## v7.2.0 (2025-06-18) diff --git a/EasyPost.Tests/ClientTest.cs b/EasyPost.Tests/ClientTest.cs index 319f765fa..8b46946e6 100644 --- a/EasyPost.Tests/ClientTest.cs +++ b/EasyPost.Tests/ClientTest.cs @@ -70,13 +70,6 @@ public void TestThreadSafety() private const string FakeApikey = "fake_api_key"; - [Fact] - public void TestClientDisposal() - { - Client client = new(new ClientConfiguration(FakeApikey)); - CustomAssertions.DoesNotThrow(() => client.Dispose()); - } - [Fact] public void TestBaseUrlOverride() { diff --git a/EasyPost.Tests/Fixture.cs b/EasyPost.Tests/Fixture.cs index 42c068afd..d344806cb 100644 --- a/EasyPost.Tests/Fixture.cs +++ b/EasyPost.Tests/Fixture.cs @@ -801,6 +801,22 @@ internal static ParameterSets.Tracker.All All(Dictionary? fixtur Carrier = fixture.GetOrNull("carrier"), }; } + + internal static ParameterSets.Tracker.Batch Batch(Dictionary? fixture) + { + fixture ??= new Dictionary(); + + return new ParameterSets.Tracker.Batch + { + PageSize = fixture.GetOrNullInt("page_size"), + BeforeId = fixture.GetOrNull("before_id"), + AfterId = fixture.GetOrNull("after_id"), + StartDatetime = fixture.GetOrNull("start_datetime"), + EndDatetime = fixture.GetOrNull("end_datetime"), + TrackingCodes = fixture.GetOrNull>("tracking_codes"), + Carrier = fixture.GetOrNull("carrier"), + }; + } } internal static class Users diff --git a/EasyPost.Tests/HttpTests/ClientConfigurationTest.cs b/EasyPost.Tests/HttpTests/ClientConfigurationTest.cs index c9be07561..969d42c1f 100644 --- a/EasyPost.Tests/HttpTests/ClientConfigurationTest.cs +++ b/EasyPost.Tests/HttpTests/ClientConfigurationTest.cs @@ -13,7 +13,6 @@ public class ClientConfigurationTest public void TestClientConfigurationDisposal() { ClientConfiguration configuration = new("not_a_real_api_key"); - CustomAssertions.DoesNotThrow(() => configuration.Dispose()); } [Fact] diff --git a/EasyPost.Tests/HttpTests/RequestTest.cs b/EasyPost.Tests/HttpTests/RequestTest.cs index 6c3f81a69..97c3cb47c 100644 --- a/EasyPost.Tests/HttpTests/RequestTest.cs +++ b/EasyPost.Tests/HttpTests/RequestTest.cs @@ -15,7 +15,6 @@ public class RequestTest public void TestRequestDisposal() { Request request = new("https://example.com", "not_a_real_endpoint", Method.Get, ApiVersion.V2, new Dictionary { { "key", "value" } }, new Dictionary { { "header_key", "header_value" } }); - CustomAssertions.DoesNotThrow(() => request.Dispose()); } [Fact] diff --git a/EasyPost.Tests/ServicesTests/ServiceTest.cs b/EasyPost.Tests/ServicesTests/ServiceTest.cs index 7ece449ce..db858384c 100644 --- a/EasyPost.Tests/ServicesTests/ServiceTest.cs +++ b/EasyPost.Tests/ServicesTests/ServiceTest.cs @@ -29,7 +29,6 @@ public void TestServiceDisposal() Client client = new(new ClientConfiguration("not_a_real_api_key")); AddressService addressService = client.Address; - CustomAssertions.DoesNotThrow(() => addressService.Dispose()); } /// diff --git a/EasyPost.Tests/ServicesTests/TrackerServiceTest.cs b/EasyPost.Tests/ServicesTests/TrackerServiceTest.cs index 52311f3ad..9bad4a2d3 100644 --- a/EasyPost.Tests/ServicesTests/TrackerServiceTest.cs +++ b/EasyPost.Tests/ServicesTests/TrackerServiceTest.cs @@ -147,26 +147,6 @@ public async Task TestRetrieve() Assert.Equal(tracker.Id, retrievedTracker.Id); } - [Fact] - [CrudOperations.Read] - [Testing.Function] - public async Task TestRetrieveBatch() - { - UseVCR("retrieve_batch"); - - Tracker tracker = await Client.Tracker.Create(Fixtures.Usps, "EZ1000000001"); - - List trackingCodes = new() { tracker.TrackingCode }; - TrackerCollection trackerCollection = await Client.Tracker.RetrieveBatch(new Dictionary { { "tracking_codes", trackingCodes } }); - - List trackers = trackerCollection.Trackers; - - foreach (Tracker singleTracker in trackers) - { - Assert.IsType(singleTracker); - } - } - #endregion #endregion diff --git a/EasyPost.Tests/ServicesTests/WithParameters/TrackerServiceTest.cs b/EasyPost.Tests/ServicesTests/WithParameters/TrackerServiceTest.cs index a9563dbc0..4848ae323 100644 --- a/EasyPost.Tests/ServicesTests/WithParameters/TrackerServiceTest.cs +++ b/EasyPost.Tests/ServicesTests/WithParameters/TrackerServiceTest.cs @@ -92,6 +92,28 @@ public async Task TestAllParameterHandOff() Assert.Equal(filters.Carrier, ((Parameters.Tracker.All)trackerCollection.Filters!).Carrier); } + [Fact] + [CrudOperations.Read] + [Testing.Function] + public async Task TestRetrieveBatch() + { + UseVCR("retrieve_batch"); + + Tracker tracker = await Client.Tracker.Create(Fixtures.Usps, "EZ1000000001"); + + Dictionary data = new Dictionary() { { "tracking_codes", new List { tracker.TrackingCode } } }; + Parameters.Tracker.Batch parameters = Fixtures.Parameters.Trackers.Batch(data); + + TrackerCollection trackerCollection = await Client.Tracker.RetrieveBatch(parameters); + + List trackers = trackerCollection.Trackers; + + foreach (Tracker singleTracker in trackers) + { + Assert.IsType(singleTracker); + } + } + #endregion #endregion diff --git a/EasyPost.Tests/_Utilities/UnitTest.cs b/EasyPost.Tests/_Utilities/UnitTest.cs index ce0a80fb5..8acd87445 100644 --- a/EasyPost.Tests/_Utilities/UnitTest.cs +++ b/EasyPost.Tests/_Utilities/UnitTest.cs @@ -9,7 +9,7 @@ namespace EasyPost.Tests._Utilities /// Base class for all unit tests. /// Sets up all available client versions for VCR and non-VCR requests. /// - public class UnitTest : IDisposable + public class UnitTest { private readonly TestUtils.VCR? _vcr; @@ -32,38 +32,6 @@ public class UnitTest : IDisposable protected UnitTest(string groupName, TestUtils.ApiKey apiKey = TestUtils.ApiKey.Test) => _vcr = new TestUtils.VCR(groupName, apiKey); #pragma warning restore CS8618 - /// - /// Called automatically by xUnit after each unit test is run. - /// Executes the CleanupFunction (passes in the _cleanupId) if set. - /// - /// Could not execute the declared clean-up function. - public void Dispose() - { - if (CleanupFunction == null) - { - return; - } - - if (_cleanupId == null) - { - return; - } - - try - { - CleanupFunction.Invoke(_cleanupId).GetAwaiter().GetResult(); - _cleanupId = null; - } - catch - { -#pragma warning disable CA2201 - throw new Exception("Could not execute clean-up function."); -#pragma warning restore CA2201 - } - - GC.SuppressFinalize(this); - } - /// /// Set the ID that will be passed into the CleanupFunction after each unit test /// diff --git a/EasyPost.Tests/cassettes/net/tracker_service/retrieve_batch.json b/EasyPost.Tests/cassettes/net/tracker_service_with_parameters/retrieve_batch.json similarity index 91% rename from EasyPost.Tests/cassettes/net/tracker_service/retrieve_batch.json rename to EasyPost.Tests/cassettes/net/tracker_service_with_parameters/retrieve_batch.json index f16da4b0a..b17e61ce4 100644 --- a/EasyPost.Tests/cassettes/net/tracker_service/retrieve_batch.json +++ b/EasyPost.Tests/cassettes/net/tracker_service_with_parameters/retrieve_batch.json @@ -1,7 +1,7 @@ [ { - "Duration": 837, - "RecordedAt": "2025-11-06T10:40:40.72857-07:00", + "Duration": 335, + "RecordedAt": "2025-11-19T16:09:30.23482-07:00", "Request": { "Body": "{\"tracker\":{\"carrier\":\"USPS\",\"tracking_code\":\"EZ1000000001\"}}", "BodyContentType": "Json", @@ -32,15 +32,15 @@ "x-download-options": "noopen", "x-permitted-cross-domain-policies": "none", "Referrer-Policy": "strict-origin-when-cross-origin", - "x-ep-request-uuid": "bcd4371f690cdd98e2b7cd6b0113cbab", + "x-ep-request-uuid": "40a2aa2a691e4e2ae2b86b180113412e", "Cache-Control": "no-store, no-cache, private", "Pragma": "no-cache", "Location": "/api/v2/trackers/trk_b88311999eaa4429bd11ce27ea62d302", - "x-runtime": "0.072554", - "x-node": "bigweb38nuq", - "x-version-label": "easypost-202511061639-9e7dc69a6b-master", + "x-runtime": "0.093700", + "x-node": "bigweb63nuq", + "x-version-label": "easypost-202511192048-3cb4e6ce9b-master", "x-backend": "easypost", - "x-proxied": "intlb5nuq c0061e0a2e,extlb2nuq cbbd141214", + "x-proxied": "intlb3nuq c0061e0a2e,extlb1nuq cbbd141214", "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload" }, "Status": { @@ -50,8 +50,8 @@ } }, { - "Duration": 100, - "RecordedAt": "2025-11-06T10:40:40.907915-07:00", + "Duration": 92, + "RecordedAt": "2025-11-19T16:09:30.334499-07:00", "Request": { "Body": "{\"tracking_codes\":[\"EZ1000000001\"]}", "BodyContentType": "Json", @@ -82,14 +82,14 @@ "x-download-options": "noopen", "x-permitted-cross-domain-policies": "none", "Referrer-Policy": "strict-origin-when-cross-origin", - "x-ep-request-uuid": "bcd4371f690cdd98e2b7cd6b0113cc8d", + "x-ep-request-uuid": "40a2aa2a691e4e2ae2b86b1801134161", "Cache-Control": "no-store, no-cache, private", "Pragma": "no-cache", - "x-runtime": "0.059364", + "x-runtime": "0.054544", "x-node": "bigweb53nuq", - "x-version-label": "easypost-202511061639-9e7dc69a6b-master", + "x-version-label": "easypost-202511192048-3cb4e6ce9b-master", "x-backend": "easypost", - "x-proxied": "intlb3nuq c0061e0a2e,extlb2nuq cbbd141214", + "x-proxied": "intlb3nuq c0061e0a2e,extlb1nuq cbbd141214", "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload" }, "Status": { diff --git a/EasyPost/BetaClient.cs b/EasyPost/BetaClient.cs index e38e5a363..a6f9a4894 100644 --- a/EasyPost/BetaClient.cs +++ b/EasyPost/BetaClient.cs @@ -42,23 +42,5 @@ internal BetaClient(ClientConfiguration configuration) Rate = new Services.Beta.RateService(this); ReferralCustomer = new Services.Beta.ReferralCustomerService(this); } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - // Dispose managed state (managed objects). - - // Dispose of the services - Rate.Dispose(); - ReferralCustomer.Dispose(); - } - - // Free native resources (unmanaged objects) and override a finalizer below. - - // Dispose of the base client - base.Dispose(disposing); - } } } diff --git a/EasyPost/Client.cs b/EasyPost/Client.cs index 2d61eb27d..a6c1a83ea 100644 --- a/EasyPost/Client.cs +++ b/EasyPost/Client.cs @@ -11,20 +11,17 @@ public class Client : EasyPostClient /// /// Access Address-related functionality. /// - public AddressService Address { get; } + public AddressService Address => new AddressService(this); /// /// Access API Key-related functionality. /// - public ApiKeyService ApiKey { get; } // TODO: Recommend renaming. Because ApiKey is used as the name of the API key service, you have to access the actual configured API key via ApiKeyInUse + public ApiKeyService ApiKey => new ApiKeyService(this); // TODO: Recommend renaming. Because ApiKey is used as the name of the API key service, you have to access the actual configured API key via ApiKeyInUse /// /// Access Batch-related functionality. /// - public BatchService Batch { get; } - - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once UnusedAutoPropertyAccessor.Global + public BatchService Batch => new BatchService(this); /// /// Access beta functionality. @@ -34,208 +31,131 @@ public class Client : EasyPostClient /// /// Access Billing-related functionality. /// - public BillingService Billing { get; } + public BillingService Billing => new BillingService(this); /// /// Access Carrier Account-related functionality. /// - public CarrierAccountService CarrierAccount { get; } + public CarrierAccountService CarrierAccount => new CarrierAccountService(this); /// /// Access Carrier Metadata-related functionality. /// - public CarrierMetadataService CarrierMetadata { get; } + public CarrierMetadataService CarrierMetadata => new CarrierMetadataService(this); /// /// Access Carrier Type-related functionality. /// - public CarrierTypeService CarrierType { get; } + public CarrierTypeService CarrierType => new CarrierTypeService(this); /// /// Access Claim-related functionality. /// - public ClaimService Claim { get; } + public ClaimService Claim => new ClaimService(this); /// /// Access Customs Info-related functionality. /// - public CustomsInfoService CustomsInfo { get; } + public CustomsInfoService CustomsInfo => new CustomsInfoService(this); /// /// Access Customs Item-related functionality. /// - public CustomsItemService CustomsItem { get; } + public CustomsItemService CustomsItem => new CustomsItemService(this); /// /// Access EndShipper-related functionality. /// - public EndShipperService EndShipper { get; } + public EndShipperService EndShipper => new EndShipperService(this); /// /// Access Event-related functionality. /// - public EventService Event { get; } + public EventService Event => new EventService(this); /// /// Access Insurance-related functionality. /// - public InsuranceService Insurance { get; } + public InsuranceService Insurance => new InsuranceService(this); /// /// Access Luma-related functionality. /// - public LumaService Luma { get; } + public LumaService Luma => new LumaService(this); /// /// Access Order-related functionality. /// - public OrderService Order { get; } + public OrderService Order => new OrderService(this); /// /// Access Parcel-related functionality. /// - public ParcelService Parcel { get; } + public ParcelService Parcel => new ParcelService(this); /// /// Access Pickup-related functionality. /// - public PickupService Pickup { get; } + public PickupService Pickup => new PickupService(this); /// /// Access Rate-related functionality. /// - public RateService Rate { get; } + public RateService Rate => new RateService(this); /// /// Access Referral Customer-related functionality. /// - public ReferralCustomerService ReferralCustomer { get; } + public ReferralCustomerService ReferralCustomer => new ReferralCustomerService(this); /// /// Access Refund-related functionality. /// - public RefundService Refund { get; } + public RefundService Refund => new RefundService(this); /// /// Access Report-related functionality. /// - public ReportService Report { get; } + public ReportService Report => new ReportService(this); /// /// Access ScanForm-related functionality. /// - public ScanFormService ScanForm { get; } + public ScanFormService ScanForm => new ScanFormService(this); /// /// Access Shipment-related functionality. /// - public ShipmentService Shipment { get; } + public ShipmentService Shipment => new ShipmentService(this); /// /// Access SmartRate-related functionality. /// - public SmartRateService SmartRate { get; } + public SmartRateService SmartRate => new SmartRateService(this); /// /// Access Tracker-related functionality. /// - public TrackerService Tracker { get; } + public TrackerService Tracker => new TrackerService(this); /// /// Access User-related functionality. /// - public UserService User { get; } + public UserService User => new UserService(this); /// /// Access Webhook-related functionality. /// - public WebhookService Webhook { get; } + public WebhookService Webhook => new WebhookService(this); /// /// Initializes a new instance of the class. /// /// for this client. -#pragma warning disable IDE0021 // Ignoring since more properties will be added during construction in the future. public Client(ClientConfiguration configuration) : base(configuration) -#pragma warning restore IDE0021 { - // We initialize the services here since initializing a new one on each property call is expensive. - Address = new AddressService(this); - ApiKey = new ApiKeyService(this); - Batch = new BatchService(this); - Billing = new BillingService(this); - CarrierAccount = new CarrierAccountService(this); - CarrierMetadata = new CarrierMetadataService(this); - CarrierType = new CarrierTypeService(this); - Claim = new ClaimService(this); - CustomsInfo = new CustomsInfoService(this); - CustomsItem = new CustomsItemService(this); - EndShipper = new EndShipperService(this); - Event = new EventService(this); - Insurance = new InsuranceService(this); - Luma = new LumaService(this); - Order = new OrderService(this); - Parcel = new ParcelService(this); - Pickup = new PickupService(this); - Rate = new RateService(this); - ReferralCustomer = new ReferralCustomerService(this); - Refund = new RefundService(this); - Report = new ReportService(this); - ScanForm = new ScanFormService(this); - Shipment = new ShipmentService(this); - SmartRate = new SmartRateService(this); - Tracker = new TrackerService(this); - User = new UserService(this); - Webhook = new WebhookService(this); - - // We go ahead and initialize the Beta client internally here as well Beta = new BetaClient(configuration); } - - /// - protected override void Dispose(bool disposing) - { - if (!disposing) return; - - // Dispose managed state (managed objects) - - // "disposing" inherently true when called from Dispose(), so don't need to pass it in. - - // Attempt to dispose of the services (some may already be disposed) - Address.Dispose(); - ApiKey.Dispose(); - Batch.Dispose(); - Billing.Dispose(); - CarrierAccount.Dispose(); - CarrierMetadata.Dispose(); - CarrierType.Dispose(); - Claim.Dispose(); - CustomsInfo.Dispose(); - CustomsItem.Dispose(); - EndShipper.Dispose(); - Event.Dispose(); - Insurance.Dispose(); - Luma.Dispose(); - Order.Dispose(); - Parcel.Dispose(); - Pickup.Dispose(); - Rate.Dispose(); - ReferralCustomer.Dispose(); - Refund.Dispose(); - Report.Dispose(); - ScanForm.Dispose(); - Shipment.Dispose(); - SmartRate.Dispose(); - Tracker.Dispose(); - User.Dispose(); - Webhook.Dispose(); - - // Attempt to dispose of the Beta client (may already be disposed) - Beta.Dispose(); - - // Dispose of the base client - base.Dispose(disposing); - } } } diff --git a/EasyPost/ClientConfiguration.cs b/EasyPost/ClientConfiguration.cs index 150cbd2ee..0675c7493 100644 --- a/EasyPost/ClientConfiguration.cs +++ b/EasyPost/ClientConfiguration.cs @@ -11,7 +11,7 @@ namespace EasyPost; /// Provides configuration options for the REST client used by the SDK. Used internally to store API key and other configuration. /// // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global -public class ClientConfiguration : IDisposable +public class ClientConfiguration { /// /// The API base URI. @@ -133,40 +133,4 @@ internal void SetUp() [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode", Justification = "ApiBase and Timeout will never change after construction")] public override int GetHashCode() => ApiKey.GetHashCode() ^ ApiBase.GetHashCode() ^ Timeout.GetHashCode(); #pragma warning restore CA1307 - - /// - private bool _isDisposed; - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (!disposing || _isDisposed) return; - - // Set the disposed flag to true before disposing of the object to avoid infinite loops - _isDisposed = true; - - // Dispose managed state (managed objects) - - // Attempt to dispose of the prepared HTTP client (may already be disposed) - PreparedHttpClient?.Dispose(); - - // Attempt to dispose of the user-provided HTTP client (may already be disposed) - CustomHttpClient?.Dispose(); - } - - /// - /// Finalizes an instance of the class. - /// - ~ClientConfiguration() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(disposing: false); - } } diff --git a/EasyPost/Http/Request.cs b/EasyPost/Http/Request.cs index 3e909545b..18cf9dd4e 100644 --- a/EasyPost/Http/Request.cs +++ b/EasyPost/Http/Request.cs @@ -11,13 +11,11 @@ namespace EasyPost.Http { #pragma warning disable CA1001 -#pragma warning disable CA1852 // Cannot be sealed because need virtual Dispose(bool) /// /// Represents an HTTP request being sent to the EasyPost API. /// - internal class Request : IDisposable + internal class Request #pragma warning restore CA1001 -#pragma warning restore CA1852 { /// /// The being executed by an . @@ -175,38 +173,5 @@ private static List AddListQueryParameter(List pairs, string key return pairs; } - - /// - private bool _isDisposed; - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (!disposing || _isDisposed) return; - - // Set the disposed flag to true before disposing of the object to avoid infinite loops - _isDisposed = true; - - // Dispose managed state (managed objects) - - // Dispose the request message - _requestMessage.Dispose(); - } - - /// - /// Finalizes an instance of the class. - /// - ~Request() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(disposing: false); - } } } diff --git a/EasyPost/Parameters/Tracker/Batch.cs b/EasyPost/Parameters/Tracker/Batch.cs new file mode 100644 index 000000000..93b0cc5c7 --- /dev/null +++ b/EasyPost/Parameters/Tracker/Batch.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using EasyPost.Utilities.Internal.Attributes; +using EasyPost.Utilities.Internal.Extensions; + +namespace EasyPost.Parameters.Tracker +{ + /// + /// Parameters for API calls. + /// + [ExcludeFromCodeCoverage] + public class Batch : BaseAllParameters + { + #region Request Parameters + + /// + /// Only records created after the given ID will be included. May not be used with . + /// + [TopLevelRequestParameter(Necessity.Optional, "after_id")] + public string? AfterId { get; set; } + + /// + /// Only records created before the given ID will be included. May not be used with . + /// + [TopLevelRequestParameter(Necessity.Optional, "before_id")] + public string? BeforeId { get; set; } + + /// + /// Only return records created before this timestamp. Defaults to 1 month ago, or 1 month before a passed . + /// + [TopLevelRequestParameter(Necessity.Optional, "end_datetime")] + public string? EndDatetime { get; set; } + + /// + /// The number of records to return on each page. The maximum value is 100, and default is 20. + /// + [TopLevelRequestParameter(Necessity.Optional, "page_size")] + public int? PageSize { get; set; } + + /// + /// Only return records created after this timestamp. Defaults to 1 month ago, or 1 month before a passed . + /// + [TopLevelRequestParameter(Necessity.Optional, "start_datetime")] + public string? StartDatetime { get; set; } + + /// + /// Only return trackers with the given carrier. + /// + [TopLevelRequestParameter(Necessity.Optional, "carrier")] + public string? Carrier { get; set; } + + /// + /// Only return trackers with the given tracking codes. + /// + [TopLevelRequestParameter(Necessity.Optional, "tracking_codes")] + public List? TrackingCodes { get; set; } + + #endregion + + /// + /// Convert a dictionary into this parameter set. + /// + /// Dictionary to parse. + /// An parameters set. + public static new Batch FromDictionary(Dictionary? dictionary) + { + if (dictionary == null) return new Batch(); + return new Batch + { + PageSize = dictionary.GetOrNullInt("page_size"), + BeforeId = dictionary.GetOrNull("before_id"), + AfterId = dictionary.GetOrNull("after_id"), + StartDatetime = dictionary.GetOrNull("start_datetime"), + EndDatetime = dictionary.GetOrNull("end_datetime"), + Carrier = dictionary.GetOrNull("carrier"), + TrackingCodes = dictionary.GetOrNull>("tracking_codes"), + }; + } + } +} diff --git a/EasyPost/Services/ReferralCustomerService.cs b/EasyPost/Services/ReferralCustomerService.cs index 48dc2bfbc..5d12becd5 100644 --- a/EasyPost/Services/ReferralCustomerService.cs +++ b/EasyPost/Services/ReferralCustomerService.cs @@ -359,10 +359,6 @@ private async Task CreateStripeToken(string number, int expirationMonth, #endif Dictionary data = JsonSerialization.ConvertJsonToObject>(content); - // Dispose of the request and response - request.Dispose(); - response.Dispose(); - data.TryGetValue("id", out object? id); return id == null ? throw new ExternalApiError("Could not send card details to Stripe, please try again later.", (int)response.StatusCode) diff --git a/EasyPost/Services/TrackerService.cs b/EasyPost/Services/TrackerService.cs index 0eccc9ed2..64259d62b 100644 --- a/EasyPost/Services/TrackerService.cs +++ b/EasyPost/Services/TrackerService.cs @@ -121,9 +121,9 @@ public async Task All(Parameters.Tracker.All parameters, Canc /// to use for the HTTP request. /// A instance. [CrudOperations.Read] - public async Task RetrieveBatch(Dictionary? parameters = null, CancellationToken cancellationToken = default) + public async Task RetrieveBatch(Parameters.Tracker.Batch parameters, CancellationToken cancellationToken = default) { - TrackerCollection collection = await RequestAsync(Method.Post, "trackers/batch", cancellationToken, parameters); + TrackerCollection collection = await RequestAsync(Method.Post, "trackers/batch", cancellationToken, parameters.ToDictionary()); return collection; } diff --git a/EasyPost/_base/EasyPostClient.cs b/EasyPost/_base/EasyPostClient.cs index 21fbb9c6a..c95910572 100644 --- a/EasyPost/_base/EasyPostClient.cs +++ b/EasyPost/_base/EasyPostClient.cs @@ -16,7 +16,7 @@ namespace EasyPost._base /// /// The base class for all EasyPost clients (i.e. and ). /// - public abstract class EasyPostClient : IDisposable + public abstract class EasyPostClient { /// /// The configuration for this client. This is immutable once set and is not accessible to end-users. @@ -145,10 +145,6 @@ public async Task RequestAsync(Method method, string endpoint, ApiVersion // Deserialize the response into an object T resource = await JsonSerialization.ConvertJsonToObject(response, null, rootElements); - // Dispose of the request and response - request.Dispose(); - response.Dispose(); - #pragma warning disable IDE0270 // Simplify null check if (resource is null) { @@ -182,10 +178,6 @@ public async Task RequestAsync(Method method, string endpoint, ApiVersion // Check whether the HTTP request produced an error (3xx, 4xx or 5xx status code) or not bool errorRaised = response.ReturnedNoError(); - // Dispose of the request and response - request.Dispose(); - response.Dispose(); - return errorRaised; } @@ -201,45 +193,5 @@ public async Task RequestAsync(Method method, string endpoint, ApiVersion /// /// The hash code for this client. public override int GetHashCode() => _configuration.GetHashCode(); - - /// - /// Whether this object has been disposed or not. - /// - private bool _isDisposed; - - /// - /// Dispose of this object. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose of this object. - /// - /// Whether this object is being disposed. - protected virtual void Dispose(bool disposing) - { - if (!disposing || _isDisposed) return; - - // Set the disposed flag to true before disposing of the object to avoid infinite loops - _isDisposed = true; - - // Dispose managed state (managed objects) - - // Dispose of the configuration - _configuration.Dispose(); - } - - /// - /// Finalizes an instance of the class. - /// - ~EasyPostClient() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(disposing: false); - } } } diff --git a/EasyPost/_base/EasyPostService.cs b/EasyPost/_base/EasyPostService.cs index 9f58c741e..61890f43d 100644 --- a/EasyPost/_base/EasyPostService.cs +++ b/EasyPost/_base/EasyPostService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -10,7 +9,7 @@ namespace EasyPost._base /// /// Base class for all EasyPost services (collection of related methods). /// - public abstract class EasyPostService : IEasyPostService, IDisposable + public abstract class EasyPostService : IEasyPostService { /// /// The that this service will use to make API requests. @@ -52,38 +51,6 @@ protected async Task RequestAsync(Http.Method method, string endpoint, Can /// None. // ReSharper disable once MemberCanBePrivate.Global protected async Task RequestAsync(Http.Method method, string endpoint, CancellationToken cancellationToken, Dictionary? parameters = null, ApiVersion? overrideApiVersion = null) => await Client.RequestAsync(method, endpoint, overrideApiVersion ?? ApiVersion.Current, cancellationToken, parameters).ConfigureAwait(false); - - /// - private bool _isDisposed; - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (!disposing || _isDisposed) return; - - // Set the disposed flag to true before disposing of the object to avoid infinite loops - _isDisposed = true; - - // Dispose managed state (managed objects) - - // Don't dispose of the associated client here, as it may be shared among multiple services - } - - /// - /// Finalizes an instance of the class. - /// - ~EasyPostService() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(disposing: false); - } } /// From d4bbe12e7795b5240fd86deb26186e7cf92b4246 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:24:13 -0700 Subject: [PATCH 2/4] fix: lint --- EasyPost/Http/Request.cs | 2 +- EasyPost/Utilities/Internal/Extensions/Dictionaries.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/EasyPost/Http/Request.cs b/EasyPost/Http/Request.cs index 18cf9dd4e..491c25be4 100644 --- a/EasyPost/Http/Request.cs +++ b/EasyPost/Http/Request.cs @@ -14,7 +14,7 @@ namespace EasyPost.Http /// /// Represents an HTTP request being sent to the EasyPost API. /// - internal class Request + internal sealed class Request #pragma warning restore CA1001 { /// diff --git a/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs b/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs index 045ae4014..a8c1c3719 100644 --- a/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs +++ b/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs @@ -100,7 +100,6 @@ public static void AddOrUpdate(this IDictionary dictionary, obj if (!dictionary.ContainsKey(key)) { var newDictionary = new Dictionary(); - newDictionary.AddOrUpdate(value, path); dictionary[key] = newDictionary; } #pragma warning restore CA1854 @@ -109,7 +108,6 @@ public static void AddOrUpdate(this IDictionary dictionary, obj if (subDirectory is Dictionary subDictionary) { subDictionary.AddOrUpdate(value, path); - dictionary[key] = subDictionary; } else { From 9988839ba9df12dfa57fd56aebbc0e6984ae81ef Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:05:28 -0700 Subject: [PATCH 3/4] chore: put back code --- EasyPost/Utilities/Internal/Extensions/Dictionaries.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs b/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs index a8c1c3719..045ae4014 100644 --- a/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs +++ b/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs @@ -100,6 +100,7 @@ public static void AddOrUpdate(this IDictionary dictionary, obj if (!dictionary.ContainsKey(key)) { var newDictionary = new Dictionary(); + newDictionary.AddOrUpdate(value, path); dictionary[key] = newDictionary; } #pragma warning restore CA1854 @@ -108,6 +109,7 @@ public static void AddOrUpdate(this IDictionary dictionary, obj if (subDirectory is Dictionary subDictionary) { subDictionary.AddOrUpdate(value, path); + dictionary[key] = subDictionary; } else { From 5f210ab11d988ba2abc0f3f8064fed088d4a6a04 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:34:35 -0700 Subject: [PATCH 4/4] fix: compiler errors for dictionaries --- EasyPost/Utilities/Internal/Extensions/Dictionaries.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs b/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs index 045ae4014..3d3277485 100644 --- a/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs +++ b/EasyPost/Utilities/Internal/Extensions/Dictionaries.cs @@ -142,8 +142,9 @@ internal static Dictionary MergeIn(this DictionaryDictionary to wrap. /// Path of keys to wrap the parameters in. /// A wrapped dictionary. - internal static Dictionary Wrap(this Dictionary dictionary, params string[] keys) => keys.Reverse() - .Aggregate(dictionary, (current, key) => new Dictionary { { key, current } }); + internal static Dictionary Wrap(this Dictionary dictionary, params string[] keys) => ((IEnumerable)keys) + .Reverse() + .Aggregate(dictionary, (current, key) => new Dictionary { { key, current } }); /// /// Wrap a list into a larger dictionary. @@ -155,9 +156,9 @@ internal static Dictionary Wrap(this Dictionary /// A wrapped dictionary. internal static Dictionary Wrap(this List list, params string[] keys) { - string firstKey = keys.Reverse().First(); + string firstKey = ((IEnumerable)keys).Reverse().First(); Dictionary dictionary = new() { { firstKey, list } }; - return keys.Reverse().Skip(1).Aggregate(dictionary, (current, key) => new Dictionary { { key, current } }); + return ((IEnumerable)keys).Reverse().Skip(1).Aggregate(dictionary, (current, key) => new Dictionary { { key, current } }); } ///