Skip to content

Implement StrategyProvider - Jules#56

Closed
MessiasLima wants to merge 4 commits into
mainfrom
feature/strategy-provider-implementation-13573005225355871000
Closed

Implement StrategyProvider - Jules#56
MessiasLima wants to merge 4 commits into
mainfrom
feature/strategy-provider-implementation-13573005225355871000

Conversation

@MessiasLima

Copy link
Copy Markdown
Owner

This change decouples strategy definitions from SomeConfig and the resolver chain by introducing a StrategyProvider. Users can now register custom strategies using strategy(MyStrategy()) and resolvers retrieve them via strategyProvider[MyStrategy::class]. This removes the maintenance bottleneck where every new strategy required manual wiring in multiple files. Additionally, register() was renamed to factory() to better differentiate it from strategy registration.

Fixes #35


PR created automatically by Jules for task 13573005225355871000 started by @MessiasLima

- Introduced Strategy marker interface and StrategyProvider interface.
- Refactored SomeConfig to implement StrategyProvider using an internal map.
- Updated SomeConfigBuilder to support strategy() registration using an inline reified function.
- Renamed SomeConfigBuilder.register() to factory() and provided a deprecated alias for backward compatibility.
- Updated FixtureContext and ResolverChain to consume StrategyProvider instead of individual strategies.
- Updated all strategy-consuming resolvers (Nullable, String, List, Set, Map, Array) and ClassResolver to use the new provider pattern.
- Updated all unit and integration tests to reflect API changes.
- Updated documentation and KDocs to match the new implementation.

Co-authored-by: MessiasLima <10220064+MessiasLima@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the configuration system to use a dynamic strategy registry instead of hardcoded strategy fields. It introduces a Strategy marker interface and a StrategyProvider interface, updating SomeConfig, SomeConfigBuilder, and all resolvers to fetch strategies dynamically. Additionally, register is deprecated in favor of factory in SomeConfigBuilder. The review feedback highlights two important points: first, a critical bug in SomeConfigBuilder.strategy where registering a strategy with a specific subclass registers it under the subclass key instead of the base strategy interface key, causing resolvers to ignore it; second, an improvement in SomeConfig.get to separate the null check from the type cast to ensure clearer error messages in case of type mismatches.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +67 to +69
inline fun <reified T : Strategy> strategy(strategy: T) {
_strategies[T::class] = strategy
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When registering a strategy using strategy(MyStrategy()) or strategy(NullableStrategy.AlwaysNull), the reified type T is inferred as the specific subclass/implementation type (e.g., NullableStrategy.AlwaysNull). This registers the strategy in _strategies under the key NullableStrategy.AlwaysNull::class.

However, the built-in resolvers (like NullableResolver, StringResolver, etc.) query the StrategyProvider using the base strategy interface class (e.g., NullableStrategy::class). Since the base interface key in the map is not updated, the custom strategy is completely ignored by the resolvers, and they continue to use the default strategy.

To fix this, we should also update any keys in _strategies that are supertypes of the registered strategy (i.e., where key.isInstance(strategy) is true).

    inline fun <reified T : Strategy> strategy(strategy: T) {
        _strategies[T::class] = strategy
        _strategies.keys.toList().forEach { key ->
            if (key.isInstance(strategy)) {
                _strategies[key] = strategy
            }
        }
    }

Comment on lines +70 to +74
@Suppress("UNCHECKED_CAST")
override fun <T : Strategy> get(key: KClass<T>): T {
return strategies[key] as? T
?: throw IllegalArgumentException("Strategy ${key.simpleName} not registered")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using as? T and throwing a "not registered" exception can be misleading if the strategy is registered but the cast fails due to a type mismatch. Performing an explicit null check first allows us to throw the correct IllegalArgumentException for missing strategies, while letting the cast throw a ClassCastException if there is an unexpected type mismatch.

Suggested change
@Suppress("UNCHECKED_CAST")
override fun <T : Strategy> get(key: KClass<T>): T {
return strategies[key] as? T
?: throw IllegalArgumentException("Strategy ${key.simpleName} not registered")
}
@Suppress("UNCHECKED_CAST")
override fun <T : Strategy> get(key: KClass<T>): T {
val strategy = strategies[key] ?: throw IllegalArgumentException("Strategy ${key.simpleName} not registered")
return strategy as T
}

- Introduced Strategy marker interface and StrategyProvider interface.
- Refactored SomeConfig to implement StrategyProvider using an internal map.
- Updated SomeConfigBuilder to support strategy() registration using an inline reified function.
- Renamed SomeConfigBuilder.register() to factory() and provided a deprecated alias for backward compatibility.
- Updated FixtureContext and ResolverChain to consume StrategyProvider instead of individual strategies.
- Updated all strategy-consuming resolvers (Nullable, String, List, Set, Map, Array) and ClassResolver to use the new provider pattern.
- Updated all unit and integration tests to reflect API changes.
- Updated documentation and KDocs to match the new implementation.
- Fixed Detekt naming convention issue in SomeConfigBuilder.

Co-authored-by: MessiasLima <10220064+MessiasLima@users.noreply.github.com>
@MessiasLima MessiasLima changed the title Implement StrategyProvider-based strategy provision Implement StrategyProvider - Jules Jun 12, 2026
…entation-13573005225355871000' into feature/strategy-provider-implementation-13573005225355871000
@MessiasLima MessiasLima deleted the feature/strategy-provider-implementation-13573005225355871000 branch June 12, 2026 10:53
@github-project-automation github-project-automation Bot moved this from Ready to Done in Some Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Implement StrategyProvider-based strategy provision for SomeConfig

1 participant