Skip to main content

opensearch_dsl/search/queries/params/
script_object.rs

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