mirror of https://github.com/smithy-lang/smithy-rs
Merge branch 'main' into fahadzub/cbor-constraint
This commit is contained in:
commit
27ca7f1671
|
@ -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",
|
||||
|
|
|
@ -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>",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)`
|
||||
|
|
|
@ -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 } }
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue