mapstic 0.1.0

Tooling to generate Elasticsearch index mappings from type definitions
Documentation
//! Mapstic allows [explicit Elasticsearch mappings][explicit] to be derived from Rust type
//! definitions.
//!
//! <div class="warning">
//! Note that it is the responsibility of the user to ensure that documents generated by the Rust
//! type and indexed by Elasticsearch have the same data types and shape as the mapping generated
//! by deriving <code>ToMapping</code>. In most normal cases where the types derive
//! <code>Serialize</code> or implement <code>Serialize</code> in an idiomatic way, the mapping and
//! data should match, but special care should be taken when using explicit mapping types with the
//! <code>#[mapstic(mapping_type = ...)]</code> attribute.
//! </div>
//!
//! ## Deriving mappings
//!
//! Mapstic can derive many common Rust types automatically:
//!
//! ```
//! use mapstic::ToMapping;
//!
//! #[derive(ToMapping)]
//! struct MyIndex {
//!     id: u64,
//!     score: f64,
//!     time: std::time::SystemTime,
//!     tags: Vec<Tag>,
//! }
//!
//! #[derive(ToMapping)]
//! struct Tag(String);
//!
//! # // We can't use insta here because it always prepends a header that we don't want below.
//! # let expected: serde_json::Value = serde_json::from_str(include_str!("lib.expected.json")).unwrap();
//! # let have = serde_json::to_value(&MyIndex::to_mapping()).unwrap();
//! # assert_eq!(expected, have);
//! ```
//!
//! This will generate a mapping that looks like this when serialised to JSON:
//!
//! ```json
#![doc = include_str!("lib.expected.json")]
//! ```
//!
//! It is also possible to control the type and parameters derived for each type and/or field: see
//! the documentation for [the `ToMapping` derive macro][derive@ToMapping] for more detail.
//!
//! ## Implicitly derived types
//!
//! The following [Elasticsearch types][types] are derived by default:
//!
//! | Elasticsearch type | Rust types | Notes |
//! |---|---|---|
//! | `binary` | [`[u8]`][u8], [`&CStr`][std::ffi::CStr], [`CString`][std::ffi::CString] |
//! | `boolean` | [`bool`] |
//! | `long` | [`i64`], [`NonZeroI64`][std::num::NonZeroI64], [`AtomicI64`][std::sync::atomic::AtomicI64] |
//! | `integer` | [`i32`], [`NonZeroI32`][std::num::NonZeroI32], [`AtomicI32`][std::sync::atomic::AtomicI32] |
//! | `short` | [`i16`], [`NonZeroI16`][std::num::NonZeroI16], [`AtomicI16`][std::sync::atomic::AtomicI16] |
//! | `byte` | [`i8`], [`NonZeroI8`][std::num::NonZeroI8], [`AtomicI8`][std::sync::atomic::AtomicI8] |
//! | `unsigned_long` | [`u64`], [`u32`], [`u16`], [`u8`] | (and their atomic and non-zero variants) |
//! | `double` | [`f64`] |
//! | `float` | [`f32`] |
//! | `half_float` | [`f16`] | (only on nightly, and with the `nightly` feature enabled) |
//! | `date` | [`SystemTime`][std::time::SystemTime] |
//! | `date` | [`chrono::DateTime`][chrono-datetime], [`chrono::Date`][chrono-date], [`chrono::NaiveDateTime`][chrono-naivedatetime], [`chrono::NaiveDate`][chrono-naivedate] | (with the `chrono` feature enabled) |
//! | `ip` | [`IpAddr`][std::net::IpAddr], [`Ipv4Addr`][std::net::Ipv4Addr], [`Ipv6Addr`][std::net::Ipv6Addr] |
//! | `text` | [`&str`][`str`], [`String`], [`Cow<str>`][std::borrow::Cow] | (the derived mapping will match the default behaviour when Elasticsearch uses [dynamic field mapping][dynamic]: specifically, by also specifying a `.keyword` sub-field with type `keyword`) |
//!
//! ### Options and collections and containers, oh my
//!
//! A number of container types will be automatically derived if their inner types (indicated as
//! `T` below) implement [`ToMapping`]:
//!
//! * [`Option<T>`]
//! * [`BTreeSet<T>`][std::collections::BTreeSet]
//! * [`Box<T>`]
//! * [`Cell<T>`][std::cell::Cell]
//! * [`Cow<T>`][std::borrow::Cow] (where `T` also implements [`Clone`])
//! * [`HashSet<T>`][std::collections::HashSet]
//! * [`LinkedList<T>`][std::collections::LinkedList]
//! * [`Mutex<T>`][std::sync::Mutex]
//! * [`RefCell<T>`][std::cell::RefCell]
//! * [`Reverse<T>`][std::cmp::Reverse]
//! * [`RwLock<T>`][std::sync::RwLock]
//! * [`Vec<T>`][Vec]
//! * [`VecDeque<T>`][std::collections::VecDeque]
//!
//! If the `rc` feature is enabled, the following types will also be automatically derived:
//!
//! * [`Arc<T>`][std::sync::Arc]
//! * [`Rc<T>`][std::rc::Rc]
//! * [`std::rc::Weak<T>`]
//! * [`std::sync::Weak<T>`]
//!
//! ## Usage with `elasticsearch`
//!
//! The [`Mapping`] type can be used directly as a body with the [`elasticsearch`
//! crate](https://crates.io/crates/elasticsearch). See [the `Mapping` documentation][Mapping] for
//! an example.
//!
//! ## Optional features
//!
//! * `chrono`: enables support for [the `chrono` crate](https://crates.io/crates/chrono)
//! * `nightly`: enables support for additional `std` types in nightly Rust
//! * `rc`: enables support for a handful of ref-counted types, much like Serde's `rc` feature
//!
//! [chrono-datetime]: https://docs.rs/chrono/latest/chrono/struct.DateTime.html
//! [chrono-date]: https://docs.rs/chrono/latest/chrono/struct.Date.html
//! [chrono-naivedatetime]: https://docs.rs/chrono/latest/chrono/struct.NaiveDateTime.html
//! [chrono-naivedate]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html
//! [dynamic]: https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html
//! [explicit]: https://www.elastic.co/guide/en/elasticsearch/reference/current/explicit-mapping.html
//! [types]: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html

pub use mapstic_core::{Mapping, ToMapping};

/// Derives [`ToMapping`] for the given type.
///
/// Mappings can be derived automatically for `struct`s with named fields, and tuple `struct`s with
/// one field. Other `struct` and `enum` types can only be derived if they specify the
/// `#[mapstic(mapping_type)]` container attribute.
///
/// ## Container attributes
///
/// The `#[mapstic(...)]` attribute can be optionally specified to control the mapping that is derived.
/// The following attributes are supported within this attribute:
///
/// ### `mapping_type`
///
/// Specifying `mapping_type` disables all type inference for the container, and will result in the
/// container being treated as a single [Elasticsearch mapping type][types], regardless of the
/// fields it contains.
///
/// For example, a `struct` that wraps a [`HashMap`][std::collections::HashMap] may want to
/// explicitly set its Elasticsearch mapping to [`flattened`][flattened] to prevent a [mappings
/// explosion][boom]:
///
/// ```
/// # use mapstic::ToMapping;
/// # use std::collections::HashMap;
/// # struct Value;
/// #[derive(ToMapping)]
/// #[mapstic(mapping_type = "flattened")]
/// struct KeyValueStore(HashMap<String, Value>);
///
/// # insta::assert_yaml_snapshot!(
/// #   "derive-container-mapping-flattened",
/// #   KeyValueStore::to_mapping(),
/// # );
/// ```
///
/// Or a string that is known to be a [`keyword`][keyword]:
///
/// ```
/// # use mapstic::ToMapping;
/// #[derive(ToMapping)]
/// #[mapstic(mapping_type = "keyword")]
/// struct Keyword(String);
///
/// # insta::assert_yaml_snapshot!("derive-container-mapping-keyword", Keyword::to_mapping());
/// ```
///
/// ### `params`
///
/// [Mapping parameters][params] can be specified using the `params` attribute.
///
/// For example, to change the analyzer used for a [text][text] field:
///
/// ```
/// # use mapstic::ToMapping;
/// #[derive(ToMapping)]
/// #[mapstic(params(analyzer = "whitespace"))]
/// struct Keyword(String);
///
/// # insta::assert_yaml_snapshot!("derive-container-param-analyzer", Keyword::to_mapping());
/// ```
///
/// Parameters can also be nested to allow for the specification of more complicated parameters,
/// such as [`multi-fields`][fields]:
///
/// ```
/// # use mapstic::ToMapping;
/// #[derive(ToMapping)]
/// // Note the use of r#type here: since type is a Rust keyword, we have to use the raw
/// // identifier.
/// #[mapstic(params(fields(completion(r#type="completion"), keyword(r#type = "keyword"))))]
/// struct Keyword(String);
///
/// # insta::assert_yaml_snapshot!("derive-container-param-keyword", Keyword::to_mapping());
/// ```
///
/// ## Field attributes
///
/// Each field within a `struct` also supports the aforementioned container attributes.
///
/// ### `mapping_type`
///
/// As above, any use of `mapping_type` disables further type inference for the field. It does not
/// matter if the field type implements [`ToMapping`].
///
/// ### `params`
///
/// The semantics of `params` are identical to parameters specified on the container.
///
/// In the event that both a field attribute and a container attribute specify `params`, they will
/// be merged together, with the lowest level parameter value winning.
///
/// ### `skip`
///
/// To ignore a field completely, you can apply the skip attribute. This is most useful when a
/// type contains a field that cannot be mapped, but also isn't indexed in Elasticsearch:
///
/// ```
/// # use mapstic::ToMapping;
/// #[derive(ToMapping)]
/// struct MyIndex {
///     id: i32,
///     author: String,
///
///     #[mapstic(skip)]
///     runtime_cache: Cache,
/// }
///
/// struct Cache {
///     // various fields that cannot derive a mapping, eg:
///     key: u128,
/// }
///
/// # insta::assert_yaml_snapshot!("derive-field-skip", MyIndex::to_mapping());
/// ```
///
/// ## Generic types
///
/// Unlike Serde, Mapstic does not attempt to automatically add bounds to generic types. In
/// practice, this means that any generic type will need to ensure that [`trait@ToMapping`] is
/// derived for all fields used in the mapping.
///
/// For example, a basic wrapper object around a generic type might look like this:
///
/// ```
/// # use mapstic::ToMapping;
/// # use std::fmt::Display;
/// #[derive(ToMapping)]
/// struct Wrapper<T>
/// where
///     T: ToMapping
/// {
///     id: i32,
///     inner: T,
/// }
///
/// #[derive(ToMapping)]
/// struct InnerVariant(String);
///
/// # insta::assert_yaml_snapshot!("derive-generic", Wrapper::<InnerVariant>::to_mapping());
/// ```
///
/// [boom]: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html#mapping-limit-settings
/// [fields]: https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html
/// [flattened]: https://www.elastic.co/guide/en/elasticsearch/reference/current/flattened.html
/// [keyword]: https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html
/// [params]: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html
/// [text]: https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html
/// [types]: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
pub use mapstic_derive::ToMapping;

#[doc(hidden)]
pub use mapstic_core::{Object, ParamValue, Params, Property};