Skip to content

Commit ccfeb72

Browse files
authored
RUST-1512 Better links for action return types (#1054)
1 parent 7f798b0 commit ccfeb72

26 files changed

+296
-113
lines changed

action_macro/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "Apache-2.0"
99
[dependencies]
1010
proc-macro2 = "1.0.78"
1111
quote = "1.0.35"
12-
syn = { version = "2.0.52", features = ["full", "parsing", "proc-macro"] }
12+
syn = { version = "2.0.52", features = ["full", "parsing", "proc-macro", "extra-traits"] }
1313

1414
[lib]
1515
proc-macro = true

action_macro/src/lib.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
extern crate proc_macro;
22

3-
use quote::quote;
3+
use quote::{quote, ToTokens};
44
use syn::{
55
braced,
66
parenthesized,
@@ -11,9 +11,13 @@ use syn::{
1111
spanned::Spanned,
1212
Block,
1313
Error,
14+
Expr,
1415
Generics,
1516
Ident,
17+
ImplItemFn,
1618
Lifetime,
19+
Lit,
20+
Meta,
1721
Token,
1822
Type,
1923
};
@@ -218,3 +222,106 @@ fn parse_name(input: ParseStream, name: &str) -> syn::Result<()> {
218222
}
219223
Ok(())
220224
}
225+
226+
/// Enables rustdoc links to types that link individually to each type
227+
/// component.
228+
#[proc_macro_attribute]
229+
pub fn deeplink(
230+
_attr: proc_macro::TokenStream,
231+
item: proc_macro::TokenStream,
232+
) -> proc_macro::TokenStream {
233+
let mut impl_fn = parse_macro_input!(item as ImplItemFn);
234+
235+
for attr in &mut impl_fn.attrs {
236+
// Skip non-`doc` attrs
237+
if attr.path() != &parse_quote! { doc } {
238+
continue;
239+
}
240+
// Get the string literal value from #[doc = "lit"]
241+
let mut text = match &mut attr.meta {
242+
Meta::NameValue(nv) => match &mut nv.value {
243+
Expr::Lit(el) => match &mut el.lit {
244+
Lit::Str(ls) => ls.value(),
245+
_ => continue,
246+
},
247+
_ => continue,
248+
},
249+
_ => continue,
250+
};
251+
// Process substrings delimited by "d[...]"
252+
while let Some(ix) = text.find("d[") {
253+
let pre = &text[..ix];
254+
let rest = &text[ix + 2..];
255+
let end = match rest.find(']') {
256+
Some(v) => v,
257+
None => {
258+
return Error::new(attr.span(), "unterminated d[")
259+
.into_compile_error()
260+
.into()
261+
}
262+
};
263+
let body = &rest[..end];
264+
let post = &rest[end + 1..];
265+
// Strip inner backticks, if any
266+
let (fixed, body) = if body.starts_with('`') && body.ends_with('`') {
267+
(
268+
true,
269+
body.strip_prefix('`').unwrap().strip_suffix('`').unwrap(),
270+
)
271+
} else {
272+
(false, body)
273+
};
274+
// Build new string
275+
let mut new_text = pre.to_owned();
276+
if fixed {
277+
new_text.push_str("<code>");
278+
}
279+
new_text.push_str(&text_link(body));
280+
if fixed {
281+
new_text.push_str("</code>");
282+
}
283+
new_text.push_str(post);
284+
text = new_text;
285+
}
286+
*attr = parse_quote! { #[doc = #text] };
287+
}
288+
289+
impl_fn.into_token_stream().into()
290+
}
291+
292+
fn text_link(text: &str) -> String {
293+
// Break into segments delimited by '<' or '>'
294+
let segments = text.split_inclusive(&['<', '>'])
295+
// Put each delimiter in its own segment
296+
.flat_map(|s| {
297+
if s == "<" || s == ">" {
298+
vec![s]
299+
} else if let Some(sub) = s.strip_suffix(&['<', '>']) {
300+
vec![sub, &s[sub.len()..]]
301+
} else {
302+
vec![s]
303+
}
304+
});
305+
306+
// Build output
307+
let mut out = vec![];
308+
for segment in segments {
309+
match segment {
310+
// Escape angle brackets
311+
"<" => out.push("&lt;"),
312+
">" => out.push("&gt;"),
313+
// Don't link unit
314+
"()" => out.push("()"),
315+
// Link to types
316+
_ => {
317+
// Use the short name
318+
let short = segment
319+
.rsplit_once("::")
320+
.map(|(_, short)| short)
321+
.unwrap_or(segment);
322+
out.extend(["[", short, "](", segment, ")"]);
323+
}
324+
}
325+
}
326+
out.concat()
327+
}

src/action.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ pub trait Action: private::Sealed + IntoFuture {
143143
}
144144
}
145145

146-
pub(crate) use action_macro::action_impl;
146+
pub(crate) use action_macro::{action_impl, deeplink};
147147

148148
use crate::Collection;
149149

src/action/aggregate.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@ use crate::{
1616
SessionCursor,
1717
};
1818

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

2121
impl Database {
2222
/// Runs an aggregation operation.
2323
///
2424
/// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more
2525
/// information on aggregations.
2626
///
27-
/// `await` will return `Result<`[`Cursor`]`<Document>>` or `Result<SessionCursor<Document>>` if
27+
/// `await` will return d[`Result<Cursor<Document>>`] or d[`Result<SessionCursor<Document>>`] if
2828
/// a `ClientSession` is provided.
29+
#[deeplink]
2930
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
3031
Aggregate {
3132
target: AggregateTargetRef::Database(self),
@@ -45,8 +46,9 @@ where
4546
/// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more
4647
/// information on aggregations.
4748
///
48-
/// `await` will return `Result<Cursor<Document>>` or `Result<SessionCursor<Document>>` if
49-
/// a `ClientSession` is provided.
49+
/// `await` will return d[`Result<Cursor<Document>>`] or d[`Result<SessionCursor<Document>>`] if
50+
/// a [`ClientSession`] is provided.
51+
#[deeplink]
5052
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
5153
Aggregate {
5254
target: AggregateTargetRef::Collection(CollRef::new(self)),
@@ -64,8 +66,9 @@ impl crate::sync::Database {
6466
/// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more
6567
/// information on aggregations.
6668
///
67-
/// [`run`](Aggregate::run) will return `Result<`[`Cursor`]`<Document>>` or
68-
/// `Result<SessionCursor<Document>>` if a `ClientSession` is provided.
69+
/// [`run`](Aggregate::run) will return d[`Result<crate::sync::Cursor<Document>>`] or
70+
/// d[`Result<crate::sync::SessionCursor<Document>>`] if a [`ClientSession`] is provided.
71+
#[deeplink]
6972
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
7073
self.async_database.aggregate(pipeline)
7174
}
@@ -81,14 +84,15 @@ where
8184
/// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more
8285
/// information on aggregations.
8386
///
84-
/// [`run`](Aggregate::run) will return `Result<Cursor<Document>>` or
85-
/// `Result<SessionCursor<Document>>` if a `ClientSession` is provided.
87+
/// [`run`](Aggregate::run) will return d[`Result<crate::sync::Cursor<Document>>`] or
88+
/// d[`Result<crate::sync::SessionCursor<Document>>`] if a `ClientSession` is provided.
89+
#[deeplink]
8690
pub fn aggregate(&self, pipeline: impl IntoIterator<Item = Document>) -> Aggregate {
8791
self.async_collection.aggregate(pipeline)
8892
}
8993
}
9094

91-
/// Run an aggregation operation. Create by calling [`Database::aggregate`] or
95+
/// Run an aggregation operation. Construct with [`Database::aggregate`] or
9296
/// [`Collection::aggregate`].
9397
#[must_use]
9498
pub struct Aggregate<'a, Session = ImplicitSession> {

src/action/count.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
Collection,
88
};
99

10-
use super::{action_impl, option_setters, CollRef};
10+
use super::{action_impl, deeplink, option_setters, CollRef};
1111

1212
impl<T> Collection<T>
1313
where
@@ -25,7 +25,8 @@ where
2525
/// For more information on the behavior of the `count` server command, see
2626
/// [Count: Behavior](https://www.mongodb.com/docs/manual/reference/command/count/#behavior).
2727
///
28-
/// `await` will return `Result<u64>`.
28+
/// `await` will return d[`Result<u64>`].
29+
#[deeplink]
2930
pub fn estimated_document_count(&self) -> EstimatedDocumentCount {
3031
EstimatedDocumentCount {
3132
cr: CollRef::new(self),
@@ -37,7 +38,8 @@ where
3738
///
3839
/// Note that this method returns an accurate count.
3940
///
40-
/// `await` will return `Result<u64>`.
41+
/// `await` will return d[`Result<u64>`].
42+
#[deeplink]
4143
pub fn count_documents(&self, filter: Document) -> CountDocuments {
4244
CountDocuments {
4345
cr: CollRef::new(self),
@@ -65,7 +67,8 @@ where
6567
/// For more information on the behavior of the `count` server command, see
6668
/// [Count: Behavior](https://www.mongodb.com/docs/manual/reference/command/count/#behavior).
6769
///
68-
/// [`run`](EstimatedDocumentCount::run) will return `Result<u64>`.
70+
/// [`run`](EstimatedDocumentCount::run) will return d[`Result<u64>`].
71+
#[deeplink]
6972
pub fn estimated_document_count(&self) -> EstimatedDocumentCount {
7073
self.async_collection.estimated_document_count()
7174
}
@@ -74,13 +77,14 @@ where
7477
///
7578
/// Note that this method returns an accurate count.
7679
///
77-
/// [`run`](CountDocuments::run) will return `Result<u64>`.
80+
/// [`run`](CountDocuments::run) will return d[`Result<u64>`].
81+
#[deeplink]
7882
pub fn count_documents(&self, filter: Document) -> CountDocuments {
7983
self.async_collection.count_documents(filter)
8084
}
8185
}
8286

83-
/// Gather an estimated document count. Create by calling [`Collection::estimated_document_count`].
87+
/// Gather an estimated document count. Construct with [`Collection::estimated_document_count`].
8488
#[must_use]
8589
pub struct EstimatedDocumentCount<'a> {
8690
cr: CollRef<'a>,
@@ -108,7 +112,7 @@ action_impl! {
108112
}
109113
}
110114

111-
/// Get an accurate count of documents. Create by calling [`Collection::count_documents`].
115+
/// Get an accurate count of documents. Construct with [`Collection::count_documents`].
112116
#[must_use]
113117
pub struct CountDocuments<'a> {
114118
cr: CollRef<'a>,

src/action/create_collection.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ use bson::Document;
22

33
use crate::{options::CreateCollectionOptions, ClientSession, Database};
44

5-
use crate::action::option_setters;
5+
use crate::action::{deeplink, option_setters};
66

77
impl Database {
88
/// Creates a new collection in the database with the given `name`.
99
///
1010
/// Note that MongoDB creates collections implicitly when data is inserted, so this method is
1111
/// not needed if no special options are required.
1212
///
13-
/// `await` will return `Result<()>`.
13+
/// `await` will return d[`Result<()>`].
14+
#[deeplink]
1415
pub fn create_collection(&self, name: impl AsRef<str>) -> CreateCollection {
1516
CreateCollection {
1617
db: self,
@@ -28,13 +29,14 @@ impl crate::sync::Database {
2829
/// Note that MongoDB creates collections implicitly when data is inserted, so this method is
2930
/// not needed if no special options are required.
3031
///
31-
/// [`run`](CreateCollection::run) will return `Result<()>`.
32+
/// [`run`](CreateCollection::run) will return d[`Result<()>`].
33+
#[deeplink]
3234
pub fn create_collection(&self, name: impl AsRef<str>) -> CreateCollection {
3335
self.async_database.create_collection(name)
3436
}
3537
}
3638

37-
/// Creates a new collection. Create by calling [`Database::create_collection`].
39+
/// Creates a new collection. Construct with [`Database::create_collection`].
3840
#[must_use]
3941
pub struct CreateCollection<'a> {
4042
pub(crate) db: &'a Database,

src/action/create_index.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ use crate::{
1313
IndexModel,
1414
};
1515

16-
use super::{action_impl, option_setters, CollRef, Multiple, Single};
16+
use super::{action_impl, deeplink, option_setters, CollRef, Multiple, Single};
1717

1818
impl<T> Collection<T>
1919
where
2020
T: Send + Sync,
2121
{
2222
/// Creates the given index on this collection.
2323
///
24-
/// `await` will return `Result<CreateIndexResult>`.
24+
/// `await` will return d[`Result<CreateIndexResult>`].
25+
#[deeplink]
2526
pub fn create_index(&self, index: IndexModel) -> CreateIndex {
2627
CreateIndex {
2728
coll: CollRef::new(self),
@@ -34,7 +35,8 @@ where
3435

3536
/// Creates the given indexes on this collection.
3637
///
37-
/// `await` will return `Result<CreateIndexesResult>`.
38+
/// `await` will return d[`Result<CreateIndexesResult>`].
39+
#[deeplink]
3840
pub fn create_indexes(
3941
&self,
4042
indexes: impl IntoIterator<Item = IndexModel>,
@@ -56,14 +58,16 @@ where
5658
{
5759
/// Creates the given index on this collection.
5860
///
59-
/// [`run`](CreateIndex::run) will return `Result<CreateIndexResult>`.
61+
/// [`run`](CreateIndex::run) will return d[`Result<CreateIndexResult>`].
62+
#[deeplink]
6063
pub fn create_index(&self, index: IndexModel) -> CreateIndex {
6164
self.async_collection.create_index(index)
6265
}
6366

6467
/// Creates the given indexes on this collection.
6568
///
66-
/// [`run`](CreateIndex::run) will return `Result<CreateIndexesResult>`.
69+
/// [`run`](CreateIndex::run) will return d[`Result<CreateIndexesResult>`].
70+
#[deeplink]
6771
pub fn create_indexes(
6872
&self,
6973
indexes: impl IntoIterator<Item = IndexModel>,

src/action/csfle/create_data_key.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::client_encryption::{ClientEncryption, MasterKey};
22

3-
use super::super::option_setters;
3+
use super::super::{deeplink, option_setters};
44

55
impl ClientEncryption {
66
/// Creates a new key document and inserts into the key vault collection.
77
///
8-
/// `await` will return `Result<Binary>` (subtype 0x04) with the _id of the created
8+
/// `await` will return d[`Result<Binary>`] (subtype 0x04) with the _id of the created
99
/// document as a UUID.
10+
#[deeplink]
1011
pub fn create_data_key(&self, master_key: MasterKey) -> CreateDataKey {
1112
CreateDataKey {
1213
client_enc: self,

0 commit comments

Comments
 (0)