Skip to content

Add ServiceLoader-based resolver discovery for third-party packages #36

Description

@MessiasLima

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:

  1. Define a TypeResolverProvider interface in dev.appoutlet.some.core:

    interface TypeResolverProvider {
        fun createResolvers(
            random: Random,
            nullableStrategy: NullableStrategy,
            stringStrategy: StringStrategy,
            collectionStrategy: CollectionStrategy,
        ): List<TypeResolver>
    }
    
  2. 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).

  3. 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.

  4. 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

  • GIVEN a third-party package on the classpath that provides a TypeResolverProvider implementation registered via META-INF/services
    WHEN some<T>() is called for a type handled by that provider
    THEN the value is resolved and returned without any someSetup {} configuration

  • GIVEN a third-party package on the classpath that provides a TypeResolverProvider implementation
    WHEN someSetup { } is used and some<T>() is called for a type handled by that provider
    THEN the value is resolved and returned

  • GIVEN a SomeConfig with a custom register<T>() factory for a type that a discovered resolver also handles
    WHEN resolving that type
    THEN CustomFactoryResolver takes precedence over the discovered resolver (existing first-match-wins semantics preserved)

  • GIVEN no third-party packages on the classpath
    WHEN buildResolvers() is called
    THEN 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 called
    THEN no exception is thrown and the chain falls back to only built-in resolvers

  • GIVEN a TypeResolverProvider that returns an empty list
    WHEN buildResolvers() is called
    THEN the resolver chain contains only the built-in resolvers

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status
    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions