1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#![warn(clippy::all)]
#![warn(clippy::pedantic)]

//! Merge multiple [Juniper](https://docs.rs/juniper) object definitions into a single object type.
//!
//! [crates.io](https://crates.io/crates/juniper-compose) | [docs](https://docs.rs/juniper-compose) | [github](https://github.com/nikis05/juniper-compose)
//!
//! ## Motivation
//!
//! You are building a GraphQL server using Juniper. At some point you realize that you have gigantic
//! Query and Mutation types:
//!
//! ```
//! #[derive(Default)]
//! struct Query;
//!
//! #[juniper::graphql_object]
//! impl Query {
//!     async fn user(ctx: &Context, id: Uuid) -> User {
//!         // ...
//!     }
//!
//!     async fn users(ctx: &Context) -> Vec<User> {
//!         // ...
//!     }
//!
//!     async fn task(ctx: &Context, id: Uuid) -> Task {
//!         // ...
//!     }
//!
//!     async fn tasks(ctx: &Context) -> Vec<Task> {
//!         // ...
//!     }
//!     
//!     // ...many more
//! }
//! ```
//!
//! You would like to split it up into multiple domain-specific files, and have e.g. all User
//! queries in one file and all Task queries in the other. With current Juniper API, it is very
//! hard to do, but this crate can help you.
//!
//! ## Usage
//!
//! ```
//! #[derive(Default)]
//! struct UserQueries;
//!
//! #[composable_object]
//! #[juniper::graphql_object]
//! impl UserQueries {
//!     async fn user(ctx: &Context, id: Uuid) -> User {
//!         // ...
//!     }
//!
//!     async fn users(ctx: &Context) -> Vec<User> {
//!         // ...
//!     }
//! }
//!
//! #[derive(Default)]
//! struct TaskQueries;
//!
//! #[composable_object]
//! #[juniper::graphql_object]
//! impl TaskQueries {
//!     async fn task(ctx: &Context, id: Uuid) -> Task {
//!         // ...
//!     }
//!
//!     async fn tasks(ctx: &Context) -> Vec<Task> {
//!         // ...
//!     }
//! }
//!
//! composite_object!(Query(UserQueries, TaskQueries));
//! ```
//!
//! Custom contexts are supported:
//!
//! ```
//! composite_object!(Query<Context = MyCustomContext>(UserQueries, TaskQueries));
//! ```
//!
//! Custom scalars are currently not supported, but will be added if requested.

use juniper::{GraphQLTypeAsync, Type};
use std::borrow::Cow;

/// Implements [ComposableObject](ComposableObject) for a GraphQL object type.
/// **Important**: must be applied before the `juniper::graphql_object` macro.
///
/// ## Example
///
/// ```
/// #[composable_object]
/// #[graphql_object]
/// impl UserQueries {
///     // ...
/// }
/// ```
pub use juniper_compose_macros::composable_object;

/// Composes an object type from multiple [ComposableObject](ComposableObject)s.
/// Custom context type may be specified, otherwise defaults to `()`.
///
/// ## Examples
///
/// ```
/// composite_object!(Query(UserQueries, TaskQueries));
/// composite_object!(Mutation<Context = MyContextType>(UserMutations, TaskMutations));
/// ```
pub use juniper_compose_macros::composite_object;

/// Object types that you want to compose into one must implement this trait.
/// Use [composable_object](composable_object) to implement it.
pub trait ComposableObject: GraphQLTypeAsync + Default
where
    Self::Context: Sync,
    Self::TypeInfo: Sync,
{
    /// Returns a list of fields that exist on this object type.
    fn fields() -> &'static [&'static str];
}

#[doc(hidden)]
#[allow(clippy::must_use_candidate)]
pub fn type_to_owned<'a>(ty: &Type<'a>) -> Type<'static> {
    match ty {
        Type::Named(name) => Type::Named(Cow::Owned(name.to_string())),
        Type::NonNullNamed(name) => Type::NonNullNamed(Cow::Owned(name.to_string())),
        Type::List(inner) => Type::List(Box::new(type_to_owned(inner))),
        Type::NonNullList(inner) => Type::NonNullList(Box::new(type_to_owned(inner))),
    }
}