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:
Sunli 2020-09-14 16:17:53 +08:00
parent 99ff3a8b96
commit 42817a3aa6
16 changed files with 834 additions and 113 deletions

35
src/types/external/char.rs vendored Normal file
View 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())
}
}

View 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)
}
}

View 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
View File

@ -0,0 +1,2 @@
mod btreemap;
mod hashmap;

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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())
}
}

View File

@ -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
View 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))
}
}

View File

@ -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 }
}
})
);
}
}

View File

@ -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],