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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.26.0"
".": "0.27.0"
}
2 changes: 1 addition & 1 deletion .stats.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
configured_endpoints: 103
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-8663e8fc543041d9694eddcd2f7e9784611369606700f99340e6dc80607b2dfa.yml
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-0dbb8ba730f755468357ebda41332664e8396faf29a6a6a64ad37cf35cf70d0c.yml
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 0.27.0 (2025-02-04)

Full Changelog: [v0.26.0...v0.27.0](https://github.com/orbcorp/orb-java/compare/v0.26.0...v0.27.0)

### Features

* **api:** api update ([#229](https://github.com/orbcorp/orb-java/issues/229)) ([a0b4d91](https://github.com/orbcorp/orb-java/commit/a0b4d9178723acb177202d88d4355ae8d4dd3196))
* **client:** send client-side timeout headers ([#228](https://github.com/orbcorp/orb-java/issues/228)) ([720ac83](https://github.com/orbcorp/orb-java/commit/720ac83e1167d1c24dd71e2d2b7b98b0390c0f17))


### Chores

* **internal:** codegen related update ([#224](https://github.com/orbcorp/orb-java/issues/224)) ([ba91826](https://github.com/orbcorp/orb-java/commit/ba91826dde6c642771682ce5b56f61638bcab7a5))


### Documentation

* fix incorrect additional properties info ([#227](https://github.com/orbcorp/orb-java/issues/227)) ([8fe2133](https://github.com/orbcorp/orb-java/commit/8fe2133556f1cd283180a045f47a084b40664502))

## 0.26.0 (2025-01-30)

Full Changelog: [v0.25.0...v0.26.0](https://github.com/orbcorp/orb-java/compare/v0.25.0...v0.26.0)
Expand Down
38 changes: 17 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- x-release-please-start-version -->

[![Maven Central](https://img.shields.io/maven-central/v/com.withorb.api/orb-java)](https://central.sonatype.com/artifact/com.withorb.api/orb-java/0.26.0)
[![Maven Central](https://img.shields.io/maven-central/v/com.withorb.api/orb-java)](https://central.sonatype.com/artifact/com.withorb.api/orb-java/0.27.0)

<!-- x-release-please-end -->

Expand All @@ -19,7 +19,7 @@ The REST API documentation can be found on [docs.withorb.com](https://docs.witho
### Gradle

```kotlin
implementation("com.withorb.api:orb-java:0.26.0")
implementation("com.withorb.api:orb-java:0.27.0")
```

### Maven
Expand All @@ -28,7 +28,7 @@ implementation("com.withorb.api:orb-java:0.26.0")
<dependency>
<groupId>com.withorb.api</groupId>
<artifactId>orb-java</artifactId>
<version>0.26.0</version>
<version>0.27.0</version>
</dependency>
```

Expand Down Expand Up @@ -140,19 +140,7 @@ See [Pagination](#pagination) below for more information on transparently workin

To make a request to the Orb API, you generally build an instance of the appropriate `Params` class.

In [Example: creating a resource](#example-creating-a-resource) above, we used the `CustomerCreateParams.builder()` to pass to the `create` method of the `customers` service.

Sometimes, the API may support other properties that are not yet supported in the Java SDK types. In that case, you can attach them using the `putAdditionalProperty` method.

```java
import com.withorb.api.core.JsonValue;
import com.withorb.api.models.CustomerCreateParams;

CustomerCreateParams params = CustomerCreateParams.builder()
// ... normal properties
.putAdditionalProperty("secret_param", JsonValue.from("4242"))
.build();
```
See [Undocumented request params](#undocumented-request-params) for how to send arbitrary parameters.

## Responses

Expand Down Expand Up @@ -344,18 +332,26 @@ This library is typed for convenient access to the documented API. If you need t

### Undocumented request params

To make requests using undocumented parameters, you can provide or override parameters on the params object while building it.
In [Example: creating a resource](#example-creating-a-resource) above, we used the `CustomerCreateParams.builder()` to pass to the `create` method of the `customers` service.

Sometimes, the API may support other properties that are not yet supported in the Java SDK types. In that case, you can attach them using raw setters:

```java
FooCreateParams address = FooCreateParams.builder()
.id("my_id")
.putAdditionalProperty("secret_prop", JsonValue.from("hello"))
import com.withorb.api.core.JsonValue;
import com.withorb.api.models.CustomerCreateParams;

CustomerCreateParams params = CustomerCreateParams.builder()
.putAdditionalHeader("Secret-Header", "42")
.putAdditionalQueryParam("secret_query_param", "42")
.putAdditionalBodyProperty("secretProperty", JsonValue.from("42"))
.build();
```

You can also use the `putAdditionalProperty` method on nested headers, query params, or body objects.

### Undocumented response properties

To access undocumented response properties, you can use `res._additionalProperties()` on a response object to get a map of untyped fields of type `Map<String, JsonValue>`. You can then access fields like `._additionalProperties().get("secret_prop").asString()` or use other helpers defined on the `JsonValue` class to extract it to a desired type.
To access undocumented response properties, you can use `res._additionalProperties()` on a response object to get a map of untyped fields of type `Map<String, JsonValue>`. You can then access fields like `res._additionalProperties().get("secret_prop").asString()` or use other helpers defined on the `JsonValue` class to extract it to a desired type.

## Logging

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
allprojects {
group = "com.withorb.api"
version = "0.26.0" // x-release-please-version
version = "0.27.0" // x-release-please-version
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,11 @@ class OkHttpClient
private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val baseUrl: HttpUrl) :
HttpClient {

private fun getClient(requestOptions: RequestOptions): okhttp3.OkHttpClient {
val clientBuilder = okHttpClient.newBuilder()

val logLevel =
when (System.getenv("ORB_LOG")?.lowercase()) {
"info" -> HttpLoggingInterceptor.Level.BASIC
"debug" -> HttpLoggingInterceptor.Level.BODY
else -> null
}
if (logLevel != null) {
clientBuilder.addNetworkInterceptor(
HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("Authorization") }
)
}

val timeout = requestOptions.timeout
if (timeout != null) {
clientBuilder
.connectTimeout(timeout)
.readTimeout(timeout)
.writeTimeout(timeout)
.callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
}

return clientBuilder.build()
}

override fun execute(
request: HttpRequest,
requestOptions: RequestOptions,
): HttpResponse {
val call = getClient(requestOptions).newCall(request.toRequest())
val call = newCall(request, requestOptions)

return try {
call.execute().toResponse()
Expand All @@ -81,18 +54,18 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val

request.body?.run { future.whenComplete { _, _ -> close() } }

val call = getClient(requestOptions).newCall(request.toRequest())
call.enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
future.complete(response.toResponse())
}
newCall(request, requestOptions)
.enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
future.complete(response.toResponse())
}

override fun onFailure(call: Call, e: IOException) {
future.completeExceptionally(OrbIoException("Request failed", e))
override fun onFailure(call: Call, e: IOException) {
future.completeExceptionally(OrbIoException("Request failed", e))
}
}
}
)
)

return future
}
Expand All @@ -103,7 +76,35 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
okHttpClient.cache?.close()
}

private fun HttpRequest.toRequest(): Request {
private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
val clientBuilder = okHttpClient.newBuilder()

val logLevel =
when (System.getenv("ORB_LOG")?.lowercase()) {
"info" -> HttpLoggingInterceptor.Level.BASIC
"debug" -> HttpLoggingInterceptor.Level.BODY
else -> null
}
if (logLevel != null) {
clientBuilder.addNetworkInterceptor(
HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("Authorization") }
)
}

val timeout = requestOptions.timeout
if (timeout != null) {
clientBuilder
.connectTimeout(timeout)
.readTimeout(timeout)
.writeTimeout(timeout)
.callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
}

val client = clientBuilder.build()
return client.newCall(request.toRequest(client))
}

private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient): Request {
var body: RequestBody? = body?.toRequestBody()
// OkHttpClient always requires a request body for PUT and POST methods.
if (body == null && (method == HttpMethod.PUT || method == HttpMethod.POST)) {
Expand All @@ -115,6 +116,21 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
headers.values(name).forEach { builder.header(name, it) }
}

if (
!headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
) {
builder.header(
"X-Stainless-Read-Timeout",
Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString()
)
}
if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
builder.header(
"X-Stainless-Timeout",
Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString()
)
}

return builder.build()
}

Expand Down Expand Up @@ -171,7 +187,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
@JvmStatic fun builder() = Builder()
}

class Builder {
class Builder internal constructor() {

private var baseUrl: HttpUrl? = null
// The default timeout is 1 minute.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class OrbOkHttpClient private constructor() {
@JvmStatic fun fromEnv(): OrbClient = builder().fromEnv().build()
}

class Builder {
/** A builder for [OrbOkHttpClient]. */
class Builder internal constructor() {

private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var baseUrl: String = ClientOptions.PRODUCTION_URL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class OrbOkHttpClientAsync private constructor() {
@JvmStatic fun fromEnv(): OrbClientAsync = builder().fromEnv().build()
}

class Builder {
/** A builder for [OrbOkHttpClientAsync]. */
class Builder internal constructor() {

private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var baseUrl: String = ClientOptions.PRODUCTION_URL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,28 @@ import com.withorb.api.services.blocking.SubscriptionService
import com.withorb.api.services.blocking.TopLevelService
import com.withorb.api.services.blocking.WebhookService

/**
* A client for interacting with the Orb REST API synchronously. You can also switch to asynchronous
* execution via the [async] method.
*
* This client performs best when you create a single instance and reuse it for all interactions
* with the REST API. This is because each client holds its own connection pool and thread pools.
* Reusing connections and threads reduces latency and saves memory. The client also handles rate
* limiting per client. This means that creating and using multiple instances at the same time will
* not respect rate limits.
*
* The threads and connections that are held will be released automatically if they remain idle. But
* if you are writing an application that needs to aggressively release unused resources, then you
* may call [close].
*/
interface OrbClient {

/**
* Returns a version of this client that uses asynchronous execution.
*
* The returned client shares its resources, like its connection pool and thread pools, with
* this client.
*/
fun async(): OrbClientAsync

fun topLevel(): TopLevelService
Expand All @@ -46,9 +66,22 @@ interface OrbClient {

fun subscriptions(): SubscriptionService

fun webhooks(): WebhookService

fun alerts(): AlertService

fun dimensionalPriceGroups(): DimensionalPriceGroupService

fun webhooks(): WebhookService
/**
* Closes this client, relinquishing any underlying resources.
*
* This is purposefully not inherited from [AutoCloseable] because the client is long-lived and
* usually should not be synchronously closed via try-with-resources.
*
* It's also usually not necessary to call this method at all. the default HTTP client
* automatically releases threads and connections if they remain idle, but if you are writing an
* application that needs to aggressively release unused resources, then you may call this
* method.
*/
fun close()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,28 @@ import com.withorb.api.services.async.PriceServiceAsync
import com.withorb.api.services.async.SubscriptionServiceAsync
import com.withorb.api.services.async.TopLevelServiceAsync

/**
* A client for interacting with the Orb REST API asynchronously. You can also switch to synchronous
* execution via the [sync] method.
*
* This client performs best when you create a single instance and reuse it for all interactions
* with the REST API. This is because each client holds its own connection pool and thread pools.
* Reusing connections and threads reduces latency and saves memory. The client also handles rate
* limiting per client. This means that creating and using multiple instances at the same time will
* not respect rate limits.
*
* The threads and connections that are held will be released automatically if they remain idle. But
* if you are writing an application that needs to aggressively release unused resources, then you
* may call [close].
*/
interface OrbClientAsync {

/**
* Returns a version of this client that uses synchronous execution.
*
* The returned client shares its resources, like its connection pool and thread pools, with
* this client.
*/
fun sync(): OrbClient

fun topLevel(): TopLevelServiceAsync
Expand Down Expand Up @@ -48,4 +68,17 @@ interface OrbClientAsync {
fun alerts(): AlertServiceAsync

fun dimensionalPriceGroups(): DimensionalPriceGroupServiceAsync

/**
* Closes this client, relinquishing any underlying resources.
*
* This is purposefully not inherited from [AutoCloseable] because the client is long-lived and
* usually should not be synchronously closed via try-with-resources.
*
* It's also usually not necessary to call this method at all. the default HTTP client
* automatically releases threads and connections if they remain idle, but if you are writing an
* application that needs to aggressively release unused resources, then you may call this
* method.
*/
fun close()
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ import com.withorb.api.services.async.SubscriptionServiceAsyncImpl
import com.withorb.api.services.async.TopLevelServiceAsync
import com.withorb.api.services.async.TopLevelServiceAsyncImpl

class OrbClientAsyncImpl
constructor(
class OrbClientAsyncImpl(
private val clientOptions: ClientOptions,
) : OrbClientAsync {

Expand Down Expand Up @@ -131,4 +130,6 @@ constructor(

override fun dimensionalPriceGroups(): DimensionalPriceGroupServiceAsync =
dimensionalPriceGroups

override fun close() = clientOptions.httpClient.close()
}
Loading