Skip to content

Commit 7146d45

Browse files
committed
Address reviews
1 parent 1c0febf commit 7146d45

7 files changed

Lines changed: 85 additions & 80 deletions

File tree

codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,4 @@ private AwsConfiguration() {}
6060
.build())
6161
.defaultValue("RetryStrategyOptions(retry_mode=\"standard\")")
6262
.build();
63-
64-
public static final ConfigProperty USER_AGENT_EXTRA = ConfigProperty.builder()
65-
.name("user_agent_extra")
66-
.type(Symbol.builder().name("str").build())
67-
.documentation("Additional suffix to be appended to the User-Agent header.")
68-
.nullable(false)
69-
.useDescriptor(true)
70-
.defaultValue("''")
71-
.build();
72-
73-
public static final ConfigProperty SDK_UA_APP_ID = ConfigProperty.builder()
74-
.name("sdk_ua_app_id")
75-
.type(Symbol.builder().name("str").build())
76-
.documentation("A unique and opaque application ID that is appended to the User-Agent header.")
77-
.nullable(false)
78-
.useDescriptor(true)
79-
.defaultValue("''")
80-
.build();
81-
82-
public static final ConfigProperty ENDPOINT_URL = ConfigProperty.builder()
83-
.name("endpoint_url")
84-
.type(Symbol.builder().name("str").build())
85-
.documentation("The endpoint URL to use for requests. If not set, the standard endpoint for the service and region will be used.")
86-
.nullable(true)
87-
.useDescriptor(true)
88-
.build();
8963
}

codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55
package software.amazon.smithy.python.aws.codegen;
66

7-
import static software.amazon.smithy.python.aws.codegen.AwsConfiguration.ENDPOINT_URL;
87
import static software.amazon.smithy.python.aws.codegen.AwsConfiguration.REGION;
98

109
import java.util.List;
@@ -24,7 +23,6 @@ public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
2423
return List.of(
2524
RuntimeClientPlugin.builder()
2625
.addConfigProperty(REGION)
27-
.addConfigProperty(ENDPOINT_URL)
2826
.build());
2927
} else {
3028
return List.of();

codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
import software.amazon.smithy.codegen.core.Symbol;
1010
import software.amazon.smithy.codegen.core.SymbolReference;
1111
import software.amazon.smithy.python.codegen.CodegenUtils;
12+
import software.amazon.smithy.python.codegen.ConfigProperty;
1213
import software.amazon.smithy.python.codegen.GenerationContext;
1314
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
1415
import software.amazon.smithy.python.codegen.integrations.RuntimeClientPlugin;
1516
import software.amazon.smithy.utils.SmithyInternalApi;
1617

17-
import static software.amazon.smithy.python.aws.codegen.AwsConfiguration.SDK_UA_APP_ID;
18-
import static software.amazon.smithy.python.aws.codegen.AwsConfiguration.USER_AGENT_EXTRA;
19-
2018
/**
2119
* Adds a runtime plugin to set user agent.
2220
*/
@@ -38,6 +36,27 @@ def aws_user_agent_plugin(config: $1T):
3836
@Override
3937
public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
4038
if (context.applicationProtocol().isHttpProtocol()) {
39+
final ConfigProperty userAgentExtra = ConfigProperty.builder()
40+
.name("user_agent_extra")
41+
.documentation("Additional suffix to be added to the User-Agent header.")
42+
.type(Symbol.builder().name("str").build())
43+
.nullable(true)
44+
.build();
45+
46+
final ConfigProperty uaAppId = ConfigProperty.builder()
47+
.name("sdk_ua_app_id")
48+
.documentation(
49+
"A unique and opaque application ID that is appended to the User-Agent header.")
50+
.type(Symbol.builder().name("str").build())
51+
.nullable(true)
52+
.useDescriptor(true)
53+
.validator(Symbol.builder()
54+
.name("validate_and_sanitize_ua_string")
55+
.namespace("smithy_aws_core.config.validators", ".")
56+
.addDependency(AwsPythonDependency.SMITHY_AWS_CORE)
57+
.build())
58+
.build();
59+
4160
final String user_agent_plugin_file = "user_agent";
4261

4362
final String moduleName = context.settings().moduleName();
@@ -73,8 +92,8 @@ public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
7392

7493
return List.of(
7594
RuntimeClientPlugin.builder()
76-
.addConfigProperty(USER_AGENT_EXTRA)
77-
.addConfigProperty(SDK_UA_APP_ID)
95+
.addConfigProperty(userAgentExtra)
96+
.addConfigProperty(uaAppId)
7897
.pythonPlugin(userAgentPlugin)
7998
.writeAdditionalFiles((c) -> {
8099
String filename = "src/%s/%s.py".formatted(moduleName, user_agent_plugin_file);

codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ConfigGenerator.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,6 @@ public final class ConfigGenerator implements Runnable {
9797
.build())
9898
.build())
9999
.documentation("The retry strategy or options for configuring retry behavior. Can be either a configured RetryStrategy or RetryStrategyOptions to create one.")
100-
.build(),
101-
ConfigProperty.builder()
102-
.name("user_agent_extra")
103-
.type(Symbol.builder().name("str").build())
104-
.documentation("Additional suffix to be appended to the User-Agent header.")
105-
.build(),
106-
ConfigProperty.builder()
107-
.name("sdk_ua_app_id")
108-
.type(Symbol.builder().name("str").build())
109-
.documentation("A unique and opaque application ID that is appended to the User-Agent header.")
110-
.build(),
111-
ConfigProperty.builder()
112-
.name("endpoint_url")
113-
.type(Symbol.builder().name("str").build())
114-
.documentation("The endpoint URL to use for requests. If not set, the standard endpoint for the service and region will be used.")
115-
.nullable(true)
116100
.build());
117101

118102
// This list contains any properties that must be added to any http-based

packages/smithy-aws-core/src/smithy_aws_core/config/validators.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
import re
5+
from string import ascii_letters, digits
56
from typing import Any, get_args
67

78
from smithy_core.interfaces.retries import RetryStrategy
89
from smithy_core.retries import RetryStrategyOptions, RetryStrategyType
910

1011
from smithy_aws_core.config.source_info import SourceInfo
1112

13+
_USERAGENT_ALLOWED_CHARACTERS = ascii_letters + digits + "!#$%&'*+-.^_`|~/"
14+
1215

1316
class ConfigValidationError(ValueError):
1417
"""Raised when a configuration value fails validation."""
@@ -144,3 +147,30 @@ def validate_retry_strategy(
144147
f"retry_strategy must be RetryStrategy or RetryStrategyOptions, got {type(value).__name__}",
145148
source,
146149
)
150+
151+
152+
def validate_and_sanitize_ua_string(
153+
value: Any, source: SourceInfo | None = None
154+
) -> str | None:
155+
"""Validate and sanitize a User-Agent string component.
156+
157+
Replaces all disallowed characters with a hyphen. Allowed characters are
158+
ASCII alphanumerics and "!#$%&'*+-.^_`|~/".
159+
160+
:param value: The UA string value to sanitize
161+
:param source: The source that provided this value
162+
163+
:returns: The sanitized UA string or None if value is None
164+
165+
:raises ConfigValidationError: If the value is not a string
166+
"""
167+
if value is None:
168+
return None
169+
if not isinstance(value, str):
170+
raise ConfigValidationError(
171+
"sdk_ua_app_id",
172+
value,
173+
f"UA string must be a string, got {type(value).__name__}",
174+
source,
175+
)
176+
return "".join(c if c in _USERAGENT_ALLOWED_CHARACTERS else "-" for c in value)

packages/smithy-aws-core/tests/unit/config/test_property.py

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -96,31 +96,9 @@ def test_different_properties_resolve_independently(self) -> None:
9696
assert region == "us-west-2"
9797
assert retry_mode == "adaptive"
9898

99-
def test_unresolved_ua_extra_defaults_to_empty_string(self) -> None:
100-
class ConfigWithEmptyDefault:
101-
user_agent_extra = ConfigProperty(
102-
"user_agent_extra",
103-
default_value="",
104-
)
105-
106-
def __init__(self, resolver: ConfigResolver) -> None:
107-
self._resolver = resolver
108-
109-
source = StubSource("environment", {})
110-
resolver = ConfigResolver(sources=[source])
111-
config = ConfigWithEmptyDefault(resolver)
112-
113-
result = config.user_agent_extra
114-
115-
assert result == ""
116-
assert getattr(config, "_cache_user_agent_extra") == (
117-
"",
118-
SimpleSource("default"),
119-
)
120-
121-
def test_endpoint_url_defaults_to_none(self) -> None:
99+
def test_unresolved_ua_app_id_defaults_to_none(self) -> None:
122100
class ConfigWithNoDefault:
123-
endpoint_url = ConfigProperty("endpoint_url")
101+
sdk_ua_app_id = ConfigProperty("sdk_ua_app_id")
124102

125103
def __init__(self, resolver: ConfigResolver) -> None:
126104
self._resolver = resolver
@@ -129,10 +107,10 @@ def __init__(self, resolver: ConfigResolver) -> None:
129107
resolver = ConfigResolver(sources=[source])
130108
config = ConfigWithNoDefault(resolver)
131109

132-
result = config.endpoint_url
110+
result = config.sdk_ua_app_id
133111

134112
assert result is None
135-
assert getattr(config, "_cache_endpoint_url") == (
113+
assert getattr(config, "_cache_sdk_ua_app_id") == (
136114
None,
137115
SimpleSource("default"),
138116
)
@@ -148,7 +126,6 @@ def _create_config_with_validator(
148126

149127
class ConfigWithValidator:
150128
region = ConfigProperty("region", validator=validator)
151-
retry_strategy = ConfigProperty("retry_strategy", validator=validator)
152129

153130
def __init__(self, resolver: ConfigResolver) -> None:
154131
self._resolver = resolver
@@ -194,9 +171,7 @@ class ConfigWithComplexResolver:
194171
retry_strategy = ConfigProperty(
195172
"retry_strategy",
196173
resolver_func=mock_resolver,
197-
default_value=RetryStrategyOptions(
198-
retry_mode="standard", max_attempts=3
199-
),
174+
default_value=RetryStrategyOptions(retry_mode="standard"),
200175
)
201176

202177
def __init__(self, resolver: ConfigResolver) -> None:
@@ -212,7 +187,7 @@ def __init__(self, resolver: ConfigResolver) -> None:
212187

213188
assert isinstance(result, RetryStrategyOptions)
214189
assert result.retry_mode == "standard"
215-
assert result.max_attempts == 3
190+
assert result.max_attempts is None
216191
assert source_info == SimpleSource("default")
217192

218193
def test_validator_not_called_on_cached_access(self) -> None:

packages/smithy-aws-core/tests/unit/config/test_validators.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66
from smithy_aws_core.config.validators import (
77
ConfigValidationError,
8+
validate_and_sanitize_ua_string,
89
validate_max_attempts,
910
validate_region,
1011
validate_retry_mode,
@@ -49,3 +50,27 @@ def test_invalid_retry_mode_error_message(self) -> None:
4950
"Invalid value for 'retry_mode': 'random_mode'. retry_mode must be one "
5051
"of ('standard',), got random_mode" in str(exc_info.value)
5152
)
53+
54+
55+
class TestValidateUaString:
56+
def test_allows_alphanumeric(self) -> None:
57+
assert validate_and_sanitize_ua_string("abc123") == "abc123"
58+
59+
def test_none_returns_none(self) -> None:
60+
assert validate_and_sanitize_ua_string(None) is None
61+
62+
def test_allows_spec_special_chars(self) -> None:
63+
assert validate_and_sanitize_ua_string("!#$%&'*+-.^_`|~/") == "!#$%&'*+-.^_`|~/"
64+
65+
def test_sanitizes_parentheses(self) -> None:
66+
result = validate_and_sanitize_ua_string("Java_HotSpot_(TM)_64-Bit_Server_VM")
67+
assert result == "Java_HotSpot_-TM-_64-Bit_Server_VM"
68+
69+
def test_empty_string_passthrough(self) -> None:
70+
assert validate_and_sanitize_ua_string("") == ""
71+
72+
def test_rejects_non_string(self) -> None:
73+
with pytest.raises(ConfigValidationError) as exc_info:
74+
validate_and_sanitize_ua_string(123)
75+
76+
assert exc_info.value.key == "sdk_ua_app_id"

0 commit comments

Comments
 (0)