elasticsearch_dsl/search/queries/params/
script_object.rs

1//! With scripting, you can evaluate custom expressions in Elasticsearch. For example, you can use
2//! a script to return a computed value as a field or evaluate a custom score for a query.
3//!
4//! The default scripting language is [Painless](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-painless.html).
5//! Additional `lang` plugins are available to run scripts written in other languages. You can
6//! specify the language of the script anywhere that scripts run.
7//!
8//! <https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html>
9
10use crate::{util::*, Map};
11use serde::{Serialize, Serializer};
12
13/// Wherever scripting is supported in the Elasticsearch APIs, the syntax follows the same pattern;
14/// you specify the language of your script, provide the script logic (or source, and add parameters
15/// that are passed into the script.
16///
17/// <https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html>
18#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
19pub struct Script {
20    #[serde(flatten)]
21    source: ScriptSource,
22
23    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
24    lang: Option<ScriptLang>,
25
26    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
27    params: Map<String, serde_json::Value>,
28}
29
30/// The script itself, which you specify as `source` for an inline script or
31/// `id` for a stored script. Use the
32/// [stored script APIs](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html#prefer-params)
33/// to create and manage stored scripts.
34#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
35#[serde(rename_all = "snake_case")]
36pub enum ScriptSource {
37    /// Inline script
38    Source(String),
39
40    /// Stored script
41    Id(String),
42}
43
44impl Script {
45    /// Creates an instance of inlined [`Script`]
46    pub fn source<S>(source: S) -> Self
47    where
48        S: ToString,
49    {
50        Self {
51            source: ScriptSource::Source(source.to_string()),
52            lang: None,
53            params: Map::new(),
54        }
55    }
56
57    /// Creates an instance of stored [`Script`]
58    pub fn id<S>(id: S) -> Self
59    where
60        S: ToString,
61    {
62        Self {
63            source: ScriptSource::Id(id.to_string()),
64            lang: None,
65            params: Map::new(),
66        }
67    }
68
69    /// Specifies the language the script is written in. Defaults to `painless`.
70    pub fn lang<S>(mut self, lang: S) -> Self
71    where
72        S: Into<ScriptLang>,
73    {
74        self.lang = Some(lang.into());
75        self
76    }
77
78    /// Specifies any named parameters that are passed into the script as variables. [Use parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html#prefer-params)
79    /// instead of hard-coded values to decrease compile time.
80    pub fn param<T, S>(mut self, name: S, param: T) -> Self
81    where
82        S: ToString,
83        T: Serialize,
84    {
85        if let Ok(param) = serde_json::to_value(param) {
86            let _ = self.params.entry(name.to_string()).or_insert(param);
87        }
88        self
89    }
90}
91/// Available scripting language
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum ScriptLang {
94    /// ***Painless*** is a performant, secure scripting language designed specifically for Elasticsearch.
95    /// You can use Painless to safely write inline and stored scripts anywhere scripts are supported
96    /// in Elasticsearch.
97    ///
98    /// Painless provides numerous capabilities that center around the following core principles:
99    ///
100    /// - **Safety**: Ensuring the security of your cluster is of utmost importance. To that end, Painless
101    ///   uses a fine-grained allowlist with a granularity down to the members of a class. Anything
102    ///   that is not part of the allowlist results in a compilation error. See the [Painless API
103    ///   Reference](https://www.elastic.co/guide/en/elasticsearch/painless/7.15/painless-api-reference.html)
104    ///   for a complete list of available classes, methods, and fields per script context.
105    ///
106    /// - **Performance**: Painless compiles directly into JVM bytecode to take advantage of all possible
107    ///   optimizations that the JVM provides. Also, Painless typically avoids features that require
108    ///   additional slower checks at runtime.
109    ///
110    /// - **Simplicity**: Painless implements a syntax with a natural familiarity to anyone with some
111    ///   basic coding experience. Painless uses a subset of Java syntax with some additional
112    ///   improvements to enhance readability and remove boilerplate.
113    ///
114    /// <https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-painless.html>
115    Painless,
116
117    /// Lucene’s expressions compile a `javascript` expression to bytecode. They are designed for
118    /// high-performance custom ranking and sorting functions and are enabled for `inline` and `stored`
119    /// scripting by default.
120    ///
121    /// <https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-expression.html>
122    Expression,
123
124    /// A template language to search with templates
125    ///
126    /// <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html>
127    Mustache,
128
129    /// Custom language refer to [Advanced scripts using script engines](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-engine.html)
130    Custom(String),
131}
132
133impl Serialize for ScriptLang {
134    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135    where
136        S: Serializer,
137    {
138        match self {
139            Self::Painless => serializer.serialize_str("painless"),
140            Self::Expression => serializer.serialize_str("expression"),
141            Self::Mustache => serializer.serialize_str("mustache"),
142            Self::Custom(lang) => lang.serialize(serializer),
143        }
144    }
145}
146
147impl<T> From<T> for ScriptLang
148where
149    T: ToString,
150{
151    fn from(value: T) -> Self {
152        let value = value.to_string();
153
154        match value.as_str() {
155            "painless" => Self::Painless,
156            "expression" => Self::Expression,
157            "mustache" => Self::Mustache,
158            _ => Self::Custom(value),
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn serialization() {
169        assert_serialize(
170            Script::source("Math.log(_score * 2) * params['multiplier'].len()")
171                .param("multiplier", [1, 2, 3])
172                .lang(ScriptLang::Painless),
173            json!({
174                "source": "Math.log(_score * 2) * params['multiplier'].len()",
175                "lang": "painless",
176                "params": {
177                    "multiplier": [1, 2, 3]
178                }
179            }),
180        );
181
182        assert_serialize(
183            Script::source("doc['my_field'].value * params['multiplier']")
184                .param("multiplier", 1)
185                .lang("my_lang"),
186            json!({
187                "source": "doc['my_field'].value * params['multiplier']",
188                "lang": "my_lang",
189                "params": {
190                    "multiplier": 1
191                }
192            }),
193        );
194
195        assert_serialize(
196            Script::id(123).param("multiplier", [1, 2, 3]),
197            json!({
198                "id": "123",
199                "params": {
200                    "multiplier": [1, 2, 3]
201                }
202            }),
203        );
204    }
205}