Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
package com.braintrustdata.api.client

import com.braintrustdata.api.core.ClientOptions
import com.braintrustdata.api.core.http.HttpResponse.Handler
import com.braintrustdata.api.errors.BraintrustError
import com.braintrustdata.api.models.*
import com.braintrustdata.api.services.async.*
import com.braintrustdata.api.services.errorHandler

class BraintrustClientAsyncImpl
constructor(
private val clientOptions: ClientOptions,
) : BraintrustClientAsync {

private val errorHandler: Handler<BraintrustError> = errorHandler(clientOptions.jsonMapper)

private val sync: BraintrustClient by lazy { BraintrustClientImpl(clientOptions) }

private val topLevel: TopLevelServiceAsync by lazy { TopLevelServiceAsyncImpl(clientOptions) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
package com.braintrustdata.api.client

import com.braintrustdata.api.core.ClientOptions
import com.braintrustdata.api.core.http.HttpResponse.Handler
import com.braintrustdata.api.errors.BraintrustError
import com.braintrustdata.api.models.*
import com.braintrustdata.api.services.blocking.*
import com.braintrustdata.api.services.errorHandler

class BraintrustClientImpl
constructor(
private val clientOptions: ClientOptions,
) : BraintrustClient {

private val errorHandler: Handler<BraintrustError> = errorHandler(clientOptions.jsonMapper)

private val async: BraintrustClientAsync by lazy { BraintrustClientAsyncImpl(clientOptions) }

private val topLevel: TopLevelService by lazy { TopLevelServiceImpl(clientOptions) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
@file:JvmName("HttpRequestBodies")

package com.braintrustdata.api.core

import com.braintrustdata.api.core.http.HttpRequestBody
import com.braintrustdata.api.errors.BraintrustException
import com.fasterxml.jackson.databind.json.JsonMapper
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder

@JvmSynthetic
internal inline fun <reified T> json(
jsonMapper: JsonMapper,
value: T,
): HttpRequestBody {
return object : HttpRequestBody {
private var cachedBytes: ByteArray? = null

private fun serialize(): ByteArray {
if (cachedBytes != null) return cachedBytes!!

val buffer = ByteArrayOutputStream()
try {
jsonMapper.writeValue(buffer, value)
cachedBytes = buffer.toByteArray()
return cachedBytes!!
} catch (e: Exception) {
throw BraintrustException("Error writing request", e)
}
}

override fun writeTo(outputStream: OutputStream) {
outputStream.write(serialize())
}

override fun contentType(): String = "application/json"

override fun contentLength(): Long {
return serialize().size.toLong()
}

override fun repeatable(): Boolean = true

override fun close() {}
}
}

@JvmSynthetic
internal fun multipartFormData(
jsonMapper: JsonMapper,
parts: Array<MultipartFormValue<*>?>
): HttpRequestBody {
val builder = MultipartEntityBuilder.create()
parts.forEach { part ->
if (part?.value != null) {
when (part.value) {
is JsonValue -> {
val buffer = ByteArrayOutputStream()
try {
jsonMapper.writeValue(buffer, part.value)
} catch (e: Exception) {
throw BraintrustException("Error serializing value to json", e)
}
builder.addBinaryBody(
part.name,
buffer.toByteArray(),
part.contentType,
part.filename
)
}
is Boolean ->
builder.addTextBody(
part.name,
if (part.value) "true" else "false",
part.contentType
)
is Int -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
is Long -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
is Double -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
is ByteArray ->
builder.addBinaryBody(part.name, part.value, part.contentType, part.filename)
is String -> builder.addTextBody(part.name, part.value, part.contentType)
is Enum -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
else ->
throw IllegalArgumentException(
"Unsupported content type: ${part.value::class.java.simpleName}"
)
}
}
}
val entity = builder.build()

return object : HttpRequestBody {
override fun writeTo(outputStream: OutputStream) {
try {
return entity.writeTo(outputStream)
} catch (e: Exception) {
throw BraintrustException("Error writing request", e)
}
}

override fun contentType(): String = entity.contentType

override fun contentLength(): Long = -1

override fun repeatable(): Boolean = entity.isRepeatable

override fun close() = entity.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,9 @@ fun getOsName(): String {
}
}

fun getOsVersion(): String {
return System.getProperty("os.version", "unknown")
}
fun getOsVersion(): String = System.getProperty("os.version", "unknown")

fun getPackageVersion(): String {
return Properties::class.java.`package`.implementationVersion ?: "unknown"
}
fun getPackageVersion(): String =
Properties::class.java.`package`.implementationVersion ?: "unknown"

fun getJavaVersion(): String {
return System.getProperty("java.version", "unknown")
}
fun getJavaVersion(): String = System.getProperty("java.version", "unknown")
Original file line number Diff line number Diff line change
Expand Up @@ -479,9 +479,8 @@ internal constructor(
}
}

override fun toString(): String {
return "MultipartFormValue(name='$name', contentType=$contentType, filename=$filename, value=${valueToString()})"
}
override fun toString(): String =
"MultipartFormValue{name=$name, contentType=$contentType, filename=$filename, value=${valueToString()}}"

private fun valueToString(): String =
when (value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
@file:JvmName("ErrorHandler")

package com.braintrustdata.api.core.handlers

import com.braintrustdata.api.core.http.HttpResponse
import com.braintrustdata.api.core.http.HttpResponse.Handler
import com.braintrustdata.api.errors.BadRequestException
import com.braintrustdata.api.errors.BraintrustError
import com.braintrustdata.api.errors.InternalServerException
import com.braintrustdata.api.errors.NotFoundException
import com.braintrustdata.api.errors.PermissionDeniedException
import com.braintrustdata.api.errors.RateLimitException
import com.braintrustdata.api.errors.UnauthorizedException
import com.braintrustdata.api.errors.UnexpectedStatusCodeException
import com.braintrustdata.api.errors.UnprocessableEntityException
import com.fasterxml.jackson.databind.json.JsonMapper
import com.google.common.collect.ListMultimap
import java.io.ByteArrayInputStream
import java.io.InputStream

@JvmSynthetic
internal fun errorHandler(jsonMapper: JsonMapper): Handler<BraintrustError> {
val handler = jsonHandler<BraintrustError>(jsonMapper)

return object : Handler<BraintrustError> {
override fun handle(response: HttpResponse): BraintrustError =
try {
handler.handle(response)
} catch (e: Exception) {
BraintrustError.builder().build()
}
}
}

@JvmSynthetic
internal fun <T> Handler<T>.withErrorHandler(errorHandler: Handler<BraintrustError>): Handler<T> =
object : Handler<T> {
override fun handle(response: HttpResponse): T {
when (val statusCode = response.statusCode()) {
in 200..299 -> {
return this@withErrorHandler.handle(response)
}
400 -> {
val buffered = response.buffered()
throw BadRequestException(
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
401 -> {
val buffered = response.buffered()
throw UnauthorizedException(
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
403 -> {
val buffered = response.buffered()
throw PermissionDeniedException(
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
404 -> {
val buffered = response.buffered()
throw NotFoundException(
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
422 -> {
val buffered = response.buffered()
throw UnprocessableEntityException(
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
429 -> {
val buffered = response.buffered()
throw RateLimitException(
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
in 500..599 -> {
val buffered = response.buffered()
throw InternalServerException(
statusCode,
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
else -> {
val buffered = response.buffered()
throw UnexpectedStatusCodeException(
statusCode,
buffered.headers(),
stringHandler().handle(buffered),
errorHandler.handle(buffered),
)
}
}
}
}

private fun HttpResponse.buffered(): HttpResponse {
val body = body().readBytes()

return object : HttpResponse {
override fun statusCode(): Int = this@buffered.statusCode()

override fun headers(): ListMultimap<String, String> = this@buffered.headers()

override fun body(): InputStream = ByteArrayInputStream(body)

override fun close() = this@buffered.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@file:JvmName("JsonHandler")

package com.braintrustdata.api.core.handlers

import com.braintrustdata.api.core.http.HttpResponse
import com.braintrustdata.api.core.http.HttpResponse.Handler
import com.braintrustdata.api.errors.BraintrustException
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef

@JvmSynthetic
internal inline fun <reified T> jsonHandler(jsonMapper: JsonMapper): Handler<T> =
object : Handler<T> {
override fun handle(response: HttpResponse): T {
try {
return jsonMapper.readValue(response.body(), jacksonTypeRef())
} catch (e: Exception) {
throw BraintrustException("Error reading response", e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@file:JvmName("StringHandler")

package com.braintrustdata.api.core.handlers

import com.braintrustdata.api.core.http.HttpResponse
import com.braintrustdata.api.core.http.HttpResponse.Handler

@JvmSynthetic internal fun stringHandler(): Handler<String> = StringHandlerInternal

private object StringHandlerInternal : Handler<String> {
override fun handle(response: HttpResponse): String =
response.body().readBytes().toString(Charsets.UTF_8)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private constructor(
) {

override fun toString(): String =
"HttpRequest {method=$method, pathSegments=$pathSegments, queryParams=$queryParams, headers=$headers, body=$body}"
"HttpRequest{method=$method, pathSegments=$pathSegments, queryParams=$queryParams, headers=$headers, body=$body}"

companion object {
@JvmStatic fun builder() = Builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ constructor(
return true
}

return other is BraintrustError && this.additionalProperties == other.additionalProperties
return /* spotless:off */ other is BraintrustError && this.additionalProperties == other.additionalProperties /* spotless:on */
}

override fun hashCode(): Int {
return Objects.hash(additionalProperties)
return /* spotless:off */ Objects.hash(additionalProperties) /* spotless:on */
}

override fun toString() = "BraintrustError{additionalProperties=$additionalProperties}"
Expand Down
Loading