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}