Skip to content

Commit 652d203

Browse files
feat(client): support JsonField#asX() for known values (#225)
chore(internal): update some formatting in `Values.kt`
1 parent 7cec2eb commit 652d203

File tree

2 files changed

+196
-25
lines changed

2 files changed

+196
-25
lines changed

openai-java-core/src/main/kotlin/com/openai/core/Values.kt

+52-25
Original file line numberDiff line numberDiff line change
@@ -59,46 +59,79 @@ sealed class JsonField<out T : Any> {
5959
fun asBoolean(): Optional<Boolean> =
6060
when (this) {
6161
is JsonBoolean -> Optional.of(value)
62+
is KnownValue -> Optional.ofNullable(value as? Boolean)
6263
else -> Optional.empty()
6364
}
6465

6566
fun asNumber(): Optional<Number> =
6667
when (this) {
6768
is JsonNumber -> Optional.of(value)
69+
is KnownValue -> Optional.ofNullable(value as? Number)
6870
else -> Optional.empty()
6971
}
7072

7173
fun asString(): Optional<String> =
7274
when (this) {
7375
is JsonString -> Optional.of(value)
76+
is KnownValue -> Optional.ofNullable(value as? String)
7477
else -> Optional.empty()
7578
}
7679

7780
fun asStringOrThrow(): String =
78-
when (this) {
79-
is JsonString -> value
80-
else -> throw OpenAIInvalidDataException("Value is not a string")
81-
}
81+
asString().orElseThrow { OpenAIInvalidDataException("Value is not a string") }
8282

8383
fun asArray(): Optional<List<JsonValue>> =
8484
when (this) {
8585
is JsonArray -> Optional.of(values)
86+
is KnownValue ->
87+
Optional.ofNullable(
88+
(value as? List<*>)?.map {
89+
try {
90+
JsonValue.from(it)
91+
} catch (e: IllegalArgumentException) {
92+
// The known value is a list, but not all values are convertible to
93+
// `JsonValue`.
94+
return Optional.empty()
95+
}
96+
}
97+
)
8698
else -> Optional.empty()
8799
}
88100

89101
fun asObject(): Optional<Map<String, JsonValue>> =
90102
when (this) {
91103
is JsonObject -> Optional.of(values)
104+
is KnownValue ->
105+
Optional.ofNullable(
106+
(value as? Map<*, *>)
107+
?.map { (key, value) ->
108+
if (key !is String) {
109+
return Optional.empty()
110+
}
111+
112+
val jsonValue =
113+
try {
114+
JsonValue.from(value)
115+
} catch (e: IllegalArgumentException) {
116+
// The known value is a map, but not all items are convertible
117+
// to `JsonValue`.
118+
return Optional.empty()
119+
}
120+
121+
key to jsonValue
122+
}
123+
?.toMap()
124+
)
92125
else -> Optional.empty()
93126
}
94127

95128
@JvmSynthetic
96129
internal fun getRequired(name: String): T =
97130
when (this) {
98131
is KnownValue -> value
99-
is JsonMissing -> throw OpenAIInvalidDataException("'${name}' is not set")
100-
is JsonNull -> throw OpenAIInvalidDataException("'${name}' is null")
101-
else -> throw OpenAIInvalidDataException("'${name}' is invalid, received ${this}")
132+
is JsonMissing -> throw OpenAIInvalidDataException("`$name` is not set")
133+
is JsonNull -> throw OpenAIInvalidDataException("`$name` is null")
134+
else -> throw OpenAIInvalidDataException("`$name` is invalid, received $this")
102135
}
103136

104137
@JvmSynthetic
@@ -107,7 +140,7 @@ sealed class JsonField<out T : Any> {
107140
is KnownValue -> value
108141
is JsonMissing -> null
109142
is JsonNull -> null
110-
else -> throw OpenAIInvalidDataException("'${name}' is invalid, received ${this}")
143+
else -> throw OpenAIInvalidDataException("`$name` is invalid, received $this")
111144
}
112145

113146
@JvmSynthetic
@@ -140,8 +173,11 @@ sealed class JsonField<out T : Any> {
140173
}
141174
}
142175

143-
// This class is a Jackson filter that can be used to exclude missing properties from objects
144-
// This filter should not be used directly and should instead use the @ExcludeMissing annotation
176+
/**
177+
* This class is a Jackson filter that can be used to exclude missing properties from objects.
178+
* This filter should not be used directly and should instead use the @ExcludeMissing
179+
* annotation.
180+
*/
145181
class IsMissing {
146182
override fun equals(other: Any?): Boolean = other is JsonMissing
147183

@@ -154,18 +190,13 @@ sealed class JsonField<out T : Any> {
154190
override fun createContextual(
155191
context: DeserializationContext,
156192
property: BeanProperty?,
157-
): JsonDeserializer<JsonField<*>> {
158-
return Deserializer(context.contextualType?.containedType(0))
159-
}
193+
): JsonDeserializer<JsonField<*>> = Deserializer(context.contextualType?.containedType(0))
160194

161-
override fun ObjectCodec.deserialize(node: JsonNode): JsonField<*> {
162-
return type?.let { tryDeserialize<Any>(node, type) }?.let { of(it) }
195+
override fun ObjectCodec.deserialize(node: JsonNode): JsonField<*> =
196+
type?.let { tryDeserialize<Any>(node, type) }?.let { of(it) }
163197
?: JsonValue.fromJsonNode(node)
164-
}
165198

166-
override fun getNullValue(context: DeserializationContext): JsonField<*> {
167-
return JsonNull.of()
168-
}
199+
override fun getNullValue(context: DeserializationContext): JsonField<*> = JsonNull.of()
169200
}
170201
}
171202

@@ -240,13 +271,9 @@ sealed class JsonValue : JsonField<Nothing>() {
240271
}
241272

242273
class Deserializer : BaseDeserializer<JsonValue>(JsonValue::class) {
243-
override fun ObjectCodec.deserialize(node: JsonNode): JsonValue {
244-
return fromJsonNode(node)
245-
}
274+
override fun ObjectCodec.deserialize(node: JsonNode): JsonValue = fromJsonNode(node)
246275

247-
override fun getNullValue(context: DeserializationContext?): JsonValue {
248-
return JsonNull.of()
249-
}
276+
override fun getNullValue(context: DeserializationContext?): JsonValue = JsonNull.of()
250277
}
251278
}
252279

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package com.openai.core
2+
3+
import java.util.Optional
4+
import org.assertj.core.api.Assertions.assertThat
5+
import org.junit.jupiter.params.ParameterizedTest
6+
import org.junit.jupiter.params.provider.EnumSource
7+
8+
internal class ValuesTest {
9+
companion object {
10+
private val NON_JSON = Any()
11+
}
12+
13+
enum class TestCase(
14+
val value: JsonField<*>,
15+
val expectedIsMissing: Boolean = false,
16+
val expectedIsNull: Boolean = false,
17+
val expectedAsKnown: Optional<*> = Optional.empty<Nothing>(),
18+
val expectedAsBoolean: Optional<Boolean> = Optional.empty(),
19+
val expectedAsNumber: Optional<Number> = Optional.empty(),
20+
val expectedAsString: Optional<String> = Optional.empty(),
21+
val expectedAsArray: Optional<List<JsonValue>> = Optional.empty(),
22+
val expectedAsObject: Optional<Map<String, JsonValue>> = Optional.empty(),
23+
) {
24+
MISSING(JsonMissing.of(), expectedIsMissing = true),
25+
NULL(JsonNull.of(), expectedIsNull = true),
26+
KNOWN(KnownValue.of(NON_JSON), expectedAsKnown = Optional.of(NON_JSON)),
27+
KNOWN_BOOLEAN(
28+
KnownValue.of(true),
29+
expectedAsKnown = Optional.of(true),
30+
expectedAsBoolean = Optional.of(true),
31+
),
32+
BOOLEAN(JsonBoolean.of(true), expectedAsBoolean = Optional.of(true)),
33+
KNOWN_NUMBER(
34+
KnownValue.of(42),
35+
expectedAsKnown = Optional.of(42),
36+
expectedAsNumber = Optional.of(42),
37+
),
38+
NUMBER(JsonNumber.of(42), expectedAsNumber = Optional.of(42)),
39+
KNOWN_STRING(
40+
KnownValue.of("hello"),
41+
expectedAsKnown = Optional.of("hello"),
42+
expectedAsString = Optional.of("hello"),
43+
),
44+
STRING(JsonString.of("hello"), expectedAsString = Optional.of("hello")),
45+
KNOWN_ARRAY_NOT_ALL_JSON(
46+
KnownValue.of(listOf("a", "b", NON_JSON)),
47+
expectedAsKnown = Optional.of(listOf("a", "b", NON_JSON)),
48+
),
49+
KNOWN_ARRAY(
50+
KnownValue.of(listOf("a", "b", "c")),
51+
expectedAsKnown = Optional.of(listOf("a", "b", "c")),
52+
expectedAsArray =
53+
Optional.of(listOf(JsonString.of("a"), JsonString.of("b"), JsonString.of("c"))),
54+
),
55+
ARRAY(
56+
JsonArray.of(listOf(JsonString.of("a"), JsonString.of("b"), JsonString.of("c"))),
57+
expectedAsArray =
58+
Optional.of(listOf(JsonString.of("a"), JsonString.of("b"), JsonString.of("c"))),
59+
),
60+
KNOWN_OBJECT_NOT_ALL_STRING_KEYS(
61+
KnownValue.of(mapOf("a" to "b", 42 to "c")),
62+
expectedAsKnown = Optional.of(mapOf("a" to "b", 42 to "c")),
63+
),
64+
KNOWN_OBJECT_NOT_ALL_JSON(
65+
KnownValue.of(mapOf("a" to "b", "b" to NON_JSON)),
66+
expectedAsKnown = Optional.of(mapOf("a" to "b", "b" to NON_JSON)),
67+
),
68+
KNOWN_OBJECT(
69+
KnownValue.of(mapOf("a" to "b", "b" to "c")),
70+
expectedAsKnown = Optional.of(mapOf("a" to "b", "b" to "c")),
71+
expectedAsObject =
72+
Optional.of(mapOf("a" to JsonString.of("b"), "b" to JsonString.of("c"))),
73+
),
74+
OBJECT(
75+
JsonObject.of(mapOf("a" to JsonString.of("b"), "b" to JsonString.of("c"))),
76+
expectedAsObject =
77+
Optional.of(mapOf("a" to JsonString.of("b"), "b" to JsonString.of("c"))),
78+
),
79+
}
80+
81+
@ParameterizedTest
82+
@EnumSource
83+
fun isMissing(testCase: TestCase) {
84+
val isMissing = testCase.value.isMissing()
85+
86+
assertThat(isMissing).isEqualTo(testCase.expectedIsMissing)
87+
}
88+
89+
@ParameterizedTest
90+
@EnumSource
91+
fun isNull(testCase: TestCase) {
92+
val isNull = testCase.value.isNull()
93+
94+
assertThat(isNull).isEqualTo(testCase.expectedIsNull)
95+
}
96+
97+
@ParameterizedTest
98+
@EnumSource
99+
fun asKnown(testCase: TestCase) {
100+
val known = testCase.value.asKnown()
101+
102+
assertThat(known).isEqualTo(testCase.expectedAsKnown)
103+
}
104+
105+
@ParameterizedTest
106+
@EnumSource
107+
fun asBoolean(testCase: TestCase) {
108+
val boolean = testCase.value.asBoolean()
109+
110+
assertThat(boolean).isEqualTo(testCase.expectedAsBoolean)
111+
}
112+
113+
@ParameterizedTest
114+
@EnumSource
115+
fun asNumber(testCase: TestCase) {
116+
val number = testCase.value.asNumber()
117+
118+
assertThat(number).isEqualTo(testCase.expectedAsNumber)
119+
}
120+
121+
@ParameterizedTest
122+
@EnumSource
123+
fun asString(testCase: TestCase) {
124+
val string = testCase.value.asString()
125+
126+
assertThat(string).isEqualTo(testCase.expectedAsString)
127+
}
128+
129+
@ParameterizedTest
130+
@EnumSource
131+
fun asArray(testCase: TestCase) {
132+
val array = testCase.value.asArray()
133+
134+
assertThat(array).isEqualTo(testCase.expectedAsArray)
135+
}
136+
137+
@ParameterizedTest
138+
@EnumSource
139+
fun asObject(testCase: TestCase) {
140+
val obj = testCase.value.asObject()
141+
142+
assertThat(obj).isEqualTo(testCase.expectedAsObject)
143+
}
144+
}

0 commit comments

Comments
 (0)