Add `Union` and `Interface` support for trait objects. #780
Clippy clean
This commit is contained in:
parent
5d99df4502
commit
707890e551
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue