Skip to content

Commit 778c2c6

Browse files
committed
Add endpoint rules engine
1 parent e0e989f commit 778c2c6

File tree

56 files changed

+5655
-47
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+5655
-47
lines changed

build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ plugins {
77
alias(libs.plugins.jreleaser)
88
}
99

10+
repositories {
11+
mavenLocal()
12+
mavenCentral()
13+
}
14+
1015
task("addGitHooks") {
1116
onlyIf("unix") {
1217
!Os.isFamily(Os.FAMILY_WINDOWS)

buildSrc/src/main/kotlin/smithy-java.module-conventions.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ afterEvaluate {
4949
manifest {
5050
attributes(mapOf("Automatic-Module-Name" to moduleName))
5151
}
52-
duplicatesStrategy = DuplicatesStrategy.FAIL
5352
}
5453
}
5554

client/client-core/src/main/java/software/amazon/smithy/java/client/core/CallContext.java

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package software.amazon.smithy.java.client.core;
77

8-
import java.time.Duration;
98
import java.util.HashSet;
109
import java.util.Set;
1110
import software.amazon.smithy.java.auth.api.identity.Identity;
@@ -15,30 +14,26 @@
1514

1615
/**
1716
* Context parameters made available to underlying transports like HTTP clients.
17+
*
18+
* <p>Settings that can be applied client-wide can be found in {@link ClientContext}.
1819
*/
1920
public final class CallContext {
2021
/**
21-
* The total amount of time to wait for an API call to complete, including retries, and serialization.
22+
* The read-only endpoint for the request.
2223
*/
23-
public static final Context.Key<Duration> API_CALL_TIMEOUT = Context.key("API call timeout");
24-
25-
/**
26-
* The amount of time to wait for a single, underlying network request to complete before giving up and timing out.
27-
*/
28-
public static final Context.Key<Duration> API_CALL_ATTEMPT_TIMEOUT = Context.key("API call attempt timeout");
24+
public static final Context.Key<Endpoint> ENDPOINT = Context.key("Endpoint of the request");
2925

3026
/**
3127
* The endpoint resolver used to resolve the destination endpoint for a request.
28+
*
29+
* <p>This is a read-only value; modifying this value has no effect on a request.
3230
*/
3331
public static final Context.Key<EndpointResolver> ENDPOINT_RESOLVER = Context.key("EndpointResolver");
3432

3533
/**
36-
* The read-only resolved endpoint for the request.
37-
*/
38-
public static final Context.Key<Endpoint> ENDPOINT = Context.key("Endpoint of the request");
39-
40-
/**
41-
* The identity resolved for the request.
34+
* The read-only identity resolved for the request.
35+
*
36+
* <p>This is a read-only value; modifying this value has no effect on a request.
4237
*/
4338
public static final Context.Key<Identity> IDENTITY = Context.key("Identity of the caller");
4439

@@ -74,16 +69,5 @@ public final class CallContext {
7469
"Feature IDs used with a request",
7570
HashSet::new);
7671

77-
/**
78-
* The name of the application, used in things like user-agent headers.
79-
*
80-
* <p>This value is used by AWS SDKs, but can be used generically for any client.
81-
* See <a href="https://docs.aws.amazon.com/sdkref/latest/guide/feature-appid.html">Application ID</a> for more
82-
* information.
83-
*
84-
* <p>This value should be less than 50 characters.
85-
*/
86-
public static final Context.Key<String> APPLICATION_ID = Context.key("Application ID");
87-
8872
private CallContext() {}
8973
}

client/client-core/src/main/java/software/amazon/smithy/java/client/core/Client.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import software.amazon.smithy.java.auth.api.identity.IdentityResolvers;
1414
import software.amazon.smithy.java.client.core.auth.scheme.AuthScheme;
1515
import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver;
16+
import software.amazon.smithy.java.client.core.endpoint.Endpoint;
1617
import software.amazon.smithy.java.client.core.endpoint.EndpointResolver;
1718
import software.amazon.smithy.java.client.core.interceptors.CallHook;
1819
import software.amazon.smithy.java.client.core.interceptors.ClientInterceptor;
@@ -224,6 +225,34 @@ public B endpointResolver(EndpointResolver endpointResolver) {
224225
return (B) this;
225226
}
226227

228+
/**
229+
* Set a custom endpoint for the client to use.
230+
*
231+
* <p>Note that things like "hostLabel" traits may still cause the endpoint to change. For a completely
232+
* static endpoint that never changes, use {@link EndpointResolver#staticHost}.
233+
*
234+
* @param customEndpoint Endpoint to use with the client.
235+
* @return the builder.
236+
*/
237+
@SuppressWarnings("unchecked")
238+
public B endpoint(Endpoint customEndpoint) {
239+
putConfig(ClientContext.CUSTOM_ENDPOINT, customEndpoint);
240+
return (B) this;
241+
}
242+
243+
/**
244+
* Set a custom endpoint for the client to use.
245+
*
246+
* @param customEndpoint Endpoint to use with the client.
247+
* @return the builder.
248+
* @see #endpoint(Endpoint)
249+
*/
250+
@SuppressWarnings("unchecked")
251+
public B endpoint(String customEndpoint) {
252+
putConfig(ClientContext.CUSTOM_ENDPOINT, Endpoint.builder().uri(customEndpoint).build());
253+
return (B) this;
254+
}
255+
227256
/**
228257
* Add an interceptor to the client.
229258
*

client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientCall.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ private ClientCall(Builder<I, O> builder) {
6464
supportedAuthSchemes = builder.supportedAuthSchemes.stream()
6565
.collect(Collectors.toMap(AuthScheme::schemeId, Function.identity(), (key1, key2) -> key1));
6666

67+
context.put(CallContext.ENDPOINT_RESOLVER, endpointResolver);
68+
6769
// Retries
6870
retryStrategy = Objects.requireNonNull(builder.retryStrategy, "retryStrategy is null");
6971
retryScope = Objects.requireNonNullElse(builder.retryScope, "");

client/client-core/src/main/java/software/amazon/smithy/java/client/core/ClientConfig.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,19 @@ private ClientConfig(Builder builder) {
7070
this.transport = builder.transport;
7171
ClientPipeline.validateProtocolAndTransport(protocol, transport);
7272

73-
this.endpointResolver = Objects.requireNonNull(builder.endpointResolver, "endpointResolver is null");
73+
// Use an explicitly given resolver if one was set.
74+
if (builder.endpointResolver != null) {
75+
this.endpointResolver = builder.endpointResolver;
76+
} else {
77+
// Use a custom endpoint and static endpoint resolver if a custom endpoint was given.
78+
// Things like the Smithy rules engine based resolver look for this property to know if a custom endpoint
79+
// was provided in this manner.
80+
var customEndpoint = builder.context.get(ClientContext.CUSTOM_ENDPOINT);
81+
if (customEndpoint == null) {
82+
throw new NullPointerException("Both endpointResolver and ClientContext.CUSTOM_ENDPOINT are not set");
83+
}
84+
this.endpointResolver = EndpointResolver.staticEndpoint(customEndpoint);
85+
}
7486

7587
this.interceptors = List.copyOf(builder.interceptors);
7688

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.client.core;
7+
8+
import java.time.Duration;
9+
import software.amazon.smithy.java.client.core.endpoint.Endpoint;
10+
import software.amazon.smithy.java.client.core.endpoint.EndpointResolver;
11+
import software.amazon.smithy.java.context.Context;
12+
13+
/**
14+
* Context parameters that can be provided on a client config and take effect on each request.
15+
*
16+
* <p>Other per/call settings can be found in {@link CallContext}.
17+
*/
18+
public final class ClientContext {
19+
/**
20+
* A custom endpoint used in each request.
21+
*
22+
* <p>This can be used in lieu of setting something like {@link EndpointResolver#staticEndpoint}, allowing
23+
* endpoint resolvers like the Smithy Rules Engine resolver to still process and validate endpoints even when a
24+
* custom endpoint is provided.
25+
*/
26+
public static final Context.Key<Endpoint> CUSTOM_ENDPOINT = Context.key("Custom endpoint to use with requests");
27+
28+
/**
29+
* The name of the application, used in things like user-agent headers.
30+
*
31+
* <p>This value is used by AWS SDKs, but can be used generically for any client.
32+
* See <a href="https://docs.aws.amazon.com/sdkref/latest/guide/feature-appid.html">Application ID</a> for more
33+
* information.
34+
*
35+
* <p>This value should be less than 50 characters.
36+
*/
37+
public static final Context.Key<String> APPLICATION_ID = Context.key("Application ID");
38+
39+
/**
40+
* The total amount of time to wait for an API call to complete, including retries, and serialization.
41+
*
42+
* <p>This can be overridden per/call too.
43+
*/
44+
public static final Context.Key<Duration> API_CALL_TIMEOUT = Context.key("API call timeout");
45+
46+
/**
47+
* The amount of time to wait for a single, underlying network request to complete before giving up and timing out.
48+
*
49+
* <p>This can be overridden per/call too.
50+
*/
51+
public static final Context.Key<Duration> API_CALL_ATTEMPT_TIMEOUT = Context.key("API call attempt timeout");
52+
53+
private ClientContext() {}
54+
}

client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/Endpoint.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@ public interface Endpoint {
5151
*
5252
* @return the builder.
5353
*/
54+
@SuppressWarnings("unchecked")
5455
default Builder toBuilder() {
5556
var builder = new EndpointImpl.Builder();
5657
builder.uri(uri());
57-
properties().forEach(k -> builder.properties.put(k, property(k)));
58+
for (var e : properties()) {
59+
builder.putProperty((Context.Key<Object>) e, property(e));
60+
}
5861
for (EndpointAuthScheme authScheme : authSchemes()) {
5962
builder.addAuthScheme(authScheme);
6063
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.client.core.endpoint;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
import software.amazon.smithy.java.context.Context;
11+
12+
/**
13+
* Context parameters specifically relevant to a resolved {@link Endpoint} property.
14+
*/
15+
public final class EndpointContext {
16+
17+
private EndpointContext() {}
18+
19+
/**
20+
* Assigns headers to an endpoint. These are typically HTTP headers.
21+
*/
22+
public static final Context.Key<Map<String, List<String>>> HEADERS = Context.key("Endpoint headers");
23+
}

client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/EndpointImpl.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ final class EndpointImpl implements Endpoint {
2323

2424
private EndpointImpl(Builder builder) {
2525
this.uri = Objects.requireNonNull(builder.uri);
26-
this.authSchemes = List.copyOf(builder.authSchemes);
27-
this.properties = Map.copyOf(builder.properties);
26+
this.authSchemes = builder.authSchemes == null ? List.of() : builder.authSchemes;
27+
this.properties = builder.properties == null ? Map.of() : builder.properties;
28+
// Clear out the builder, making this class immutable and the builder still reusable.
29+
builder.authSchemes = null;
30+
builder.properties = null;
2831
}
2932

3033
@Override
@@ -66,11 +69,16 @@ public int hashCode() {
6669
return Objects.hash(uri, authSchemes, properties);
6770
}
6871

72+
@Override
73+
public String toString() {
74+
return "Endpoint{uri=" + uri + ", authSchemes=" + authSchemes + ", properties=" + properties + '}';
75+
}
76+
6977
static final class Builder implements Endpoint.Builder {
7078

7179
private URI uri;
72-
private final List<EndpointAuthScheme> authSchemes = new ArrayList<>();
73-
final Map<Context.Key<?>, Object> properties = new HashMap<>();
80+
private List<EndpointAuthScheme> authSchemes;
81+
private Map<Context.Key<?>, Object> properties;
7482

7583
@Override
7684
public Builder uri(URI uri) {
@@ -89,12 +97,18 @@ public Builder uri(String uri) {
8997

9098
@Override
9199
public Builder addAuthScheme(EndpointAuthScheme authScheme) {
100+
if (this.authSchemes == null) {
101+
this.authSchemes = new ArrayList<>();
102+
}
92103
this.authSchemes.add(authScheme);
93104
return this;
94105
}
95106

96107
@Override
97108
public <T> Builder putProperty(Context.Key<T> property, T value) {
109+
if (this.properties == null) {
110+
this.properties = new HashMap<>();
111+
}
98112
properties.put(property, value);
99113
return this;
100114
}

client/client-core/src/main/java/software/amazon/smithy/java/client/core/endpoint/EndpointResolver.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static EndpointResolver staticEndpoint(URI endpoint) {
7272
*/
7373
static EndpointResolver staticHost(Endpoint endpoint) {
7474
Objects.requireNonNull(endpoint);
75-
return params -> CompletableFuture.completedFuture(endpoint);
75+
return new StaticHostResolver(endpoint);
7676
}
7777

7878
/**
@@ -83,8 +83,7 @@ static EndpointResolver staticHost(Endpoint endpoint) {
8383
*/
8484
static EndpointResolver staticHost(String endpoint) {
8585
Objects.requireNonNull(endpoint);
86-
var ep = Endpoint.builder().uri(endpoint).build();
87-
return params -> CompletableFuture.completedFuture(ep);
86+
return staticHost(Endpoint.builder().uri(endpoint).build());
8887
}
8988

9089
/**
@@ -95,7 +94,6 @@ static EndpointResolver staticHost(String endpoint) {
9594
*/
9695
static EndpointResolver staticHost(URI endpoint) {
9796
Objects.requireNonNull(endpoint);
98-
var ep = Endpoint.builder().uri(endpoint).build();
99-
return params -> CompletableFuture.completedFuture(ep);
97+
return staticHost(Endpoint.builder().uri(endpoint).build());
10098
}
10199
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.client.core.endpoint;
7+
8+
import java.util.concurrent.CompletableFuture;
9+
10+
/**
11+
* An endpoint resolver that always returns the same endpoint.
12+
*
13+
* @param endpoint Endpoint to return exactly.
14+
*/
15+
record StaticHostResolver(Endpoint endpoint) implements EndpointResolver {
16+
@Override
17+
public CompletableFuture<Endpoint> resolveEndpoint(EndpointResolverParams params) {
18+
return CompletableFuture.completedFuture(endpoint);
19+
}
20+
}

0 commit comments

Comments
 (0)