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

RUST-2103 Update the rest of actions to document options #1272

Merged
merged 33 commits into from
Jan 2, 2025

Conversation

abr-egn
Copy link
Contributor

@abr-egn abr-egn commented Dec 20, 2024

RUST-2103

This did involve one more rework of how the macros handle things (sorry!); with that, there's now a family of three related macros:

  • option_setters: takes the path to an options struct and generates setters for the fields of that struct in the impl block to which it's attached. Can be optionally told to skip setters for some of the fields.
  • export_doc: generates a notional named "doc item" for use by options_doc from an impl block. Can be optionally told to add entries to the doc item that aren't present in the code of the impl.
  • options_doc: consumes a "doc item" to add rustdoc listing option setters to a fn. Can be optionally told it's a sync fn to adjust the rustdoc output. (unchanged from last PR)

With this split, I think it's both easier to read and understand (no more multiline kwarg macro invocations) and it much better handles edge cases, of which we have surprisingly many.

Oh, and I also split up the contents of macros/src/lib.rs since that had gotten quite big.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing in here changed, just moved to its own file.

($span:expr, $($message:tt)+) => {{
return Error::new($span, format!($($message)+)).into_compile_error().into();
}};
crate::action_impl::action_impl(attrs, input)
}

/// Enables rustdoc links to types that link individually to each type
/// component.
#[proc_macro_attribute]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has to be attached to a fn at the toplevel module for whatever reason, hence the wrapper rather than just re-exporting.

impl_in.to_token_stream().into()
}

pub(crate) struct OptionSettersArgs {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously for the macro_magic custom args I was keeping every token individually and laboriously reconstructing the original in ToTokens, which was a giant hassle with any sort of change. I realized that it's much easier to just keep a clone of the entire input token stream and then only have struct members for the bits actually needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

deeplink and options_doc are unchanged.

@@ -30,7 +38,7 @@ impl Database {
/// returned cursor will be a [`SessionCursor`]. If [`with_type`](Aggregate::with_type) was
/// called, the returned cursor will be generic over the `T` specified.
#[deeplink]
#[options_doc(aggregate_setters)]
#[options_doc(aggregate)]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the name is translated into a mangled representation, it's effectively in its own namespace and won't clash with anything. Having it be the same name as the fn it's attached to seemed reasonable, the _setters suffix wasn't doing anything helpful.

range_options: RangeOptions,
);
}
#[option_setters(EncryptOptions, skip = [query_type])]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's one of the fun corner cases! The Encrypt action is shared across both encrypt and encrypt_expression; we want to expose the query_type option for the former, both in code and documentation, but not the latter. So:

  • For the generic impl block, the generated setter for query_type is skipped, and it's manually implemented for Encrypt<'_, Value> only.
  • For the generated documentation for encrypt, the manually written setter for query_type is added to the list
  • For the generated documentation for encrypt_expr, it isn't

Copy link
Contributor

Choose a reason for hiding this comment

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

Somewhat tangential, but this led me to find a niche corner case that can lead to bad behavior when using with_options in a call to encrypt_expression:

let options = EncryptOptions::builder()
    .contention_factor(0)
    .range_options(range_options)
    .build();
let result = client_encryption
    .encrypt_expression(...)
    .with_options(options)
    .await?;

The call to encrypt_expression sets the query_type to the correct value on the options internally, but chaining with_options wipes this value, and the following error is returned: EncryptExpression may only be used for range queries.

We should add the following validation to the IntoFuture impl for Encrypt<Expression>:

  • if query_type is set to Some(<string not equal to "range">), return an error indicating that query_type cannot be set via with_options for calls to encrypt_expression
  • if query_type is set to None, manually re-set the value to Some("range") as the user inadvertently overrode it by the call to with_options

I can make a follow-up PR to fix this; we're also missing a TypedBuilder impl for EncryptOptions so I'll add that too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch! Follow up PR makes sense, thank you :)

@@ -130,6 +134,7 @@ pub struct Shutdown {
pub(crate) immediate: bool,
}

#[export_doc(shutdown)]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another corner case: we want to generate documentation for the options for shutdown, but there's no corresponding options object.

@abr-egn abr-egn marked this pull request as ready for review December 30, 2024 14:55
Copy link
Contributor

@isabelatkinson isabelatkinson left a comment

Choose a reason for hiding this comment

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

the updated docs look great!

range_options: RangeOptions,
);
}
#[option_setters(EncryptOptions, skip = [query_type])]
Copy link
Contributor

Choose a reason for hiding this comment

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

Somewhat tangential, but this led me to find a niche corner case that can lead to bad behavior when using with_options in a call to encrypt_expression:

let options = EncryptOptions::builder()
    .contention_factor(0)
    .range_options(range_options)
    .build();
let result = client_encryption
    .encrypt_expression(...)
    .with_options(options)
    .await?;

The call to encrypt_expression sets the query_type to the correct value on the options internally, but chaining with_options wipes this value, and the following error is returned: EncryptExpression may only be used for range queries.

We should add the following validation to the IntoFuture impl for Encrypt<Expression>:

  • if query_type is set to Some(<string not equal to "range">), return an error indicating that query_type cannot be set via with_options for calls to encrypt_expression
  • if query_type is set to None, manually re-set the value to Some("range") as the user inadvertently overrode it by the call to with_options

I can make a follow-up PR to fix this; we're also missing a TypedBuilder impl for EncryptOptions so I'll add that too.

@abr-egn abr-egn merged commit 2815edc into mongodb:main Jan 2, 2025
17 checks passed
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.

2 participants