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 codegen helper for defining operation-specific CustomizableOperation methods. #3918

Merged
merged 3 commits into from
Dec 2, 2024

Conversation

Velfi
Copy link
Contributor

@Velfi Velfi commented Nov 20, 2024

This PR lays the groundwork for defining operation-specific CustomizableOperation methods.

Codegen Example

If we were to define pre-signable ops this way, code like the following would be emitted for each op that supported presigning.

impl<E, B> CustomizableOperation<crate::operation::put_object::PutObject, E, B> {
    /// Sends the request and returns the response.
    #[allow(unused_mut)]
    pub async fn presigned(
        mut self,
        presigning_config: crate::presigning::PresigningConfig,
    ) -> ::std::result::Result<crate::presigning::PresignedRequest, crate::error::SdkError<E>>
    where
        E: std::error::Error + ::std::marker::Send + ::std::marker::Sync + 'static,
        B: crate::client::customize::internal::CustomizablePresigned<E>,
    {
        self.execute(move |sender, conf| sender.presign(conf, presigning_config)).await
    }
}

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

@Velfi Velfi requested review from a team as code owners November 20, 2024 17:00
@Velfi Velfi marked this pull request as draft November 20, 2024 17:04
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

@ysaito1001
Copy link
Contributor

Thanks for the PR. This gives us a good starting point for discussion.

We've been generating the presign method for all S3 ops, even though it's only guaranteed to work for a subset. Before merging this, we'll probably want to undo the changes to presigning while keeping the underlying new way of defining customizable op impls.

Talked about this briefly offline, and it might be better to keep the presign method for all operations so we don’t break things for people. There's also an issue (linked off from this TODO) that says All S3 requests should be presignable—this is the behavior of the Ruby SDK. so that's something to keep in mind.

@Velfi Velfi changed the title Only generate the presign customize operation method for ops that support it Add codegen helper for defining operation-specific CustomizableOperation methods. Dec 2, 2024
@Velfi Velfi marked this pull request as ready for review December 2, 2024 16:12
@@ -80,3 +80,13 @@ fun <T : Section> RustWriter.writeCustomizationsOrElse(
orElse(this)
}
}

fun <T : Section> allCustomizationsAreEmpty(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this necessary? If customizations is empty then how would writeCustomizations end up with a "dirty" writer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great question!

How can you tell if the following function will write when called through RustWriter.writeCustomizations?

    override fun operationCustomizations(
        codegenContext: ClientCodegenContext,
        operation: OperationShape,
        baseCustomizations: List<OperationCustomization>,
    ): List<OperationCustomization> {
        return baseCustomizations +
            object : OperationCustomization() {
                override fun section(section: OperationSection): Writable {
                    return writable {
                        when (section) {
                            is OperationSection.CustomizableOperationImpl -> {
                                    rust("// Writing occurred!")
                                }
                            }

                            else -> {}
                        }
                    }
                }
            }
    }

It just returns a list of customizations with method implementations. You can't know anything about the implementations, including what sections they actually care about. There's no way to know if this will write anything without actually calling it and trying to write something.

So why is it important to know if something was written? It's important in cases where you want to avoid empty wrappers. Imagine that you want to generate a block of code with functions for converting a type. If you always have n+1 customizations, then it's always OK to write the enclosing block. But if you only have n customizations then you'll write an empty block and clippy will be mad at you.

Enter allCustomizationsAreEmpty. With this function, we can see if anything would actually be written, giving us the power to avoid writing an enclosing block when it would be empty.

Does that make sense?

Copy link
Contributor

Choose a reason for hiding this comment

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

It does, one of my concerns was triggering unrelated/additional side effects (e.g. imports that trigger dependencies being added, etc). We saw this in Kotlin though for a similar but slightly different reason.

Would be nice if we had a cleaner way to do this but I suppose it's fine for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe adding source code comment with an example Rust snippet that this function is trying to solve is useful?

Copy link

github-actions bot commented Dec 2, 2024

A new generated diff is ready to view.

A new doc preview is ready to view.

@Velfi Velfi added this pull request to the merge queue Dec 2, 2024
Merged via the queue into main with commit 65035d5 Dec 2, 2024
43 of 44 checks passed
@Velfi Velfi deleted the zhessler-gen-presign-methods-for-presignable-ops-only branch December 2, 2024 17:44
landonxjames pushed a commit that referenced this pull request Dec 2, 2024
…tion` methods. (#3918)

This PR lays the groundwork for defining operation-specific
`CustomizableOperation` methods.

## Codegen Example

If we were to define pre-signable ops this way, code like the following
would be emitted for each op that supported presigning.

```rust
impl<E, B> CustomizableOperation<crate::operation::put_object::PutObject, E, B> {
    /// Sends the request and returns the response.
    #[allow(unused_mut)]
    pub async fn presigned(
        mut self,
        presigning_config: crate::presigning::PresigningConfig,
    ) -> ::std::result::Result<crate::presigning::PresignedRequest, crate::error::SdkError<E>>
    where
        E: std::error::Error + ::std::marker::Send + ::std::marker::Sync + 'static,
        B: crate::client::customize::internal::CustomizablePresigned<E>,
    {
        self.execute(move |sender, conf| sender.presign(conf, presigning_config)).await
    }
}
```

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
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.

3 participants