feat(opensearch): configureSettings escape hatch on both client factories - #123
Merged
Merged
Conversation
bfarmer67
force-pushed
the
devs/bfarmer/ledger-field-name-contract
branch
from
August 26, 2026 17:31
d9529ee to
c0b6a9c
Compare
bfarmer67
force-pushed
the
devs/bfarmer/opensearch-connection-settings-hook
branch
2 times, most recently
from
August 26, 2026 19:36
2f23076 to
83bc0ba
Compare
bfarmer67
force-pushed
the
devs/bfarmer/ledger-field-name-contract
branch
from
August 26, 2026 21:52
c0b6a9c to
5a99951
Compare
bfarmer67
force-pushed
the
devs/bfarmer/opensearch-connection-settings-hook
branch
from
August 26, 2026 21:52
83bc0ba to
b32e1e5
Compare
…ries AddOpenSearchClient and AddOpenSearchAwsClient (and both IConfiguration overloads) take an optional trailing Action<ConnectionSettings>, applied LAST -- after the endpoint and authentication wiring -- so a consumer can override anything the library set. All parameters are optional and trailing, so every existing call site compiles unchanged. OpenSearch is the only provider whose client the library constructs. The other four resolve a consumer-registered client and already have full control. Until now the only way to reach ConnectionSettings -- for RequestTimeout, MaximumRetries, EnableHttpCompression, a proxy, ServerCertificateValidationCallback on a self-signed development cluster, DisableDirectStreaming while debugging, or a DefaultMappingFor over the consumer's OWN document types -- was to stop calling the factory and hand-roll the registration. That forks the auth-mode switch, the certificate loader, the AWS-endpoint loud-fail, and the mutual-exclusion guard, and the fork then silently misses every later fix to any of them. Both packages route through one internal BuildClient so the hook and its validation behave identically on both registration paths. Because the hook makes it reachable through supported API, registration probes one ledger property through the configured inferrer and loud-fails when field-name inference is no longer camelCase. The ledger index carries a strict camelCase mapping, so such a change breaks every ledger write -- loud on its own, but arriving at first write naming fields rather than the cause. Validation runs only when a hook is supplied. This is deliberately NOT the remedy for the _mget defect fixed earlier in this stack. Making correct operation depend on a consumer-declared mapping for a library-internal type inverts ownership; see ADR-0029. The hook is worth having on its own merits, for consumer-owned concerns. No symmetric change for the other four providers: they do not construct clients, so there is nothing to open up. Per ADR-0030. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bfarmer67
force-pushed
the
devs/bfarmer/opensearch-connection-settings-hook
branch
from
August 26, 2026 21:53
b32e1e5 to
524d587
Compare
bfarmer67
changed the base branch from
devs/bfarmer/ledger-field-name-contract
to
main
August 26, 2026 21:53
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.
Closes a real consumer gap: there was no way to reach
ConnectionSettings, so the only escape was to stop callingAddOpenSearchAwsClientand fork it.API
Both
IConfigurationoverloads get the same treatment.Purely additive: source- AND binary-compatible with 3.1.x. This ships as four new overloads, not as an appended optional parameter. Appending one is source-compatible but not binary-compatible - it changes the existing method's signature, so the 3.1.x entry point stops existing and anything compiled against it throws
MissingMethodExceptionuntil recompiled. This library claims SemVer; a minor release must not do that.The four 3.1.x signatures are preserved exactly and forward to the new ones. Their
= nulldefaults move to the new overloads, which is not a signature change (a default is parameter metadata, and a 3.1.x caller that omitted the argument already baked the null into its own call site) and is what keeps the pair unambiguous:Two reflection tests pin all eight signatures so the guarantee cannot regress silently. Reflection rather than call sites on purpose - a source-level call binds happily to a widened signature and proves nothing.
Applied last, after the endpoint and auth wiring. An escape hatch the library can silently overwrite is not an escape hatch; there's a test asserting a consumer
BasicAuthenticationbeats the library's.Action<ConnectionSettings>rather thanFunc<...>:ConnectionSettingsmutates in place and returns itself, so a required return adds only a way to get it wrong — andAction<TOptions>is the configure-callback idiom already used throughout these extensions.Both packages route through one internal
BuildClient, so the hook and its validation behave identically on both paths. That needsInternalsVisibleTofrom core to.Aws.Why this is not the
_mgetfixWorth being explicit since this PR exists because of that bug. Fixing #121 by having consumers declare
DefaultMappingFor<OpenSearchMigrationRecord>would make correct operation opt-in and push a library-internal type into consumer wiring. #121 fixes it properly; this is a separate capability for consumer-owned concerns. Recorded in ADR-0030 with ADR-0029 as the counterweight.The guardrail
The hook introduces one real foot-gun, so it ships with the safety.
The ledger index is created with a
strictmapping using camelCase fields, matching the client's default field-name inference. A hook that replaces the serializer or sets a client-wide non-camelCaseDefaultFieldNameInferrerbreaks every ledger write. That failure is loud on its own —strict_dynamic_mapping_exception— but it arrives at first write, names fields rather than the cause, and reads like a schema problem.Registration probes one known ledger property through the configured inferrer and fails with the cause and the remediation:
One probe is enough — field-name inference is a single client-wide setting. Validation runs only when a hook is supplied, so the default path costs nothing.
I deliberately did not extend the validation to the whole class of ledger-adjacent settings (serializer,
DefaultIndex,IConnection). Only field-name inference actually breaks the ledger;DefaultIndexis legitimate and #121 already makes the ledger immune to index inference. ReplacingIConnectionon the SigV4 path removes request signing — documented on the parameter rather than validated, since enumerating every way to misconfigure a transport isn't tractable.Tests
OpenSearchConnectionSettingsHookTests(9):BasicAuthenticationwins)IConfigurationoverload forwards itDefaultMappingFor<ConsumerDocument>passesDefaultIndexdoes not become the ledger's indexFull suite: 450 + 889 + 38 pass (unit tier was 433 + 889 + 38 on
main). Whole solution builds clean on net8/9/10.Docs
ADR-0030,
docs/site/opensearch.md("Tuning the client"), and the.Awspackage README.Not doing: symmetric hooks on the other providers
I looked, and the symmetry argument doesn't hold. Aerospike (
IAsyncClient), Couchbase (IClusterProvider), MongoDB (IMongoClient), and Postgres (NpgsqlDataSource) all resolve a client the consumer registered — they already have 100% control, and there's nothing to open up. OpenSearch is the outlier because it's the only one that constructs a client. Adding factories elsewhere to make the hook symmetric would be inventing the problem to justify the solution.The thing that did generalize was the underlying coupling, and that's #122.
🤖 Generated with Claude Code