qdrant_client/grpc_conversions/
extensions.rs

1use std::fmt::{Display, Formatter};
2use std::hash::{Hash, Hasher};
3
4#[cfg(feature = "uuid")]
5use uuid::Uuid;
6
7use crate::client::Payload;
8#[allow(deprecated)]
9use crate::error::NotA;
10use crate::prelude::{PointStruct, Value};
11#[cfg(feature = "uuid")]
12use crate::qdrant::point_id::PointIdOptions;
13use crate::qdrant::value::Kind;
14use crate::qdrant::{
15    HardwareUsage, InferenceUsage, ListValue, ModelUsage, PointId, RetrievedPoint, ScoredPoint,
16    Struct, Usage, Vectors,
17};
18
19/// Null value
20static NULL_VALUE: Value = Value {
21    kind: Some(Kind::NullValue(0)),
22};
23
24impl PointStruct {
25    pub fn new(
26        id: impl Into<PointId>,
27        vectors: impl Into<Vectors>,
28        payload: impl Into<Payload>,
29    ) -> Self {
30        Self {
31            id: Some(id.into()),
32            payload: payload.into().into(),
33            vectors: Some(vectors.into()),
34        }
35    }
36}
37
38impl RetrievedPoint {
39    /// Get a payload value for the specified key. If the key is not present,
40    /// this will return a null value.
41    ///
42    /// # Examples:
43    ///
44    /// ```
45    /// use qdrant_client::qdrant::RetrievedPoint;
46    /// let point = RetrievedPoint::default();
47    /// assert!(point.get("not_present").is_null());
48    /// ````
49    pub fn get(&self, key: &str) -> &Value {
50        self.try_get(key).unwrap_or(&NULL_VALUE)
51    }
52
53    /// Try to get a payload value for the specified key. If the key is not present,
54    /// this will return `None`.
55    ///
56    /// # Examples:
57    ///
58    /// ```
59    /// use qdrant_client::qdrant::RetrievedPoint;
60    /// let point = RetrievedPoint::default();
61    /// assert_eq!(point.try_get("not_present"), None);
62    /// ````
63    pub fn try_get(&self, key: &str) -> Option<&Value> {
64        self.payload.get(key)
65    }
66}
67
68impl ScoredPoint {
69    /// Get a payload value for the specified key. If the key is not present,
70    /// this will return a null value.
71    ///
72    /// # Examples:
73    ///
74    /// ```
75    /// use qdrant_client::qdrant::ScoredPoint;
76    /// let point = ScoredPoint::default();
77    /// assert!(point.get("not_present").is_null());
78    /// ````
79    pub fn get(&self, key: &str) -> &Value {
80        self.try_get(key).unwrap_or(&NULL_VALUE)
81    }
82
83    /// Get a payload value for the specified key. If the key is not present,
84    /// this will return `None`.
85    ///
86    /// # Examples:
87    ///
88    /// ```
89    /// use qdrant_client::qdrant::ScoredPoint;
90    /// let point = ScoredPoint::default();
91    /// assert_eq!(point.try_get("not_present"), None);
92    /// ````
93    pub fn try_get(&self, key: &str) -> Option<&Value> {
94        self.payload.get(key)
95    }
96}
97
98macro_rules! extract {
99    ($kind:ident, $check:ident) => {
100        /// Check if this value is a
101        #[doc = stringify!([$kind])]
102        pub fn $check(&self) -> bool {
103            matches!(self.kind, Some($kind(_)))
104        }
105    };
106    ($kind:ident, $check:ident, $extract:ident, $ty:ty) => {
107        extract!($kind, $check);
108
109        /// Get this value as
110        #[doc = stringify!([$ty])]
111        ///
112        /// Returns `None` if this value is not a
113        #[doc = stringify!([$kind].)]
114        pub fn $extract(&self) -> Option<$ty> {
115            if let Some($kind(v)) = self.kind {
116                Some(v)
117            } else {
118                None
119            }
120        }
121    };
122    ($kind:ident, $check:ident, $extract:ident, ref $ty:ty) => {
123        extract!($kind, $check);
124
125        /// Get this value as
126        #[doc = stringify!([$ty])]
127        ///
128        /// Returns `None` if this value is not a
129        #[doc = stringify!([$kind].)]
130        pub fn $extract(&self) -> Option<&$ty> {
131            if let Some($kind(v)) = &self.kind {
132                Some(v)
133            } else {
134                None
135            }
136        }
137    };
138}
139
140// Separate module to not import all enum kinds of `Kind` directly as this conflicts with other types.
141// The macro extract!() however is built to take enum kinds directly and passing Kind::<kind> is not possible.
142mod value_extract_impl {
143    use crate::qdrant::value::Kind::*;
144    use crate::qdrant::{Struct, Value};
145    impl Value {
146        extract!(NullValue, is_null);
147        extract!(BoolValue, is_bool, as_bool, bool);
148        extract!(IntegerValue, is_integer, as_integer, i64);
149        extract!(DoubleValue, is_double, as_double, f64);
150        extract!(StringValue, is_str, as_str, ref String);
151        extract!(ListValue, is_list, as_list, ref [Value]);
152        extract!(StructValue, is_struct, as_struct, ref Struct);
153    }
154}
155
156impl Value {
157    #[cfg(feature = "serde")]
158    /// Convert this into a [`serde_json::Value`]
159    ///
160    /// # Examples:
161    ///
162    /// ```
163    /// use serde_json::json;
164    /// use qdrant_client::prelude::*;
165    /// use qdrant_client::qdrant::{value::Kind::*, Struct};
166    /// let value = Value { kind: Some(StructValue(Struct {
167    ///     fields: [
168    ///         ("text".into(), Value { kind: Some(StringValue("Hi Qdrant!".into())) }),
169    ///         ("int".into(), Value { kind: Some(IntegerValue(42))}),
170    ///     ].into()
171    /// }))};
172    /// assert_eq!(value.into_json(), json!({
173    ///    "text": "Hi Qdrant!",
174    ///    "int": 42
175    /// }));
176    /// ```
177    pub fn into_json(self) -> serde_json::Value {
178        use serde_json::Value as JsonValue;
179        match self.kind {
180            Some(Kind::BoolValue(b)) => JsonValue::Bool(b),
181            Some(Kind::IntegerValue(i)) => JsonValue::from(i),
182            Some(Kind::DoubleValue(d)) => JsonValue::from(d),
183            Some(Kind::StringValue(s)) => JsonValue::String(s),
184            Some(Kind::ListValue(vs)) => vs.into_iter().map(Value::into_json).collect(),
185            Some(Kind::StructValue(s)) => s
186                .fields
187                .into_iter()
188                .map(|(k, v)| (k, v.into_json()))
189                .collect(),
190            Some(Kind::NullValue(_)) | None => JsonValue::Null,
191        }
192    }
193}
194
195#[cfg(feature = "serde")]
196impl From<Value> for serde_json::Value {
197    fn from(value: Value) -> Self {
198        value.into_json()
199    }
200}
201
202impl Display for Value {
203    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204        match &self.kind {
205            Some(Kind::BoolValue(b)) => write!(f, "{b}"),
206            Some(Kind::IntegerValue(i)) => write!(f, "{i}"),
207            Some(Kind::DoubleValue(v)) => write!(f, "{v}"),
208            Some(Kind::StringValue(s)) => write!(f, "{s:?}"),
209            Some(Kind::ListValue(vs)) => {
210                let mut i = vs.values.iter();
211                write!(f, "[")?;
212                if let Some(first) = i.next() {
213                    write!(f, "{first}")?;
214                    for v in i {
215                        write!(f, ",{v}")?;
216                    }
217                }
218                write!(f, "]")
219            }
220            Some(Kind::StructValue(s)) => {
221                let mut i = s.fields.iter();
222                write!(f, "{{")?;
223                if let Some((key, value)) = i.next() {
224                    write!(f, "{key:?}:{value}")?;
225                    for (key, value) in i {
226                        write!(f, ",{key:?}:{value}")?;
227                    }
228                }
229                write!(f, "}}")
230            }
231            _ => write!(f, "null"),
232        }
233    }
234}
235
236impl Value {
237    /// Try to get an iterator over the items of the contained list value
238    ///
239    /// Returns `None` if this is not a list.
240    pub fn try_list_iter(&self) -> Option<impl Iterator<Item = &Value>> {
241        if let Some(Kind::ListValue(values)) = &self.kind {
242            Some(values.iter())
243        } else {
244            None
245        }
246    }
247
248    /// Try to get an iterator over the items of the contained list value, if any
249    #[deprecated(since = "1.10.0", note = "use `try_list_iter` instead")]
250    #[allow(deprecated)]
251    pub fn iter_list(&self) -> Result<impl Iterator<Item = &Value>, NotA<ListValue>> {
252        if let Some(Kind::ListValue(values)) = &self.kind {
253            Ok(values.iter())
254        } else {
255            Err(NotA::default())
256        }
257    }
258
259    /// Get a value from a struct field
260    ///
261    /// Returns `None` if this is not a struct type or if the field is not present.
262    pub fn get_value(&self, key: &str) -> Option<&Value> {
263        if let Some(Kind::StructValue(Struct { fields })) = &self.kind {
264            Some(fields.get(key)?)
265        } else {
266            None
267        }
268    }
269
270    /// Try to get a field from the struct if this value contains one
271    #[deprecated(since = "1.10.0", note = "use `get_value` instead")]
272    #[allow(deprecated)]
273    pub fn get_struct(&self, key: &str) -> Result<&Value, NotA<Struct>> {
274        if let Some(Kind::StructValue(Struct { fields })) = &self.kind {
275            Ok(fields.get(key).unwrap_or(&NULL_VALUE))
276        } else {
277            Err(NotA::default())
278        }
279    }
280}
281
282impl std::ops::Deref for ListValue {
283    type Target = [Value];
284
285    fn deref(&self) -> &[Value] {
286        &self.values
287    }
288}
289
290impl IntoIterator for ListValue {
291    type Item = Value;
292
293    type IntoIter = std::vec::IntoIter<Value>;
294
295    fn into_iter(self) -> Self::IntoIter {
296        self.values.into_iter()
297    }
298}
299
300impl ListValue {
301    pub fn iter(&self) -> std::slice::Iter<'_, Value> {
302        self.values.iter()
303    }
304}
305
306#[cfg(feature = "uuid")]
307impl From<Uuid> for PointId {
308    fn from(uuid: Uuid) -> Self {
309        Self {
310            point_id_options: Some(PointIdOptions::from(uuid)),
311        }
312    }
313}
314
315#[cfg(feature = "uuid")]
316impl From<Uuid> for PointIdOptions {
317    fn from(uuid: Uuid) -> Self {
318        PointIdOptions::Uuid(uuid.to_string())
319    }
320}
321
322impl Hash for PointId {
323    fn hash<H: Hasher>(&self, state: &mut H) {
324        use crate::qdrant::point_id::PointIdOptions::{Num, Uuid};
325        match &self.point_id_options {
326            Some(Num(u)) => state.write_u64(*u),
327            Some(Uuid(s)) => s.hash(state),
328            None => {}
329        }
330    }
331}
332
333impl Hash for ScoredPoint {
334    fn hash<H: Hasher>(&self, state: &mut H) {
335        self.id.hash(state)
336    }
337}
338
339impl Hash for RetrievedPoint {
340    fn hash<H: Hasher>(&self, state: &mut H) {
341        self.id.hash(state)
342    }
343}
344
345impl Usage {
346    pub(crate) fn aggregate_opts(this: Option<Self>, other: Option<Self>) -> Option<Self> {
347        match (this, other) {
348            (Some(this), Some(other)) => Some(this.aggregate(other)),
349            (Some(this), None) => Some(this),
350            (None, Some(other)) => Some(other),
351            (None, None) => None,
352        }
353    }
354
355    pub(crate) fn aggregate(self, other: Self) -> Self {
356        Self {
357            hardware: HardwareUsage::aggregate_opts(self.hardware, other.hardware),
358            inference: InferenceUsage::aggregate_opts(self.inference, other.inference),
359        }
360    }
361}
362
363impl HardwareUsage {
364    pub(crate) fn aggregate_opts(this: Option<Self>, other: Option<Self>) -> Option<Self> {
365        match (this, other) {
366            (Some(this), Some(other)) => Some(this.aggregate(other)),
367            (Some(this), None) => Some(this),
368            (None, Some(other)) => Some(other),
369            (None, None) => None,
370        }
371    }
372
373    pub(crate) fn aggregate(self, other: Self) -> Self {
374        let Self {
375            cpu,
376            payload_io_read,
377            payload_io_write,
378            payload_index_io_read,
379            payload_index_io_write,
380            vector_io_read,
381            vector_io_write,
382        } = other;
383
384        Self {
385            cpu: self.cpu + cpu,
386            payload_io_read: self.payload_io_read + payload_io_read,
387            payload_io_write: self.payload_io_write + payload_io_write,
388            payload_index_io_read: self.payload_index_io_read + payload_index_io_read,
389            payload_index_io_write: self.payload_index_io_write + payload_index_io_write,
390            vector_io_read: self.vector_io_read + vector_io_read,
391            vector_io_write: self.vector_io_write + vector_io_write,
392        }
393    }
394}
395
396impl InferenceUsage {
397    pub(crate) fn aggregate_opts(this: Option<Self>, other: Option<Self>) -> Option<Self> {
398        match (this, other) {
399            (Some(this), Some(other)) => Some(this.aggregate(other)),
400            (Some(this), None) => Some(this),
401            (None, Some(other)) => Some(other),
402            (None, None) => None,
403        }
404    }
405
406    pub(crate) fn aggregate(self, other: Self) -> Self {
407        let mut models = self.models;
408        for (model_name, other_usage) in other.models {
409            models
410                .entry(model_name)
411                .and_modify(|usage| {
412                    *usage = usage.aggregate(other_usage);
413                })
414                .or_insert(other_usage);
415        }
416
417        Self { models }
418    }
419}
420
421impl ModelUsage {
422    pub(crate) fn aggregate(self, other: Self) -> Self {
423        Self {
424            tokens: self.tokens + other.tokens,
425        }
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use std::collections::HashMap;
432
433    use super::*;
434
435    #[test]
436    fn test_inference_usage_aggregation() {
437        let mut models1 = HashMap::new();
438        models1.insert("model_a".to_string(), ModelUsage { tokens: 100 });
439        models1.insert("model_b".to_string(), ModelUsage { tokens: 200 });
440
441        let mut models2 = HashMap::new();
442        models2.insert("model_a".to_string(), ModelUsage { tokens: 50 });
443        models2.insert("model_c".to_string(), ModelUsage { tokens: 300 });
444
445        let usage1 = InferenceUsage { models: models1 };
446        let usage2 = InferenceUsage { models: models2 };
447
448        let aggregated = usage1.aggregate(usage2);
449
450        // Check that model_a tokens were summed
451        assert_eq!(aggregated.models.get("model_a").unwrap().tokens, 150);
452
453        // Check that model_b was preserved
454        assert_eq!(aggregated.models.get("model_b").unwrap().tokens, 200);
455
456        // Check that model_c was added
457        assert_eq!(aggregated.models.get("model_c").unwrap().tokens, 300);
458
459        // Check that we have exactly 3 models
460        assert_eq!(aggregated.models.len(), 3);
461    }
462}