Merged
Conversation
Owner
diomonogatari
commented
Feb 13, 2026
- Refactor JSON deserialization to eliminate reflection fallback, optimize performance by deserializing directly from streams, and migrate to FrozenDictionary for enum lookups.
- Introduce dedicated request DTOs for write operations, implement IDisposable for resource management, and enhance input validation across public API methods.
- Update method signatures to return IReadOnlyList for better performance and clarity.
- Add tracing support with OpenTelemetry and improve architectural tests.
- All changes maintain backward compatibility where possible and ensure comprehensive test coverage.
Remove DefaultJsonTypeInfoResolver from s_jsonOptions so deserialization uses only the source-generated BitbucketJsonContext. This ensures missing type registrations fail fast with NotSupportedException rather than silently falling back to slower reflection-based serialization. - Separate s_writeJsonOptions retains reflection fallback for outbound request bodies that use anonymous types (to be replaced with typed DTOs in a future change) - Register missing collection types in BitbucketJsonContext: Dictionary<string, BuildStats>, IEnumerable<KeyedUrl>, IEnumerable<User>, IEnumerable<DefaultReviewerPullRequestCondition>, IEnumerable<RefRestriction> - Fix direct JsonSerializer.Serialize calls in Branches and Ssh clients to use s_writeJsonOptions - Add SourceGenCoverageTests verifying all model types and PagedResults<T> instantiations are registered All 773 tests pass.
Replace string-based deserialization in ReadResponseContentAsync<T> with stream-based JsonSerializer.DeserializeAsync<T>. This avoids allocating an intermediate UTF-16 string for every API response, which is especially beneficial for large paged responses (100+ items). The contentHandler overload path still reads as string for non-JSON responses. Error handling continues to read the body as string since error bodies are small and need to be included in exception messages. All 773 tests pass.
- Convert all 25 Dictionary<TEnum, string> fields to FrozenDictionary for optimal read-only lookup performance - Add 14 reverse FrozenDictionary<string, TEnum> fields with StringComparer.OrdinalIgnoreCase for O(1) string-to-enum lookups - Replace O(n) FirstOrDefault reverse scans with O(1) TryGetValue - Add missing Global value to ScopeTypes enum (Bitbucket API returns GLOBAL for hooks configured at server level) - Improve XML documentation across all helper methods
- Add IDisposable with ownership tracking via _ownsClient flag - HttpClient constructor owns the FlurlClient wrapper and disposes it - IFlurlClient constructor does not own, disposal is a no-op - Basic/token auth constructors have no disposable resources - Add ObjectDisposedException guard in GetBaseUrl() to cover all API methods - Dispose is idempotent (safe to call multiple times) - Add 6 unit tests covering all constructor variants and disposal semantics
- Add ExecuteAsync<TResult>, ExecuteAsync (bool), and ExecuteWithNoContentAsync methods that unify HTTP call + error handling - New methods make it impossible to forget error handling in future code - Add architectural test verifying all BitbucketClient HTTP calls have corresponding error handlers (HandleResponseAsync, HandleErrorsAsync, ExecuteAsync, or explicit StatusCode checks) - Existing methods continue using HandleResponseAsync/HandleErrorsAsync; new methods should prefer ExecuteAsync for safety
BREAKING CHANGE: All buffered collection methods now return Task<IReadOnlyList<T>> instead of Task<IEnumerable<T>>. This accurately reflects that results are fully materialized and enables direct Count/indexer access without LINQ. - Change GetPagedResultsAsync<T> return type to IReadOnlyList<T> - Update ~70 public method signatures across all API domains - Methods not using GetPagedResultsAsync add .ToList() to convert IEnumerable<T> results to List<T> (implements IReadOnlyList<T>) - Streaming methods (IAsyncEnumerable<T>) are unaffected - Consumers using explicit IEnumerable<T> types will get compile errors; switch to var or IReadOnlyList<T>
- Add ArgumentException.ThrowIfNullOrWhiteSpace() guards on ~130 public methods for URL-path parameters - Validate projectKey, repositorySlug, commitId, hookKey, userSlug, scmId, loggerName and other path segment parameters - Cover methods that bypass guarded URL-builder helpers - Add InputValidationTests with parameterized theories - Prevents malformed URLs and confusing server-side errors
- Extract s_jsonOptions and s_writeJsonOptions initialization into CreateReadOptions() and CreateWriteOptions() factory methods - Call MakeReadOnly() after configuration to prevent accidental mutation from any thread - Add JsonSerializerOptions_AreExplicitlyFrozen architectural test - Update CHANGELOG.md
- Introduce EnumMap<TEnum> as single source of truth for all 25 enum-to-string mappings (BitbucketEnumMaps static registry) - Replace 13 individual converter subclasses with unified BitbucketEnumConverterFactory using generic JsonEnumConverter<TEnum> - Slim BitbucketHelpers.cs from ~1100 to ~490 lines; methods now delegate to EnumMap instances - Add public ToApiString() extension methods on all enum types - Update CHANGELOG.md
- Count await vs ConfigureAwait(false) calls per source file - Catches suppressions via #pragma or files excluded from analyzer - Update CHANGELOG.md
Convert { get; set; } to { get; init; } on 106 response model classes.
32 request body model files (98 properties) retain mutable setters.
Refactored BranchMetadata getter to use object initializer.
Breaking change: consumers that assign model properties after
construction must move to object-initializer syntax.
All 799 tests pass. Zero warnings.
Create purpose-built request DTOs for all write API methods, replacing reused response models and anonymous types. DTOs use required/init-only properties and expose only API-relevant fields. New types in Models/Core/Projects/Requests/: CreateProjectRequest, UpdateProjectRequest, CreateRepositoryRequest, ForkRepositoryRequest, CreatePullRequestRequest, UpdatePullRequestRequest, CreateBranchRequest, CreateTaskRequest, UpdateTaskRequest, CreateWebHookRequest, UpdateWebHookRequest, MergePullRequestRequest New type in Models/Builds/Requests/: AssociateBuildStatusRequest 13 method signatures updated. All tests updated to use new DTOs. Breaking change: method parameter types changed. All 799 tests pass. Zero warnings.
Introduce PullRequestQueryBuilder, CommitQueryBuilder, BranchQueryBuilder, and ProjectQueryBuilder with fluent API methods. Entry points: client.PullRequests(), client.Commits(), client.Branches(), client.Projects(). Each builder supports GetAsync() for buffered results and StreamAsync() for IAsyncEnumerable streaming. Builders delegate to existing flat methods. Existing flat methods are unchanged (additive, non-breaking). 12 new tests covering all builders. 811 total tests pass. Zero warnings.
Internal zero-allocation reader extracts pagination metadata (isLastPage, nextPageStart, start, limit, size) directly from UTF-8 bytes using Utf8JsonReader, skipping full deserialization. New files: Common/PagedResultsReader.cs, Common/PagedMetadata.cs PagedResultsReaderTests.cs (5 tests) PagedResultsReaderBenchmarks.cs (6 benchmarks) Added InternalsVisibleTo for test and benchmark projects. 816 tests pass. Zero warnings.
Add RetryAfter, RateLimit, RateLimitRemaining, and RateLimitReset properties to BitbucketRateLimitException, parsed from standard HTTP rate-limit response headers (Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset). - New Create overload on BitbucketApiException accepts HttpResponseHeaders - Graceful null on missing/unparseable headers - 7 new tests (5 unit + 2 mock) — 823 total pass - Updated CHANGELOG.md
- Version 0.2.0 -> 1.0.0 - Add PackageIcon with 128x128 placeholder (assets/icon.png) - Expand PackageTags: rest-api, api-client, atlassian, sdk, dotnet - Conditional icon inclusion (Condition=Exists) - Verified dotnet pack produces BitbucketServer.Net.1.0.0.nupkg - Updated CHANGELOG.md
Replace deprecated Polly v7 AddTransientHttpErrorPolicy with Polly v8 AddStandardResilienceHandler and custom AddResilienceHandler examples. - Standard resilience handler with retry, circuit breaker, and timeout - Custom resilience pipeline for fine-grained control over retried status codes - Requires Microsoft.Extensions.Http.Resilience NuGet package
- Define IBitbucketClient with all ~230 public method signatures - Update BitbucketClient to implement IBitbucketClient - Update query builders to accept IBitbucketClient instead of concrete type - Add NSubstitute 5.3.0 for mock-based testing - Add InterfaceTests verifying parity, obsolete attributes, and mockability - Update README DI examples to register IBitbucketClient
The parity and mockability tests were meta-assertions about the interface shape, not meaningful behavioral tests.
Add distributed tracing instrumentation using System.Diagnostics.Activity, following OTel stable HTTP client semantic conventions. No dependency on OpenTelemetry SDK — pure DiagnosticSource (best practice for libraries). - Create BitbucketClient.Tracing.cs partial class with ActivitySource - Hook BeforeCall/AfterCall/OnError via Flurl 4 event handlers on GetBaseUrl() - Tag spans with http.request.method, url.full, server.address, server.port, http.response.status_code, error.type, and custom bitbucket.project_key / bitbucket.repository_slug attributes - Set ActivityStatusCode.Error for 4xx/5xx responses - Record exception events with type and message on transport errors - Store Activity ref on HttpRequestMessage.Options (AsyncLocal does not propagate back from Flurl's async RaiseEventAsync) - Near-zero overhead when no listeners are subscribed - Add TracingMockTests with ActivityListener-based assertions
Delete BitbucketEnumExtensions (38 dead enum wrappers) and TypeExtensions (dead IsNullableType helper), along with their tests.
Delete 15 StringTo* methods (keep only StringToLogLevel which has callers), StringToBool, and 4 *ToString methods that had zero call sites. Remove corresponding tests.
Delete 3 private static overloads (ExecuteAsync<TResult>, ExecuteAsync, ExecuteWithNoContentAsync) with zero call sites. The stream-based Execute*StreamAsync methods remain in use.
Replace thin extension wrappers with direct BCL calls to DateTimeOffset.FromUnixTimeMilliseconds in the JSON converters. Delete UnixDateTimeExtensions.cs and its tests.
Introduce two convenience wrappers that absorb the repeated GET + deserialize lambda used by all paginated endpoints. The underlying GetPagedResultsAsync/StreamAsync loop methods remain unchanged.
Replace inline GET + deserialize lambdas in all 64 list and 18 stream paginated methods with calls to GetPagedAsync<T> and GetPagedStreamAsync<T>. Remove async from method signatures where the only await was the paged call.
Split the monolithic IBitbucketClient (287 methods) into 12 focused sub-interfaces co-located with their implementations: - IProjectOperations, IRepositoryOperations, IPullRequestOperations - ICommitOperations, IBranchOperations, IAdminOperations - ISshOperations, IRefRestrictionOperations, IBuildOperations - IGitOperations, ISearchOperations, IBitbucketMiscOperations IBitbucketClient becomes a composition root inheriting all sub-interfaces. Non-breaking change: all existing code using IBitbucketClient continues to work. Consumers can now depend on narrower interfaces (ISP).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.