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
2 changes: 1 addition & 1 deletion .github/workflows/links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v6
- name: lychee Link Checker
id: lychee
uses: lycheeverse/lychee-action@v2.7.0
uses: lycheeverse/lychee-action@v2.8.0
with:
args: --accept=200,403,429 "**/*.html" "**/*.md" "**/*.txt" "**/*.json"
fail: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-integration-unreleased.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:

- name: Upload Reports
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: test-reports-os${{ matrix.entry.opensearch_ref }}-java${{ matrix.entry.java }}
path: opensearch-java/java-client/build/reports/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:

- name: Upload Reports
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: test-reports-os${{ matrix.entry.opensearch_version }}-java${{ matrix.entry.java }}
path: java-client/build/reports/
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

- name: Upload Reports
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: test-reports-java${{ matrix.java }}-${{ runner.os }}
path: java-client/build/reports/
Expand Down Expand Up @@ -59,7 +59,7 @@ jobs:

- name: Upload Reports
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: test-reports-java8-${{ runner.os }}
path: java-client/build/reports/
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Removed

### Fixed
- Fix NPEs while converting transport options when null options, headers, or query parameters are provided ([#1887](https://github.com/opensearch-project/opensearch-java/pull/1888))

### Security

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,25 @@ static ApacheHttpClient5Options initialOptions() {
}

static ApacheHttpClient5Options of(TransportOptions options) {
if (options == null) return DEFAULT;

if (options instanceof ApacheHttpClient5Options) {
return (ApacheHttpClient5Options) options;
}

} else {
final Builder builder = new Builder(DEFAULT.toBuilder());
options.headers().forEach(h -> builder.addHeader(h.getKey(), h.getValue()));
options.queryParameters().forEach(builder::setParameter);
builder.onWarnings(options.onWarnings());
return builder.build();
final Builder builder = new Builder(DEFAULT.toBuilder());
final Collection<Entry<String, String>> headers = options.headers();
if (headers != null && !headers.isEmpty()) {
headers.forEach(h -> builder.addHeader(h.getKey(), h.getValue()));
}

final Map<String, String> parameters = options.queryParameters();
if (parameters != null && !parameters.isEmpty()) {
parameters.forEach(builder::setParameter);
}

builder.onWarnings(options.onWarnings());
return builder.build();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,28 @@ public class RestClientOptions implements TransportOptions {
private final RequestOptions options;

static RestClientOptions of(TransportOptions options) {
if (options == null) {
return new Builder(RequestOptions.DEFAULT.toBuilder()).build();
}

if (options instanceof RestClientOptions) {
return (RestClientOptions) options;
}

final Builder builder = new Builder(RequestOptions.DEFAULT.toBuilder());

} else {
final Builder builder = new Builder(RequestOptions.DEFAULT.toBuilder());
options.headers().forEach(h -> builder.addHeader(h.getKey(), h.getValue()));
options.queryParameters().forEach(builder::setParameter);
builder.onWarnings(options.onWarnings());
return builder.build();
final Collection<Map.Entry<String, String>> headers = options.headers();
if (headers != null && !headers.isEmpty()) {
headers.forEach(h -> builder.addHeader(h.getKey(), h.getValue()));
}

final Map<String, String> parameters = options.queryParameters();
if (parameters != null && !parameters.isEmpty()) {
parameters.forEach(builder::setParameter);
}

builder.onWarnings(options.onWarnings());
return builder.build();
}

public RestClientOptions(RequestOptions options) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.client.transport.httpclient5;

import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import org.junit.Assert;
import org.junit.Test;
import org.opensearch.client.transport.TransportOptions;

public class ApacheHttpClient5OptionsTest extends Assert {
@Test
public void testOfWithNullOptions() {
// Null input should fall back to defaults instead of throwing.
ApacheHttpClient5Options options = ApacheHttpClient5Options.of(null);

assertNotNull(options);
assertNotNull(options.headers());
}

@Test
public void testOfWithNullHeaders() {
// Missing headers from source options should be treated as empty.
ApacheHttpClient5Options options = ApacheHttpClient5Options.of(
transportOptions(null, Collections.emptyMap(), warnings -> warnings != null && !warnings.isEmpty())
);

assertNotNull(options);
assertNotNull(options.headers());
assertTrue(options.headers().isEmpty());
assertNotNull(options.onWarnings());
assertTrue(options.onWarnings().apply(Collections.singletonList("warning")));
}

@Test
public void testOfWithNullQueryParameters() {
// Null query parameters should not drop other fields during conversion.
ApacheHttpClient5Options options = ApacheHttpClient5Options.of(
transportOptions(
Collections.singletonList(new AbstractMap.SimpleImmutableEntry<>("x-test-header", "value")),
null,
warnings -> warnings != null && !warnings.isEmpty()
)
);

assertNotNull(options);
assertNotNull(options.onWarnings());
assertTrue(options.onWarnings().apply(Collections.singletonList("warning")));
assertTrue(containsHeader(options.headers(), "x-test-header", "value"));
}

private static boolean containsHeader(Collection<Entry<String, String>> headers, String name, String value) {
for (Entry<String, String> header : headers) {
if (name.equals(header.getKey()) && value.equals(header.getValue())) {
return true;
}
}

return false;
}

private static TransportOptions transportOptions(
Collection<Entry<String, String>> headers,
Map<String, String> queryParameters,
Function<List<String>, Boolean> warningsHandler
) {
return new TransportOptions() {
@Override
public Collection<Entry<String, String>> headers() {
return headers;
}

@Override
public Map<String, String> queryParameters() {
return queryParameters;
}

@Override
public Function<List<String>, Boolean> onWarnings() {
return warningsHandler;
}

@Override
public Builder toBuilder() {
return new BuilderImpl(this);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.client.transport.rest_client;

import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import org.junit.Assert;
import org.junit.Test;
import org.opensearch.client.transport.TransportOptions;

public class RestClientOptionsTest extends Assert {
@Test
public void testOfWithNullOptions() {
// Null input should fall back to defaults instead of throwing.
RestClientOptions options = RestClientOptions.of(null);

assertNotNull(options);
assertNotNull(options.headers());
}

@Test
public void testOfWithNullHeaders() {
// Missing headers from source options should be treated as empty.
RestClientOptions options = RestClientOptions.of(
transportOptions(null, Collections.emptyMap(), warnings -> warnings != null && !warnings.isEmpty())
);

assertNotNull(options);
assertNotNull(options.headers());
assertTrue(options.headers().isEmpty());
assertNotNull(options.onWarnings());
assertTrue(options.onWarnings().apply(Collections.singletonList("warning")));
}

@Test
public void testOfWithNullQueryParameters() {
// Null query parameters should not drop other fields during conversion.
RestClientOptions options = RestClientOptions.of(
transportOptions(
Collections.singletonList(new AbstractMap.SimpleImmutableEntry<>("x-test-header", "value")),
null,
warnings -> warnings != null && !warnings.isEmpty()
)
);

assertNotNull(options);
assertNotNull(options.onWarnings());
assertTrue(options.onWarnings().apply(Collections.singletonList("warning")));
assertTrue(containsHeader(options.headers(), "x-test-header", "value"));
}

private static boolean containsHeader(Collection<Entry<String, String>> headers, String name, String value) {
for (Entry<String, String> header : headers) {
if (name.equals(header.getKey()) && value.equals(header.getValue())) {
return true;
}
}

return false;
}

private static TransportOptions transportOptions(
Collection<Entry<String, String>> headers,
Map<String, String> queryParameters,
Function<List<String>, Boolean> warningsHandler
) {
return new TransportOptions() {
@Override
public Collection<Entry<String, String>> headers() {
return headers;
}

@Override
public Map<String, String> queryParameters() {
return queryParameters;
}

@Override
public Function<List<String>, Boolean> onWarnings() {
return warningsHandler;
}

@Override
public Builder toBuilder() {
return new BuilderImpl(this);
}
};
}
}
Loading