Skip to content

Commit 2cb188f

Browse files
committed
feat(derive): Implement advanced nested and flattened field handling
This commit introduces a comprehensive and robust implementation for handling complex document structures within the `#[derive(Typesense)]` macro, enabling powerful schema generation directly from Rust structs. The macro now supports the full range of advanced indexing strategies offered by Typesense, including automatic object indexing, field flattening with prefix control, and patterns for manual flattening. ### Key Features & Implementation Details - **Automatic Object Indexing:** - A field containing a nested struct that also derives `Document` is now automatically mapped to a Typesense `object` (or `object[]` for `Vec<T>`). - This feature requires `#[typesense(enable_nested_fields = true)]` on the parent collection, which the macro now supports. - **Automatic Field Flattening with `#[typesense(flatten)]`:** - A field marked `#[typesense(flatten)]` has its sub-fields expanded into the parent schema using dot-notation. - By default, the Rust field's name is used as the prefix for all sub-fields (e.g., `details: ProductDetails` results in schema fields like `details.part_number`). - **Prefix Override for Flattening:** - The `flatten` attribute can be combined with `rename` to provide a custom prefix for the flattened fields. - Usage: `#[typesense(flatten, rename = "custom_prefix")]` - This provides powerful schema mapping flexibility, allowing the Rust struct's field name to differ from the prefix used in the Typesense schema. - **Manual Flattening Pattern (`skip` + `rename`):** - A new `#[typesense(skip)]` attribute has been introduced to completely exclude a field from the generated Typesense schema. - This enables the powerful pattern of sending both nested and flattened data to Typesense: the nested version can be used for display/deserialization, while a separate set of flattened fields is used for indexing. This is achieved by: 1. Marking the nested struct field (e.g., `details: Details`) with `#[typesense(skip)]`. 2. Adding corresponding top-level fields to the Rust struct, marked with `#[typesense(rename = "details.field_name")]`. - **Ergonomic Boolean Attributes:** - All boolean attributes (`facet`, `sort`, `index`, `store`, `infix`, `stem`, `optional`, `range_index`) now support shorthand "flag" syntax. - For example, `#[typesense(sort)]` is a valid and recommended equivalent to `#[typesense(sort = true)]`, dramatically improving readability and consistency. - **Robust Error Handling & Validation:** - The macro provides clear, compile-time errors for invalid or ambiguous attribute usage. - It correctly detects and reports duplicate attributes, whether they are in the same `#[typesense(...)]` block or across multiple attributes on the same field. ### Testing - **Comprehensive Integration Test (`derive_integration.rs`):** - A new, full-lifecycle integration test has been added to validate the entire feature set. - The test defines a complex struct using every new attribute and pattern, generates a schema, creates a real collection, and uses the generic client (`collection_of<T>`) to perform and validate a full Create, Read, Update, Delete, and Search lifecycle. - A second integration test was added to specifically validate the manual flattening pattern. - **UI Tests:** - `trybuild` UI tests have been added to verify that the macro produces the correct compile-time errors for invalid attribute combinations, such as duplicate attributes.
1 parent 5a84fb3 commit 2cb188f

File tree

11 files changed

+1413
-172
lines changed

11 files changed

+1413
-172
lines changed

typesense/src/field/field_type.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use crate::document::Document;
12
use std::collections::{BTreeMap, HashMap};
2-
33
/// Type for a field. Currently it is a wrapping to a `String` but it could be extended to a enum
44
pub type FieldType = String;
55

@@ -10,6 +10,19 @@ pub trait ToTypesenseField {
1010
fn to_typesense_type() -> &'static str;
1111
}
1212

13+
/// Generic implementation for any type that is also a Typesense document.
14+
impl<T: Document> ToTypesenseField for T {
15+
fn to_typesense_type() -> &'static str {
16+
"object"
17+
}
18+
}
19+
20+
/// Generic implementation for a Vec of any type that is also a Typesense document.
21+
impl<T: Document> ToTypesenseField for Vec<T> {
22+
fn to_typesense_type() -> &'static str {
23+
"object[]"
24+
}
25+
}
1326
/// macro used internally to add implementations of ToTypesenseField for several rust types.
1427
#[macro_export]
1528
macro_rules! impl_to_typesense_field (

typesense/src/field/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ pub struct FieldBuilder {
2020
num_dim: Option<i32>,
2121
drop: Option<bool>,
2222
embed: Option<Box<FieldEmbed>>,
23+
store: Option<bool>,
24+
stem: Option<bool>,
25+
range_index: Option<bool>,
26+
vec_dist: Option<String>,
2327
}
2428

2529
impl FieldBuilder {
@@ -89,6 +93,34 @@ impl FieldBuilder {
8993
self
9094
}
9195

96+
/// Set store attribute for field
97+
#[inline]
98+
pub fn store(mut self, store: Option<bool>) -> Self {
99+
self.store = store;
100+
self
101+
}
102+
103+
/// Set stem attribute for field
104+
#[inline]
105+
pub fn stem(mut self, stem: Option<bool>) -> Self {
106+
self.stem = stem;
107+
self
108+
}
109+
110+
/// Set range_index attribute for field
111+
#[inline]
112+
pub fn range_index(mut self, range_index: Option<bool>) -> Self {
113+
self.range_index = range_index;
114+
self
115+
}
116+
117+
/// Set vec_dist attribute for field
118+
#[inline]
119+
pub fn vec_dist(mut self, vec_dist: Option<String>) -> Self {
120+
self.vec_dist = vec_dist;
121+
self
122+
}
123+
92124
/// Create a `Field` with the current values of the builder,
93125
/// It can fail if the name or the typesense_type are not defined.
94126
#[inline]
@@ -105,6 +137,10 @@ impl FieldBuilder {
105137
num_dim: self.num_dim,
106138
drop: self.drop,
107139
embed: self.embed,
140+
store: self.store,
141+
stem: self.stem,
142+
range_index: self.range_index,
143+
vec_dist: self.vec_dist,
108144
..Default::default()
109145
}
110146
}

0 commit comments

Comments
 (0)