Skip to main content

influxlp_tools/
lib.rs

1//! InfluxDB V2 Line Protocol Tools is a parsing and building library for
2//! InfluxDB v2's line protocol. It provides easy-to-use functionality with
3//! built-in validation, support for a builder pattern and dynamic population,
4//! and options for modifying existing line protocols
5//!
6//! # Example
7//! Below is an example of building a line protocol string and parsing one
8//!
9//! ## Building a line protocol string
10//!
11//! At minimum the measurement name and a field is required to build a valid
12//! line protocol string
13//!
14//! ```rust
15//! let line_protocol = LineProtocol::new("measurement")
16//!     .add_field("field", "value")
17//!     .build()
18//!     .unwrap();
19//! ```
20//!
21//! You can overwrite the measurement name by calling the `measurement` method
22//!
23//! ```rust
24//! let mut line_protocol = LineProtocol::new("measurement")
25//!     .add_field("field", "value")
26//!     .build()
27//!     .unwrap();
28//!
29//! line_protocol = line_protocol.measurement("new_measurement");
30//! ```
31//!
32//! Multiple fields can be add by calling the `add_field` method multiple times
33//!
34//! ```rust
35//! let line_protocol = LineProtocol::new("measurement")
36//!     .add_field("field1", "value")
37//!     .add_field("field2", "value")
38//!     .build()
39//!     .unwrap();
40//! ```
41//!
42//! Optionally tags can be added. More tags can be added as with fields
43//!
44//! ```rust
45//! let line_protocol = LineProtocol::new("measurement")
46//!     .add_tag("tag1", "value")
47//!     .add_tag("tag2", "value")
48//!     .add_field("field", "value")
49//!     .build()
50//!     .unwrap();
51//! ```
52//!
53//! A timestamp can be added with the `with_timestamp` method. By default the
54//! timestamp is defined in nanosecond precision. If you are using any other
55//! precision, e.g., seconds, it needs be defined when querying influx
56//!
57//! ```rust
58//! let line_protocol = LineProtocol::new("measurement")
59//!     .add_field("field", "value")
60//!     .with_timestamp(1729270461612452700i64)
61//!     .build()
62//!     .unwrap();
63//! ```
64//!
65//! A field, tag, and timestamp can be deleted if needed. This is done by
66//! calling the respective `delete` function
67//!
68//! ```rust
69//! let mut line_protocol = LineProtocol::new("measurement")
70//!     .add_tag("tag", "value")
71//!     .add_field("field", "value");
72//!
73//! line_protocol.delete_tag("tag")
74//! ```
75//!
76//! **Note:** that deleting all fields will cause the building to fail as
77//! atleast **one** field is required
78//!
79//! ## Parsing a line protocol string
80//!
81//! To parse a line protocol string the `parse_line` method can be used
82//!
83//! ```rust
84//! let line =
85//!     "measurement,tag2=value,tag=value field=\"hello\",field2=\"world\" 1729270461612452700";
86//! let line_protocol = LineProtocol::parse_line(line).unwrap();
87//! ```
88//!
89//! To parse multiple lines seperated by a newline the `parse_lines` method can
90//! be used instead
91//!
92//! ```rust
93//! let lines = vec![
94//!     "measurement,tag=value field=\"value\"",
95//!     "measurement field=\"{\\\"test\\\": \\\"hello\\\"}\"",
96//!     "measurement,tag2=value,tag=value field=\"value\",field2=\"{\\\"test\\\": \
97//!      \\\"hello\\\"}\" 1729270461612452700",
98//! ]
99//! .join("\n");
100//!
101//! let result = LineProtocol::parse_lines(&lines);
102//! ```
103//!
104//! **Note:** The parsed line can be modified and rebuilt if needed
105
106use std::{collections::HashMap, fmt::Display};
107
108use element::{FieldKey, FieldValue, Measurement, TagKey, TagValue};
109
110pub mod builder;
111pub mod element;
112pub mod error;
113pub mod parser;
114pub mod traits;
115
116#[derive(Debug, Clone)]
117pub struct LineProtocol {
118    /// The data point measurement name
119    pub measurement: Measurement,
120
121    /// The data point tag set
122    pub tags: Option<HashMap<TagKey, TagValue>>,
123
124    /// The data point field set
125    pub fields: HashMap<FieldKey, FieldValue>,
126
127    /// To ensure a data point includes the time a metric is observed (not
128    /// received by InfluxDB), include a timestamp if not defined
129    ///
130    /// By default the timestamp is defined in nanoseconds. If you are using any
131    /// other form of precision it needs to be defined when making the insert
132    /// request
133    // Unfortunately there is no way of knowing the timestamp precision from just the given number
134    // as the precision is defined when you query the database. But the min/max timestamp value is
135    // exactly a i64 https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#unix-timestamp
136    pub timestamp: Option<i64>,
137}
138
139impl PartialEq for LineProtocol {
140    fn eq(&self, other: &Self) -> bool {
141        if self.measurement != other.measurement {
142            println!("name not equal");
143            return false;
144        }
145
146        let tags_matches = match (&self.tags, &other.tags) {
147            (Some(tags1), Some(tags2)) => tags1 == tags2,
148            (None, None) => true,
149            _ => return false,
150        };
151
152        let timestamp_matches = match (self.timestamp, other.timestamp) {
153            (Some(ts1), Some(ts2)) => ts1 == ts2,
154            (None, None) => true,
155            _ => return false,
156        };
157
158        // At this point we know the measurement is equal. If the tags and timestamp are
159        // also equal its a duplicate line
160        tags_matches && timestamp_matches
161    }
162}
163
164impl Display for LineProtocol {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        let lp = match &self.build() {
167            Ok(lp) => lp.to_string(),
168            Err(e) => format!("invalid line protocol: {e}"),
169        };
170
171        write!(f, "{}", lp)
172    }
173}
174
175impl LineProtocol {
176    /// Get a cloned version of the measurement
177    pub fn get_measurement(&self) -> Measurement {
178        self.measurement.clone()
179    }
180
181    /// Get a reference of the measurement
182    pub fn get_measurement_ref(&self) -> &Measurement {
183        &self.measurement
184    }
185
186    /// Get a mutable reference of the measurement
187    pub fn get_measurement_mut(&mut self) -> &mut Measurement {
188        &mut self.measurement
189    }
190
191    /// Get the tag value associated with the provided tag key
192    ///
193    /// # Args
194    /// * `key` - A [valid](https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#special-characters)
195    ///   tag key
196    pub fn get_tag<K>(&self, key: K) -> Option<TagValue>
197    where
198        K: Into<TagKey>,
199    {
200        match &self.tags {
201            Some(tags) => tags.get(&key.into()).cloned(),
202            None => None,
203        }
204    }
205
206    /// Get a reference to the tag value associated with the provided tag key
207    ///
208    /// # Args
209    /// * `key` - A [valid](https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#special-characters)
210    ///   tag key
211    pub fn get_tag_ref<K>(&self, key: K) -> Option<&TagValue>
212    where
213        K: Into<TagKey>,
214    {
215        match &self.tags {
216            Some(tags) => tags.get(&key.into()),
217            None => None,
218        }
219    }
220
221    /// Get a mutable reference to the tag value associated with the provided
222    /// tag key
223    ///
224    /// # Args
225    /// * `key` - A [valid](https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#special-characters)
226    ///   tag key
227    pub fn get_tag_mut<K>(&mut self, key: K) -> Option<&mut TagValue>
228    where
229        K: Into<TagKey>,
230    {
231        match &mut self.tags {
232            Some(tags) => tags.get_mut(&key.into()),
233            None => None,
234        }
235    }
236
237    /// Get the field value associated with the provided field key
238    ///
239    /// # Args
240    /// * `key` - A [valid](https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#special-characters)
241    ///   field key
242    pub fn get_field<K>(&self, key: K) -> Option<FieldValue>
243    where
244        K: Into<FieldKey>,
245    {
246        self.fields.get(&key.into()).cloned()
247    }
248
249    /// Get a reference to the field value associated with the provided field
250    /// key
251    ///
252    /// # Args
253    /// * `key` - A [valid](https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#special-characters)
254    ///   field key
255    pub fn get_field_ref<K>(&self, key: K) -> Option<&FieldValue>
256    where
257        K: Into<FieldKey>,
258    {
259        self.fields.get(&key.into())
260    }
261
262    /// Get a mutable reference to the field value associated with the provided
263    /// field key
264    ///
265    /// # Args
266    /// * `key` - A [valid](https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#special-characters)
267    ///   field key
268    pub fn get_field_mut<K>(&mut self, key: K) -> Option<&mut FieldValue>
269    where
270        K: Into<FieldKey>,
271    {
272        self.fields.get_mut(&key.into())
273    }
274
275    /// Get a cloned version of the timestamp
276    pub fn get_timestamp(&self) -> Option<i64> {
277        self.timestamp
278    }
279
280    /// Get a reference of the timestamp
281    pub fn get_timestamp_ref(&self) -> Option<&i64> {
282        self.timestamp.as_ref()
283    }
284
285    /// Get a mutable reference of the timestamp
286    pub fn get_timestamp_mut(&mut self) -> Option<&mut i64> {
287        self.timestamp.as_mut()
288    }
289}