diff --git a/CHANGELOG.md b/CHANGELOG.md index 79138b8c..31b7d253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- Moved `Variables` from `async_graphql::context::Variables` to `async_graphql::Variables`. + ## [2.5.8] - 2021-02-27 +### Added + - Allow the `deprecation` attribute to have no reason. ```rust @@ -27,10 +35,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.5.7] - 2021-02-23 +### Fixed + - Fix the problem that the borrowing lifetime returned by the `Context::data` function is too small. ## [2.5.6] - 2021-02-23 +### Changed + - When introspection is disabled, introspection related types are no longer registered. ## [2.5.5] - 2021-02-22 diff --git a/src/context.rs b/src/context.rs index 191e6ea4..41190c7d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,17 +1,15 @@ //! Query context. use std::any::{Any, TypeId}; -use std::collections::{BTreeMap, HashMap}; -use std::convert::TryFrom; +use std::collections::HashMap; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Deref; use std::sync::atomic::AtomicUsize; use std::sync::Arc; -use async_graphql_value::Value as InputValue; +use async_graphql_value::{Value as InputValue, Variables}; use fnv::FnvHashMap; use http::header::{AsHeaderName, HeaderMap, IntoHeaderName}; -use serde::de::{Deserialize, Deserializer}; use serde::ser::{SerializeSeq, Serializer}; use serde::Serialize; @@ -25,81 +23,6 @@ use crate::{ UploadValue, Value, }; -/// Variables of a query. -#[derive(Debug, Clone, Default, Serialize)] -#[serde(transparent)] -pub struct Variables(pub BTreeMap); - -impl Display for Variables { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str("{")?; - for (i, (name, value)) in self.0.iter().enumerate() { - write!(f, "{}{}: {}", if i == 0 { "" } else { ", " }, name, value)?; - } - f.write_str("}") - } -} - -impl<'de> Deserialize<'de> for Variables { - fn deserialize>(deserializer: D) -> Result { - Ok(Self( - >>::deserialize(deserializer)?.unwrap_or_default(), - )) - } -} - -impl Variables { - /// Get the variables from a GraphQL value. - /// - /// If the value is not a map, then no variables will be returned. - #[must_use] - pub fn from_value(value: Value) -> Self { - match value { - Value::Object(obj) => Self(obj), - _ => Self::default(), - } - } - - /// Get the values from a JSON value. - /// - /// If the value is not a map or the keys of a map are not valid GraphQL names, then no - /// variables will be returned. - #[must_use] - pub fn from_json(value: serde_json::Value) -> Self { - Value::from_json(value) - .map(Self::from_value) - .unwrap_or_default() - } - - /// Get the variables as a GraphQL value. - #[must_use] - pub fn into_value(self) -> Value { - Value::Object(self.0) - } - - pub(crate) fn variable_path(&mut self, path: &str) -> Option<&mut Value> { - let mut parts = path.strip_prefix("variables.")?.split('.'); - - let initial = self.0.get_mut(parts.next().unwrap())?; - - parts.try_fold(initial, |current, part| match current { - Value::List(list) => part - .parse::() - .ok() - .and_then(|idx| usize::try_from(idx).ok()) - .and_then(move |idx| list.get_mut(idx)), - Value::Object(obj) => obj.get_mut(part), - _ => None, - }) - } -} - -impl From for Value { - fn from(variables: Variables) -> Self { - variables.into_value() - } -} - /// Schema/Context data. /// /// This is a type map, allowing you to store anything inside it. @@ -574,7 +497,6 @@ impl<'a, T> ContextBase<'a, T> { .and_then(|def| { self.query_env .variables - .0 .get(&def.node.name.node) .or_else(|| def.node.default_value()) }) diff --git a/src/lib.rs b/src/lib.rs index 52044e57..34ff8d78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,7 +208,7 @@ pub use subscription::SubscriptionType; pub use async_graphql_parser as parser; pub use async_graphql_value::{ from_value, to_value, value, ConstValue as Value, DeserializerError, Name, Number, - SerializerError, + SerializerError, Variables, }; pub use base::{ Description, InputObjectType, InputType, InterfaceType, ObjectType, OutputType, Type, UnionType, diff --git a/src/request.rs b/src/request.rs index d510c189..60319c32 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,5 +1,6 @@ use std::any::Any; use std::collections::HashMap; +use std::convert::TryFrom; use std::fmt::{self, Debug, Formatter}; use serde::{Deserialize, Deserializer, Serialize}; @@ -79,7 +80,23 @@ impl Request { /// `request.variables["files"][2]["content"]`. If no variable exists at the path this function /// won't do anything. pub fn set_upload(&mut self, var_path: &str, upload: UploadValue) { - let variable = match self.variables.variable_path(var_path) { + fn variable_path<'a>(variables: &'a mut Variables, path: &str) -> Option<&'a mut Value> { + let mut parts = path.strip_prefix("variables.")?.split('.'); + + let initial = variables.get_mut(parts.next().unwrap())?; + + parts.try_fold(initial, |current, part| match current { + Value::List(list) => part + .parse::() + .ok() + .and_then(|idx| usize::try_from(idx).ok()) + .and_then(move |idx| list.get_mut(idx)), + Value::Object(obj) => obj.get_mut(part), + _ => None, + }) + } + + let variable = match variable_path(&mut self.variables, var_path) { Some(variable) => variable, None => return, }; @@ -171,7 +188,7 @@ mod tests { "query": "{ a b c }" })) .unwrap(); - assert!(request.variables.0.is_empty()); + assert!(request.variables.is_empty()); assert!(request.operation_name.is_none()); assert_eq!(request.query, "{ a b c }"); } @@ -183,7 +200,7 @@ mod tests { "operationName": "a" })) .unwrap(); - assert!(request.variables.0.is_empty()); + assert!(request.variables.is_empty()); assert_eq!(request.operation_name.as_deref(), Some("a")); assert_eq!(request.query, "{ a b c }"); } @@ -219,7 +236,7 @@ mod tests { })) .unwrap(); assert!(request.operation_name.is_none()); - assert!(request.variables.0.is_empty()); + assert!(request.variables.is_empty()); } #[test] @@ -230,7 +247,7 @@ mod tests { .unwrap(); if let BatchRequest::Single(request) = request { - assert!(request.variables.0.is_empty()); + assert!(request.variables.is_empty()); assert!(request.operation_name.is_none()); assert_eq!(request.query, "{ a b c }"); } else { @@ -251,11 +268,11 @@ mod tests { .unwrap(); if let BatchRequest::Batch(requests) = request { - assert!(requests[0].variables.0.is_empty()); + assert!(requests[0].variables.is_empty()); assert!(requests[0].operation_name.is_none()); assert_eq!(requests[0].query, "{ a b c }"); - assert!(requests[1].variables.0.is_empty()); + assert!(requests[1].variables.is_empty()); assert!(requests[1].operation_name.is_none()); assert_eq!(requests[1].query, "{ d e }"); } else { diff --git a/src/validation/rules/arguments_of_correct_type.rs b/src/validation/rules/arguments_of_correct_type.rs index 32618dc2..9c3a4ef8 100644 --- a/src/validation/rules/arguments_of_correct_type.rs +++ b/src/validation/rules/arguments_of_correct_type.rs @@ -49,7 +49,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> { .clone() .into_const_with(|var_name| { ctx.variables - .and_then(|variables| variables.0.get(&var_name)) + .and_then(|variables| variables.get(&var_name)) .map(Clone::clone) .ok_or(()) }) diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index 3703a2da..b1947cce 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -118,7 +118,6 @@ impl<'a> VisitorContext<'a> { .and_then(|def| { if let Some(variables) = self.variables { variables - .0 .get(&def.node.name.node) .or_else(|| def.node.default_value()) } else { diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 52dac256..4977f4bb 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -1688,9 +1688,7 @@ pub async fn test_input_validator_variable() { let validator_length = 6; for case in &test_cases { let mut variables = Variables::default(); - variables - .0 - .insert(Name::new("id"), Value::String(case.to_string())); + variables.insert(Name::new("id"), Value::String(case.to_string())); let field_query = "query($id: String!) {fieldParameter(id: $id)}"; let object_query = "query($id: String!) {inputObject(input: {id: $id})}"; diff --git a/value/src/lib.rs b/value/src/lib.rs index bd220a3f..127ecd76 100644 --- a/value/src/lib.rs +++ b/value/src/lib.rs @@ -6,6 +6,7 @@ mod de; mod macros; mod ser; +mod variables; use std::borrow::{Borrow, Cow}; use std::collections::BTreeMap; @@ -22,6 +23,8 @@ pub use de::{from_value, DeserializerError}; pub use ser::{to_value, SerializerError}; pub use serde_json::Number; +pub use variables::Variables; + /// A GraphQL name. /// /// [Reference](https://spec.graphql.org/June2018/#Name). diff --git a/value/src/variables.rs b/value/src/variables.rs new file mode 100644 index 00000000..7e9d7d1e --- /dev/null +++ b/value/src/variables.rs @@ -0,0 +1,80 @@ +use std::collections::BTreeMap; +use std::fmt::{self, Display, Formatter}; +use std::ops::{Deref, DerefMut}; + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::{ConstValue, Name}; + +/// Variables of a query. +#[derive(Debug, Clone, Default, Serialize)] +#[serde(transparent)] +pub struct Variables(BTreeMap); + +impl Display for Variables { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("{")?; + for (i, (name, value)) in self.0.iter().enumerate() { + write!(f, "{}{}: {}", if i == 0 { "" } else { ", " }, name, value)?; + } + f.write_str("}") + } +} + +impl<'de> Deserialize<'de> for Variables { + fn deserialize>(deserializer: D) -> Result { + Ok(Self( + >>::deserialize(deserializer)?.unwrap_or_default(), + )) + } +} + +impl Variables { + /// Get the variables from a GraphQL value. + /// + /// If the value is not a map, then no variables will be returned. + #[must_use] + pub fn from_value(value: ConstValue) -> Self { + match value { + ConstValue::Object(obj) => Self(obj), + _ => Self::default(), + } + } + + /// Get the values from a JSON value. + /// + /// If the value is not a map or the keys of a map are not valid GraphQL names, then no + /// variables will be returned. + #[must_use] + pub fn from_json(value: serde_json::Value) -> Self { + ConstValue::from_json(value) + .map(Self::from_value) + .unwrap_or_default() + } + + /// Get the variables as a GraphQL value. + #[must_use] + pub fn into_value(self) -> ConstValue { + ConstValue::Object(self.0) + } +} + +impl From for ConstValue { + fn from(variables: Variables) -> Self { + variables.into_value() + } +} + +impl Deref for Variables { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Variables { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +}