dsntk_feel/
context.rs

1//! FEEL context.
2
3use crate::errors::*;
4use crate::names::Name;
5use crate::qualified_names::QualifiedName;
6use crate::strings::ToFeelString;
7use crate::value_null;
8use crate::values::Value;
9use dsntk_common::{DsntkError, Jsonify, Result};
10use std::collections::btree_map::Iter;
11use std::collections::BTreeMap;
12use std::fmt;
13use std::ops::Deref;
14
15/// Type alias for context entries.
16type FeelContextEntries = BTreeMap<Name, Value>;
17
18/// The FEEL context.
19#[derive(Debug, Clone, PartialEq, Default)]
20pub struct FeelContext(FeelContextEntries);
21
22impl Deref for FeelContext {
23  type Target = FeelContextEntries;
24  fn deref(&self) -> &Self::Target {
25    &self.0
26  }
27}
28
29impl TryFrom<Value> for FeelContext {
30  type Error = DsntkError;
31  /// Converts [Value] to [FeelContext].
32  fn try_from(value: Value) -> Result<Self, Self::Error> {
33    let Value::Context(ctx) = value else {
34      return Err(err_value_is_not_a_context(&value));
35    };
36    Ok(ctx)
37  }
38}
39
40impl From<FeelContext> for Value {
41  /// Converts [FeelContext] to [Value].
42  fn from(ctx: FeelContext) -> Self {
43    Value::Context(ctx)
44  }
45}
46
47impl fmt::Display for FeelContext {
48  /// Convert [FeelContext] to string.
49  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50    write!(
51      f,
52      "{{{}}}",
53      self
54        .0
55        .iter()
56        .map(|(name, value)| { format!("{}: {}", if name.is_empty() { r#""""#.to_string() } else { name.to_string() }, value) })
57        .collect::<Vec<String>>()
58        .join(", ")
59    )
60  }
61}
62
63impl ToFeelString for FeelContext {
64  /// Converts [FeelContext] to FEEL string.
65  fn to_feel_string(&self) -> String {
66    format!(
67      "{{{}}}",
68      self
69        .0
70        .iter()
71        .map(|(name, value)| {
72          let name_str = format!("{name}");
73          let padded_name_str = match name_str.as_str() {
74            "{" | "}" | ":" | "," => format!("\"{name_str}\""),
75            "\"" => "\"\\\"\"".to_string(),
76            _ => name_str,
77          };
78          format!(r#"{padded_name_str}: {value}"#)
79        })
80        .collect::<Vec<String>>()
81        .join(", ")
82    )
83  }
84}
85
86impl Jsonify for FeelContext {
87  /// Converts [FeelContext] to JSON string.
88  fn jsonify(&self) -> String {
89    format!(
90      "{{{}}}",
91      self
92        .0
93        .iter()
94        .map(|(name, value)| format!(r#""{}": {}"#, name, value.jsonify()))
95        .collect::<Vec<String>>()
96        .join(", ")
97    )
98  }
99}
100
101impl FeelContext {
102  /// Creates a new, empty context.
103  pub fn new() -> Self {
104    Self::default()
105  }
106
107  /// Returns `true` if context contains an entry specified by **name**.
108  pub fn contains_entry(&self, name: &Name) -> bool {
109    self.0.contains_key(name)
110  }
111
112  /// Returns `true` if this [FeelContext] contains an entry specified by [QualifiedName].
113  pub fn contains_entries(&self, qname: &QualifiedName) -> bool {
114    self.contains_deep(qname.as_slice())
115  }
116
117  /// Returns a value of an entry with specified name.
118  pub fn get_entry(&self, name: &Name) -> Option<&Value> {
119    self.0.get(name)
120  }
121
122  /// Sets a value for specified entry name.
123  pub fn set_entry(&mut self, name: &Name, value: Value) {
124    self.0.insert(name.clone(), value);
125  }
126
127  /// Removes a value of an entry with specified name.
128  pub fn remove_entry(&mut self, name: &Name) -> Option<Value> {
129    self.0.remove(name)
130  }
131
132  /// Sets a null value for specified entry.
133  pub fn set_null(&mut self, name: Name) {
134    self.0.insert(name, value_null!());
135  }
136
137  /// Returns a list of all [FeelContext] entries.
138  pub fn get_entries(&self) -> Vec<(&Name, &Value)> {
139    self.0.iter().collect::<Vec<(&Name, &Value)>>()
140  }
141
142  /// Returns an iterator over all entries in [FeelContext].
143  pub fn iter(&self) -> Iter<Name, Value> {
144    self.0.iter()
145  }
146
147  /// Returns a first value contained by context.
148  pub fn get_first(&self) -> Option<&Value> {
149    self.0.values().next()
150  }
151
152  /// Returns the number of entries in [FeelContext].
153  pub fn len(&self) -> usize {
154    self.0.len()
155  }
156
157  /// Returns `true` if [FeelContext] is empty.
158  pub fn is_empty(&self) -> bool {
159    self.0.is_empty()
160  }
161
162  /// Returns `true` if [FeelContext] contains an entry with specified name, and the value is context.
163  pub fn is_context(&self, name: &Name) -> bool {
164    matches!(self.0.get(name), Some(Value::Context(_)))
165  }
166
167  pub fn zip(&mut self, other: &FeelContext) {
168    for (name, value) in &other.0 {
169      self.0.insert(name.clone(), value.clone());
170    }
171  }
172
173  pub fn overwrite(&mut self, other: &FeelContext) {
174    for (name, value) in &other.0 {
175      if self.0.contains_key(name) {
176        self.0.insert(name.clone(), value.clone());
177      }
178    }
179  }
180
181  //TODO refactor the name, this operation is not moving, it is like prefixing
182  pub fn move_entry(&mut self, name: Name, parent: Name) {
183    if let Some(value) = self.0.remove(&name) {
184      self.create_entries(&[parent, name], value);
185    }
186  }
187
188  /// Creates an entry with a value for specified [QualifiedName].
189  /// All non-existing intermediary contexts will be created.
190  pub fn create_entry(&mut self, qname: &QualifiedName, value: Value) {
191    self.create_entries(qname.as_slice(), value);
192  }
193
194  /// Searches for a value of an entry pointed by specified qualified name.
195  pub fn search_entry<'a>(&'a self, qname: &'a QualifiedName) -> Option<&'a Value> {
196    self.search_deep(qname.as_slice())
197  }
198
199  /// Deep check for a value pointed by slice of names.
200  pub fn contains_deep(&self, names: &[Name]) -> bool {
201    if names.is_empty() {
202      return false;
203    }
204    let tail = &names[1..];
205    if let Some(value) = self.0.get(&names[0]) {
206      if tail.is_empty() {
207        return true;
208      }
209      if let Value::Context(context) = value {
210        return context.contains_deep(tail);
211      }
212    }
213    false
214  }
215
216  /// Creates entries with intermediary contexts when needed.
217  pub fn create_entries(&mut self, names: &[Name], value: Value) {
218    // if there are no entry names provided, then fo nothing
219    if names.is_empty() {
220      return;
221    }
222    let key = names[0].clone();
223    let tail = &names[1..];
224    // if tail is empty, then insert the value under the key in current context and return
225    if tail.is_empty() {
226      self.0.insert(key, value);
227      return;
228    }
229    // if there is a context under the key, then insert value to this context and return
230    if let Some(Value::Context(ctx)) = self.0.get_mut(&key) {
231      ctx.create_entries(tail, value);
232      return;
233    }
234    // insert a value from tail to newly created context
235    let mut ctx = FeelContext::default();
236    ctx.create_entries(tail, value);
237    self.0.insert(key, ctx.into());
238  }
239
240  /// ???
241  pub fn apply_entries(&mut self, names: &[Name], value: Value) -> Result<()> {
242    // if there are no entry names provided, then do nothing
243    if names.is_empty() {
244      return Ok(());
245    }
246    let key = names[0].clone();
247    let tail = &names[1..];
248    // if tail is empty, then insert the value under the key in current context and return
249    if tail.is_empty() {
250      self.0.insert(key, value);
251      return Ok(());
252    }
253    // if there is a context under the key, then insert value to this context and return
254    match self.0.get_mut(&key) {
255      Some(Value::Context(ctx)) => {
256        ctx.apply_entries(tail, value)?;
257        return Ok(());
258      }
259      Some(other) => {
260        return Err(err_value_is_not_a_context(other));
261      }
262      _ => {}
263    }
264    // insert a value from tail to new created context
265    let mut ctx = FeelContext::default();
266    ctx.apply_entries(tail, value)?;
267    self.0.insert(key, ctx.into());
268    Ok(())
269  }
270
271  /// Deep search for a value pointed by names.
272  pub fn search_deep(&self, names: &[Name]) -> Option<&Value> {
273    if !names.is_empty() {
274      let tail = &names[1..];
275      if let Some(value) = self.0.get(&names[0]) {
276        if let Value::Context(ctx) = value {
277          return if tail.is_empty() { Some(value) } else { ctx.search_deep(tail) };
278        } else if tail.is_empty() {
279          return Some(value);
280        }
281      }
282    }
283    None
284  }
285}