From ee968b62d7bcedd1d692de56cdf5cc7e091c25b4 Mon Sep 17 00:00:00 2001 From: skyecodes Date: Thu, 3 Oct 2024 22:49:31 +0200 Subject: [PATCH] feat: allow creating custom qualifier annotations --- .../reference/koin-annotations/definitions.md | 22 +++++++++++++++++++ .../kotlin/org/koin/example/animal/Animal.kt | 21 ++++++++++++++---- .../kotlin/org.koin.example/TestModule.kt | 15 +++++++++---- .../org/koin/compiler/scanner/ext/KspExt.kt | 8 +++++-- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/docs/reference/koin-annotations/definitions.md b/docs/reference/koin-annotations/definitions.md index e368a8e..8353577 100644 --- a/docs/reference/koin-annotations/definitions.md +++ b/docs/reference/koin-annotations/definitions.md @@ -90,6 +90,28 @@ When resolving a dependency, just use the qualifier with `named` function: val logger: LoggerDataSource by inject(named("InMemoryLogger")) ``` +It is also possible to create custom qualifier annotations. Using the previous example: + +```kotlin +@Named +annotation class InMemoryLogger + +@Named +annotation class DatabaseLogger + +@Single +@InMemoryLogger +class LoggerInMemoryDataSource : LoggerDataSource + +@Single +@DatabaseLogger +class LoggerLocalDataSource(private val logDao: LogDao) : LoggerDataSource +``` + +```kotlin +val logger: LoggerDataSource by inject(named()) +``` + ## Injected Parameters with @InjectedParam You can tag a constructor member as "injected parameter", which means that the dependency will be passed in the graph when calling for resolution. diff --git a/examples/other-ksp/src/main/kotlin/org/koin/example/animal/Animal.kt b/examples/other-ksp/src/main/kotlin/org/koin/example/animal/Animal.kt index d0e3335..e25e6dc 100644 --- a/examples/other-ksp/src/main/kotlin/org/koin/example/animal/Animal.kt +++ b/examples/other-ksp/src/main/kotlin/org/koin/example/animal/Animal.kt @@ -1,9 +1,6 @@ package org.koin.example.animal -import org.koin.core.annotation.ComponentScan -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.annotation.Single +import org.koin.core.annotation.* import kotlin.random.Random public interface Animal @@ -14,6 +11,14 @@ public class Dog : Animal @Single(binds = []) public class Cat : Animal +public class Bunny(public val color: String) : Animal + +@Named +public annotation class WhiteBunny + +@Qualifier +public annotation class BlackBunny + @Module @ComponentScan public class AnimalModule { @@ -21,5 +26,13 @@ public class AnimalModule { @Factory public fun animal(cat: Cat, dog: Dog): Animal = if (randomBoolean()) cat else dog + @Single + @WhiteBunny + public fun whiteBunny(): Bunny = Bunny("White") + + @Single + @BlackBunny + public fun blackBunny(): Bunny = Bunny("Black") + private fun randomBoolean(): Boolean = Random.nextBoolean() } \ No newline at end of file diff --git a/examples/other-ksp/src/test/kotlin/org.koin.example/TestModule.kt b/examples/other-ksp/src/test/kotlin/org.koin.example/TestModule.kt index 3bcce1a..0de1cbc 100644 --- a/examples/other-ksp/src/test/kotlin/org.koin.example/TestModule.kt +++ b/examples/other-ksp/src/test/kotlin/org.koin.example/TestModule.kt @@ -2,13 +2,12 @@ package org.koin.example import org.junit.Test import org.koin.core.Koin +import org.koin.core.error.NoDefinitionFoundException import org.koin.core.logger.Level import org.koin.core.qualifier.named +import org.koin.core.qualifier.qualifier import org.koin.dsl.koinApplication -import org.koin.example.animal.Animal -import org.koin.example.animal.AnimalModule -import org.koin.example.animal.Cat -import org.koin.example.animal.Dog +import org.koin.example.animal.* import org.koin.example.`interface`.MyInterfaceExt import org.koin.example.newmodule.* import org.koin.example.newmodule.ComponentWithProps.Companion.DEFAULT_ID @@ -19,6 +18,8 @@ import org.koin.example.scope.MyScopedInstance import org.koin.example.scope.ScopeModule import org.koin.ksp.generated.defaultModule import org.koin.ksp.generated.module +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertTrue class TestModule { @@ -65,6 +66,12 @@ class TestModule { assertTrue { koin.get().msi == koin.get().msi } + + assertFailsWith(NoDefinitionFoundException::class) { + koin.get() + } + + assertEquals("White", koin.get(qualifier()).color) } private fun randomGetAnimal(koin: Koin): Animal { diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ext/KspExt.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ext/KspExt.kt index 5e14060..3ab928c 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ext/KspExt.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ext/KspExt.kt @@ -73,16 +73,20 @@ fun List.getQualifier(): KoinMetaData.Qualifier { ?: error("Qualifier annotation needs parameters: either type value or name") } +private val qualifierAnnotations = listOf("Named", "Qualifier") fun KSAnnotated.getQualifier(): String? { val qualifierAnnotation = annotations.firstOrNull { a -> val annotationName = a.shortName.asString() - (annotationName == "Named" || annotationName == "Qualifier") + if (annotationName in qualifierAnnotations) true + else (a.annotationType.resolve().declaration as KSClassDeclaration).annotations.any { a2 -> + a2.shortName.asString() in qualifierAnnotations + } } return qualifierAnnotation?.let { when(it.shortName.asString()){ "${Named::class.simpleName}" -> it.arguments.getNamed().getValue() "${Qualifier::class.simpleName}" -> it.arguments.getQualifier().getValue() - else -> null + else -> it.annotationType.resolve().declaration.qualifiedName?.asString() } } }