diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java index 42329691..df703730 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java @@ -49,6 +49,12 @@ public List getClientPlugins(GenerationContext context) { "A unique and opaque application ID that is appended to the User-Agent header.") .type(Symbol.builder().name("str").build()) .nullable(true) + .useDescriptor(true) + .validator(Symbol.builder() + .name("validate_ua_string") + .namespace("smithy_aws_core.config.validators", ".") + .addDependency(AwsPythonDependency.SMITHY_AWS_CORE) + .build()) .build(); final String user_agent_plugin_file = "user_agent"; diff --git a/packages/smithy-aws-core/src/smithy_aws_core/config/validators.py b/packages/smithy-aws-core/src/smithy_aws_core/config/validators.py index 41b670e0..a2766ecb 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/config/validators.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/config/validators.py @@ -144,3 +144,25 @@ def validate_retry_strategy( f"retry_strategy must be RetryStrategy or RetryStrategyOptions, got {type(value).__name__}", source, ) + + +def validate_ua_string(value: Any, source: SourceInfo | None = None) -> str | None: + """Validate a User-Agent string component. + + :param value: The UA string value to validate + :param source: The source that provided this value + + :returns: The UA string or None if value is None + + :raises ConfigValidationError: If the value is not a string + """ + if value is None: + return None + if not isinstance(value, str): + raise ConfigValidationError( + "sdk_ua_app_id", + value, + f"UA string must be a string, got {type(value).__name__}", + source, + ) + return value diff --git a/packages/smithy-aws-core/tests/unit/config/test_property.py b/packages/smithy-aws-core/tests/unit/config/test_property.py index 42ea3129..4246d060 100644 --- a/packages/smithy-aws-core/tests/unit/config/test_property.py +++ b/packages/smithy-aws-core/tests/unit/config/test_property.py @@ -107,7 +107,6 @@ def _create_config_with_validator( class ConfigWithValidator: region = ConfigProperty("region", validator=validator) - retry_strategy = ConfigProperty("retry_strategy", validator=validator) def __init__(self, resolver: ConfigResolver) -> None: self._resolver = resolver @@ -153,9 +152,7 @@ class ConfigWithComplexResolver: retry_strategy = ConfigProperty( "retry_strategy", resolver_func=mock_resolver, - default_value=RetryStrategyOptions( - retry_mode="standard", max_attempts=3 - ), + default_value=RetryStrategyOptions(retry_mode="standard"), ) def __init__(self, resolver: ConfigResolver) -> None: @@ -171,7 +168,7 @@ def __init__(self, resolver: ConfigResolver) -> None: assert isinstance(result, RetryStrategyOptions) assert result.retry_mode == "standard" - assert result.max_attempts == 3 + assert result.max_attempts is None assert source_info == SimpleSource("default") def test_validator_not_called_on_cached_access(self) -> None: diff --git a/packages/smithy-aws-core/tests/unit/config/test_validators.py b/packages/smithy-aws-core/tests/unit/config/test_validators.py index 546af536..dbdbb7b6 100644 --- a/packages/smithy-aws-core/tests/unit/config/test_validators.py +++ b/packages/smithy-aws-core/tests/unit/config/test_validators.py @@ -8,6 +8,7 @@ validate_max_attempts, validate_region, validate_retry_mode, + validate_ua_string, ) @@ -49,3 +50,20 @@ def test_invalid_retry_mode_error_message(self) -> None: "Invalid value for 'retry_mode': 'random_mode'. retry_mode must be one " "of ('standard',), got random_mode" in str(exc_info.value) ) + + +class TestValidateUaString: + def test_allows_string(self) -> None: + assert validate_ua_string("abc123") == "abc123" + + def test_none_returns_none(self) -> None: + assert validate_ua_string(None) is None + + def test_empty_string_passthrough(self) -> None: + assert validate_ua_string("") == "" + + def test_rejects_non_string(self) -> None: + with pytest.raises(ConfigValidationError) as exc_info: + validate_ua_string(123) + + assert exc_info.value.key == "sdk_ua_app_id"