Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support named arguments #304

Merged
merged 16 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion edgedb-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ bitflags = "2.4.0"
serde = {version="1.0.190", optional=true}

[features]
default = []
default = ["macros"]
with-num-bigint = ["num-bigint", "num-traits"]
with-bigdecimal = ["bigdecimal", "num-bigint", "num-traits"]
with-chrono = ["chrono"]
all-types = ["with-num-bigint", "with-bigdecimal", "with-chrono"]
with-serde = ["serde"]
macros = []

[dev-dependencies]
rand = "0.8"
Expand Down
2 changes: 1 addition & 1 deletion edgedb-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ pub mod descriptors;
pub mod value;
pub mod codec;
pub mod queryable;
#[macro_use]
pub mod query_arg;
pub mod model;


pub use query_result::QueryResult;
108 changes: 107 additions & 1 deletion edgedb-protocol/src/query_arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use edgedb_errors::ParameterTypeMismatchError;
use edgedb_errors::{ClientEncodingError, DescriptorMismatch, ProtocolError};
use edgedb_errors::{Error, ErrorKind, InvalidReferenceError};

use crate::codec::{self, build_codec, Codec};
use crate::codec::{self, build_codec, Codec, ObjectShape, ShapeElement};
use crate::descriptors::TypePos;
use crate::descriptors::{Descriptor, EnumerationTypeDescriptor};
use crate::errors;
Expand Down Expand Up @@ -526,3 +526,109 @@ implement_tuple! {9, T0, T1, T2, T3, T4, T5, T6, T7, T8, }
implement_tuple! {10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, }
implement_tuple! {11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, }
implement_tuple! {12, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, }

// This supertype allows user to provide both
// Into<Value>, Option<Into<Value>>, Vec<Into<Value>>, Option<Vec<Into<Value>>>
// types in HashMap
pub struct UserValue(Option<Value>);

impl<V: Into<Value>> From<V> for UserValue {
fn from(value: V) -> Self {
UserValue(Some(value.into()))
}
}
impl<V: Into<Value>> From<Option<V>> for UserValue
where
Value: From<V>
{
fn from(value: Option<V>) -> Self {
UserValue(value.map(Value::from))
}
}
impl<V: Into<Value>> From<Vec<V>> for UserValue
where
Value: From<V>
{
fn from(value: Vec<V>) -> Self {
UserValue(Some(Value::Array(value.into_iter().map(Value::from).collect())))
}
}
impl<V: Into<Value>> From<Option<Vec<V>>> for UserValue
where
Value: From<V>
{
fn from(value: Option<Vec<V>>) -> Self {
let mapped = value.map(|value| Value::Array(value.into_iter().map(Value::from).collect()));
UserValue(mapped)
}
}
impl From<UserValue> for Option<Value> {
fn from(value: UserValue) -> Self {
value.0
}
}

use std::collections::HashMap;
impl QueryArgs for HashMap<&str, UserValue> {
fn encode(&self, encoder: &mut Encoder) -> Result<(), Error> {
let target_shape = {
let root_pos = encoder.ctx.root_pos.ok_or_else(|| {
let msg = format!(
"provided {} positional arguments, but no arguments expected by the server",
self.len()
);
ClientEncodingError::with_message(msg)
})?;
match encoder.ctx.get(root_pos)? {
Descriptor::ObjectShape(shape) => shape,
_ => return Err(ClientEncodingError::with_message("query didn't expect named arguments"))
}
};

let mut mapped_shapes: Vec<ShapeElement> = Vec::new();
let mut field_values: Vec<Option<Value>> = Vec::new();

for target_shape in target_shape.elements.iter() {
let user_value = self.get(target_shape.name.as_str());

if let Some(value) = user_value {
// these structs are actually from different crates
mapped_shapes.push(ShapeElement {
name: target_shape.name.clone(),
cardinality: target_shape.cardinality,
flag_implicit: target_shape.flag_implicit,
flag_link: target_shape.flag_link,
flag_link_property: target_shape.flag_link_property
});

field_values.push(value.0.clone());
continue;
}

let error_message = format!("argument for {} missing", target_shape.name);
return Err(ClientEncodingError::with_message(error_message));
}

Value::Object {
shape: ObjectShape::new(mapped_shapes),
fields: field_values
}
.encode(encoder)
}
}

#[cfg(feature = "macros")]
#[macro_export]
macro_rules! eargs {
($($key:expr => $value:expr,)+) => { $crate::eargs!($($key => $value),+) };
($($key:expr => $value:expr),*) => {
{
const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]);
let mut map = std::collections::HashMap::with_capacity(CAP);
$(
map.insert($key, $crate::query_arg::UserValue::from($value));
)*
map
}
};
}