Skip to content

Add validation in S3ClientConfiguration #2538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,14 @@
</goals>
</execution>
</executions>
<configuration>
<excludes>
<exclude>software/amazon/awssdk/modulepath/tests/**/*.class</exclude>
Copy link
Contributor Author

@zoewangg zoewangg Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to exclude test coverage reporting for test classes

<exclude>software/amazon/awssdk/nativeimagetest/**/*.class</exclude>
<exclude>software/amazon/awssdk/testutils/service/**/*.class</exclude>
<exclude>**/*Benchmark.class</exclude>
</excludes>
</configuration>
</plugin>

<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

package software.amazon.awssdk.transfer.s3;

import java.util.Objects;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkPreviewApi;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;

Expand All @@ -33,16 +35,17 @@
public final class S3ClientConfiguration implements ToCopyableBuilder<S3ClientConfiguration.Builder, S3ClientConfiguration> {
private final AwsCredentialsProvider credentialsProvider;
private final Region region;
private final Long partSizeBytes;
private final Long minimumPartSizeInBytes;
private final Double targetThroughputInGbps;
private final Integer maxConcurrency;

private S3ClientConfiguration(DefaultBuilder builder) {
this.credentialsProvider = builder.credentialsProvider;
this.region = builder.region;
this.partSizeBytes = builder.partSizeBytes;
this.targetThroughputInGbps = builder.targetThroughputInGbps;
this.maxConcurrency = builder.maxConcurrency;
this.minimumPartSizeInBytes = Validate.isPositiveOrNull(builder.minimumPartSizeInBytes, "minimumPartSizeInBytes");
this.targetThroughputInGbps = Validate.isPositiveOrNull(builder.targetThroughputInGbps, "targetThroughputInGbps");
this.maxConcurrency = Validate.isPositiveOrNull(builder.maxConcurrency,
"maxConcurrency");
}

/**
Expand All @@ -63,7 +66,7 @@ public Optional<Region> region() {
* @return the optional minimum part size for transfer parts.
*/
public Optional<Long> minimumPartSizeInBytes() {
return Optional.ofNullable(partSizeBytes);
return Optional.ofNullable(minimumPartSizeInBytes);
}

/**
Expand All @@ -85,6 +88,42 @@ public Builder toBuilder() {
return new DefaultBuilder(this);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

S3ClientConfiguration that = (S3ClientConfiguration) o;

if (!Objects.equals(credentialsProvider, that.credentialsProvider)) {
return false;
}
if (!Objects.equals(region, that.region)) {
return false;
}
if (!Objects.equals(minimumPartSizeInBytes, that.minimumPartSizeInBytes)) {
return false;
}
if (!Objects.equals(targetThroughputInGbps, that.targetThroughputInGbps)) {
return false;
}
return Objects.equals(maxConcurrency, that.maxConcurrency);
}

@Override
public int hashCode() {
int result = credentialsProvider != null ? credentialsProvider.hashCode() : 0;
result = 31 * result + (region != null ? region.hashCode() : 0);
result = 31 * result + (minimumPartSizeInBytes != null ? minimumPartSizeInBytes.hashCode() : 0);
result = 31 * result + (targetThroughputInGbps != null ? targetThroughputInGbps.hashCode() : 0);
result = 31 * result + (maxConcurrency != null ? maxConcurrency.hashCode() : 0);
return result;
}

public static Builder builder() {
return new DefaultBuilder();
}
Expand Down Expand Up @@ -152,7 +191,7 @@ public interface Builder extends CopyableBuilder<Builder, S3ClientConfiguration>
private static final class DefaultBuilder implements Builder {
private AwsCredentialsProvider credentialsProvider;
private Region region;
private Long partSizeBytes;
private Long minimumPartSizeInBytes;
private Double targetThroughputInGbps;
private Integer maxConcurrency;

Expand All @@ -162,7 +201,7 @@ private DefaultBuilder() {
private DefaultBuilder(S3ClientConfiguration configuration) {
this.credentialsProvider = configuration.credentialsProvider;
this.region = configuration.region;
this.partSizeBytes = configuration.partSizeBytes;
this.minimumPartSizeInBytes = configuration.minimumPartSizeInBytes;
this.targetThroughputInGbps = configuration.targetThroughputInGbps;
this.maxConcurrency = configuration.maxConcurrency;
}
Expand All @@ -181,7 +220,7 @@ public Builder region(Region region) {

@Override
public Builder minimumPartSizeInBytes(Long partSizeBytes) {
this.partSizeBytes = partSizeBytes;
this.minimumPartSizeInBytes = partSizeBytes;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.transfer.s3;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static software.amazon.awssdk.transfer.s3.SizeConstant.MB;

import org.junit.Test;
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;

public class S3ClientConfigurationTest {

@Test
public void nonPositiveMinimumPartSizeInBytes_shouldThrowException() {
assertThatThrownBy(() -> S3ClientConfiguration.builder()
.minimumPartSizeInBytes(-10L)
.build())
.hasMessageContaining("must be positive");
assertThatThrownBy(() -> S3ClientConfiguration.builder()
.minimumPartSizeInBytes(0L)
.build())
.hasMessageContaining("must be positive");

}

@Test
public void nonPositiveTargetThroughput_shouldThrowException() {
assertThatThrownBy(() -> S3ClientConfiguration.builder()
.targetThroughputInGbps(-10.0)
.build())
.hasMessageContaining("must be positive");
assertThatThrownBy(() -> S3ClientConfiguration.builder()
.targetThroughputInGbps(0.0)
.build())
.hasMessageContaining("must be positive");
}

@Test
public void nonPositiveMaxConcurrency_shouldThrowException() {
assertThatThrownBy(() -> S3ClientConfiguration.builder()
.maxConcurrency(-10)
.build())
.hasMessageContaining("must be positive");
assertThatThrownBy(() -> S3ClientConfiguration.builder()
.maxConcurrency(0)
.build())
.hasMessageContaining("must be positive");
}

@Test
public void build_allProperties() {
AwsCredentialsProvider credentials = () -> AwsBasicCredentials.create("test"
, "test");
S3ClientConfiguration configuration = S3ClientConfiguration.builder()
.credentialsProvider(credentials)
.maxConcurrency(100)
.targetThroughputInGbps(10.0)
.region(Region.US_WEST_2)
.minimumPartSizeInBytes(5 * MB)
.build();

assertThat(configuration.credentialsProvider()).contains(credentials);
assertThat(configuration.maxConcurrency()).contains(100);
assertThat(configuration.region()).contains(Region.US_WEST_2);
assertThat(configuration.targetThroughputInGbps()).contains(10.0);
assertThat(configuration.minimumPartSizeInBytes()).contains(5 * MB);
}

@Test
public void build_emptyBuilder() {
S3ClientConfiguration configuration = S3ClientConfiguration.builder()
.build();

assertThat(configuration.credentialsProvider()).isEmpty();
assertThat(configuration.maxConcurrency()).isEmpty();
assertThat(configuration.region()).isEmpty();
assertThat(configuration.targetThroughputInGbps()).isEmpty();
assertThat(configuration.minimumPartSizeInBytes()).isEmpty();
}

@Test
public void equalsHashCode() {
AwsCredentialsProvider credentials = () -> AwsBasicCredentials.create("test"
, "test");
S3ClientConfiguration configuration1 = S3ClientConfiguration.builder()
.credentialsProvider(credentials)
.maxConcurrency(100)
.targetThroughputInGbps(10.0)
.region(Region.US_WEST_2)
.minimumPartSizeInBytes(5 * MB)
.build();

S3ClientConfiguration configuration2 = S3ClientConfiguration.builder()
.credentialsProvider(credentials)
.maxConcurrency(100)
.targetThroughputInGbps(10.0)
.region(Region.US_WEST_2)
.minimumPartSizeInBytes(5 * MB)
.build();

S3ClientConfiguration configuration3 = configuration1.toBuilder()
.credentialsProvider(AnonymousCredentialsProvider.create())
.maxConcurrency(50)
.targetThroughputInGbps(1.0)
.build();

assertThat(configuration1).isEqualTo(configuration2);
assertThat(configuration1.hashCode()).isEqualTo(configuration2.hashCode());
assertThat(configuration1).isNotEqualTo(configuration3);
assertThat(configuration1.hashCode()).isNotEqualTo(configuration3.hashCode());
}
}
22 changes: 22 additions & 0 deletions utils/src/main/java/software/amazon/awssdk/utils/Validate.java
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,13 @@ public static long isPositive(long num, String fieldName) {
return num;
}

public static double isPositive(double num, String fieldName) {
if (num <= 0) {
throw new IllegalArgumentException(String.format("%s must be positive", fieldName));
}
return num;
}

public static int isNotNegative(int num, String fieldName) {

if (num < 0) {
Expand Down Expand Up @@ -685,6 +692,21 @@ public static Integer isPositiveOrNull(Integer num, String fieldName) {
return isPositive(num, fieldName);
}

/**
* Asserts that the given boxed double is positive (non-negative and non-zero) or null.
*
* @param num Boxed double to validate
* @param fieldName Field name to display in exception message if not positive.
* @return Duration if double or null.
*/
public static Double isPositiveOrNull(Double num, String fieldName) {
if (num == null) {
return null;
}

return isPositive(num, fieldName);
}

/**
* Asserts that the given boxed long is positive (non-negative and non-zero) or null.
*
Expand Down
25 changes: 25 additions & 0 deletions utils/src/test/java/software/amazon/awssdk/utils/ValidateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,31 @@ public void isPositiveOrNullLong_negative_throws() {
Validate.isPositiveOrNull(-1L, "foo");
}

@Test
public void isPositiveOrNullDouble_null_returnsNull() {
assertNull(Validate.isPositiveOrNull((Double) null, "foo"));
}

@Test
public void isPositiveOrNullDouble_positive_returnsInteger() {
Double num = 100.0;
assertEquals(num, Validate.isPositiveOrNull(num, "foo"));
}

@Test
public void isPositiveOrNullDouble_zero_throws() {
expected.expect(IllegalArgumentException.class);
expected.expectMessage("foo");
Validate.isPositiveOrNull(0.0, "foo");
}

@Test
public void isPositiveOrNullDouble_negative_throws() {
expected.expect(IllegalArgumentException.class);
expected.expectMessage("foo");
Validate.isPositiveOrNull(-1.0, "foo");
}

@Test
public void isNull_notNull_shouldThrow() {
expected.expect(IllegalArgumentException.class);
Expand Down