Skip to content

Commit 24566d2

Browse files
authored
RUST-2157 Complete set of Atlas Search helpers (#1462)
1 parent e2d9d4c commit 24566d2

37 files changed

+1464
-407
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

etc/gen_atlas_search/src/main.rs

Lines changed: 126 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::path::Path;
2+
13
use convert_case::{Case, Casing};
24
use proc_macro2::TokenStream;
35
use quote::format_ident;
@@ -39,31 +41,43 @@ impl Operator {
3941

4042
fn gen_helper(&self) -> TokenStream {
4143
let name_text = &self.name;
42-
let name_ident = format_ident!("{}", name_text.to_case(Case::Pascal));
43-
let constr_ident = format_ident!("{}", name_text.to_case(Case::Snake));
44+
let ident_base = match name_text.as_str() {
45+
"in" => "searchIn",
46+
_ => name_text,
47+
};
48+
let name_ident = format_ident!("{}", ident_base.to_case(Case::Pascal));
49+
let constr_ident = format_ident!("{}", ident_base.to_case(Case::Snake));
4450

4551
let mut required_args = TokenStream::new();
52+
let mut required_arg_names = TokenStream::new();
4653
let mut init_doc = TokenStream::new();
4754
let mut setters = TokenStream::new();
4855

4956
for arg in &self.arguments {
50-
let ident = format_ident!("{}", arg.name.to_case(Case::Snake));
51-
let rust_type = arg.rust_type();
57+
let ident = format_ident!(
58+
"{}",
59+
match arg.name.as_str() {
60+
// `box` is a reserved word
61+
"box" => "geo_box".to_owned(),
62+
_ => arg.name.to_case(Case::Snake),
63+
}
64+
);
65+
let rust_type = arg.rust_type(&self.name);
5266
let type_ = rust_type.tokens();
5367
let arg_name = &arg.name;
5468
let init_expr = rust_type.bson_expr(&ident);
5569

5670
if arg.optional.unwrap_or(false) {
57-
let tvars = rust_type.variables();
5871
setters.push(parse_quote! {
5972
#[allow(missing_docs)]
60-
pub fn #ident<#tvars>(mut self, #ident: #type_) -> Self {
61-
self.stage.insert(#arg_name, #init_expr);
73+
pub fn #ident(mut self, #ident: #type_) -> Self {
74+
self.spec.insert(#arg_name, #init_expr);
6275
self
6376
}
6477
});
6578
} else {
6679
required_args.push(parse_quote! { #ident : #type_, });
80+
required_arg_names.push(parse_quote! { #ident, });
6781
init_doc.push(parse_quote! { #arg_name : #init_expr, });
6882
}
6983
}
@@ -73,21 +87,27 @@ impl Operator {
7387
"For more details, see the [{name_text} operator reference]({}).",
7488
self.link
7589
);
90+
let struct_doc = format!(
91+
"`{name_text}` Atlas Search operator. Construct with \
92+
[`{constr_ident}`]({constr_ident}())."
93+
);
7694
parse_quote! {
77-
#[allow(missing_docs)]
95+
#[doc = #struct_doc]
7896
pub struct #name_ident;
7997

80-
impl AtlasSearch<#name_ident> {
81-
#[doc = #desc]
82-
#[doc = ""]
83-
#[doc = #link]
84-
pub fn #constr_ident(#required_args) -> Self {
85-
AtlasSearch {
86-
name: #name_text,
87-
stage: doc! { #init_doc },
88-
_t: PhantomData,
89-
}
90-
}
98+
#[doc = #desc]
99+
#[doc = ""]
100+
#[doc = #link]
101+
#[options_doc(#constr_ident, "into_stage")]
102+
pub fn #constr_ident(#required_args) -> SearchOperator<#name_ident> {
103+
SearchOperator::new(
104+
#name_text,
105+
doc! { #init_doc },
106+
)
107+
}
108+
109+
#[export_doc(#constr_ident)]
110+
impl SearchOperator<#name_ident> {
91111
#setters
92112
}
93113
}
@@ -107,80 +127,114 @@ struct Argument {
107127
#[derive(Debug, Deserialize)]
108128
#[serde(rename_all = "camelCase")]
109129
enum ArgumentType {
110-
String,
111-
Object,
112-
SearchScore,
113-
SearchPath,
114-
SearchOperator,
130+
Any,
115131
Array,
132+
BinData,
133+
Bool,
134+
Date,
135+
Geometry,
116136
Int,
137+
Null,
138+
Number,
139+
Object,
140+
ObjectId,
141+
SearchOperator,
142+
SearchPath,
143+
SearchScore,
144+
String,
117145
}
118146

119-
static QUERY: &str = "query";
120-
static TOKEN_ORDER: &str = "tokenOrder";
121-
static MATCH_CRITERIA: &str = "matchCriteria";
122-
123147
impl Argument {
124-
fn rust_type(&self) -> ArgumentRustType {
125-
if self.name == QUERY {
126-
return ArgumentRustType::StringOrArray;
127-
}
128-
if self.name == TOKEN_ORDER {
129-
return ArgumentRustType::TokenOrder;
130-
}
131-
if self.name == MATCH_CRITERIA {
132-
return ArgumentRustType::MatchCriteria;
148+
fn rust_type(&self, operator: &str) -> ArgumentRustType {
149+
match (operator, self.name.as_str()) {
150+
("autocomplete" | "text", "query") => return ArgumentRustType::StringOrArray,
151+
("autocomplete", "tokenOrder") => return ArgumentRustType::TokenOrder,
152+
("text", "matchCriteria") => return ArgumentRustType::MatchCriteria,
153+
("equals", "value") => return ArgumentRustType::IntoBson,
154+
("geoShape", "relation") => return ArgumentRustType::Relation,
155+
("range", "gt" | "gte" | "lt" | "lte") => return ArgumentRustType::RangeValue,
156+
("near", "origin") => return ArgumentRustType::NearOrigin,
157+
_ => (),
133158
}
159+
use ArgumentType::*;
134160
match self.type_.as_slice() {
135-
[ArgumentType::String] => ArgumentRustType::String,
136-
[ArgumentType::Object] => ArgumentRustType::Document,
137-
[ArgumentType::SearchScore] => ArgumentRustType::Document,
138-
[ArgumentType::SearchPath] => ArgumentRustType::StringOrArray,
139-
[ArgumentType::SearchOperator, ArgumentType::Array] => ArgumentRustType::Operator,
140-
[ArgumentType::Int] => ArgumentRustType::I32,
161+
[String] => ArgumentRustType::String,
162+
[Object] => ArgumentRustType::Document,
163+
[SearchScore] => ArgumentRustType::Document,
164+
[SearchPath] => ArgumentRustType::StringOrArray,
165+
[SearchOperator] => ArgumentRustType::SearchOperator,
166+
[SearchOperator, Array] => ArgumentRustType::SeachOperatorIter,
167+
[Int] => ArgumentRustType::I32,
168+
[Geometry] => ArgumentRustType::Document,
169+
[Any, Array] => ArgumentRustType::IntoBson,
170+
[Object, Array] => ArgumentRustType::DocumentOrArray,
171+
[Number] => ArgumentRustType::BsonNumber,
172+
[String, Array] => ArgumentRustType::StringOrArray,
173+
[Bool] => ArgumentRustType::Bool,
141174
_ => panic!("Unexpected argument types: {:?}", self.type_),
142175
}
143176
}
144177
}
145178

146179
enum ArgumentRustType {
147-
String,
180+
Bool,
181+
BsonNumber,
148182
Document,
183+
DocumentOrArray,
184+
I32,
185+
IntoBson,
186+
MatchCriteria,
187+
NearOrigin,
188+
RangeValue,
189+
Relation,
190+
SearchOperator,
191+
SeachOperatorIter,
192+
String,
149193
StringOrArray,
150194
TokenOrder,
151-
MatchCriteria,
152-
Operator,
153-
I32,
154195
}
155196

156197
impl ArgumentRustType {
157198
fn tokens(&self) -> syn::Type {
158199
match self {
159-
Self::String => parse_quote! { impl AsRef<str> },
200+
Self::Bool => parse_quote! { bool },
201+
Self::BsonNumber => parse_quote! { impl BsonNumber },
160202
Self::Document => parse_quote! { Document },
203+
Self::DocumentOrArray => parse_quote! { impl DocumentOrArray },
204+
Self::I32 => parse_quote! { i32 },
205+
Self::IntoBson => parse_quote! { impl Into<Bson> },
206+
Self::MatchCriteria => parse_quote! { MatchCriteria },
207+
Self::NearOrigin => parse_quote! { impl NearOrigin },
208+
Self::RangeValue => parse_quote! { impl RangeValue },
209+
Self::Relation => parse_quote! { Relation },
210+
Self::SearchOperator => parse_quote! { impl SearchOperatorParam },
211+
Self::SeachOperatorIter => {
212+
parse_quote! { impl IntoIterator<Item = impl SearchOperatorParam> }
213+
}
214+
Self::String => parse_quote! { impl AsRef<str> },
161215
Self::StringOrArray => parse_quote! { impl StringOrArray },
162216
Self::TokenOrder => parse_quote! { TokenOrder },
163-
Self::MatchCriteria => parse_quote! { MatchCriteria },
164-
Self::Operator => parse_quote! { impl IntoIterator<Item = AtlasSearch<T>> },
165-
Self::I32 => parse_quote! { i32 },
166-
}
167-
}
168-
169-
fn variables(&self) -> TokenStream {
170-
match self {
171-
Self::Operator => parse_quote! { T },
172-
_ => parse_quote! {},
173217
}
174218
}
175219

176220
fn bson_expr(&self, ident: &syn::Ident) -> syn::Expr {
177221
match self {
222+
Self::Document | Self::I32 | Self::Bool => parse_quote! { #ident },
223+
Self::IntoBson => parse_quote! { #ident.into() },
224+
Self::SeachOperatorIter => {
225+
parse_quote! { #ident.into_iter().map(|o| o.to_bson()).collect::<Vec<_>>() }
226+
}
178227
Self::String => parse_quote! { #ident.as_ref() },
179-
Self::StringOrArray => parse_quote! { #ident.to_bson() },
180-
Self::TokenOrder | Self::MatchCriteria => parse_quote! { #ident.name() },
181-
Self::Document | Self::I32 => parse_quote! { #ident },
182-
Self::Operator => {
183-
parse_quote! { #ident.into_iter().map(Document::from).collect::<Vec<_>>() }
228+
Self::StringOrArray
229+
| Self::DocumentOrArray
230+
| Self::SearchOperator
231+
| Self::NearOrigin
232+
| Self::RangeValue
233+
| Self::BsonNumber => {
234+
parse_quote! { #ident.to_bson() }
235+
}
236+
Self::TokenOrder | Self::MatchCriteria | Self::Relation => {
237+
parse_quote! { #ident.name() }
184238
}
185239
}
186240
}
@@ -200,11 +254,14 @@ impl TokenStreamExt for TokenStream {
200254

201255
fn main() {
202256
let mut operators = TokenStream::new();
203-
for path in [
204-
"yaml/search/autocomplete.yaml",
205-
"yaml/search/text.yaml",
206-
"yaml/search/compound.yaml",
207-
] {
257+
let mut paths = Path::new("yaml/search")
258+
.read_dir()
259+
.unwrap()
260+
.map(|e| e.unwrap().path())
261+
.filter(|p| p.extension().is_some_and(|e| e == "yaml"))
262+
.collect::<Vec<_>>();
263+
paths.sort();
264+
for path in paths {
208265
let contents = std::fs::read_to_string(path).unwrap();
209266
let parsed = serde_yaml::from_str::<Operator>(&contents)
210267
.unwrap()
@@ -215,6 +272,7 @@ fn main() {
215272
let file = parse_quote! {
216273
//! This file was autogenerated. Do not manually edit.
217274
use super::*;
275+
use mongodb_internal_macros::{export_doc, options_doc};
218276

219277
#operators
220278
};

macros/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target/
2+
Cargo.lock

macros/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ pub fn deeplink(
3030
crate::rustdoc::deeplink(attr, item)
3131
}
3232

33+
/// Generate setters for the given options struct.
34+
/// Arguments:
35+
/// * the fully-qualified path of the struct type
3336
#[import_tokens_attr]
3437
#[with_custom_parsing(crate::option::OptionSettersArgs)]
3538
#[proc_macro_attribute]
@@ -40,6 +43,10 @@ pub fn option_setters(
4043
crate::option::option_setters(attr, item, __custom_tokens)
4144
}
4245

46+
/// Export the setters in this `impl` block so they can be used in `options_doc`.
47+
/// Arguments:
48+
/// * an identifier for the exported list
49+
/// * an optional `extra = [fn_name[,..]]` list of additional setters to include
4350
#[proc_macro_attribute]
4451
pub fn export_doc(
4552
attr: proc_macro::TokenStream,
@@ -48,6 +55,10 @@ pub fn export_doc(
4855
crate::rustdoc::export_doc(attr, item)
4956
}
5057

58+
/// Include options documentation generated by `export_doc` in the rustdoc for this method:
59+
/// Arguments:
60+
/// * the doc identifier given to `export_doc`
61+
/// * an optional `sync` keyword that alters the documentation to be appropriate for a sync action
5162
#[import_tokens_attr]
5263
#[with_custom_parsing(crate::rustdoc::OptionsDocArgs)]
5364
#[proc_macro_attribute]

macros/src/rustdoc.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ pub(crate) fn options_doc(
153153
});
154154
let preamble = format!(
155155
"These methods can be chained before `{}` to set options:",
156-
if args.is_async() { ".await" } else { "run" }
156+
args.term
157+
.as_ref()
158+
.map(|(_, b)| b.as_str())
159+
.unwrap_or(".await")
157160
);
158161
impl_fn.attrs.push(parse_quote! {
159162
#[doc = #preamble]
@@ -169,34 +172,29 @@ pub(crate) fn options_doc(
169172

170173
pub(crate) struct OptionsDocArgs {
171174
foreign_path: syn::Path,
172-
sync: Option<(Token![,], Ident)>,
173-
}
174-
175-
impl OptionsDocArgs {
176-
fn is_async(&self) -> bool {
177-
self.sync.is_none()
178-
}
175+
term: Option<(Token![,], String)>,
179176
}
180177

181178
impl Parse for OptionsDocArgs {
182179
fn parse(input: ParseStream) -> syn::Result<Self> {
183180
let foreign_path = input.parse()?;
184-
let sync = if input.is_empty() {
181+
let term = if input.is_empty() {
185182
None
186183
} else {
187-
Some((input.parse()?, parse_name(input, "sync")?))
184+
let (comma, lit) = (input.parse()?, input.parse::<syn::LitStr>()?);
185+
Some((comma, lit.value()))
188186
};
189187

190-
Ok(Self { foreign_path, sync })
188+
Ok(Self { foreign_path, term })
191189
}
192190
}
193191

194192
impl ToTokens for OptionsDocArgs {
195193
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
196194
tokens.extend(self.foreign_path.to_token_stream());
197-
if let Some((comma, ident)) = &self.sync {
195+
if let Some((comma, lit)) = &self.term {
198196
tokens.extend(comma.to_token_stream());
199-
tokens.extend(ident.to_token_stream());
197+
tokens.extend(lit.to_token_stream());
200198
}
201199
}
202200
}

0 commit comments

Comments
 (0)