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}