Skip to main content

tantivy_derive/
lib.rs

1mod options;
2
3use std::net::Ipv6Addr;
4use tantivy::schema::*;
5pub use tantivy_derive_impl::{Document, tantivy_document};
6
7pub use crate::options::FieldOptions;
8
9pub trait Field: Sized {
10    type Target;
11
12    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions);
13    fn count_fields() -> u32 {
14        1
15    }
16    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self);
17}
18
19pub trait Mappable: Field {
20    fn map_value(value: &OwnedValue) -> Option<Self::Target>;
21}
22
23pub trait Extractable: Field {
24    fn extract_from_document(document: &TantivyDocument, field_id: u32) -> Option<Self::Target>;
25}
26
27pub trait Schema {
28    fn schema() -> tantivy::schema::Schema;
29}
30
31impl<T> Extractable for T
32where
33    T: Mappable,
34{
35    fn extract_from_document(document: &TantivyDocument, field_id: u32) -> Option<Self::Target> {
36        let field = tantivy::schema::Field::from_field_id(field_id);
37
38        document
39            .get_first(field)
40            .and_then(|v| Self::map_value(&v.into()))
41    }
42}
43
44impl Field for bool {
45    type Target = Self;
46
47    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
48        let options: NumericOptions = options.into();
49        builder.add_bool_field(name, options);
50    }
51
52    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
53        let field = tantivy::schema::Field::from_field_id(field_id);
54
55        document.add_bool(field, *value);
56    }
57}
58
59impl Mappable for bool {
60    fn map_value(value: &OwnedValue) -> Option<Self::Target> {
61        value.as_bool()
62    }
63}
64
65impl Field for u64 {
66    type Target = Self;
67
68    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
69        let options: NumericOptions = options.into();
70        builder.add_u64_field(name, options);
71    }
72
73    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
74        let field = tantivy::schema::Field::from_field_id(field_id);
75
76        document.add_u64(field, *value);
77    }
78}
79
80impl Mappable for u64 {
81    fn map_value(value: &OwnedValue) -> Option<Self::Target> {
82        value.as_u64()
83    }
84}
85
86impl Field for i64 {
87    type Target = Self;
88
89    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
90        let options: NumericOptions = options.into();
91        builder.add_u64_field(name, options);
92    }
93
94    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
95        let field = tantivy::schema::Field::from_field_id(field_id);
96
97        document.add_i64(field, *value);
98    }
99}
100
101impl Mappable for i64 {
102    fn map_value(value: &OwnedValue) -> Option<Self::Target> {
103        value.as_i64()
104    }
105}
106
107impl Field for f64 {
108    type Target = Self;
109
110    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
111        let options: NumericOptions = options.into();
112        builder.add_f64_field(name, options);
113    }
114
115    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
116        let field = tantivy::schema::Field::from_field_id(field_id);
117
118        document.add_f64(field, *value);
119    }
120}
121
122impl Mappable for f64 {
123    fn map_value(value: &OwnedValue) -> Option<Self::Target> {
124        value.as_f64()
125    }
126}
127
128impl Field for String {
129    type Target = Self;
130
131    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
132        let options: TextOptions = options.into();
133        builder.add_text_field(name, options);
134    }
135
136    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
137        let field = tantivy::schema::Field::from_field_id(field_id);
138
139        document.add_text(field, value.as_str());
140    }
141}
142
143impl Mappable for String {
144    fn map_value(value: &OwnedValue) -> Option<Self::Target> {
145        value.as_str().map(|s| s.to_string())
146    }
147}
148
149impl Field for Facet {
150    type Target = String;
151
152    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
153        let options: FacetOptions = options.into();
154        builder.add_facet_field(name, options);
155    }
156
157    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
158        let field = tantivy::schema::Field::from_field_id(field_id);
159
160        document.add_facet(field, value.clone());
161    }
162}
163
164impl Mappable for Facet {
165    fn map_value(value: &OwnedValue) -> Option<Self::Target> {
166        Some(value.as_facet()?.to_string())
167    }
168}
169
170impl Field for Ipv6Addr {
171    type Target = Self;
172
173    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
174        let options: IpAddrOptions = options.into();
175        builder.add_ip_addr_field(name, options);
176    }
177
178    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
179        let field = tantivy::schema::Field::from_field_id(field_id);
180
181        document.add_ip_addr(field, *value);
182    }
183}
184
185impl Mappable for Ipv6Addr {
186    fn map_value(value: &OwnedValue) -> Option<Self::Target> {
187        value.as_ip_addr()
188    }
189}
190
191impl<T: Mappable> Field for Option<T> {
192    type Target = Self;
193
194    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
195        T::add_field(builder, name, options);
196    }
197
198    fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
199        if let Some(value) = value {
200            T::insert_into_document(document, field_id, value);
201        }
202    }
203}
204
205impl<T: Mappable<Target = T>> Extractable for Option<T> {
206    fn extract_from_document(document: &TantivyDocument, field_id: u32) -> Option<Self::Target> {
207        let field = tantivy::schema::Field::from_field_id(field_id);
208
209        Some(
210            document
211                .get_first(field)
212                .and_then(|v| T::map_value(&v.into())),
213        )
214    }
215}
216
217impl<T: Mappable> Field for Vec<T>
218where
219    std::vec::Vec<T>: FromIterator<<T as Field>::Target>,
220{
221    type Target = Self;
222
223    fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
224        T::add_field(builder, name, options);
225    }
226
227    fn insert_into_document(document: &mut TantivyDocument, mut field_id: u32, value: &Self) {
228        for value in value {
229            T::insert_into_document(document, field_id, value);
230            field_id += 1;
231        }
232    }
233}
234
235impl<T: Mappable> Extractable for Vec<T>
236where
237    std::vec::Vec<T>: FromIterator<<T as Field>::Target>,
238{
239    fn extract_from_document(document: &TantivyDocument, field_id: u32) -> Option<Self::Target> {
240        let field = tantivy::schema::Field::from_field_id(field_id);
241
242        document
243            .get_all(field)
244            .map(|v| T::map_value(&v.into()))
245            .collect()
246    }
247}
248
249#[cfg(feature = "bytes")]
250mod bytes {
251    use crate::{Field, FieldOptions, Mappable};
252    use bytes::Bytes;
253    use tantivy::schema::*;
254
255    impl Field for Bytes {
256        type Target = Self;
257
258        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
259            let options: BytesOptions = options.into();
260            builder.add_bytes_field(name, options);
261        }
262
263        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
264            let field = tantivy::schema::Field::from_field_id(field_id);
265
266            document.add_bytes(field, &value[..]);
267        }
268    }
269
270    impl Mappable for Bytes {
271        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
272            value.as_bytes().map(|bytes| Bytes::from(bytes.to_vec()))
273        }
274    }
275}
276
277#[cfg(feature = "chrono")]
278mod chrono {
279    use crate::{Field, FieldOptions, Mappable};
280    use chrono::{DateTime, NaiveDate, Utc};
281    use tantivy::schema::*;
282
283    impl Field for DateTime<Utc> {
284        type Target = Self;
285
286        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
287            let options: DateOptions = options.into();
288            builder.add_date_field(name, options);
289        }
290
291        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
292            let field = tantivy::schema::Field::from_field_id(field_id);
293
294            let nanos = value.timestamp_nanos_opt().unwrap_or(0);
295            let value = tantivy::DateTime::from_timestamp_nanos(nanos);
296            document.add_date(field, value);
297        }
298    }
299
300    impl Mappable for DateTime<Utc> {
301        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
302            value
303                .as_datetime()
304                .map(|v| DateTime::from_timestamp_nanos(v.into_timestamp_nanos()))
305        }
306    }
307
308    impl Field for NaiveDate {
309        type Target = Self;
310
311        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
312            let options: DateOptions = options.into();
313            builder.add_date_field(name, options);
314        }
315
316        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
317            let field = tantivy::schema::Field::from_field_id(field_id);
318
319            let value = value.and_hms_opt(0, 0, 0).unwrap().and_utc();
320            let nanos = value.timestamp_nanos_opt().unwrap_or(0);
321            let value = tantivy::DateTime::from_timestamp_nanos(nanos);
322            document.add_date(field, value);
323        }
324    }
325
326    impl Mappable for NaiveDate {
327        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
328            value
329                .as_datetime()
330                .map(|v| DateTime::from_timestamp_nanos(v.into_timestamp_nanos()).date_naive())
331        }
332    }
333}
334
335#[cfg(feature = "decimal")]
336mod decimal {
337    use crate::{Field, FieldOptions, Mappable};
338    use rust_decimal::Decimal;
339    use tantivy::schema::*;
340
341    impl Field for Decimal {
342        type Target = Self;
343
344        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
345            let options: BytesOptions = options.into();
346            builder.add_bytes_field(name, options);
347        }
348
349        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
350            let field = tantivy::schema::Field::from_field_id(field_id);
351            let slice = Decimal::serialize(value);
352
353            document.add_bytes(field, &slice);
354        }
355    }
356
357    impl Mappable for Decimal {
358        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
359            value.as_bytes().and_then(|bytes| {
360                if bytes.len() != 16 {
361                    return None;
362                }
363
364                let mut slice = [0u8; 16];
365                slice.copy_from_slice(&bytes[..16]);
366
367                Some(Decimal::deserialize(slice))
368            })
369        }
370    }
371
372    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
373    #[cfg_attr(
374        feature = "serde",
375        derive(serde::Deserialize, serde::Serialize),
376        serde(transparent)
377    )]
378    pub struct FixedDecimal<const N: u32>(pub Decimal);
379
380    impl<const N: u32> Field for FixedDecimal<N> {
381        type Target = Self;
382
383        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
384            let options: NumericOptions = options.into();
385            builder.add_i64_field(name, options);
386        }
387
388        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
389            let field = tantivy::schema::Field::from_field_id(field_id);
390            let mut value = value.0;
391            value.rescale(N);
392            let value = value.mantissa() as i64;
393
394            document.add_i64(field, value);
395        }
396    }
397
398    impl<const N: u32> Mappable for FixedDecimal<N> {
399        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
400            value
401                .as_i64()
402                .map(|value| FixedDecimal(Decimal::from_i128_with_scale(value as i128, N)))
403        }
404    }
405}
406
407#[cfg(feature = "decimal")]
408pub use decimal::FixedDecimal;
409
410#[cfg(feature = "jiff")]
411mod jiff {
412    use crate::{Field, FieldOptions, Mappable};
413    use jiff::Timestamp;
414    use tantivy::schema::*;
415
416    impl Field for Timestamp {
417        type Target = Self;
418
419        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
420            let options: DateOptions = options.into();
421            builder.add_date_field(name, options);
422        }
423
424        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
425            let field = tantivy::schema::Field::from_field_id(field_id);
426
427            let nanos = value.as_nanosecond() as i64;
428            let value = tantivy::DateTime::from_timestamp_nanos(nanos);
429            document.add_date(field, value);
430        }
431    }
432
433    impl Mappable for Timestamp {
434        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
435            value
436                .as_datetime()
437                .and_then(|v| Timestamp::from_nanosecond(v.into_timestamp_nanos() as i128).ok())
438        }
439    }
440}
441
442#[cfg(feature = "url")]
443mod url {
444    use crate::{Field, FieldOptions, Mappable};
445    use std::str::FromStr as _;
446    use tantivy::schema::*;
447    use url::Url;
448
449    impl Field for Url {
450        type Target = Self;
451
452        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
453            let options: TextOptions = options.into();
454            builder.add_text_field(name, options);
455        }
456
457        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
458            let field = tantivy::schema::Field::from_field_id(field_id);
459
460            let value = value.to_string();
461            document.add_text(field, value);
462        }
463    }
464
465    impl Mappable for Url {
466        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
467            value.as_str().and_then(|v| Url::from_str(v).ok())
468        }
469    }
470}
471
472#[cfg(feature = "uuid")]
473mod uuid {
474    use crate::{Field, FieldOptions, Mappable};
475    use tantivy::schema::*;
476    use uuid::Uuid;
477
478    impl Field for Uuid {
479        type Target = Uuid;
480
481        fn add_field(builder: &mut SchemaBuilder, name: &str, options: FieldOptions) {
482            let options: TextOptions = options.into();
483            builder.add_text_field(name, options);
484        }
485
486        fn insert_into_document(document: &mut TantivyDocument, field_id: u32, value: &Self) {
487            let field = tantivy::schema::Field::from_field_id(field_id);
488
489            let value = value.to_string();
490            document.add_text(field, value);
491        }
492    }
493
494    impl Mappable for Uuid {
495        fn map_value(value: &OwnedValue) -> Option<Self::Target> {
496            value.as_str().and_then(|v| Uuid::parse_str(v).ok())
497        }
498    }
499}