use std::collections::HashMap; use serde::Serialize; /// Contains all the information related to an update of the field values for an item. /// It reports all the new values of the fields. /// /// COMMAND Subscription: /// If the involved Subscription is a COMMAND Subscription, then the values for the current update /// are meant as relative to the same key. /// /// Moreover, if the involved Subscription has a two-level behavior enabled, then each update may be /// associated with either a first-level or a second-level item. In this case, the reported fields are /// always the union of the first-level and second-level fields and each single update can only change /// either the first-level or the second-level fields (but for the "command" field, which is first-level /// and is always set to "UPDATE" upon a second-level update); note that the second-level field values /// are always None until the first second-level update occurs). When the two-level behavior is enabled, /// in all methods where a field name has to be supplied, the following convention should be followed: /// /// - The field name can always be used, both for the first-level and the second-level fields. In case of /// name conflict, the first-level field is meant. /// - The field position can always be used; however, the field positions for the second-level fields start /// at the highest position of the first-level field list + 1. If a field schema had been specified for /// either first-level or second-level Subscriptions, then client-side knowledge of the first-level schema /// length would be required. #[derive(Debug, Clone, Serialize)] pub struct ItemUpdate { pub item_name: Option, pub item_pos: usize, pub fields: HashMap>, pub changed_fields: HashMap, pub is_snapshot: bool, } impl ItemUpdate { /// Returns a map containing the values for each field changed with the last server update. /// The related field name is used as key for the values in the map. Note that if the Subscription /// mode of the involved Subscription is COMMAND, then changed fields are meant as relative to the /// previous update for the same key. On such tables if a DELETE command is received, all the fields, /// excluding the key field, will be present as changed, with None value. All of this is also true on /// tables that have the two-level behavior enabled, but in case of DELETE commands second-level fields /// will not be iterated. /// /// # Raises /// - `IllegalStateException` – if the Subscription was initialized using a field schema. /// /// # Returns /// A map containing the values for each field changed with the last server update. pub fn get_changed_fields(&self) -> HashMap { self.changed_fields.clone() } /// Returns a map containing the values for each field changed with the last server update. /// The 1-based field position within the field schema or field list is used as key for the values in /// the map. Note that if the Subscription mode of the involved Subscription is COMMAND, then changed /// fields are meant as relative to the previous update for the same key. On such tables if a DELETE /// command is received, all the fields, excluding the key field, will be present as changed, with None /// value. All of this is also true on tables that have the two-level behavior enabled, but in case of /// DELETE commands second-level fields will not be iterated. /// /// # Returns /// A map containing the values for each field changed with the last server update. pub fn get_changed_fields_by_position(&self) -> HashMap { self.changed_fields .iter() .map(|(name, value)| (self.get_field_position(name), value.clone())) .collect() } /// Returns a map containing the values for each field in the Subscription. /// The related field name is used as key for the values in the map. /// /// # Raises /// - `IllegalStateException` – if the Subscription was initialized using a field schema. /// /// # Returns /// A map containing the values for each field in the Subscription. pub fn get_fields(&self) -> HashMap> { self.fields.clone() } /// Returns a map containing the values for each field in the Subscription. /// The 1-based field position within the field schema or field list is used as key for the values in the map. /// /// # Returns /// A map containing the values for each field in the Subscription. pub fn get_fields_by_position(&self) -> HashMap> { self.fields .iter() .map(|(name, value)| (self.get_field_position(name), value.clone())) .collect() } /// Inquiry method that retrieves the name of the item to which this update pertains. /// /// The name will be None if the related Subscription was initialized using an "Item Group". /// /// # Returns /// The name of the item to which this update pertains. pub fn get_item_name(&self) -> Option<&str> { self.item_name.as_deref() } /// Inquiry method that retrieves the position in the "Item List" or "Item Group" of the item /// to which this update pertains. /// /// # Returns /// The 1-based position of the item to which this update pertains. pub fn get_item_pos(&self) -> usize { self.item_pos } /// Inquiry method that gets the value for a specified field, as received from the Server with the /// current or previous update. /// /// # Raises /// - `IllegalArgumentException` – if the specified field is not part of the Subscription. /// /// # Parameters /// - `field_name_or_pos` – The field name or the 1-based position of the field within the "Field List" or "Field Schema". /// /// # Returns /// The value of the specified field; it can be None in the following cases: /// /// - a None value has been received from the Server, as None is a possible value for a field; /// - no value has been received for the field yet; /// - the item is subscribed to with the COMMAND mode and a DELETE command is received (only the fields /// used to carry key and command information are valued). pub fn get_value(&self, field_name_or_pos: &str) -> Option<&str> { match field_name_or_pos.parse::() { Ok(pos) => self .fields .iter() .find(|(name, _)| self.get_field_position(name) == pos) .and_then(|(_, value)| value.as_deref()), Err(_) => self .fields .get(field_name_or_pos) .and_then(|v| v.as_deref()), } } /// Inquiry method that gets the difference between the new value and the previous one as a JSON Patch structure, /// provided that the Server has used the JSON Patch format to send this difference, as part of the "delta delivery" /// mechanism. This, in turn, requires that: /// /// - the Data Adapter has explicitly indicated JSON Patch as the privileged type of compression for this field; /// - both the previous and new value are suitable for the JSON Patch computation (i.e. they are valid JSON representations); /// - the item was subscribed to in MERGE or DISTINCT mode (note that, in case of two-level behavior, this holds for all /// fields related with second-level items, as these items are in MERGE mode); /// - sending the JSON Patch difference has been evaluated by the Server as more efficient than sending the full new value. /// /// Note that the last condition can be enforced by leveraging the Server's configuration flag, /// so that the availability of the JSON Patch form would only depend on the Client and the Data Adapter. /// /// When the above conditions are not met, the method just returns None; in this case, the new value can only be determined /// through `ItemUpdate.get_value()`. For instance, this will always be needed to get the first value received. /// /// # Raises /// - `IllegalArgumentException` – if the specified field is not part of the Subscription. /// /// # Parameters /// - `field_name_or_pos` – The field name or the 1-based position of the field within the "Field List" or "Field Schema". /// /// # Returns /// A JSON Patch structure representing the difference between the new value and the previous one, /// or None if the difference in JSON Patch format is not available for any reason. pub fn get_value_as_json_patch_if_available(&self, _field_name_or_pos: &str) -> Option { // Implementation pending None } /// Inquiry method that asks whether the current update belongs to the item snapshot (which carries the current item state /// at the time of Subscription). Snapshot events are sent only if snapshot information was requested for the items through /// `Subscription.set_requested_snapshot()` and precede the real time events. Snapshot information takes different forms in /// different subscription modes and can be spanned across zero, one or several update events. In particular: /// /// - if the item is subscribed to with the RAW subscription mode, then no snapshot is sent by the Server; /// - if the item is subscribed to with the MERGE subscription mode, then the snapshot consists of exactly one event, /// carrying the current value for all fields; /// - if the item is subscribed to with the DISTINCT subscription mode, then the snapshot consists of some of the most recent /// updates; these updates are as many as specified through `Subscription.set_requested_snapshot()`, unless fewer are available; /// - if the item is subscribed to with the COMMAND subscription mode, then the snapshot consists of an "ADD" event for each key /// that is currently present. /// /// Note that, in case of two-level behavior, snapshot-related updates for both the first-level item (which is in COMMAND mode) /// and any second-level items (which are in MERGE mode) are qualified with this flag. /// /// # Returns /// `true` if the current update event belongs to the item snapshot; `false` otherwise. pub fn is_snapshot(&self) -> bool { self.is_snapshot } /// Inquiry method that asks whether the value for a field has changed after the reception of the last update from the Server /// for an item. If the Subscription mode is COMMAND then the change is meant as relative to the same key. /// /// # Parameters /// - `field_name_or_pos` – The field name or the 1-based position of the field within the field list or field schema. /// /// # Returns /// Unless the Subscription mode is COMMAND, the return value is `true` in the following cases: /// /// - It is the first update for the item; /// - the new field value is different than the previous field value received for the item. /// /// If the Subscription mode is COMMAND, the return value is `true` in the following cases: /// /// - it is the first update for the involved key value (i.e. the event carries an "ADD" command); /// - the new field value is different than the previous field value received for the item, relative to the same key value /// (the event must carry an "UPDATE" command); /// - the event carries a "DELETE" command (this applies to all fields other than the field used to carry key information). /// /// In all other cases, the return value is `false`. /// /// # Raises /// - `IllegalArgumentException` – if the specified field is not part of the Subscription. pub fn is_value_changed(&self, field_name_or_pos: &str) -> bool { match field_name_or_pos.parse::() { Ok(pos) => self .changed_fields .iter() .any(|(name, _)| self.get_field_position(name) == pos), Err(_) => self.changed_fields.contains_key(field_name_or_pos), } } /// Helper method to get the 1-based position of a field within the field list or field schema. /// /// # Parameters /// - `field_name` – The name of the field. /// /// # Returns /// The 1-based position of the field within the field list or field schema. fn get_field_position(&self, _field_name: &str) -> usize { // Implementation pending // This method should return the 1-based position of the field based on the field list or field schema // If the field is not found, it should raise an IllegalArgumentException unimplemented!() } }