gel_protocol/
value_opt.rs

1use std::collections::HashMap;
2
3use gel_errors::{ClientEncodingError, Error, ErrorKind};
4
5use crate::codec::{ObjectShape, ShapeElement};
6use crate::descriptors::Descriptor;
7use crate::query_arg::{Encoder, QueryArgs};
8use crate::value::Value;
9
10/// An optional [Value] that can be constructed from `impl Into<Value>`,
11/// `Option<impl Into<Value>>`, `Vec<impl Into<Value>>` or
12/// `Option<Vec<impl Into<Value>>>`.
13/// Used by [named_args!](`crate::named_args!`) macro.
14#[derive(Clone, Debug, PartialEq)]
15pub struct ValueOpt(Option<Value>);
16
17impl<V: Into<Value>> From<V> for ValueOpt {
18    fn from(value: V) -> Self {
19        ValueOpt(Some(value.into()))
20    }
21}
22impl<V: Into<Value>> From<Option<V>> for ValueOpt
23where
24    Value: From<V>,
25{
26    fn from(value: Option<V>) -> Self {
27        ValueOpt(value.map(Value::from))
28    }
29}
30impl<V: Into<Value>> From<Vec<V>> for ValueOpt
31where
32    Value: From<V>,
33{
34    fn from(value: Vec<V>) -> Self {
35        ValueOpt(Some(Value::Array(
36            value.into_iter().map(Value::from).collect(),
37        )))
38    }
39}
40impl<V: Into<Value>> From<Option<Vec<V>>> for ValueOpt
41where
42    Value: From<V>,
43{
44    fn from(value: Option<Vec<V>>) -> Self {
45        let mapped = value.map(|value| Value::Array(value.into_iter().map(Value::from).collect()));
46        ValueOpt(mapped)
47    }
48}
49impl From<ValueOpt> for Option<Value> {
50    fn from(value: ValueOpt) -> Self {
51        value.0
52    }
53}
54
55impl QueryArgs for HashMap<&str, ValueOpt> {
56    fn encode(&self, encoder: &mut Encoder) -> Result<(), Error> {
57        if self.is_empty() && encoder.ctx.root_pos.is_none() {
58            return Ok(());
59        }
60
61        let root_pos = encoder.ctx.root_pos.ok_or_else(|| {
62            ClientEncodingError::with_message(format!(
63                "provided {} named arguments, but no arguments were expected by the server",
64                self.len()
65            ))
66        })?;
67
68        let Descriptor::ObjectShape(target_shape) = encoder.ctx.get(root_pos)? else {
69            return Err(ClientEncodingError::with_message(
70                "query didn't expect named arguments",
71            ));
72        };
73
74        let mut shape_elements: Vec<ShapeElement> = Vec::new();
75        let mut fields: Vec<Option<Value>> = Vec::new();
76
77        for param_descriptor in target_shape.elements.iter() {
78            let value = self.get(param_descriptor.name.as_str());
79
80            let Some(value) = value else {
81                return Err(ClientEncodingError::with_message(format!(
82                    "argument for ${} missing",
83                    param_descriptor.name
84                )));
85            };
86
87            shape_elements.push(ShapeElement::from(param_descriptor));
88            fields.push(value.0.clone());
89        }
90
91        Value::Object {
92            shape: ObjectShape::new(shape_elements),
93            fields,
94        }
95        .encode(encoder)
96    }
97}
98
99/// Constructs named query arguments that implement [QueryArgs] so they can be passed
100/// into any query method.
101/// ```no_run
102/// use gel_protocol::value::Value;
103///
104/// let query = "SELECT (<str>$my_str, <int64>$my_int)";
105/// let args = gel_protocol::named_args! {
106///     "my_str" => "Hello world!".to_string(),
107///     "my_int" => Value::Int64(42),
108/// };
109/// ```
110///
111/// The value side of an argument must be `impl Into<ValueOpt>`.
112/// The type of the returned object is `HashMap<&str, ValueOpt>`.
113#[macro_export]
114macro_rules! named_args {
115    ($($key:expr => $value:expr,)+) => { $crate::named_args!($($key => $value),+) };
116    ($($key:expr => $value:expr),*) => {
117        {
118            const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]);
119            let mut map = ::std::collections::HashMap::<&str, $crate::value_opt::ValueOpt>::with_capacity(CAP);
120            $(
121                map.insert($key, $crate::value_opt::ValueOpt::from($value));
122            )*
123            map
124        }
125    };
126}