-
Notifications
You must be signed in to change notification settings - Fork 53
Description
Context
Related to #1882 (Gaps identified relative to js-sdk reference implementation).
When using FirstSuccessfulStrategy with MultiProvider, if all providers fail, the user receives only a generic error:
errorCode: GENERAL
errorMessage: "No provider successfully responded"
There is no information about which providers failed, why they failed, or what error codes each provider returned. Exceptions thrown by individual providers are caught and silently swallowed. Error-coded results from providers are discarded without any record.
This makes debugging multi-provider setups very difficult, especially in production where the root cause may differ across providers (e.g., one provider has a connection timeout, another has a parse error, a third returns flag-not-found).
FirstMatchStrategy has the same issue in its all-FLAG_NOT_FOUND fallback path, though it is less impactful since it only skips FLAG_NOT_FOUND errors.
Current Behavior
FirstSuccessfulStrategy
FirstSuccessfulStrategy.java:30-41:
for (FeatureProvider provider : providers.values()) {
try {
ProviderEvaluation<T> res = providerFunction.apply(provider);
if (res.getErrorCode() == null) {
return res;
}
// error-coded result is silently discarded here
} catch (Exception ignored) {
// thrown exception is silently swallowed here
}
}
return ProviderEvaluation.<T>builder()
.errorMessage("No provider successfully responded")
.errorCode(ErrorCode.GENERAL)
.build();Both paths (error-coded results and thrown exceptions) are discarded with no tracking. The final fallback result carries no per-provider information.
FirstMatchStrategy
FirstMatchStrategy.java:39-61 -- similarly discards individual FLAG_NOT_FOUND errors without collecting them:
return ProviderEvaluation.<T>builder()
.errorMessage("Flag not found in any provider")
.errorCode(FLAG_NOT_FOUND)
.build();Expected Behavior (js-sdk reference)
The js-sdk reference implementation handles this correctly:
-
Every provider error is captured -- both thrown exceptions (stored as
{ thrownError }) and error-coded results are tracked in aresolutions[]array during iteration. -
Errors are aggregated on all-fail -- When all providers fail,
collectProviderErrors()iterates all resolutions and builds an array of{ providerName, error }for each failed provider. -
User receives an
AggregateError-- TheMultiProviderthrows anAggregateErrorthat includes:.message-- contains the first error's provider name and message for convenience.originalErrors[]-- array of{ source: providerName, error }for every failed provider
Example of what a js-sdk user sees when 3 providers fail:
AggregateError: Provider errors occurred: ProviderA: Connection timeout
originalErrors: [
{ source: "ProviderA", error: ConnectionTimeoutError },
{ source: "ProviderB", error: ParseError },
{ source: "ProviderC", error: FlagNotFoundError }
]
Versus what a java-sdk user sees for the same scenario:
errorCode: GENERAL
errorMessage: "No provider successfully responded"
Proposed Solution
-
Collect per-provider errors during iteration -- Both
FirstSuccessfulStrategyandFirstMatchStrategyshould accumulate error details (provider name, error code, error message, original exception) as they iterate through providers. -
Expose per-provider errors in the result -- Add a
List<ProviderError>field toProviderEvaluation(and propagate through toFlagEvaluationDetails) so that on an all-fail result, the user can programmatically inspect each provider's error. AProviderErrortype would carry:providerName(String)errorCode(ErrorCode)errorMessage(String)exception(Exception, nullable)
-
Build a descriptive aggregate error message -- The
errorMessageon the finalProviderEvaluationshould summarize the per-provider errors, e.g.:No provider successfully responded. Provider errors: [provider1: PARSE_ERROR (parse failed), provider2: GENERAL (connection timeout)] -
Ensure the
Strategyinterface contract is backwards-compatible -- TheStrategyinterface itself doesn't change. TheProviderEvaluationfield would be additive (defaults to an empty list).
References
- js-sdk
FirstSuccessfulStrategy:first-successful-strategy.ts - js-sdk
BaseEvaluationStrategy.collectProviderErrors():base-evaluation-strategy.ts#L130-L145 - js-sdk
AggregateError/constructAggregateError:errors.ts - js-sdk
MultiProvidererror propagation:multi-provider.ts#L194-L202 - Related tracking issue: [Multi-provider] Gaps identified relative to js-sdk reference implementation #1882
- Multi-provider spec: https://openfeature.dev/specification/appendix-a/#multi-provider