Merge branch 'main' into fahadzub/cbor-constraint

This commit is contained in:
AWS SDK Rust Bot 2024-10-01 12:32:52 +01:00 committed by GitHub
commit 27ca7f1671
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 207 additions and 22 deletions

View File

@ -45,7 +45,7 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "aws-config"
version = "1.5.6"
version = "1.5.8"
dependencies = [
"aws-credential-types",
"aws-runtime",
@ -298,7 +298,7 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.2.6"
version = "1.2.7"
dependencies = [
"base64-simd",
"bytes",

View File

@ -1,6 +1,6 @@
[package]
name = "aws-config"
version = "1.5.7"
version = "1.5.8"
authors = [
"AWS Rust SDK Team <aws-sdk-rust@amazon.com>",
"Russell Cohen <rcoh@amazon.com>",

View File

@ -19,7 +19,7 @@
//! WebIdentityTokenCredentialProvider will load the following environment variables:
//! - `AWS_WEB_IDENTITY_TOKEN_FILE`: **required**, location to find the token file containing a JWT token
//! - `AWS_ROLE_ARN`: **required**, role ARN to assume
//! - `AWS_IAM_ROLE_SESSION_NAME`: **optional**: Session name to use when assuming the role
//! - `AWS_ROLE_SESSION_NAME`: **optional**: Session name to use when assuming the role
//!
//! ## AWS Profile Configuration
//! _Note: Configuration of the web identity token provider via a shared profile is only supported

View File

@ -40,9 +40,11 @@ import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rust.codegen.core.util.orNullIfEmpty
import software.amazon.smithy.rust.codegen.core.util.runCommand
import java.io.File
import java.io.FileInputStream
import java.nio.file.Files
import java.nio.file.Files.createTempDirectory
import java.nio.file.Path
import java.util.Properties
import kotlin.io.path.absolutePathString
import kotlin.io.path.writeText
@ -54,6 +56,8 @@ val TestModuleDocProvider =
}
}
val projectRootDir by lazy { File("git rev-parse --show-toplevel".runCommand().replace("\n", "")) }
/**
* Waiting for Kotlin to stabilize their temp directory functionality
*/
@ -65,6 +69,38 @@ private fun tempDir(directory: File? = null): File {
}
}
/**
* This function returns the minimum supported Rust version, as specified in the `gradle.properties` file
* located at the root of the project.
*/
fun msrv(): String {
val properties = Properties()
val propertiesFilePath = projectRootDir.resolve("gradle.properties")
FileInputStream(propertiesFilePath).use { inputStream ->
properties.load(inputStream)
}
return properties.getProperty("rust.msrv")
}
/**
* Generates the `rust-toolchain.toml` file in the specified directory.
*
* The compiler version is set in `gradle.properties` under the `rust.msrv` property.
* The Gradle task `GenerateMsrvTask` generates the Kotlin class
* `software.amazon.smithy.rust.codegen.core.Msrv` and writes the value of `rust.msrv` into it.
*/
private fun File.generateRustToolchainToml() {
resolve("rust-toolchain.toml").writeText(
// Help rust select the right version when we run cargo test.
"""
[toolchain]
channel = "${msrv()}"
""".trimIndent(),
)
}
/**
* Creates a Cargo workspace shared among all tests
*
@ -87,8 +123,7 @@ object TestWorkspace {
private val subprojects = mutableListOf<String>()
private val cargoLock: File by lazy {
val projectDir = "git rev-parse --show-toplevel".runCommand().replace("\n", "")
File(projectDir).resolve("aws/sdk/Cargo.lock")
projectRootDir.resolve("aws/sdk/Cargo.lock")
}
init {
@ -121,12 +156,7 @@ object TestWorkspace {
version = "0.0.1"
""".trimIndent(),
)
newProject.resolve("rust-toolchain.toml").writeText(
// help rust select the right version when we run cargo test
// TODO(https://github.com/smithy-lang/smithy-rs/issues/2048): load this from the msrv property using a
// method as we do for runtime crate versions
"[toolchain]\nchannel = \"1.78.0\"\n",
)
newProject.generateRustToolchainToml()
// ensure there at least an empty lib.rs file to avoid broken crates
newProject.resolve("src").mkdirs()
newProject.resolve("src/lib.rs").writeText("")
@ -181,7 +211,11 @@ fun generatePluginContext(
runtimeConfig: RuntimeConfig? = null,
overrideTestDir: File? = null,
): Pair<PluginContext, Path> {
val testDir = overrideTestDir ?: TestWorkspace.subproject()
val testDir =
overrideTestDir?.apply {
mkdirs()
generateRustToolchainToml()
} ?: TestWorkspace.subproject()
val moduleName = "test_${testDir.nameWithoutExtension}"
val testPath = testDir.toPath()
val manifest = FileManifest.create(testPath)

View File

@ -0,0 +1,88 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.core.util
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.paths.shouldExist
import io.kotest.matchers.shouldNotBe
import org.junit.jupiter.api.Test
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.testutil.generatePluginContext
import software.amazon.smithy.rust.codegen.core.testutil.projectRootDir
import java.nio.file.Files.createTempDirectory
import java.util.regex.Pattern
internal class RustToolChainTomlTest {
val model =
"""
namespace test
service TestService {
version: "123",
operations: [TestOperation]
}
operation TestOperation {
input:= {}
output:= {}
}
""".asSmithyModel(smithyVersion = "2")
@Test
fun `override test directory in integration test has a rust-toolchain toml file`() {
val dir = createTempDirectory("smithy-test").toFile()
val (_, path) = generatePluginContext(model, overrideTestDir = dir)
path.shouldExist()
val rustToolchainTomlPath = path.resolve("rust-toolchain.toml")
rustToolchainTomlPath.shouldExist()
}
@Test
fun `rust-toolchain toml file has correct value from gradle properties for rust-msrv`() {
val (_, path) = generatePluginContext(model)
val rustToolchainTomlPath = path.resolve("rust-toolchain.toml")
rustToolchainTomlPath.shouldExist()
// Read the MSRV written in `gradle.properties` file.
val msrvPattern = Pattern.compile("rust\\.msrv=(.+)")
val gradlePropertiesPath = projectRootDir.resolve("gradle.properties")
val msrv =
gradlePropertiesPath.useLines { lines ->
lines.firstNotNullOfOrNull { line ->
msrvPattern.matcher(line).let { matcher ->
if (matcher.find()) matcher.group(1) else null
}
}
}
msrv shouldNotBe null
// Read `channel = (\d+)` from `rust-toolchain.toml` file, and
// ensure it matches the one in `gradle.properties`.
val toolchainPattern = Pattern.compile("\\[toolchain]")
val channelPattern = Pattern.compile("channel\\s*=\\s*\"(.+)\"")
val channelMatches =
rustToolchainTomlPath.toFile().useLines { lines ->
// Skip lines until the [toolchain] table is found, then take all lines until the next table.
val toolchainSection =
lines
.dropWhile { !toolchainPattern.matcher(it).find() }
.drop(1)
.takeWhile { !it.trim().startsWith("[") }
// There should be a [toolchain] table, and it must have a key called 'channel' whose value must
// match the `rust.msrv` specified in gradle.properties.
toolchainSection != null &&
toolchainSection.any { line ->
channelPattern.matcher(line).let { matcher ->
matcher.find() && matcher.group(1) == msrv
}
}
}
channelMatches.shouldBeTrue()
}
}

View File

@ -10,6 +10,8 @@ import software.amazon.smithy.model.shapes.BlobShape
import software.amazon.smithy.model.shapes.BooleanShape
import software.amazon.smithy.model.shapes.CollectionShape
import software.amazon.smithy.model.shapes.DocumentShape
import software.amazon.smithy.model.shapes.DoubleShape
import software.amazon.smithy.model.shapes.FloatShape
import software.amazon.smithy.model.shapes.MapShape
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.NumberShape
@ -209,7 +211,10 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) {
*/
private fun serializeNumber(shape: NumberShape): RuntimeType {
val numericType = SimpleShapes.getValue(shape::class)
return RuntimeType.forInlineFun(
return when (shape) {
is FloatShape, is DoubleShape -> serializeFloat(shape)
else ->
RuntimeType.forInlineFun(
numericType.toString(),
PrimitiveShapesModule,
) {
@ -218,6 +223,35 @@ class SerializeImplGenerator(private val codegenContext: CodegenContext) {
}
}
}
}
private fun serializeFloat(shape: NumberShape): RuntimeType {
val numericType = SimpleShapes.getValue(shape::class)
return RuntimeType.forInlineFun(
numericType.toString(),
PrimitiveShapesModule,
) {
implSerializeConfigured(symbolBuilder(shape, numericType).build()) {
rustTemplate(
"""
if !self.settings.out_of_range_floats_as_strings {
return self.value.serialize(serializer)
}
if self.value.is_nan() {
serializer.serialize_str("NaN")
} else if *self.value == #{ty}::INFINITY {
serializer.serialize_str("Infinity")
} else if *self.value == #{ty}::NEG_INFINITY {
serializer.serialize_str("-Infinity")
} else {
self.value.serialize(serializer)
}
""",
"ty" to numericType,
)
}
}
}
/**
* Serialize a type that already implements `Serialize` directly via `value.serialize(serializer)`

View File

@ -47,7 +47,7 @@ object SupportStructures {
{
use #{serde}::Serialize;
value
.serialize_ref(&#{SerializationSettings} { redact_sensitive_fields: true })
.serialize_ref(&#{SerializationSettings}::redact_sensitive_fields())
.serialize(serializer)
}
""",
@ -70,7 +70,7 @@ object SupportStructures {
{
use #{serde}::Serialize;
value
.serialize_ref(&#{SerializationSettings} { redact_sensitive_fields: false })
.serialize_ref(&#{SerializationSettings}::leak_sensitive_fields())
.serialize(serializer)
}
""",
@ -211,7 +211,6 @@ object SupportStructures {
private fun serializationSettings() =
RuntimeType.forInlineFun("SerializationSettings", supportModule) {
// TODO(serde): Consider removing `derive(Default)`
rustTemplate(
"""
/// Settings for use when serializing structures
@ -220,6 +219,12 @@ object SupportStructures {
pub struct SerializationSettings {
/// Replace all sensitive fields with `<redacted>` during serialization
pub redact_sensitive_fields: bool,
/// Serialize Nan, infinity and negative infinity as strings.
///
/// For protocols like JSON, this avoids the loss-of-information that occurs when these out-of-range values
/// are serialized as null.
pub out_of_range_floats_as_strings: bool,
}
impl SerializationSettings {
@ -227,10 +232,10 @@ object SupportStructures {
///
/// Note: This may alter the type of the serialized output and make it impossible to deserialize as
/// numerical fields will be replaced with strings.
pub const fn redact_sensitive_fields() -> Self { Self { redact_sensitive_fields: true } }
pub const fn redact_sensitive_fields() -> Self { Self { redact_sensitive_fields: true, out_of_range_floats_as_strings: false } }
/// Preserve the contents of sensitive fields during serializing
pub const fn leak_sensitive_fields() -> Self { Self { redact_sensitive_fields: false } }
pub const fn leak_sensitive_fields() -> Self { Self { redact_sensitive_fields: false, out_of_range_floats_as_strings: false } }
}
""",
)

View File

@ -70,7 +70,9 @@ class SerdeDecoratorTest {
blob: SensitiveBlob,
constrained: Constrained,
recursive: Recursive,
map: EnumKeyedMap
map: EnumKeyedMap,
float: Float,
double: Double
}
structure Constrained {
@ -134,6 +136,8 @@ class SerdeDecoratorTest {
structure Nested {
@required
int: Integer,
float: Float,
double: Double,
sensitive: Timestamps,
notSensitive: AlsoTimestamps,
manyEnums: TestEnumList,
@ -202,8 +206,12 @@ class SerdeDecoratorTest {
.e(Some(TestEnum::A))
.document(Some(Document::String("hello!".into())))
.blob(Some(Blob::new("hello")))
.float(Some(f32::INFINITY))
.double(Some(f64::NAN))
.nested(Some(Nested::builder()
.int(5)
.float(Some(f32::NEG_INFINITY))
.double(Some(f64::NEG_INFINITY))
.sensitive(Some(sensitive_map.clone()))
.not_sensitive(Some(sensitive_map))
.many_enums(Some(vec![TestEnum::A]))
@ -274,6 +282,8 @@ class SerdeDecoratorTest {
"e": "A",
"nested": {
"int": 5,
"float": "-Infinity",
"double": "-Infinity",
"sensitive": {
"a": "1970-01-01T00:00:00Z"
},
@ -289,7 +299,9 @@ class SerdeDecoratorTest {
"enum": "B"
},
"document": "hello!",
"blob": "aGVsbG8="
"blob": "aGVsbG8=",
"float": "Infinity",
"double": "NaN"
}""".replace("\\s".toRegex(), "")
private val expectedRedacted =
@ -298,6 +310,8 @@ class SerdeDecoratorTest {
"e": "<redacted>",
"nested": {
"int": 5,
"float": "-Infinity",
"double": "-Infinity",
"sensitive": {
"a": "<redacted>"
},
@ -311,7 +325,9 @@ class SerdeDecoratorTest {
},
"union": "<redacted>",
"document": "hello!",
"blob": "<redacted>"
"blob": "<redacted>",
"float": "Infinity",
"double": "NaN"
}
""".replace("\\s".toRegex(), "")
@ -343,8 +359,12 @@ class SerdeDecoratorTest {
.e("A".into())
.document(Document::String("hello!".into()))
.blob(Blob::new("hello"))
.float(f32::INFINITY)
.double(f64::NAN)
.nested(Nested::builder()
.int(5)
.float(f32::NEG_INFINITY)
.double(f64::NEG_INFINITY)
.sensitive("a", DateTime::from(UNIX_EPOCH))
.not_sensitive("a", DateTime::from(UNIX_EPOCH))
.many_enums("A".into())
@ -355,11 +375,15 @@ class SerdeDecoratorTest {
.build()
.unwrap();
let mut settings = #{crate}::serde::SerializationSettings::default();
settings.out_of_range_floats_as_strings = true;
let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize");
assert_eq!(serialized, ${expectedNoRedactions.dq()});
settings.redact_sensitive_fields = true;
let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize");
assert_eq!(serialized, ${expectedRedacted.dq()});
settings.out_of_range_floats_as_strings = false;
let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize");
assert_ne!(serialized, ${expectedRedacted.dq()});
""",
*codegenScope,
)