Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Pending

### Update
- fix: make Horizon request builder URL generation idempotent so repeated `buildUri()` or `execute()` calls on the same builder do not duplicate path segments.
- docs: add an [Agent Skill](https://agentskills.io/) for the Java Stellar SDK under `skills/`, plus Claude Code plugin manifests in `.claude-plugin/`. The skill gives AI coding agents concise, Stellar-specific guidance (transactions, operations, Horizon, Soroban, XDR/SCVal, and SEP protocols) when generating application code with `stellar-sdk`.
- feat: add SEP-0046, SEP-0047, and SEP-0048 contract introspection support. New `ContractMeta`, `ContractSpec`, and `ContractInfo` wrappers under `org.stellar.sdk.contract` parse contract Wasm metadata and interface specs locally. `SorobanServer` adds `getContractWasm`, `getContractWasmByHash`, `getContractMeta`, `getContractSpec`, and `getContractInfo` for RPC-backed retrieval.

Expand Down
8 changes: 6 additions & 2 deletions src/main/java/org/stellar/sdk/requests/RequestBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@

/** Abstract class for request builders. */
public abstract class RequestBuilder {
private final HttpUrl serverURI;
protected HttpUrl.Builder uriBuilder;
protected OkHttpClient httpClient;
private final ArrayList<String> segments;
private boolean segmentsAdded;

RequestBuilder(OkHttpClient httpClient, HttpUrl serverURI, String defaultSegment) {
this.httpClient = httpClient;
this.serverURI = serverURI;
uriBuilder = serverURI.newBuilder();
segments = new ArrayList<>();
if (defaultSegment != null) {
Expand Down Expand Up @@ -123,12 +125,14 @@ private String encodeAsset(Asset asset) {
}

HttpUrl buildUri() {
HttpUrl.Builder builder = serverURI.newBuilder();
if (!segments.isEmpty()) {
for (String segment : segments) {
uriBuilder.addPathSegment(segment);
builder.addPathSegment(segment);
Comment thread
overcat marked this conversation as resolved.
}
}
return uriBuilder.build();
builder.encodedQuery(uriBuilder.build().encodedQuery());
return builder.build();
}

/** Represents possible <code>order</code> parameter values. */
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/org/stellar/sdk/requests/SSEStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ private SSEStream(
this.reconnectTimeout = reconnectTimeout;

executorService = Executors.newSingleThreadScheduledExecutor();
requestBuilder.buildUri(); // call this once to add the segments
}

private void start() {
Expand Down Expand Up @@ -117,7 +116,7 @@ private void restart() {
requestBuilder,
responseClass,
listener,
requestBuilder.uriBuilder.build().toString(),
requestBuilder.buildUri().toString(),
source -> isClosed.compareAndSet(false, true),
newListenerId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ public void testAccounts() {
server.close();
}

@Test
public void testBuildUriIsIdempotent() {
Server server = new Server("https://horizon-testnet.stellar.org");
AccountsRequestBuilder builder = server.accounts().limit(200).order(RequestBuilder.Order.ASC);

assertEquals(
"https://horizon-testnet.stellar.org/accounts?limit=200&order=asc",
builder.buildUri().toString());
assertEquals(
"https://horizon-testnet.stellar.org/accounts?limit=200&order=asc",
builder.buildUri().toString());

builder.cursor("13537736921089");
assertEquals(
"https://horizon-testnet.stellar.org/accounts?limit=200&order=asc&cursor=13537736921089",
builder.buildUri().toString());
server.close();
}

@Test
public void testForSigner() {
Server server = new Server("https://horizon-testnet.stellar.org");
Expand Down
40 changes: 40 additions & 0 deletions src/test/java/org/stellar/sdk/requests/StreamingSmokeTest.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,55 @@
package org.stellar.sdk.requests;

import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.stellar.sdk.Server;
import org.stellar.sdk.responses.AccountResponse;
import org.stellar.sdk.responses.LedgerResponse;

public class StreamingSmokeTest {

@Test
public void shouldStreamFromRequestBuilderEndpointPath() throws Exception {
MockWebServer mockWebServer = new MockWebServer();
mockWebServer.enqueue(
new MockResponse()
.setHeader("Content-Type", "text/event-stream")
.setBody("data: \"hello\"\n\n"));

Server server = new Server(mockWebServer.url("").toString());
SSEStream<AccountResponse> stream =
server.accounts().limit(10).stream(
new EventListener<AccountResponse>() {
@Override
public void onEvent(AccountResponse response) {}

@Override
public void onFailure(Optional<Throwable> error, Optional<Integer> responseCode) {}
});

try {
RecordedRequest request = mockWebServer.takeRequest(5, TimeUnit.SECONDS);
Assert.assertNotNull(request);
HttpUrl url = HttpUrl.parse("https://horizon-testnet.stellar.org" + request.getPath());
Assert.assertNotNull(url);
Assert.assertEquals("/accounts", url.encodedPath());
Assert.assertEquals("10", url.queryParameter("limit"));
Assert.assertEquals("java-stellar-sdk", url.queryParameter("X-Client-Name"));
} finally {
stream.close();
server.close();
mockWebServer.shutdown();
}
}

@Test
@Ignore // lets not run this by default for now
public void shouldStreamLedgersFromTestNet() {
Expand Down
Loading