Rework to implement `InputType` and `OutputType` for `HashMap` and `BTreeMap`.

This commit is contained in:
Sunli 2021-12-07 11:28:43 +08:00
parent eb27b0856e
commit f718df1a42
4 changed files with 159 additions and 26 deletions

View File

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [3.0.13] 2021-12-06
- No longer assumes that a subscription stream that failed to resolve has ended. [#744](https://github.com/async-graphql/async-graphql/issues/744)
- Rework to implement `InputType` and `OutputType` for `HashMap` and `BTreeMap`.
## [3.0.12] 2021-12-05

View File

@ -1,31 +1,54 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt::Display;
use std::str::FromStr;
use async_graphql_parser::types::Field;
use async_graphql_parser::Positioned;
use async_graphql_value::{from_value, to_value};
use indexmap::IndexMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::registry::{MetaType, Registry};
use crate::{
InputType, InputValueError, InputValueResult, Name, OutputType, Scalar, ScalarType, Value,
ContextSelectionSet, InputType, InputValueError, InputValueResult, Name, OutputType,
ServerResult, Value,
};
/// A scalar that can represent any JSON Object value.
#[Scalar(internal, name = "JSONObject")]
impl<K, T> ScalarType for BTreeMap<K, T>
impl<K, V> InputType for BTreeMap<K, V>
where
K: ToString + FromStr + Ord + Sync + Send,
K: ToString + FromStr + Ord + Send + Sync,
K::Err: Display,
T: OutputType + InputType,
V: Serialize + DeserializeOwned + Send + Sync,
{
fn parse(value: Value) -> InputValueResult<Self> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("JSONObject")
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_input_type::<Self, _>(|_| MetaType::Scalar {
name: <Self as InputType>::type_name().to_string(),
description: Some("A scalar that can represent any JSON Object value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,
})
}
fn parse(value: Option<Value>) -> InputValueResult<Self> {
let value = value.unwrap_or_default();
match value {
Value::Object(map) => map
.into_iter()
.map(|(name, value)| {
Ok((
K::from_str(&name).map_err(|err| {
InputValueError::custom(format!("object key: {}", err))
InputValueError::<Self>::custom(format!("object key: {}", err))
})?,
T::parse(Some(value))?,
from_value(value).map_err(|err| format!("object value: {}", err))?,
))
})
.collect::<Result<_, _>>()
@ -37,8 +60,51 @@ where
fn to_value(&self) -> Value {
let mut map = IndexMap::new();
for (name, value) in self {
map.insert(Name::new(name.to_string()), value.to_value());
map.insert(
Name::new(name.to_string()),
to_value(value).unwrap_or_default(),
);
}
Value::Object(map)
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]
impl<K, V> OutputType for BTreeMap<K, V>
where
K: ToString + Ord + Send + Sync,
V: Serialize + Send + Sync,
{
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("JSONObject")
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<Self, _>(|_| MetaType::Scalar {
name: <Self as OutputType>::type_name().to_string(),
description: Some("A scalar that can represent any JSON Object value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,
})
}
async fn resolve(
&self,
_ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>,
) -> ServerResult<Value> {
let mut map = IndexMap::new();
for (name, value) in self {
map.insert(
Name::new(name.to_string()),
to_value(value).unwrap_or_default(),
);
}
Ok(Value::Object(map))
}
}

View File

@ -1,32 +1,55 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Display;
use std::hash::Hash;
use std::str::FromStr;
use async_graphql_parser::types::Field;
use async_graphql_parser::Positioned;
use async_graphql_value::{from_value, to_value};
use indexmap::IndexMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::registry::{MetaType, Registry};
use crate::{
InputType, InputValueError, InputValueResult, Name, OutputType, Scalar, ScalarType, Value,
ContextSelectionSet, InputType, InputValueError, InputValueResult, Name, OutputType,
ServerResult, Value,
};
/// A scalar that can represent any JSON Object value.
#[Scalar(internal, name = "JSONObject")]
impl<K, V> ScalarType for HashMap<K, V>
impl<K, V> InputType for HashMap<K, V>
where
K: ToString + FromStr + Eq + Hash + Sync + Send,
K: ToString + FromStr + Eq + Hash + Send + Sync,
K::Err: Display,
V: OutputType + InputType,
V: Serialize + DeserializeOwned + Send + Sync,
{
fn parse(value: Value) -> InputValueResult<Self> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("JSONObject")
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_input_type::<Self, _>(|_| MetaType::Scalar {
name: <Self as InputType>::type_name().to_string(),
description: Some("A scalar that can represent any JSON Object value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,
})
}
fn parse(value: Option<Value>) -> InputValueResult<Self> {
let value = value.unwrap_or_default();
match value {
Value::Object(map) => map
.into_iter()
.map(|(name, value)| {
Ok((
K::from_str(&name).map_err(|err| {
InputValueError::custom(format!("object key: {}", err))
InputValueError::<Self>::custom(format!("object key: {}", err))
})?,
V::parse(Some(value))?,
from_value(value).map_err(|err| format!("object value: {}", err))?,
))
})
.collect::<Result<_, _>>()
@ -38,8 +61,51 @@ where
fn to_value(&self) -> Value {
let mut map = IndexMap::new();
for (name, value) in self {
map.insert(Name::new(name.to_string()), value.to_value());
map.insert(
Name::new(name.to_string()),
to_value(value).unwrap_or_default(),
);
}
Value::Object(map)
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]
impl<K, V> OutputType for HashMap<K, V>
where
K: ToString + Eq + Hash + Send + Sync,
V: Serialize + Send + Sync,
{
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("JSONObject")
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<Self, _>(|_| MetaType::Scalar {
name: <Self as OutputType>::type_name().to_string(),
description: Some("A scalar that can represent any JSON Object value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,
})
}
async fn resolve(
&self,
_ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>,
) -> ServerResult<Value> {
let mut map = IndexMap::new();
for (name, value) in self {
map.insert(
Name::new(name.to_string()),
to_value(value).unwrap_or_default(),
);
}
Ok(Value::Object(map))
}
}

View File

@ -46,9 +46,9 @@ impl<T: DeserializeOwned + Serialize + Send + Sync> InputType for Json<T> {
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<Json<T>, _>(|_| MetaType::Scalar {
registry.create_input_type::<Json<T>, _>(|_| MetaType::Scalar {
name: <Self as InputType>::type_name().to_string(),
description: None,
description: Some("A scalar that can represent any JSON value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,
@ -77,7 +77,7 @@ impl<T: Serialize + Send + Sync> OutputType for Json<T> {
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<Json<T>, _>(|_| MetaType::Scalar {
name: <Self as OutputType>::type_name().to_string(),
description: None,
description: Some("A scalar that can represent any JSON value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,
@ -101,9 +101,9 @@ impl InputType for serde_json::Value {
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<serde_json::Value, _>(|_| MetaType::Scalar {
registry.create_input_type::<serde_json::Value, _>(|_| MetaType::Scalar {
name: <Self as InputType>::type_name().to_string(),
description: None,
description: Some("A scalar that can represent any JSON value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,
@ -132,7 +132,7 @@ impl OutputType for serde_json::Value {
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<serde_json::Value, _>(|_| MetaType::Scalar {
name: <Self as OutputType>::type_name().to_string(),
description: None,
description: Some("A scalar that can represent any JSON value."),
is_valid: |_| true,
visible: None,
specified_by_url: None,