Description
Currently SomeConfig.buildResolvers() (SomeConfig.kt:71) contains a hardcoded listOf(...) of 30 resolvers — the only way to add a new resolver is to modify the core library. Ther is no mechanism for external packages (e.g., a hypothetical some-android) to contribute resolvers without requiring users to write someSetup {} boilerplate.
This feature adds a java.util.ServiceLoader-based discovery mechanism so that third-party packages can provide resolvers that are automatically picked up at runtime. Users get new resolvers by simply adding a dependency — no configuration required.
Technical approach:
-
Define a TypeResolverProvider interface in dev.appoutlet.some.core:
interface TypeResolverProvider {
fun createResolvers(
random: Random,
nullableStrategy: NullableStrategy,
stringStrategy: StringStrategy,
collectionStrategy: CollectionStrategy,
): List<TypeResolver>
}
-
Modify SomeConfig.buildResolvers() to call ServiceLoader.load(TypeResolverProvider::class.java) and flatten the resulting resolvers into the chain, inserted between the built-in resolvers and DataClassResolver (the catch-all fallback).
-
The some<T>() top-level convenience function uses defaultResolvers (lazily initialized via SomeConfig().buildResolvers()), so it automatically picks up discovered resolvers with no user action.
-
External packages need only: (a) implement TypeResolverProvider, (b) add a META-INF/services/dev.appoutlet.some.core.TypeResolverProvider file listing the implementation class.
Key files involved:
src/main/kotlin/dev/appoutlet/some/config/SomeConfig.kt
src/main/kotlin/dev/appoutlet/some/Some.kt
src/main/kotlin/dev/appoutlet/some/core/ (new interface)
src/test/kotlin/dev/appoutlet/some/ (tests for discovery behavior)
Scope
- In scope: Define
TypeResolverProvider interface; wire ServiceLoader discovery into buildResolvers(); ensure some<T>() and someSetup {} both benefit; tests that verify third-party resolvers are discovered and invoked.
- Out of scope: Changing existing resolver constructors to be no-arg; annotation-processing-based discovery; classpath scanning; auto-publishing of
META-INF/services via annotation processing.
Acceptance criteria
Description
Currently
SomeConfig.buildResolvers()(SomeConfig.kt:71) contains a hardcodedlistOf(...)of 30 resolvers — the only way to add a new resolver is to modify the core library. Ther is no mechanism for external packages (e.g., a hypotheticalsome-android) to contribute resolvers without requiring users to writesomeSetup {}boilerplate.This feature adds a
java.util.ServiceLoader-based discovery mechanism so that third-party packages can provide resolvers that are automatically picked up at runtime. Users get new resolvers by simply adding a dependency — no configuration required.Technical approach:
Define a
TypeResolverProviderinterface indev.appoutlet.some.core:Modify
SomeConfig.buildResolvers()to callServiceLoader.load(TypeResolverProvider::class.java)and flatten the resulting resolvers into the chain, inserted between the built-in resolvers andDataClassResolver(the catch-all fallback).The
some<T>()top-level convenience function usesdefaultResolvers(lazily initialized viaSomeConfig().buildResolvers()), so it automatically picks up discovered resolvers with no user action.External packages need only: (a) implement
TypeResolverProvider, (b) add aMETA-INF/services/dev.appoutlet.some.core.TypeResolverProviderfile listing the implementation class.Key files involved:
src/main/kotlin/dev/appoutlet/some/config/SomeConfig.ktsrc/main/kotlin/dev/appoutlet/some/Some.ktsrc/main/kotlin/dev/appoutlet/some/core/(new interface)src/test/kotlin/dev/appoutlet/some/(tests for discovery behavior)Scope
TypeResolverProviderinterface; wireServiceLoaderdiscovery intobuildResolvers(); ensuresome<T>()andsomeSetup {}both benefit; tests that verify third-party resolvers are discovered and invoked.META-INF/servicesvia annotation processing.Acceptance criteria
GIVEN a third-party package on the classpath that provides a
TypeResolverProviderimplementation registered viaMETA-INF/servicesWHEN
some<T>()is called for a type handled by that providerTHEN the value is resolved and returned without any
someSetup {}configurationGIVEN a third-party package on the classpath that provides a
TypeResolverProviderimplementationWHEN
someSetup { }is used andsome<T>()is called for a type handled by that providerTHEN the value is resolved and returned
GIVEN a
SomeConfigwith a customregister<T>()factory for a type that a discovered resolver also handlesWHEN resolving that type
THEN
CustomFactoryResolvertakes precedence over the discovered resolver (existing first-match-wins semantics preserved)GIVEN no third-party packages on the classpath
WHEN
buildResolvers()is calledTHEN the resolver chain is identical to the current behavior (no resolvers added, no resolvers removed)
GIVEN an environment with ServiceLoader disabled or not available (e.g., some constrained runtimes)
WHEN
buildResolvers()is calledTHEN no exception is thrown and the chain falls back to only built-in resolvers
GIVEN a
TypeResolverProviderthat returns an empty listWHEN
buildResolvers()is calledTHEN the resolver chain contains only the built-in resolvers