Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial implementation of a Serde Decorator #3753

Merged
merged 21 commits into from
Aug 12, 2024
Merged

Add initial implementation of a Serde Decorator #3753

merged 21 commits into from
Aug 12, 2024

Conversation

rcoh
Copy link
Collaborator

@rcoh rcoh commented Jul 11, 2024

Motivation and Context

Customers want to be able to use serde with Smithy models. For details, see the RFC

Description

Implementation of serde::Serialize for smithy-rs code generators. This takes the approach of a trait, SerializeConfigured which returns a type that implements Serialize. This allows customers to control serde behavior for their use case.

/// Trait that allows configuring serialization
/// **This trait should not be implemented directly!** Instead, `impl Serialize for ConfigurableSerdeRef<T>`**
pub trait SerializeConfigured {
    /// Return a `Serialize` implementation for this object that owns the object. This is what you want
    /// If you need to pass something that `impl`s serialize elsewhere.
    fn serialize_owned(self, settings: SerializationSettings) -> impl Serialize;

    /// Return a `Serialize` implementation for this object that borrows from the given object
    fn serialize_ref<'a>(&'a self, settings: &'a SerializationSettings) -> impl Serialize + 'a;
}

This can be used as follows:

serde_json::to_string(&my_struct.serialize_ref(&SerializationSettings::redact_sensitive_fields());

Currently, this codegen plugin is not used by anything. It can be enabled by bringing it in scope during code generation & adding the @serde trait to the model for the fields where serialization is desired. This will allow the SDK (if they choose to) roll it out only for specific types or services that have customer demand.

There are a number of follow on items required:

  • Ensure the generated impls work for Server codegen
  • Generate Deserialize implementations
  • Test the implementation all Smithy protocol test models (ensure it compiles)

Testing

Unit test testing serialization with a kitchen-sink model

Checklist

  • I have updated CHANGELOG.next.toml if I made changes to the smithy-rs codegen or runtime crates
  • I have updated CHANGELOG.next.toml if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

@rcoh rcoh force-pushed the configurable-serde branch from c4f5e56 to a5a247d Compare July 12, 2024 18:32
Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

@rcoh rcoh force-pushed the configurable-serde branch from a5a247d to 10fc203 Compare July 12, 2024 19:03
Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

@rcoh rcoh marked this pull request as ready for review July 15, 2024 15:36
@rcoh rcoh requested review from a team as code owners July 15, 2024 15:36
Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

}

java {
sourceCompatibility = JavaVersion.VERSION_11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we on v17 now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh dono, I copied this from codegen core, can change it to whatever.

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()")) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super cool, I didn't realize we could conditionally extend writables like this!

@rcoh rcoh force-pushed the configurable-serde branch 2 times, most recently from 971662e to a88f021 Compare July 16, 2024 15:07
Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

@rcoh rcoh force-pushed the configurable-serde branch from 6caee67 to 986201a Compare July 17, 2024 12:55
Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

Copy link
Contributor

@Velfi Velfi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea separating the implementation of serialization and deserialization.

Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

Copy link
Contributor

@ysaito1001 ysaito1001 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliant piece of work!

design/src/rfcs/rfc0045_configurable_serde.md Outdated Show resolved Hide resolved
Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

Copy link
Contributor

@david-perez david-perez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the solution explained in the RFC.

Main issue with current implementation is that it won't handle constrained shapes in the server, for which we generate Rust newtypes.

Something that would make us gain confidence in the implementation is if we integration tested against the constraints.smithy model (or any large model like the restJson1 one), post transforming it to add @serde to the service shape.

Have a look at this test:

design/src/rfcs/rfc0045_configurable_serde.md Outdated Show resolved Hide resolved
This RFC defines how smithy-rs will enable customers to use the [serde](serde.rs) library with generated clients & servers. This is a common request
for myriad reasons, but as we have written about [before](https://github.com/awslabs/aws-sdk-rust/issues/269#issuecomment-1227518721) this is a challenging design area. This RFC proposes a new approach: **Rather than implement `Serialize` directly, add a method to types that returns a type that implements `Serialize`.** This solves a number of issues:

1. It is minimally impactful: It doesn't lock us into one `Serialize` implementation. It contains only one public trait, `SerializeConfigured`. This trait will initially be defined on a per-crate basis to avoid the orphan-trait rule. It doesn't also doesn't have any impact on shared runtime crates (since no types actually need to implement serialize).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. It is minimally impactful: It doesn't lock us into one `Serialize` implementation. It contains only one public trait, `SerializeConfigured`. This trait will initially be defined on a per-crate basis to avoid the orphan-trait rule. It doesn't also doesn't have any impact on shared runtime crates (since no types actually need to implement serialize).
1. It is minimally impactful: It doesn't lock us into one `Serialize` implementation. It contains only one public trait, `SerializeConfigured`. This trait will initially be defined on a per-crate basis to avoid the orphan-trait rule. It also doesn't have any impact on shared runtime crates (since no types actually need to implement serialize).

design/src/rfcs/rfc0045_configurable_serde.md Outdated Show resolved Hide resolved
design/src/rfcs/rfc0045_configurable_serde.md Outdated Show resolved Hide resolved
design/src/rfcs/rfc0045_configurable_serde.md Outdated Show resolved Hide resolved
return writable {
serializerFn(target) {
rust("&$memberRef")
}.plus { rust(".serialize_ref(&self.settings)") }(this)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm Clippy should catch this, there's no need for & here. Which makes me think that we're not running cargo clippy --all-features in CI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed we aren't. I'm guessing toggling --all-features will cause other things to fail, so we should probably cut a separate GitHub issue.

commandLine("cargo", "clippy")

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed clippy lints in the generated code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unnecessary iters are still there. Can we run integration tests with --all-features in CI, or at least have a tracking issue for it?

#{Document}::Object(v) => {
use #{serde}::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(v.len()))?;
for (k, v) in v.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these iter() needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're still there.

import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext
import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator

val SerdeFeature = Feature("serde", false, listOf("dep:serde"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the feature name overridable? If we were to add this to the AWS SDK I would imagine we would want to introduce it under something like experimental-serde to let it bake for awhile before we finalize it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not currently, but this would be an easy follow-on

@rcoh rcoh force-pushed the configurable-serde branch from 4539f31 to 0e2f994 Compare July 23, 2024 20:01
Copy link

A new generated diff is ready to view.

  • No codegen difference in the AWS SDK
  • No codegen difference in the Client Test
  • No codegen difference in the Server Test
  • No codegen difference in the Server Test Python
  • No codegen difference in the Server Test Typescript

A new doc preview is ready to view.

Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

Copy link

github-actions bot commented Aug 5, 2024

A new generated diff is ready to view.

A new doc preview is ready to view.

@rcoh rcoh force-pushed the configurable-serde branch from a0ab7a4 to 5aa1794 Compare August 6, 2024 17:44
Copy link

github-actions bot commented Aug 6, 2024

A new generated diff is ready to view.

A new doc preview is ready to view.

cargoCommand = "cargo test --all-features",
additionalSettings = constrainedShapesSettings,
),
) { clientCodegenContext, rustCrate ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) { clientCodegenContext, rustCrate ->
) { serverCodegenContext, rustCrate ->

"""
use #{serde}::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(#{value}.len()))?;
for (k, v) in #{value}.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary iters still here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are load bearing

@rcoh rcoh added this pull request to the merge queue Aug 12, 2024
Merged via the queue into main with commit a107c01 Aug 12, 2024
44 checks passed
@rcoh rcoh deleted the configurable-serde branch August 12, 2024 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants