-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add NullOnCircularReference strategy to NullableStrategy #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
f41266d
550a3d8
338ef46
406f3b1
421c7eb
d92fd8b
957b781
c478326
b1c6b85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,18 +10,20 @@ import kotlin.reflect.full.createType | |
| /** | ||
| * Resolves nullable Kotlin types according to the configured [NullableStrategy]. | ||
| * | ||
| * - **NullOnCircularReference** – delegates to the chain to resolve normally (might be null if a cycle is detected). | ||
| * - **AlwaysNull** – always returns `null`. | ||
| * - **NeverNull** – always resolves a non‑null value. | ||
| * - **Random** – returns `null` based on the strategy's probability. | ||
| */ | ||
| class NullableResolver( | ||
| private val nullableStrategy: NullableStrategy = NullableStrategy.Random(), | ||
| private val nullableStrategy: NullableStrategy = NullableStrategy.NullOnCircularReference, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is now a redundancy between the |
||
| private val random: Random | ||
| ) : TypeResolver { | ||
| override fun canResolve(type: KType): Boolean = type.isMarkedNullable | ||
|
|
||
| override fun resolve(type: KType, chain: ResolverChain): Any? { | ||
| return when (nullableStrategy) { | ||
| is NullableStrategy.NullOnCircularReference -> createNonNullInstance(type, chain) | ||
| is NullableStrategy.AlwaysNull -> null | ||
| is NullableStrategy.NeverNull -> createNonNullInstance(type, chain) | ||
| is NullableStrategy.Random -> { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package dev.appoutlet.some.integration | ||
|
|
||
| import dev.appoutlet.some.some | ||
| import dev.appoutlet.some.config.NullableStrategy | ||
| import dev.appoutlet.some.exception.SomeCircularReferenceException | ||
| import kotlin.test.Test | ||
| import kotlin.test.assertIs | ||
| import kotlin.test.assertNull | ||
| import kotlin.test.assertFailsWith | ||
|
|
||
| class CircularReferenceIntegrationTest { | ||
| data class Node(val next: Node?) | ||
| data class StrictNode(val next: StrictNode) | ||
| data class IndirectA(val b: IndirectB?) | ||
| data class IndirectB(val a: IndirectA?) | ||
|
|
||
| @Test | ||
| fun `circular nullable field returns null under NullOnCircularReference by default`() { | ||
| val node: Node = some<Node>() | ||
| assertNull(node.next) | ||
| } | ||
|
|
||
| @Test | ||
| fun `circular non-nullable field still throws SomeCircularReferenceException`() { | ||
| assertFailsWith<SomeCircularReferenceException> { | ||
| some<StrictNode>() | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| fun `indirect circular reference returns null for nullable field`() { | ||
| val a: IndirectA = some<IndirectA>() | ||
| assertIs<IndirectB>(a.b) | ||
| assertNull(a.b.a) | ||
| } | ||
|
|
||
| @Test | ||
| fun `NeverNull strategy still throws on circular reference even if nullable`() { | ||
| assertFailsWith<SomeCircularReferenceException> { | ||
| some<Node> { | ||
| nullableStrategy = NullableStrategy.NeverNull | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| fun `AlwaysNull strategy handles circular reference by returning null`() { | ||
| // In this case NullableResolver will return null even before ResolverChain detects a cycle | ||
| val node: Node = some<Node> { | ||
| nullableStrategy = NullableStrategy.AlwaysNull | ||
| } | ||
| assertNull(node.next) | ||
| } | ||
|
|
||
| @Test | ||
| fun `Random strategy still throws on circular reference when it decides to resolve non-null`() { | ||
| // With probability 0, it's like NeverNull | ||
| assertFailsWith<SomeCircularReferenceException> { | ||
| some<Node> { | ||
| nullableStrategy = NullableStrategy.Random(probability = 0.0) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that
defaultResolversis lazily initialized using the initial state ofdefaultConfig. SinceSomeConfigproperties (likenullableStrategy) are mutable, any changes made todefaultConfigafterdefaultResolvershas been accessed will not be reflected in the existing resolvers. This creates a mismatch where theResolverChainuses the updated strategy but the resolvers continue using the old one. Consider makingSomeConfigimmutable or refactoring resolvers to access strategies dynamically from the session context.