From 02375f74ca951786b15e831c3fb73f9505707a8d Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Mon, 15 Jul 2024 11:10:48 -0400 Subject: [PATCH] Add support for document shapes, refactor into class --- .../serde/KotlinClientSerdeDecorator.kt | 581 ++++++++++-------- .../codegen/serde/KotlinSerdeImplGenerator.kt | 13 - .../serde/KotlinClientSerdeDecoratorTest.kt | 13 +- 3 files changed, 335 insertions(+), 272 deletions(-) delete mode 100644 codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinSerdeImplGenerator.kt diff --git a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecorator.kt b/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecorator.kt index 368f2258ef2..bd8437ea4f5 100644 --- a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecorator.kt +++ b/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecorator.kt @@ -7,7 +7,8 @@ package software.amazon.smithy.rust.codegen.serde import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.neighbor.Walker -import software.amazon.smithy.model.shapes.ListShape +import software.amazon.smithy.model.shapes.CollectionShape +import software.amazon.smithy.model.shapes.DocumentShape import software.amazon.smithy.model.shapes.MapShape import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.NumberShape @@ -66,10 +67,10 @@ class KotlinClientSerdeDecorator : ClientCodegenDecorator { rustCrate: RustCrate, ) { rustCrate.mergeFeature(SerdeFeature) + val generator = SerializeImplGenerator(codegenContext) rustCrate.withModule(Module) { serializationRoots(codegenContext).forEach { - rootSerializer( - codegenContext, + generator.generateRootSerializerForShape( it, )(this) } @@ -79,289 +80,337 @@ class KotlinClientSerdeDecorator : ClientCodegenDecorator { } } -fun rootSerializer( - codegenContext: CodegenContext, - shape: Shape, -): Writable = serializerFn(codegenContext, shape, null) - -fun serializerFn( - codegenContext: CodegenContext, - shape: Shape, - applyTo: Writable?, -): Writable { - val name = codegenContext.symbolProvider.shapeFunctionName(codegenContext.serviceShape, shape) + "_serde" - val deps = - when (shape) { - is StructureShape -> RuntimeType.forInlineFun(name, Module, structSerdeImpl(codegenContext, shape)) - is UnionShape -> RuntimeType.forInlineFun(name, Module, serializeUnionImpl(codegenContext, shape)) - is TimestampShape -> serializeDateTime(codegenContext, shape) - is StringShape, is NumberShape -> directSerde(codegenContext, shape) - else -> null - } - return writable { - val wrapper = - when { - deps != null -> null - shape is MapShape -> serializeMap(codegenContext, shape) - shape is ListShape -> serializeList(codegenContext, shape) - else -> serializeWithTodo(codegenContext, shape) +/** + * All entry points for serialization in the service closure. + */ +fun serializationRoots(ctx: CodegenContext): List { + val serviceShape = ctx.serviceShape + val walker = Walker(ctx.model) + return walker.walkShapes(serviceShape).filter { it.hasTrait() } +} + +class SerializeImplGenerator(private val codegenContext: CodegenContext) { + fun generateRootSerializerForShape(shape: Shape): Writable = serializerFn(shape, null) + + /** + * Generates a serializer for a given shape. Collection serializer require using a wrapper structure. To handle this, + * `applyTo` allows passing in a `Writable` referring to an input. This returns a writable that wraps the input if necessary, + * e.g. + * + * `&self.map_shape` => `SerializeSomeSpecificMap(&self.map_shape)` + * + * Even if no wrapping is required, the writable that is returned includes the required serializer as dependencies. + */ + private fun serializerFn( + shape: Shape, + applyTo: Writable?, + ): Writable { + val name = codegenContext.symbolProvider.shapeFunctionName(codegenContext.serviceShape, shape) + "_serde" + val deps = + when (shape) { + is StructureShape -> RuntimeType.forInlineFun(name, Module, structSerdeImpl(shape)) + is UnionShape -> RuntimeType.forInlineFun(name, Module, serializeUnionImpl(shape)) + is TimestampShape -> serializeDateTime(shape) + is StringShape, is NumberShape -> directSerde(shape) + is DocumentShape -> serializeDocument(shape) + else -> null + } + return writable { + val wrapper = + when { + deps != null -> null + shape is MapShape -> serializeMap(shape) + shape is CollectionShape -> serializeList(shape) + // Need to figure out the best default here. + else -> serializeWithTodo(shape) + } + if (wrapper != null && applyTo != null) { + rustTemplate("&#{wrapper}(#{applyTo})", "wrapper" to wrapper, "applyTo" to applyTo) + } else { + deps?.toSymbol().also { addDependency(it) } + applyTo?.invoke(this) } - if (wrapper != null && applyTo != null) { - rustTemplate("&#{wrapper}(#{applyTo})", "wrapper" to wrapper, "applyTo" to applyTo) - } else { - deps?.toSymbol().also { addDependency(it) } - applyTo?.invoke(this) } } -} -fun serializeWithTodo( - codegenContext: CodegenContext, - shape: Shape, -): RuntimeType = - serializeWithWrapper(codegenContext, shape) { _ -> - writable { - rust("serializer.serialize_str(\"todo\")") + private fun serializeWithTodo(shape: Shape): RuntimeType = + serializeWithWrapper(shape) { _ -> + // PANIC("cant serialize $shape") + writable { + rust("serializer.serialize_str(\"todo\")") + } } - } -fun serializeMap( - codegenContext: CodegenContext, - shape: MapShape, -): RuntimeType = - serializeWithWrapper(codegenContext, shape) { value -> - writable { - rustTemplate( - """ - use #{SerializeConfigured}; - use #{serde}::ser::SerializeMap; - let mut map = serializer.serialize_map(Some(#{value}.len()))?; - for (k, v) in self.value.0.iter() { - map.serialize_entry(k, #{member})?; - } - map.end() - """, - *SupportStructures.codegenScope, - "value" to value, - "member" to serializeMember(codegenContext, shape.value, "v"), - ) + private fun serializeMap(shape: MapShape): RuntimeType = + serializeWithWrapper(shape) { value -> + writable { + rustTemplate( + """ + use #{SerializeConfigured}; + use #{serde}::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(#{value}.len()))?; + for (k, v) in self.value.0.iter() { + map.serialize_entry(k, #{member})?; + } + map.end() + """, + *SupportStructures.codegenScope, + "value" to value, + "member" to serializeMember(shape.value, "v"), + ) + } } - } -fun serializeList( - codegenContext: CodegenContext, - shape: ListShape, -): RuntimeType = - serializeWithWrapper(codegenContext, shape) { value -> - writable { - rustTemplate( - """ - use #{SerializeConfigured}; - use #{serde}::ser::SerializeSeq; - let mut seq = serializer.serialize_seq(Some(#{value}.len()))?; - for v in self.value.0.iter() { - seq.serialize_element(#{member})?; - } - seq.end() - """, - *SupportStructures.codegenScope, - "value" to value, - "member" to serializeMember(codegenContext, shape.member, "v"), - ) + private fun serializeList(shape: CollectionShape): RuntimeType = + serializeWithWrapper(shape) { value -> + writable { + rustTemplate( + """ + use #{SerializeConfigured}; + use #{serde}::ser::SerializeSeq; + let mut seq = serializer.serialize_seq(Some(#{value}.len()))?; + for v in self.value.0.iter() { + seq.serialize_element(#{member})?; + } + seq.end() + """, + *SupportStructures.codegenScope, + "value" to value, + "member" to serializeMember(shape.member, "v"), + ) + } } - } -fun directSerde( - codegenContext: CodegenContext, - shape: Shape, -): RuntimeType { - return RuntimeType.forInlineFun(codegenContext.symbolProvider.toSymbol(shape).rustType().toString(), Module) { - implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { - val baseValue = - writable { rust("self.value") }.letIf(shape.isStringShape) { it.plus(writable(".as_str()")) } - rustTemplate("#{base}.serialize(serializer)", "base" to baseValue) + /** + * Serialize a type that already implements `Serialize` directly via `value.serialize(serializer)` + */ + private fun directSerde(shape: Shape): RuntimeType { + return RuntimeType.forInlineFun(codegenContext.symbolProvider.toSymbol(shape).rustType().toString(), Module) { + implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { + val baseValue = + writable { rust("self.value") }.letIf(shape.isStringShape) { it.plus(writable(".as_str()")) } + rustTemplate("#{base}.serialize(serializer)", "base" to baseValue) + } } } -} -fun serializeWithWrapper( - codegenContext: CodegenContext, - shape: Shape, - body: (Writable) -> Writable, -): RuntimeType { - val name = - "Serializer" + - codegenContext.symbolProvider.shapeFunctionName(codegenContext.serviceShape, shape) - .toPascalCase() - val type = Module.toType().resolve(name).toSymbol() - val base = writable { rust("self.value.0") } - // val baseValue = writable { rust("self.0.value") }.letIf(shape.isStringShape) { it.plus(writable(".as_str()")) } - val serialization = - implSerializeConfiguredWrapper(type) { - body(base)(this) - } - val wrapperStruct = - RuntimeType.forInlineFun(name, Module) { - rustTemplate( - """ - struct $name<'a>(&'a #{Shape}); - #{serializer} - """, - "Shape" to codegenContext.symbolProvider.toSymbol(shape), - "serializer" to serialization, - ) - } - return wrapperStruct -} + /** + * Serialize a shape by first generating a wrapper struct: + * ```rust + * struct WrapperType<'a>(&'a Type); + * ``` + * + * Then implementing `Serialize` for `ConfigurableSerdeRef<'a WrapperType>` + * + * This exists to allow differing implementations for same-shaped-rust types. For example, consider + * the following Smithy model: + * + * ```smithy + * list SensitiveList { + * member: SensitiveString + * } + * + * list StringList { + * member: String + * } + * ``` + * + * These both are `Vec` but must be serialized differently. + */ + private fun serializeWithWrapper( + shape: Shape, + body: (Writable) -> Writable, + ): RuntimeType { + val name = + "Serializer" + + codegenContext.symbolProvider.shapeFunctionName(codegenContext.serviceShape, shape) + .toPascalCase() + // awkward hack to recover the symbol referring to the type + val type = Module.toType().resolve(name).toSymbol() + val base = writable { rust("self.value.0") } + val serialization = + implSerializeConfiguredWrapper(type) { + body(base)(this) + } + val wrapperStruct = + RuntimeType.forInlineFun(name, Module) { + rustTemplate( + """ + struct $name<'a>(&'a #{Shape}); + #{serializer} + """, + "Shape" to codegenContext.symbolProvider.toSymbol(shape), + "serializer" to serialization, + ) + } + return wrapperStruct + } -fun serializeMember( - codegenContext: CodegenContext, - shape: MemberShape, - memberRef: String, -): Writable { - val target = codegenContext.model.expectShape(shape.target) - return writable { - serializerFn(codegenContext, target) { - rust("&$memberRef") - }.plus { rust(".serialize_ref(&self.settings)") }(this) - }.letIf(target.hasTrait()) { memberSerialization -> - memberSerialization.map { - rustTemplate( - "&#{Sensitive}(#{it}).serialize_ref(&self.settings)", - *SupportStructures.codegenScope, - "it" to it, - ) + /** + * Serialize the field of a structure, union, list or map. + * + * All actual serialization MUST go through this path as it handles applying the `Sensitive` wrapper. + */ + private fun serializeMember( + shape: MemberShape, + memberRef: String, + ): Writable { + val target = codegenContext.model.expectShape(shape.target) + return writable { + serializerFn(target) { + rust("&$memberRef") + }.plus { rust(".serialize_ref(&self.settings)") }(this) + }.letIf(target.hasTrait()) { memberSerialization -> + memberSerialization.map { + rustTemplate( + "&#{Sensitive}(#{it}).serialize_ref(&self.settings)", + *SupportStructures.codegenScope, + "it" to it, + ) + } } } -} -fun structSerdeImpl( - codegenContext: CodegenContext, - shape: StructureShape, -): Writable { - return writable { - implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { - rustTemplate( - """ - use #{serde}::ser::SerializeStruct; - use #{SerializeConfigured}; - """, - *SupportStructures.codegenScope, - ) - rust( - "let mut s = serializer.serialize_struct(${ - shape.contextName(codegenContext.serviceShape).dq() - }, ${shape.members().size})?;", - ) - rust("let inner = &self.value;") - for (member in shape.members()) { - val serializedName = member.memberName.dq() - val fieldName = codegenContext.symbolProvider.toMemberName(member) - val field = safeName("member") - val fieldSerialization = - writable { + private fun structSerdeImpl(shape: StructureShape): Writable { + return writable { + implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { + rustTemplate( + """ + use #{serde}::ser::SerializeStruct; + use #{SerializeConfigured}; + """, + *SupportStructures.codegenScope, + ) + rust( + "let mut s = serializer.serialize_struct(${ + shape.contextName(codegenContext.serviceShape).dq() + }, ${shape.members().size})?;", + ) + rust("let inner = &self.value;") + for (member in shape.members()) { + val serializedName = member.memberName.dq() + val fieldName = codegenContext.symbolProvider.toMemberName(member) + val field = safeName("member") + val fieldSerialization = + writable { + rustTemplate( + "s.serialize_field($serializedName, #{member})?;", + "member" to serializeMember(member, field), + ) + } + if (codegenContext.symbolProvider.toSymbol(member).isOptional()) { rustTemplate( - "s.serialize_field($serializedName, #{member})?;", - "member" to serializeMember(codegenContext, member, field), + "if let Some($field) = &inner.$fieldName { #{serializeField} }", + "serializeField" to fieldSerialization, + ) + } else { + rustTemplate( + "let $field = &inner.$fieldName; #{serializeField}", + "serializeField" to fieldSerialization, ) } - if (codegenContext.symbolProvider.toSymbol(member).isOptional()) { - rustTemplate( - "if let Some($field) = &inner.$fieldName { #{serializeField} }", - "serializeField" to fieldSerialization, - ) - } else { - rustTemplate( - "let $field = &inner.$fieldName; #{serializeField}", - "serializeField" to fieldSerialization, - ) } + rust("s.end()") } - rust("s.end()") } } -} -fun serializeUnionImpl( - codegenContext: CodegenContext, - shape: UnionShape, -): Writable { - val unionName = shape.contextName(codegenContext.serviceShape) - val symbolProvider = codegenContext.symbolProvider - val unionSymbol = symbolProvider.toSymbol(shape) - - return writable { - implSerializeConfigured(unionSymbol) { - rustTemplate( - """ - use #{SerializeConfigured}; - """, - *SupportStructures.codegenScope, - ) - rustBlock("match self.value") { - shape.members().forEachIndexed { index, member -> - val fieldName = member.memberName.dq() - val variantName = - if (member.isTargetUnit()) { - symbolProvider.toMemberName(member) - } else { - "${symbolProvider.toMemberName(member)}(inner)" + private fun serializeUnionImpl(shape: UnionShape): Writable { + val unionName = shape.contextName(codegenContext.serviceShape) + val symbolProvider = codegenContext.symbolProvider + val unionSymbol = symbolProvider.toSymbol(shape) + + return writable { + implSerializeConfigured(unionSymbol) { + rustTemplate( + """ + use #{SerializeConfigured}; + """, + *SupportStructures.codegenScope, + ) + rustBlock("match self.value") { + shape.members().forEachIndexed { index, member -> + val fieldName = member.memberName.dq() + val variantName = + if (member.isTargetUnit()) { + symbolProvider.toMemberName(member) + } else { + "${symbolProvider.toMemberName(member)}(inner)" + } + withBlock("#T::$variantName => {", "},", symbolProvider.toSymbol(shape)) { + rustTemplate( + "serializer.serialize_newtype_variant(${unionName.dq()}, $index, $fieldName, #{member})", + "member" to serializeMember(member, "inner"), + ) } - withBlock("#T::$variantName => {", "},", symbolProvider.toSymbol(shape)) { + } + if (codegenContext.target.renderUnknownVariant()) { rustTemplate( - "serializer.serialize_newtype_variant(${unionName.dq()}, $index, $fieldName, #{member})", - "member" to serializeMember(codegenContext, member, "inner"), + "#{Union}::${UnionGenerator.UNKNOWN_VARIANT_NAME} => serializer.serialize_str(\"unknown variant!\")", + "Union" to unionSymbol, ) } } - if (codegenContext.target.renderUnknownVariant()) { - rustTemplate( - "#{Union}::${UnionGenerator.UNKNOWN_VARIANT_NAME} => serializer.serialize_str(\"unknown variant!\")", - "Union" to unionSymbol, - ) - } } } } -} -fun serializeDateTime( - codegenContext: CodegenContext, - shape: TimestampShape, -): RuntimeType = - RuntimeType.forInlineFun("SerializeDateTime", Module) { - implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { - rust("serializer.serialize_str(&self.value.to_string())") + private fun serializeDateTime(shape: TimestampShape): RuntimeType = + RuntimeType.forInlineFun("SerializeDateTime", Module) { + implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { + rust("serializer.serialize_str(&self.value.to_string())") + } } - } -private fun RustWriter.implSerializeConfigured( - shape: Symbol, - block: Writable, -) { - rustTemplate( - """ - impl<'a> #{serde}::Serialize for #{ConfigurableSerdeRef}<'a, #{Shape}> { - fn serialize(&self, serializer: S) -> Result - where - S: #{serde}::Serializer, - { - #{body} + private fun serializeDocument(shape: DocumentShape): RuntimeType = + RuntimeType.forInlineFun("SerializeDocument", Module) { + implSerializeConfigured(codegenContext.symbolProvider.toSymbol(shape)) { + rustTemplate( + """ + use #{SerializeConfigured}; + match self.value { + #{Document}::String(v) => serializer.serialize_str(v), + #{Document}::Object(v) => { + use #{serde}::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(v.len()))?; + for (k, v) in v.iter() { + map.serialize_entry(k, &v.serialize_ref(&self.settings))?; + } + map.end() + }, + #{Document}::Array(v) => { + use #{serde}::ser::SerializeSeq; + let mut seq = serializer.serialize_seq(Some(v.len()))?; + for e in v.iter() { + seq.serialize_element(&e.serialize_ref(&self.settings))?; + } + seq.end() + }, + #{Document}::Number(#{Number}::Float(value)) => value.serialize(serializer), + #{Document}::Number(#{Number}::PosInt(value)) => { + value.serialize(serializer) + }, + #{Document}::Number(#{Number}::NegInt(value)) => { + value.serialize(serializer) + }, + #{Document}::Bool(b) => b.serialize(serializer), + #{Document}::Null => serializer.serialize_none(), + } + """, + *SupportStructures.codegenScope, + "Document" to RuntimeType.document(codegenContext.runtimeConfig), + "Number" to RuntimeType.smithyTypes(codegenContext.runtimeConfig).resolve("Number"), + ) } } - """, - "Shape" to shape, "body" to block, *SupportStructures.codegenScope, - ) -} -private fun implSerializeConfiguredWrapper( - shape: Symbol, - block: Writable, -): Writable { - return writable { + private fun RustWriter.implSerializeConfigured( + shape: Symbol, + block: Writable, + ) { rustTemplate( """ - impl<'a, 'b> #{serde}::Serialize for #{ConfigurableSerdeRef}<'a, #{Shape}<'b>> { + impl<'a> #{serde}::Serialize for #{ConfigurableSerdeRef}<'a, #{Shape}> { fn serialize(&self, serializer: S) -> Result where S: #{serde}::Serializer, @@ -373,6 +422,27 @@ private fun implSerializeConfiguredWrapper( "Shape" to shape, "body" to block, *SupportStructures.codegenScope, ) } + + private fun implSerializeConfiguredWrapper( + shape: Symbol, + block: Writable, + ): Writable { + return writable { + rustTemplate( + """ + impl<'a, 'b> #{serde}::Serialize for #{ConfigurableSerdeRef}<'a, #{Shape}<'b>> { + fn serialize(&self, serializer: S) -> Result + where + S: #{serde}::Serializer, + { + #{body} + } + } + """, + "Shape" to shape, "body" to block, *SupportStructures.codegenScope, + ) + } + } } object SupportStructures { @@ -556,21 +626,24 @@ object SupportStructures { rustTemplate( """ /// Settings for use when serializing structures + ##[non_exhaustive] ##[derive(Copy, Clone, Debug, Default)] pub struct SerializationSettings { /// Replace all sensitive fields with `` during serialization pub redact_sensitive_fields: bool, } + + impl SerializationSettings { + /// Replace all `@sensitive` fields with `` when serializing. + /// + /// 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 fn redact_sensitive_fields() -> Self { Self { redact_sensitive_fields: true } } + + /// Preserve the contents of sensitive fields during serializing + pub fn leak_sensitive_fields() -> Self { Self { redact_sensitive_fields: false } } + } """, ) } } - -/** - * All entry points for serialization in the service closure. - */ -fun serializationRoots(ctx: CodegenContext): List { - val serviceShape = ctx.serviceShape - val walker = Walker(ctx.model) - return walker.walkShapes(serviceShape).filter { it.hasTrait() } -} diff --git a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinSerdeImplGenerator.kt b/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinSerdeImplGenerator.kt deleted file mode 100644 index 281978d07fb..00000000000 --- a/codegen-serde/src/main/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinSerdeImplGenerator.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.rust.codegen.serde - -class KotlinSerdeImplGenerator { - init { - - println("ok") - } -} diff --git a/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecoratorTest.kt b/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecoratorTest.kt index 1c4b821d1d7..0f4f38670a4 100644 --- a/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecoratorTest.kt +++ b/codegen-serde/src/test/kotlin/software/amazon/smithy/rust/codegen/serde/KotlinClientSerdeDecoratorTest.kt @@ -38,7 +38,8 @@ class KotlinClientSerdeDecoratorTest { foo: SensitiveString, e: TestEnum, nested: Nested, - union: U + union: U, + document: Document } @sensitive @@ -59,6 +60,7 @@ class KotlinClientSerdeDecoratorTest { } structure Nested { + @required int: Integer, sensitive: Timestamps, notSensitive: AlsoTimestamps, @@ -104,26 +106,27 @@ class KotlinClientSerdeDecoratorTest { use #{crate}::types::{Nested, U}; use #{crate}::serde_impl::support::*; use std::time::UNIX_EPOCH; - use aws_smithy_types::DateTime; + use aws_smithy_types::{DateTime, Document}; let input = #{crate}::operation::say_hello::SayHelloInput::builder() .foo("foo-value") .e("A".into()) + .document(Document::String("hello!".into())) .nested(Nested::builder() .int(5) .sensitive("a", DateTime::from(UNIX_EPOCH)) .not_sensitive("a", DateTime::from(UNIX_EPOCH)) .many_enums("A".into()) - .build() + .build().unwrap() ) .union(U::Enum("B".into())) .build() .unwrap(); let mut settings = #{crate}::serde_impl::support::SerializationSettings::default(); let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); - assert_eq!(serialized, "{\"foo\":\"foo-value\",\"e\":\"A\",\"nested\":{\"int\":5,\"sensitive\":{\"a\":\"1970-01-01T00:00:00Z\"},\"notSensitive\":{\"a\":\"1970-01-01T00:00:00Z\"},\"manyEnums\":[\"A\"]},\"union\":{\"enum\":\"B\"}}"); + assert_eq!(serialized, "{\"foo\":\"foo-value\",\"e\":\"A\",\"nested\":{\"int\":5,\"sensitive\":{\"a\":\"1970-01-01T00:00:00Z\"},\"notSensitive\":{\"a\":\"1970-01-01T00:00:00Z\"},\"manyEnums\":[\"A\"]},\"union\":{\"enum\":\"B\"},\"document\":\"hello!\"}"); settings.redact_sensitive_fields = true; let serialized = #{serde_json}::to_string(&input.serialize_ref(&settings)).expect("failed to serialize"); - assert_eq!(serialized, "{\"foo\":\"\",\"e\":\"\",\"nested\":{\"int\":5,\"sensitive\":{\"a\":\"\"},\"notSensitive\":{\"a\":\"1970-01-01T00:00:00Z\"},\"manyEnums\":[\"\"]},\"union\":\"\"}"); + assert_eq!(serialized, "{\"foo\":\"\",\"e\":\"\",\"nested\":{\"int\":5,\"sensitive\":{\"a\":\"\"},\"notSensitive\":{\"a\":\"1970-01-01T00:00:00Z\"},\"manyEnums\":[\"\"]},\"union\":\"\",\"document\":\"hello!\"}"); """, *codegenScope, )