diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponse.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponse.kt index 19dd63b37..ce61f887f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponse.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponse.kt @@ -7104,7 +7104,28 @@ private constructor( * @throws OpenAIInvalidDataException if the JSON field has an unexpected type * (e.g. if the server responded with an unexpected value). */ - fun value(): Optional = value.getOptional("value") + fun value(): Optional { + val knownValue = value.asKnown() + if (knownValue.isPresent || value.isMissing() || value.isNull()) { + return knownValue + } + + val stringValue = value.asString() + if (stringValue.isPresent) { + val rawValue = stringValue.get() + val parsedValue = + if (DECIMAL_NUMBER_PATTERN.matches(rawValue)) { + rawValue.toDoubleOrNull() + } else { + null + } + if (parsedValue?.isFinite() == true) { + return Optional.of(parsedValue) + } + } + + throw OpenAIInvalidDataException("`value` is invalid, received $value") + } /** * Returns the raw JSON value of [currency]. @@ -7138,6 +7159,9 @@ private constructor( companion object { + private val DECIMAL_NUMBER_PATTERN = + Regex("""-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?""") + /** Returns a mutable builder for constructing an instance of [Amount]. */ @JvmStatic fun builder() = Builder() } diff --git a/openai-java-core/src/test/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponseTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponseTest.kt index ba2f20641..2e306d332 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponseTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/admin/organization/usage/UsageCostsResponseTest.kt @@ -4,11 +4,91 @@ package com.openai.models.admin.organization.usage import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource internal class UsageCostsResponseTest { + @Test + fun parsesStringEncodedCostAmounts() { + val response = + jsonMapper() + .readValue( + """ + { + "data": [ + { + "end_time": 1, + "results": [ + { + "object": "organization.costs.result", + "amount": {"value": "0E-6176", "currency": "usd"} + }, + { + "object": "organization.costs.result", + "amount": { + "value": "0.003627500000000000000000000000000000", + "currency": "usd" + } + } + ], + "start_time": 0 + } + ], + "has_more": false, + "object": "page" + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + val amounts = + response.data().single().results().map { + it.asOrganizationCosts().amount().get().value().get() + } + + assertThat(amounts).containsExactly(0.0, 0.0036275) + } + + @ParameterizedTest + @ValueSource(strings = ["not-a-number", "1f", "0x1.0p0", " 1 ", "01", "+1"]) + fun rejectsInvalidStringEncodedCostAmount(invalidValue: String) { + val response = + jsonMapper() + .readValue( + """ + { + "data": [ + { + "end_time": 1, + "results": [ + { + "object": "organization.costs.result", + "amount": {"value": "$invalidValue", "currency": "usd"} + } + ], + "start_time": 0 + } + ], + "has_more": false, + "object": "page" + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + val amount = + response.data().single().results().single().asOrganizationCosts().amount().get() + + assertThatThrownBy { amount.value() }.isInstanceOf(OpenAIInvalidDataException::class.java) + } + @Test fun create() { val usageCostsResponse =