juniper_compose/lib.rs
1#![warn(clippy::all)]
2#![warn(clippy::pedantic)]
3
4//! Merge multiple [Juniper](https://docs.rs/juniper) object definitions into a single object type.
5//!
6//! [crates.io](https://crates.io/crates/juniper-compose) | [docs](https://docs.rs/juniper-compose) | [github](https://github.com/nikis05/juniper-compose)
7//!
8//! ## Motivation
9//!
10//! You are building a GraphQL server using Juniper. At some point you realize that you have gigantic
11//! Query and Mutation types:
12//!
13//! ```
14//! #[derive(Default)]
15//! struct Query;
16//!
17//! #[juniper::graphql_object]
18//! impl Query {
19//! async fn user(ctx: &Context, id: Uuid) -> User {
20//! // ...
21//! }
22//!
23//! async fn users(ctx: &Context) -> Vec<User> {
24//! // ...
25//! }
26//!
27//! async fn task(ctx: &Context, id: Uuid) -> Task {
28//! // ...
29//! }
30//!
31//! async fn tasks(ctx: &Context) -> Vec<Task> {
32//! // ...
33//! }
34//!
35//! // ...many more
36//! }
37//! ```
38//!
39//! You would like to split it up into multiple domain-specific files, and have e.g. all User
40//! queries in one file and all Task queries in the other. With current Juniper API, it is very
41//! hard to do, but this crate can help you.
42//!
43//! ## Usage
44//!
45//! ```
46//! #[derive(Default)]
47//! struct UserQueries;
48//!
49//! #[composable_object]
50//! #[juniper::graphql_object]
51//! impl UserQueries {
52//! async fn user(ctx: &Context, id: Uuid) -> User {
53//! // ...
54//! }
55//!
56//! async fn users(ctx: &Context) -> Vec<User> {
57//! // ...
58//! }
59//! }
60//!
61//! #[derive(Default)]
62//! struct TaskQueries;
63//!
64//! #[composable_object]
65//! #[juniper::graphql_object]
66//! impl TaskQueries {
67//! async fn task(ctx: &Context, id: Uuid) -> Task {
68//! // ...
69//! }
70//!
71//! async fn tasks(ctx: &Context) -> Vec<Task> {
72//! // ...
73//! }
74//! }
75//!
76//! composite_object!(Query(UserQueries, TaskQueries));
77//! ```
78//!
79//! Custom contexts are supported:
80//!
81//! ```
82//! composite_object!(Query<Context = MyCustomContext>(UserQueries, TaskQueries));
83//! ```
84//!
85//! Visibility specifier for generated type is supported:
86//!
87//! ```
88//! composite_object!(pub(crate) Query<Context = MyCustomContext>(UserQueries, TaskQueries));
89//! ```
90//!
91//! Custom scalars are currently not supported, but will be added if requested.
92
93use juniper::{GraphQLTypeAsync, Type};
94use std::borrow::Cow;
95
96/// Implements [ComposableObject](ComposableObject) for a GraphQL object type.
97/// **Important**: must be applied before the `juniper::graphql_object` macro.
98///
99/// ## Example
100///
101/// ```
102/// #[composable_object]
103/// #[graphql_object]
104/// impl UserQueries {
105/// // ...
106/// }
107/// ```
108pub use juniper_compose_macros::composable_object;
109
110/// Composes an object type from multiple [ComposableObject](ComposableObject)s.
111/// Custom context type may be specified, otherwise defaults to `()`.
112/// Custom visibility fro generated type may be specified.
113///
114/// ## Examples
115///
116/// ```
117/// composite_object!(Query(UserQueries, TaskQueries));
118/// composite_object!(Mutation<Context = MyContextType>(UserMutations, TaskMutations));
119/// composite_object!(pub Query(UserQueries, TaskQueries));
120/// ```
121pub use juniper_compose_macros::composite_object;
122
123/// Object types that you want to compose into one must implement this trait.
124/// Use [composable_object](composable_object) to implement it.
125pub trait ComposableObject: GraphQLTypeAsync + Default
126where
127 Self::Context: Sync,
128 Self::TypeInfo: Sync,
129{
130 /// Returns a list of fields that exist on this object type.
131 fn fields() -> &'static [&'static str];
132}
133
134#[doc(hidden)]
135#[allow(clippy::must_use_candidate)]
136pub fn type_to_owned<'a>(ty: &Type<'a>) -> Type<'static> {
137 match ty {
138 Type::Named(name) => Type::Named(Cow::Owned(name.to_string())),
139 Type::NonNullNamed(name) => Type::NonNullNamed(Cow::Owned(name.to_string())),
140 Type::List(inner) => Type::List(Box::new(type_to_owned(inner))),
141 Type::NonNullList(inner) => Type::NonNullList(Box::new(type_to_owned(inner))),
142 }
143}