Skip to content

Commit f87af8f

Browse files
authored
RUST-2103 Better documentation for action options, batch 2 (#1261)
1 parent a55919e commit f87af8f

File tree

10 files changed

+274
-122
lines changed

10 files changed

+274
-122
lines changed

macros/src/lib.rs

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
extern crate proc_macro;
22

3-
use macro_magic::import_tokens_attr;
3+
use macro_magic::{import_tokens_attr, mm_core::ForeignPath};
44
use quote::{quote, ToTokens};
55
use syn::{
66
braced,
7+
bracketed,
78
parenthesized,
89
parse::{Parse, ParseStream},
910
parse_macro_input,
1011
parse_quote,
1112
parse_quote_spanned,
13+
punctuated::Punctuated,
1214
spanned::Spanned,
15+
token::Bracket,
1316
Attribute,
1417
Block,
1518
Error,
@@ -206,15 +209,15 @@ impl Parse for ActionImplAttrs {
206209
}
207210

208211
/// Parse an identifier with a specific expected value.
209-
fn parse_name(input: ParseStream, name: &str) -> syn::Result<()> {
212+
fn parse_name(input: ParseStream, name: &str) -> syn::Result<Ident> {
210213
let ident = input.parse::<Ident>()?;
211214
if ident.to_string() != name {
212215
return Err(Error::new(
213216
ident.span(),
214217
format!("expected '{}', got '{}'", name, ident),
215218
));
216219
}
217-
Ok(())
220+
Ok(ident)
218221
}
219222

220223
macro_rules! compile_error {
@@ -461,13 +464,15 @@ impl Parse for OptionSetter {
461464
}
462465

463466
#[import_tokens_attr]
467+
#[with_custom_parsing(OptionSettersArgs)]
464468
#[proc_macro_attribute]
465469
pub fn option_setters_2(
466470
attr: proc_macro::TokenStream,
467471
item: proc_macro::TokenStream,
468472
) -> proc_macro::TokenStream {
469473
let opt_struct = parse_macro_input!(attr as ItemStruct);
470474
let mut impl_in = parse_macro_input!(item as ItemImpl);
475+
let args = parse_macro_input!(__custom_tokens as OptionSettersArgs);
471476

472477
// Gather information about each option struct field
473478
struct OptInfo {
@@ -545,21 +550,134 @@ pub fn option_setters_2(
545550
self.options().#name = Some(#value);
546551
self
547552
}
553+
});
554+
}
555+
556+
// Build rustdoc information.
557+
let doc_name = args.doc_name;
558+
let mut doc_impl = impl_in.clone();
559+
// Synthesize a fn entry for each extra listed so it'll get a rustdoc entry
560+
if let Some((_, extra)) = args.extra {
561+
for name in &extra.names {
562+
doc_impl.items.push(parse_quote! {
563+
pub fn #name(&self) {}
564+
});
565+
}
566+
}
567+
568+
// All done. Export the tokens for doc use as their own distinct (uncompiled) item.
569+
quote! {
570+
#impl_in
571+
572+
#[macro_magic::export_tokens_no_emit(#doc_name)]
573+
#doc_impl
574+
}
575+
.into()
576+
}
577+
578+
struct OptionSettersArgs {
579+
source_text: (Ident, Token![=]), // source =
580+
foreign_path: syn::Path,
581+
name_text: (Token![,], Ident, Token![=]), // , doc_name =
582+
doc_name: Ident,
583+
extra: Option<(Token![,], OptionSettersArgsExtra)>,
584+
}
585+
586+
#[derive(Debug)]
587+
struct OptionSettersArgsExtra {
588+
extra_text: (Ident, Token![=]), // extra =
589+
bracket: Bracket,
590+
names: Punctuated<Ident, Token![,]>,
591+
}
592+
593+
impl Parse for OptionSettersArgs {
594+
fn parse(input: ParseStream) -> syn::Result<Self> {
595+
let source_text = (parse_name(input, "source")?, input.parse()?);
596+
let foreign_path = input.parse()?;
597+
let name_text = (
598+
input.parse()?,
599+
parse_name(input, "doc_name")?,
600+
input.parse()?,
601+
);
602+
let doc_name = input.parse()?;
603+
let extra = if input.is_empty() {
604+
None
605+
} else {
606+
Some((input.parse()?, input.parse()?))
607+
};
608+
Ok(Self {
609+
source_text,
610+
foreign_path,
611+
name_text,
612+
doc_name,
613+
extra,
548614
})
549615
}
616+
}
617+
618+
impl ToTokens for OptionSettersArgs {
619+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
620+
let Self {
621+
source_text,
622+
foreign_path,
623+
name_text,
624+
doc_name,
625+
extra,
626+
} = &self;
627+
tokens.extend(source_text.0.to_token_stream());
628+
tokens.extend(source_text.1.to_token_stream());
629+
tokens.extend(foreign_path.to_token_stream());
630+
tokens.extend(name_text.0.to_token_stream());
631+
tokens.extend(name_text.1.to_token_stream());
632+
tokens.extend(name_text.2.to_token_stream());
633+
tokens.extend(doc_name.to_token_stream());
634+
if let Some(extra) = extra {
635+
tokens.extend(extra.0.to_token_stream());
636+
tokens.extend(extra.1.to_token_stream());
637+
}
638+
}
639+
}
550640

551-
// All done.
552-
impl_in.to_token_stream().into()
641+
impl ForeignPath for OptionSettersArgs {
642+
fn foreign_path(&self) -> &syn::Path {
643+
&self.foreign_path
644+
}
645+
}
646+
647+
impl Parse for OptionSettersArgsExtra {
648+
fn parse(input: ParseStream) -> syn::Result<Self> {
649+
let extra_text = (parse_name(input, "extra")?, input.parse::<Token![=]>()?);
650+
let content;
651+
let bracket = bracketed!(content in input);
652+
let names = Punctuated::parse_separated_nonempty(&content)?;
653+
Ok(Self {
654+
extra_text,
655+
bracket,
656+
names,
657+
})
658+
}
659+
}
660+
661+
impl ToTokens for OptionSettersArgsExtra {
662+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
663+
tokens.extend(self.extra_text.0.to_token_stream());
664+
tokens.extend(self.extra_text.1.to_token_stream());
665+
self.bracket.surround(tokens, |content| {
666+
content.extend(self.names.to_token_stream());
667+
});
668+
}
553669
}
554670

555671
#[import_tokens_attr]
672+
#[with_custom_parsing(OptionsDocArgs)]
556673
#[proc_macro_attribute]
557674
pub fn options_doc(
558675
attr: proc_macro::TokenStream,
559676
item: proc_macro::TokenStream,
560677
) -> proc_macro::TokenStream {
561678
let setters = parse_macro_input!(attr as ItemImpl);
562679
let mut impl_fn = parse_macro_input!(item as ImplItemFn);
680+
let args = parse_macro_input!(__custom_tokens as OptionsDocArgs);
563681

564682
// Collect a list of names from the setters impl
565683
let mut setter_names = vec![];
@@ -586,8 +704,12 @@ pub fn options_doc(
586704
impl_fn.attrs.push(parse_quote! {
587705
#[doc = ""]
588706
});
707+
let preamble = format!(
708+
"These methods can be chained before `{}` to set options:",
709+
if args.is_async() { ".await" } else { "run" }
710+
);
589711
impl_fn.attrs.push(parse_quote! {
590-
#[doc = "These methods can be chained before calling `.await` to set options:"]
712+
#[doc = #preamble]
591713
});
592714
for name in setter_names {
593715
let docstr = format!(" * [`{0}`]({1}::{0})", name, doc_path);
@@ -597,3 +719,43 @@ pub fn options_doc(
597719
}
598720
impl_fn.into_token_stream().into()
599721
}
722+
723+
struct OptionsDocArgs {
724+
foreign_path: syn::Path,
725+
sync: Option<(Token![,], Ident)>,
726+
}
727+
728+
impl OptionsDocArgs {
729+
fn is_async(&self) -> bool {
730+
self.sync.is_none()
731+
}
732+
}
733+
734+
impl Parse for OptionsDocArgs {
735+
fn parse(input: ParseStream) -> syn::Result<Self> {
736+
let foreign_path = input.parse()?;
737+
let sync = if input.is_empty() {
738+
None
739+
} else {
740+
Some((input.parse()?, parse_name(input, "sync")?))
741+
};
742+
743+
Ok(Self { foreign_path, sync })
744+
}
745+
}
746+
747+
impl ToTokens for OptionsDocArgs {
748+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
749+
tokens.extend(self.foreign_path.to_token_stream());
750+
if let Some((comma, ident)) = &self.sync {
751+
tokens.extend(comma.to_token_stream());
752+
tokens.extend(ident.to_token_stream());
753+
}
754+
}
755+
}
756+
757+
impl ForeignPath for OptionsDocArgs {
758+
fn foreign_path(&self) -> &syn::Path {
759+
&self.foreign_path
760+
}
761+
}

src/action/aggregate.rs

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use std::{marker::PhantomData, time::Duration};
22

3-
use bson::Document;
3+
use bson::{Bson, Document};
4+
use mongodb_internal_macros::{option_setters_2, options_doc};
45

56
use crate::{
6-
coll::options::AggregateOptions,
7+
coll::options::{AggregateOptions, Hint},
8+
collation::Collation,
79
error::Result,
810
operation::aggregate::AggregateTarget,
911
options::{ReadConcern, WriteConcern},
@@ -16,7 +18,7 @@ use crate::{
1618
SessionCursor,
1719
};
1820

19-
use super::{action_impl, deeplink, option_setters, CollRef, ExplicitSession, ImplicitSession};
21+
use super::{action_impl, deeplink, CollRef, ExplicitSession, ImplicitSession};
2022

2123
impl Database {
2224
/// Runs an aggregation operation.
@@ -28,6 +30,7 @@ impl Database {
2830
/// returned cursor will be a [`SessionCursor`]. If [`with_type`](Aggregate::with_type) was
2931
/// called, the returned cursor will be generic over the `T` specified.
3032
#[deeplink]
33+
#[options_doc(aggregate_setters)]
3134
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
3235
Aggregate {
3336
target: AggregateTargetRef::Database(self),
@@ -52,6 +55,7 @@ where
5255
/// returned cursor will be a [`SessionCursor`]. If [`with_type`](Aggregate::with_type) was
5356
/// called, the returned cursor will be generic over the `T` specified.
5457
#[deeplink]
58+
#[options_doc(aggregate_setters)]
5559
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
5660
Aggregate {
5761
target: AggregateTargetRef::Collection(CollRef::new(self)),
@@ -75,6 +79,7 @@ impl crate::sync::Database {
7579
/// [`crate::sync::SessionCursor`]. If [`with_type`](Aggregate::with_type) was called, the
7680
/// returned cursor will be generic over the `T` specified.
7781
#[deeplink]
82+
#[options_doc(aggregate_setters, sync)]
7883
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
7984
self.async_database.aggregate(pipeline)
8085
}
@@ -95,6 +100,7 @@ where
95100
/// `crate::sync::SessionCursor`. If [`with_type`](Aggregate::with_type) was called, the
96101
/// returned cursor will be generic over the `T` specified.
97102
#[deeplink]
103+
#[options_doc(aggregate_setters, sync)]
98104
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
99105
self.async_collection.aggregate(pipeline)
100106
}
@@ -111,39 +117,11 @@ pub struct Aggregate<'a, Session = ImplicitSession, T = Document> {
111117
_phantom: PhantomData<T>,
112118
}
113119

114-
impl<Session, T> Aggregate<'_, Session, T> {
115-
option_setters!(options: AggregateOptions;
116-
allow_disk_use: bool,
117-
batch_size: u32,
118-
bypass_document_validation: bool,
119-
collation: crate::collation::Collation,
120-
comment: bson::Bson,
121-
hint: crate::coll::options::Hint,
122-
max_await_time: Duration,
123-
max_time: Duration,
124-
read_concern: ReadConcern,
125-
selection_criteria: SelectionCriteria,
126-
write_concern: WriteConcern,
127-
let_vars: Document,
128-
);
129-
}
130-
131-
impl<'a, T> Aggregate<'a, ImplicitSession, T> {
132-
/// Use the provided session when running the operation.
133-
pub fn session(
134-
self,
135-
value: impl Into<&'a mut ClientSession>,
136-
) -> Aggregate<'a, ExplicitSession<'a>> {
137-
Aggregate {
138-
target: self.target,
139-
pipeline: self.pipeline,
140-
options: self.options,
141-
session: ExplicitSession(value.into()),
142-
_phantom: PhantomData,
143-
}
144-
}
145-
}
146-
120+
#[option_setters_2(
121+
source = crate::coll::options::AggregateOptions,
122+
doc_name = aggregate_setters,
123+
extra = [session]
124+
)]
147125
impl<'a, Session, T> Aggregate<'a, Session, T> {
148126
/// Use the provided type for the returned cursor.
149127
///
@@ -178,6 +156,22 @@ impl<'a, Session, T> Aggregate<'a, Session, T> {
178156
}
179157
}
180158

159+
impl<'a, T> Aggregate<'a, ImplicitSession, T> {
160+
/// Use the provided session when running the operation.
161+
pub fn session(
162+
self,
163+
value: impl Into<&'a mut ClientSession>,
164+
) -> Aggregate<'a, ExplicitSession<'a>> {
165+
Aggregate {
166+
target: self.target,
167+
pipeline: self.pipeline,
168+
options: self.options,
169+
session: ExplicitSession(value.into()),
170+
_phantom: PhantomData,
171+
}
172+
}
173+
}
174+
181175
#[action_impl(sync = crate::sync::Cursor<T>)]
182176
impl<'a, T> Action for Aggregate<'a, ImplicitSession, T> {
183177
type Future = AggregateFuture;

0 commit comments

Comments
 (0)