Implement Type for more stdlib collection types #189
Implement InputValueType/OutputValueType for HashSet<T>/BTreeSet<T>/VecDeque<T>/LinkedList<T> Implement ScalarType for char/NonZero*/HashMap<String,T>/BTreeMap<String, T>
This commit is contained in:
parent
99ff3a8b96
commit
42817a3aa6
35
src/types/external/char.rs
vendored
Normal file
35
src/types/external/char.rs
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
use crate::{GQLScalar, InputValueError, InputValueResult, ScalarType, Value};
|
||||
|
||||
/// The `Char` scalar type represents a unicode char.
|
||||
/// The input and output values are a string, and there can only be one unicode character in this string.
|
||||
#[GQLScalar(internal)]
|
||||
impl ScalarType for char {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::String(s) => {
|
||||
let mut chars = s.chars();
|
||||
match chars.next() {
|
||||
Some(ch) if chars.next() == None => Ok(ch),
|
||||
Some(_) => Err(InputValueError::Custom(
|
||||
"There can only be one unicode character in the string.".into(),
|
||||
)),
|
||||
None => Err(InputValueError::Custom(
|
||||
"A unicode character is required.".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::String(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::String((*self).into())
|
||||
}
|
||||
}
|
36
src/types/external/json_object/btreemap.rs
vendored
Normal file
36
src/types/external/json_object/btreemap.rs
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::parser::types::Name;
|
||||
use crate::{
|
||||
GQLScalar, InputValueError, InputValueResult, InputValueType, OutputValueType, ScalarType,
|
||||
Value,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A scalar that can represent any JSON Object value.
|
||||
#[GQLScalar(internal, name = "JSONObject")]
|
||||
impl<T> ScalarType for BTreeMap<String, T>
|
||||
where
|
||||
T: OutputValueType + InputValueType + Send + Sync,
|
||||
{
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let mut result = BTreeMap::new();
|
||||
for (name, value) in map {
|
||||
result.insert(name.to_string(), T::parse(Some(value))?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
let mut map = BTreeMap::new();
|
||||
for (name, value) in self {
|
||||
if let Ok(name) = Name::new(name.clone()) {
|
||||
map.insert(name, value.to_value());
|
||||
}
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
}
|
36
src/types/external/json_object/hashmap.rs
vendored
Normal file
36
src/types/external/json_object/hashmap.rs
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::parser::types::Name;
|
||||
use crate::{
|
||||
GQLScalar, InputValueError, InputValueResult, InputValueType, OutputValueType, ScalarType,
|
||||
Value,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
/// A scalar that can represent any JSON Object value.
|
||||
#[GQLScalar(internal, name = "JSONObject")]
|
||||
impl<T> ScalarType for HashMap<String, T>
|
||||
where
|
||||
T: OutputValueType + InputValueType + Send + Sync,
|
||||
{
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let mut result = HashMap::new();
|
||||
for (name, value) in map {
|
||||
result.insert(name.to_string(), T::parse(Some(value))?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
let mut map = BTreeMap::new();
|
||||
for (name, value) in self {
|
||||
if let Ok(name) = Name::new(name.clone()) {
|
||||
map.insert(name, value.to_value());
|
||||
}
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
}
|
2
src/types/external/json_object/mod.rs
vendored
Normal file
2
src/types/external/json_object/mod.rs
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod btreemap;
|
||||
mod hashmap;
|
106
src/types/external/list.rs
vendored
106
src/types/external/list.rs
vendored
|
@ -1,106 +0,0 @@
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
|
||||
Result, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<T: Type> Type for Vec<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InputValueType> InputValueType for Vec<T> {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
Value::List(values) => {
|
||||
let mut result = Vec::new();
|
||||
for elem_value in values {
|
||||
result.push(InputValueType::parse(Some(elem_value))?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
value => Ok(vec![InputValueType::parse(Some(value))?]),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::List(self.iter().map(InputValueType::to_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for Vec<T> {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in self.iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Type + 'a> Type for &'a [T] {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for &[T] {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in (*self).iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Type;
|
||||
|
||||
#[test]
|
||||
fn test_list_type() {
|
||||
assert_eq!(Vec::<i32>::type_name(), "[Int!]");
|
||||
assert_eq!(Vec::<Option<i32>>::type_name(), "[Int]");
|
||||
assert_eq!(Option::<Vec::<Option<i32>>>::type_name(), "[Int]");
|
||||
|
||||
assert_eq!(Vec::<i32>::qualified_type_name(), "[Int!]!");
|
||||
assert_eq!(Vec::<Option<i32>>::qualified_type_name(), "[Int]!");
|
||||
assert_eq!(Option::<Vec::<Option<i32>>>::qualified_type_name(), "[Int]");
|
||||
|
||||
assert_eq!(<&[i32] as Type>::qualified_type_name(), "[Int!]!");
|
||||
}
|
||||
}
|
61
src/types/external/list/btree_set.rs
vendored
Normal file
61
src/types/external/list/btree_set.rs
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
|
||||
Result, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
impl<T: Type> Type for BTreeSet<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InputValueType + Ord> InputValueType for BTreeSet<T> {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
Value::List(values) => {
|
||||
let mut result = Self::default();
|
||||
for elem_value in values {
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(elem_value))?));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
value => Ok({
|
||||
let mut result = Self::default();
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(value))?));
|
||||
result
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::List(self.iter().map(InputValueType::to_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync + Ord> OutputValueType for BTreeSet<T> {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in self.iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
63
src/types/external/list/hash_set.rs
vendored
Normal file
63
src/types/external/list/hash_set.rs
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
|
||||
Result, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Eq;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
|
||||
impl<T: Type> Type for HashSet<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InputValueType + Hash + Eq> InputValueType for HashSet<T> {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
Value::List(values) => {
|
||||
let mut result = Self::default();
|
||||
for elem_value in values {
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(elem_value))?));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
value => Ok({
|
||||
let mut result = Self::default();
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(value))?));
|
||||
result
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::List(self.iter().map(InputValueType::to_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync + Hash + Eq> OutputValueType for HashSet<T> {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in self.iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
61
src/types/external/list/linked_list.rs
vendored
Normal file
61
src/types/external/list/linked_list.rs
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
|
||||
Result, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::LinkedList;
|
||||
|
||||
impl<T: Type> Type for LinkedList<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InputValueType> InputValueType for LinkedList<T> {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
Value::List(values) => {
|
||||
let mut result = Self::default();
|
||||
for elem_value in values {
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(elem_value))?));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
value => Ok({
|
||||
let mut result = Self::default();
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(value))?));
|
||||
result
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::List(self.iter().map(InputValueType::to_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for LinkedList<T> {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in self.iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
6
src/types/external/list/mod.rs
vendored
Normal file
6
src/types/external/list/mod.rs
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod btree_set;
|
||||
mod hash_set;
|
||||
mod linked_list;
|
||||
mod slice;
|
||||
mod vec;
|
||||
mod vec_deque;
|
34
src/types/external/list/slice.rs
vendored
Normal file
34
src/types/external/list/slice.rs
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
use crate::parser::types::Field;
|
||||
use crate::{registry, ContextSelectionSet, OutputValueType, Positioned, Result, Type};
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<'a, T: Type + 'a> Type for &'a [T] {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for &[T] {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in (*self).iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
60
src/types/external/list/vec.rs
vendored
Normal file
60
src/types/external/list/vec.rs
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
|
||||
Result, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<T: Type> Type for Vec<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InputValueType> InputValueType for Vec<T> {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
Value::List(values) => {
|
||||
let mut result = Self::default();
|
||||
for elem_value in values {
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(elem_value))?));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
value => Ok({
|
||||
let mut result = Self::default();
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(value))?));
|
||||
result
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::List(self.iter().map(InputValueType::to_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for Vec<T> {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in self.iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
61
src/types/external/list/vec_deque.rs
vendored
Normal file
61
src/types/external/list/vec_deque.rs
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::parser::types::Field;
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
|
||||
Result, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
impl<T: Type> Type for VecDeque<T> {
|
||||
fn type_name() -> Cow<'static, str> {
|
||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||
}
|
||||
|
||||
fn qualified_type_name() -> String {
|
||||
format!("[{}]!", T::qualified_type_name())
|
||||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
T::create_type_info(registry);
|
||||
Self::qualified_type_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InputValueType> InputValueType for VecDeque<T> {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
Value::List(values) => {
|
||||
let mut result = Self::default();
|
||||
for elem_value in values {
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(elem_value))?));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
value => Ok({
|
||||
let mut result = Self::default();
|
||||
result.extend(std::iter::once(InputValueType::parse(Some(value))?));
|
||||
result
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::List(self.iter().map(InputValueType::to_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for VecDeque<T> {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(self.len());
|
||||
for (idx, item) in self.iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, field).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
}
|
3
src/types/external/mod.rs
vendored
3
src/types/external/mod.rs
vendored
|
@ -1,10 +1,13 @@
|
|||
//! Implementations of `Type`, `ScalarType`, etc on external types.
|
||||
|
||||
mod bool;
|
||||
mod char;
|
||||
mod datetime;
|
||||
mod floats;
|
||||
mod integers;
|
||||
mod json_object;
|
||||
mod list;
|
||||
mod non_zero_integers;
|
||||
mod optional;
|
||||
mod string;
|
||||
mod uuid;
|
||||
|
|
276
src/types/external/non_zero_integers.rs
vendored
Normal file
276
src/types/external/non_zero_integers.rs
vendored
Normal file
|
@ -0,0 +1,276 @@
|
|||
use crate::{GQLScalar, InputValueError, InputValueResult, ScalarType, Value};
|
||||
use std::num::{
|
||||
NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8,
|
||||
};
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroI8 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_i64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n < i8::MIN as i64 || n > i8::MAX as i64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
i8::MIN,
|
||||
i8::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroI8::new(n as i8).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_i64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as i64))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroI16 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_i64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n < i16::MIN as i64 || n > i16::MAX as i64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
i16::MIN,
|
||||
i16::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroI16::new(n as i16).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_i64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as i64))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroI32 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_i64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n < i32::MIN as i64 || n > i32::MAX as i64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
i32::MIN,
|
||||
i32::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroI32::new(n as i32).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_i64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as i64))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroI64 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_i64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n < i64::MIN as i64 || n > i64::MAX as i64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
i64::MIN,
|
||||
i64::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroI64::new(n as i64).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_i64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as i64))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroU8 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_u64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n > u8::MAX as u64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
1,
|
||||
u8::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroU8::new(n as u8).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_u64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as u64))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroU16 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_u64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n > u16::MAX as u64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
1,
|
||||
u16::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroU16::new(n as u16).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_u64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as u64))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroU32 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_u64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n > u32::MAX as u64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
1,
|
||||
u32::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroU32::new(n as u32).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_u64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as u64))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Int` scalar type represents non-fractional whole numeric values.
|
||||
#[GQLScalar(internal, name = "Int")]
|
||||
impl ScalarType for NonZeroU64 {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::Number(n) => {
|
||||
let n = n
|
||||
.as_u64()
|
||||
.ok_or_else(|| InputValueError::from("Invalid number"))?;
|
||||
if n > u64::MAX as u64 || n == 0 {
|
||||
return Err(InputValueError::from(format!(
|
||||
"Only integers from {} to {} or non zero are accepted.",
|
||||
1,
|
||||
u64::MAX
|
||||
)));
|
||||
}
|
||||
Ok(NonZeroU64::new(n as u64).unwrap())
|
||||
}
|
||||
_ => Err(InputValueError::ExpectedType(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(value: &Value) -> bool {
|
||||
match value {
|
||||
Value::Number(n) if n.is_u64() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Number(serde_json::Number::from(self.get() as u64))
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ impl<T> DerefMut for Json<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Json<T> {
|
||||
impl<T: DeserializeOwned + Serialize> From<T> for Json<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ impl<T> DerefMut for OutputJson<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for OutputJson<T> {
|
||||
impl<T: Serialize> From<T> for OutputJson<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
@ -137,4 +137,46 @@ mod test {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_output_json_type() {
|
||||
#[derive(Serialize)]
|
||||
struct MyStruct {
|
||||
a: i32,
|
||||
b: i32,
|
||||
c: HashMap<String, i32>,
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[GQLObject(internal)]
|
||||
impl Query {
|
||||
async fn obj(&self) -> OutputJson<MyStruct> {
|
||||
MyStruct {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: {
|
||||
let mut values = HashMap::new();
|
||||
values.insert("a".to_string(), 11);
|
||||
values.insert("b".to_string(), 22);
|
||||
values
|
||||
},
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
let query = r#"{ obj }"#;
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
assert_eq!(
|
||||
schema.execute(query).await.into_result().unwrap().data,
|
||||
serde_json::json!({
|
||||
"obj": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": { "a": 11, "b": 22 }
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use async_graphql::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeSet, HashSet, LinkedList, VecDeque};
|
||||
|
||||
#[async_std::test]
|
||||
pub async fn test_list_type() {
|
||||
|
@ -8,17 +10,37 @@ pub async fn test_list_type() {
|
|||
}
|
||||
|
||||
struct Root {
|
||||
value: Vec<i32>,
|
||||
value_vec: Vec<i32>,
|
||||
value_hash_set: HashSet<i32>,
|
||||
value_btree_set: BTreeSet<i32>,
|
||||
value_linked_list: LinkedList<i32>,
|
||||
value_vec_deque: VecDeque<i32>,
|
||||
}
|
||||
|
||||
#[GQLObject]
|
||||
impl Root {
|
||||
async fn value_vec(&self) -> Vec<i32> {
|
||||
self.value.clone()
|
||||
self.value_vec.clone()
|
||||
}
|
||||
|
||||
async fn value_slice(&self) -> &[i32] {
|
||||
&self.value
|
||||
&self.value_vec
|
||||
}
|
||||
|
||||
async fn value_linked_list(&self) -> LinkedList<i32> {
|
||||
self.value_linked_list.clone()
|
||||
}
|
||||
|
||||
async fn value_hash_set(&self) -> HashSet<i32> {
|
||||
self.value_hash_set.clone()
|
||||
}
|
||||
|
||||
async fn value_btree_set(&self) -> BTreeSet<i32> {
|
||||
self.value_btree_set.clone()
|
||||
}
|
||||
|
||||
async fn value_vec_deque(&self) -> VecDeque<i32> {
|
||||
self.value_vec_deque.clone()
|
||||
}
|
||||
|
||||
async fn value_input_slice(&self, a: Vec<i32>) -> Vec<i32> {
|
||||
|
@ -36,7 +58,11 @@ pub async fn test_list_type() {
|
|||
|
||||
let schema = Schema::new(
|
||||
Root {
|
||||
value: vec![1, 2, 3, 4, 5],
|
||||
value_vec: vec![1, 2, 3, 4, 5],
|
||||
value_hash_set: vec![1, 2, 3, 4, 5].into_iter().collect(),
|
||||
value_btree_set: vec![1, 2, 3, 4, 5].into_iter().collect(),
|
||||
value_linked_list: vec![1, 2, 3, 4, 5].into_iter().collect(),
|
||||
value_vec_deque: vec![1, 2, 3, 4, 5].into_iter().collect(),
|
||||
},
|
||||
EmptyMutation,
|
||||
EmptySubscription,
|
||||
|
@ -46,6 +72,10 @@ pub async fn test_list_type() {
|
|||
r#"{{
|
||||
valueVec
|
||||
valueSlice
|
||||
valueLinkedList
|
||||
valueHashSet
|
||||
valueBtreeSet
|
||||
valueVecDeque
|
||||
testArg(input: {0})
|
||||
testInput(input: {{value: {0}}})
|
||||
valueInputSlice1: valueInputSlice(a: [1, 2, 3])
|
||||
|
@ -54,11 +84,32 @@ pub async fn test_list_type() {
|
|||
"#,
|
||||
json_value
|
||||
);
|
||||
let mut res = schema.execute(&query).await.data;
|
||||
|
||||
if let serde_json::Value::Object(obj) = &mut res {
|
||||
if let Some(value_hash_set) = obj.get_mut("valueHashSet") {
|
||||
if let serde_json::Value::Array(array) = value_hash_set {
|
||||
array.sort_by(|a, b| {
|
||||
if let (serde_json::Value::Number(a), serde_json::Value::Number(b)) = (a, b) {
|
||||
if let (Some(a), Some(b)) = (a.as_i64(), b.as_i64()) {
|
||||
return a.cmp(&b);
|
||||
}
|
||||
}
|
||||
Ordering::Less
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.data,
|
||||
res,
|
||||
serde_json::json!({
|
||||
"valueVec": vec![1, 2, 3, 4, 5],
|
||||
"valueSlice": vec![1, 2, 3, 4, 5],
|
||||
"valueLinkedList": vec![1, 2, 3, 4, 5],
|
||||
"valueHashSet": vec![1, 2, 3, 4, 5],
|
||||
"valueBtreeSet": vec![1, 2, 3, 4, 5],
|
||||
"valueVecDeque": vec![1, 2, 3, 4, 5],
|
||||
"testArg": vec![1, 2, 3, 4, 5],
|
||||
"testInput": vec![1, 2, 3, 4, 5],
|
||||
"valueInputSlice1": vec![1, 2, 3],
|
||||
|
|
Loading…
Reference in New Issue
Block a user