Add `Union` and `Interface` support for trait objects. #780

Clippy clean
This commit is contained in:
Sunli 2022-01-11 09:35:09 +08:00
parent 5d99df4502
commit 707890e551
15 changed files with 118 additions and 4 deletions

View File

@ -4,6 +4,10 @@ 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).
## [3.0.21] 2022-1-11
- Add `Union` and `Interface` support for trait objects. [#780](https://github.com/async-graphql/async-graphql/issues/780)
## [3.0.20] 2022-1-5
- Bump `lru` to `0.7.1`. [#773](https://github.com/async-graphql/async-graphql/pull/773)

View File

@ -205,5 +205,6 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
impl #impl_generics #crate_name::UnionType for #ident #ty_generics #where_clause {}
};
Ok(expanded.into())
}

View File

@ -46,7 +46,7 @@ pub(super) fn block_string_value(raw: &str) -> String {
.iter()
.copied()
.position(line_has_content)
.unwrap_or_else(|| lines.len());
.unwrap_or(lines.len());
let ending_lines_start = lines
.iter()
.copied()

View File

@ -131,7 +131,13 @@ impl<T: OutputType + Sync, E: Into<Error> + Send + Sync + Clone> OutputType for
pub trait ObjectType: ContainerType {}
#[async_trait::async_trait]
impl<T: ObjectType> ObjectType for &T {}
impl<T: ObjectType + ?Sized> ObjectType for &T {}
#[async_trait::async_trait]
impl<T: ObjectType + ?Sized> ObjectType for Box<T> {}
#[async_trait::async_trait]
impl<T: ObjectType + ?Sized> ObjectType for Arc<T> {}
/// A GraphQL interface.
pub trait InterfaceType: ContainerType {}

View File

@ -530,6 +530,7 @@ impl<'a, T> ContextBase<'a, T> {
impl<'a> ContextBase<'a, &'a Positioned<SelectionSet>> {
#[doc(hidden)]
#[must_use]
pub fn with_index(&'a self, idx: usize) -> ContextBase<'a, &'a Positioned<SelectionSet>> {
ContextBase {
path_node: Some(QueryPathNode {

View File

@ -226,6 +226,7 @@ impl<T, C: CacheFactory> DataLoader<T, C> {
}
/// Specify the delay time for loading data, the default is `1ms`.
#[must_use]
pub fn delay(self, delay: Duration) -> Self {
Self { delay, ..self }
}
@ -233,6 +234,7 @@ impl<T, C: CacheFactory> DataLoader<T, C> {
/// pub fn Specify the max batch size for loading data, the default is `1000`.
///
/// If the keys waiting to be loaded reach the threshold, they are loaded immediately.
#[must_use]
pub fn max_batch_size(self, max_batch_size: usize) -> Self {
Self {
max_batch_size,

View File

@ -22,6 +22,7 @@ pub struct MultipartOptions {
impl MultipartOptions {
/// Set maximum file size.
#[must_use]
pub fn max_file_size(self, size: usize) -> Self {
MultipartOptions {
max_file_size: Some(size),
@ -30,6 +31,7 @@ impl MultipartOptions {
}
/// Set maximum number of files.
#[must_use]
pub fn max_num_files(self, n: usize) -> Self {
MultipartOptions {
max_num_files: Some(n),

View File

@ -581,12 +581,14 @@ impl<'a> GraphQLPlaygroundConfig<'a> {
}
/// Set subscription endpoint, for example: `ws://localhost:8000`.
#[must_use]
pub fn subscription_endpoint(mut self, endpoint: &'a str) -> Self {
self.subscription_endpoint = Some(endpoint);
self
}
/// Set HTTP header for per query.
#[must_use]
pub fn with_header(mut self, name: &'a str, value: &'a str) -> Self {
if let Some(headers) = &mut self.headers {
headers.insert(name, value);
@ -607,6 +609,7 @@ impl<'a> GraphQLPlaygroundConfig<'a> {
/// .with_setting("setting", false)
/// .with_setting("other", Value::Null);
/// ```
#[must_use]
pub fn with_setting(mut self, name: &'a str, value: impl Into<Value>) -> Self {
let value = value.into();

View File

@ -138,6 +138,7 @@ where
/// This data usually comes from HTTP requests.
/// When the `GQL_CONNECTION_INIT` message is received, this data will be merged with the data
/// returned by the closure specified by `with_initializer` into the final subscription context data.
#[must_use]
pub fn connection_data(mut self, data: Data) -> Self {
self.connection_data = Some(data);
self
@ -148,6 +149,7 @@ where
/// This function if present, will be called with the data sent by the client in the
/// [`GQL_CONNECTION_INIT` message](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init).
/// From that point on the returned data will be accessible to all requests.
#[must_use]
pub fn on_connection_init<F, R>(
self,
callback: F,

View File

@ -52,7 +52,7 @@ impl Registry {
writeln!(
sdl,
"\t\"\"\"\n\t{}\n\t\"\"\"",
field.description.unwrap().replace("\n", "\n\t")
field.description.unwrap().replace('\n', "\n\t")
)
.ok();
}

View File

@ -68,6 +68,7 @@ impl<'a> MetaTypeName<'a> {
}
#[inline]
#[must_use]
pub fn unwrap_non_null(&self) -> Self {
match self {
MetaTypeName::NonNull(ty) => MetaTypeName::create(ty),

View File

@ -74,6 +74,7 @@ impl Request {
}
/// Insert some data for this request.
#[must_use]
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
self.data.insert(data);
self
@ -184,6 +185,7 @@ impl BatchRequest {
}
/// Specify the variables for each requests.
#[must_use]
pub fn variables(mut self, variables: Variables) -> Self {
for request in self.iter_mut() {
request.variables = variables.clone();
@ -192,6 +194,7 @@ impl BatchRequest {
}
/// Insert some data for for each requests.
#[must_use]
pub fn data<D: Any + Clone + Send + Sync>(mut self, data: D) -> Self {
for request in self.iter_mut() {
request.data.insert(data.clone());
@ -200,6 +203,7 @@ impl BatchRequest {
}
/// Disable introspection queries for for each requests.
#[must_use]
pub fn disable_introspection(mut self) -> Self {
for request in self.iter_mut() {
request.disable_introspection = true;

View File

@ -1,6 +1,7 @@
use futures_util::FutureExt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use indexmap::IndexMap;
@ -51,7 +52,7 @@ pub trait ContainerType: OutputType {
}
#[async_trait::async_trait]
impl<T: ContainerType> ContainerType for &T {
impl<T: ContainerType + ?Sized> ContainerType for &T {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
T::resolve_field(*self, ctx).await
}
@ -61,6 +62,28 @@ impl<T: ContainerType> ContainerType for &T {
}
}
#[async_trait::async_trait]
impl<T: ContainerType + ?Sized> ContainerType for Arc<T> {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
T::resolve_field(self, ctx).await
}
async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
T::find_entity(self, ctx, params).await
}
}
#[async_trait::async_trait]
impl<T: ContainerType + ?Sized> ContainerType for Box<T> {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
T::resolve_field(self, ctx).await
}
async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
T::find_entity(self, ctx, params).await
}
}
/// Resolve an container by executing each of the fields concurrently.
pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
ctx: &ContextSelectionSet<'a>,

View File

@ -40,6 +40,7 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
/// Manually register a input type in the schema.
///
/// You can use this function to register schema types that are not directly referenced.
#[must_use]
pub fn register_input_type<T: InputType>(mut self) -> Self {
T::create_type_info(&mut self.registry);
self
@ -48,24 +49,28 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
/// Manually register a output type in the schema.
///
/// You can use this function to register schema types that are not directly referenced.
#[must_use]
pub fn register_output_type<T: OutputType>(mut self) -> Self {
T::create_type_info(&mut self.registry);
self
}
/// Disable introspection queries.
#[must_use]
pub fn disable_introspection(mut self) -> Self {
self.registry.disable_introspection = true;
self
}
/// Set the maximum complexity a query can have. By default, there is no limit.
#[must_use]
pub fn limit_complexity(mut self, complexity: usize) -> Self {
self.complexity = Some(complexity);
self
}
/// Set the maximum depth a query can have. By default, there is no limit.
#[must_use]
pub fn limit_depth(mut self, depth: usize) -> Self {
self.depth = Some(depth);
self
@ -91,24 +96,28 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
/// .extension(extensions::Logger)
/// .finish();
/// ```
#[must_use]
pub fn extension(mut self, extension: impl ExtensionFactory) -> Self {
self.extensions.push(Box::new(extension));
self
}
/// Add a global data that can be accessed in the `Schema`. You access it with `Context::data`.
#[must_use]
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
self.data.insert(data);
self
}
/// Set the validation mode, default is `ValidationMode::Strict`.
#[must_use]
pub fn validation_mode(mut self, validation_mode: ValidationMode) -> Self {
self.validation_mode = validation_mode;
self
}
/// Enable federation, which is automatically enabled if the Query has least one entity definition.
#[must_use]
pub fn enable_federation(mut self) -> Self {
self.registry.enable_federation = true;
self
@ -117,18 +126,21 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
/// Make the Federation SDL include subscriptions.
///
/// Note: Not included by default, in order to be compatible with Apollo Server.
#[must_use]
pub fn enable_subscription_in_federation(mut self) -> Self {
self.registry.federation_subscription = true;
self
}
/// Override the name of the specified input type.
#[must_use]
pub fn override_input_type_description<T: InputType>(mut self, desc: &'static str) -> Self {
self.registry.set_description(&*T::type_name(), desc);
self
}
/// Override the name of the specified output type.
#[must_use]
pub fn override_output_type_description<T: OutputType>(mut self, desc: &'static str) -> Self {
self.registry.set_description(&*T::type_name(), desc);
self
@ -139,6 +151,7 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
/// # Panics
///
/// Panics if the directive with the same name is already registered.
#[must_use]
pub fn directive<T: CustomDirectiveFactory>(mut self, directive: T) -> Self {
let name = directive.name();
let instance = Box::new(directive);

View File

@ -367,3 +367,55 @@ pub async fn test_union_flatten() {
})
);
}
#[tokio::test]
pub async fn test_trait_object_in_union() {
pub trait ProductTrait: Send + Sync {
fn id(&self) -> &str;
}
#[Object]
impl dyn ProductTrait {
#[graphql(name = "id")]
async fn gql_id(&self, _ctx: &Context<'_>) -> &str {
self.id()
}
}
struct MyProduct;
impl ProductTrait for MyProduct {
fn id(&self) -> &str {
"abc"
}
}
#[derive(Union)]
pub enum Content {
Product(Box<dyn ProductTrait>),
}
struct Query;
#[Object]
impl Query {
async fn value(&self) -> Content {
Content::Product(Box::new(MyProduct))
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema
.execute("{ value { ... on ProductTrait { id } } }")
.await
.into_result()
.unwrap()
.data,
value!({
"value": {
"id": "abc"
}
})
);
}