Skip to content

Commit 03eb60b

Browse files
committed
rename class
1 parent 93f2194 commit 03eb60b

4 files changed

Lines changed: 39 additions & 34 deletions

File tree

docs/supported-types.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ For types not listed here, register a [custom factory](custom-factories.md).
2525
| **Standard Library** | | |
2626
| `kotlin.uuid.Uuid` | `some<Uuid>()` | Requires `@OptIn(ExperimentalUuidApi::class)` |
2727
| `java.util.UUID` | `some<UUID>()` | |
28+
| `java.util.Optional<T>` | `some<Optional<String>>()` | See [NullableStrategy](configuration/nullable-strategy.md) |
2829
| `kotlin.time.Duration` | `some<Duration>()` | |
2930
| `java.time.Duration` | `some<java.time.Duration>()` | |
3031
| `kotlin.time.Instant` | `some<Instant>()` | |

src/main/kotlin/dev/appoutlet/some/config/SomeConfig.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import dev.appoutlet.some.resolver.FloatResolver
1717
import dev.appoutlet.some.resolver.IntResolver
1818
import dev.appoutlet.some.resolver.JavaDurationResolver
1919
import dev.appoutlet.some.resolver.JavaInstantResolver
20-
import dev.appoutlet.some.resolver.JavaOptionalResolver
2120
import dev.appoutlet.some.resolver.JavaUuidResolver
2221
import dev.appoutlet.some.resolver.JavaZonedDateTimeResolver
2322
import dev.appoutlet.some.resolver.KotlinDurationResolver
@@ -31,6 +30,7 @@ import dev.appoutlet.some.resolver.MapResolver
3130
import dev.appoutlet.some.resolver.NullableResolver
3231
import dev.appoutlet.some.resolver.NumberResolver
3332
import dev.appoutlet.some.resolver.ObjectResolver
33+
import dev.appoutlet.some.resolver.OptionalResolver
3434
import dev.appoutlet.some.resolver.SealedClassResolver
3535
import dev.appoutlet.some.resolver.SetResolver
3636
import dev.appoutlet.some.resolver.ShortResolver
@@ -119,7 +119,7 @@ data class SomeConfig(
119119
JavaInstantResolver(random),
120120
JavaDurationResolver(random),
121121
JavaZonedDateTimeResolver(random),
122-
JavaOptionalResolver(this[NullableStrategy::class], random),
122+
OptionalResolver(this[NullableStrategy::class], random),
123123
BigDecimalResolver(random),
124124
BigIntegerResolver(random),
125125
LocalDateResolver(random),

src/main/kotlin/dev/appoutlet/some/resolver/JavaOptionalResolver.kt renamed to src/main/kotlin/dev/appoutlet/some/resolver/OptionalResolver.kt

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package dev.appoutlet.some.resolver
33
import dev.appoutlet.some.config.NullableStrategy
44
import dev.appoutlet.some.core.ResolverChain
55
import dev.appoutlet.some.core.TypeResolver
6+
import dev.appoutlet.some.exception.SomeCircularReferenceException
67
import java.util.Optional
78
import kotlin.random.Random
89
import kotlin.reflect.KClass
910
import kotlin.reflect.KType
10-
import kotlin.reflect.full.createType
1111
import kotlin.reflect.full.isSubclassOf
1212

1313
/**
@@ -21,17 +21,43 @@ import kotlin.reflect.full.isSubclassOf
2121
* @param nullableStrategy Strategy for resolving optional types. Defaults to [NullableStrategy.default] when null.
2222
* @param random Random source used by [NullableStrategy.Random].
2323
*/
24-
class JavaOptionalResolver(
24+
class OptionalResolver(
2525
nullableStrategy: NullableStrategy?,
2626
private val random: Random
2727
) : TypeResolver {
28+
/**
29+
* Resolved nullable strategy used to decide whether the resulting [Optional] is empty or present.
30+
*
31+
* Initialized from the constructor parameter, falling back to [NullableStrategy.default] when null.
32+
*/
2833
private val nullableStrategy = nullableStrategy ?: NullableStrategy.default
2934

35+
/**
36+
* Returns `true` when [type] is a subclass of [Optional].
37+
*
38+
* @param type The type to inspect.
39+
* @return `true` if the type's classifier is a [KClass] that extends [Optional], `false` otherwise.
40+
*/
3041
override fun canResolve(type: KType): Boolean {
3142
val kClass = type.classifier as? KClass<*> ?: return false
3243
return kClass.isSubclassOf(Optional::class)
3344
}
3445

46+
/**
47+
* Resolves an [Optional] of the wrapped type described by [type].
48+
*
49+
* The behavior depends on the configured [nullableStrategy]:
50+
* - [NullableStrategy.AlwaysNull] – always returns [Optional.empty].
51+
* - [NullableStrategy.NeverNull] – always returns a present [Optional] containing the resolved value.
52+
* - [NullableStrategy.Random] – returns [Optional.empty] with probability defined by the strategy.
53+
* - [NullableStrategy.NullOnCircularReference] – resolves the wrapped type and returns it inside a
54+
* present [Optional]. If the chain throws [SomeCircularReferenceException], returns [Optional.empty].
55+
*
56+
* @param type The [Optional] type to resolve. Its first type argument is used as the wrapped value type.
57+
* @param chain Resolver chain used to generate the wrapped value.
58+
* @return An [Optional] that may be empty or present depending on the active strategy.
59+
* @throws IllegalArgumentException If the [Optional] type uses a star projection.
60+
*/
3561
override fun resolve(type: KType, chain: ResolverChain): Any {
3662
val valueType = requireNotNull(type.arguments.firstOrNull()?.type) {
3763
"Star projection not supported in Optional"
@@ -48,12 +74,11 @@ class JavaOptionalResolver(
4874
}
4975
}
5076
is NullableStrategy.NullOnCircularReference -> {
51-
val nullableValueType = (valueType.classifier as? KClass<*>)?.createType(
52-
arguments = valueType.arguments,
53-
nullable = true
54-
) ?: valueType
55-
56-
Optional.ofNullable(chain.resolve(nullableValueType))
77+
try {
78+
Optional.ofNullable(chain.resolve(valueType))
79+
} catch (_: SomeCircularReferenceException) {
80+
Optional.empty<Any>()
81+
}
5782
}
5883
}
5984
}

src/test/kotlin/dev/appoutlet/some/resolver/JavaOptionalResolverTest.kt renamed to src/test/kotlin/dev/appoutlet/some/resolver/OptionalResolverTest.kt

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.test.assertIs
1010
import kotlin.test.assertNotNull
1111
import kotlin.test.assertTrue
1212

13-
class JavaOptionalResolverTest {
13+
class OptionalResolverTest {
1414
@Test
1515
fun `JavaOptionalResolver generates Optional values`() {
1616
val result: Optional<String> = some()
@@ -57,41 +57,20 @@ class JavaOptionalResolverTest {
5757
val result: OptionalCircular = some {
5858
strategy(NullableStrategy.NullOnCircularReference)
5959
}
60-
6160
assertNotNull(result)
62-
// Since Optional<OptionalCircular> is treated as OptionalCircular?,
63-
// and we are at the first level of circularity for OptionalCircular,
64-
// it should resolve to a non-null OptionalCircular, but wrapped in Optional.
65-
66-
// Wait, if it's the first time it sees OptionalCircular, it should resolve it.
67-
// If it's a circular reference, it should be null.
68-
69-
// Let's re-examine how ResolverChain works.
70-
// resolve(OptionalCircular)
71-
// -> ClassResolver resolves OptionalCircular
72-
// -> resolve(Optional<OptionalCircular>)
73-
// -> JavaOptionalResolver
74-
// -> resolve(OptionalCircular?)
75-
// -> NullableResolver
76-
// -> resolve(OptionalCircular)
77-
// -> CIRCULAR REFERENCE!
78-
79-
// If NullableStrategy is NullOnCircularReference, resolve(OptionalCircular?) returns null.
80-
// So Optional.ofNullable(null) is Optional.empty().
81-
8261
assertFalse(result.optional.isPresent, "Circular reference should result in empty optional")
8362
}
8463

8564
@Test
8665
fun `JavaOptionalResolver canResolve detects Optional types`() {
87-
val resolver = JavaOptionalResolver(NullableStrategy.default, kotlin.random.Random.Default)
66+
val resolver = OptionalResolver(NullableStrategy.default, kotlin.random.Random.Default)
8867
assertTrue(resolver.canResolve(typeOf<Optional<String>>()))
8968
assertTrue(resolver.canResolve(typeOf<Optional<Int>>()))
9069
}
9170

9271
@Test
9372
fun `JavaOptionalResolver rejects non-Optional types`() {
94-
val resolver = JavaOptionalResolver(NullableStrategy.default, kotlin.random.Random.Default)
73+
val resolver = OptionalResolver(NullableStrategy.default, kotlin.random.Random.Default)
9574
assertFalse(resolver.canResolve(typeOf<String>()))
9675
assertFalse(resolver.canResolve(typeOf<List<String>>()))
9776
}

0 commit comments

Comments
 (0)