Skip to main content

juniper/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(any(doc, test), doc = include_str!("../README.md"))]
3#![cfg_attr(not(any(doc, test)), doc = env!("CARGO_PKG_NAME"))]
4// Due to `schema_introspection` test.
5#![cfg_attr(test, recursion_limit = "256")]
6
7// Required for using `juniper_codegen` macros inside this crate to resolve
8// absolute `::juniper` path correctly, without errors.
9extern crate core;
10extern crate self as juniper;
11
12#[cfg(test)]
13mod for_benches_only {
14    use bencher as _;
15}
16
17// These are required by the code generated via the `juniper_codegen` macros.
18#[doc(hidden)]
19pub use {async_trait::async_trait, futures, serde, static_assertions as sa};
20
21#[doc(inline)]
22pub use futures::future::{BoxFuture, LocalBoxFuture};
23
24pub use arcstr::{self, ArcStr};
25
26// Depend on juniper_codegen and re-export everything in it.
27// This allows users to just depend on juniper and get the derive
28// functionality automatically.
29pub use juniper_codegen::{
30    GraphQLEnum, GraphQLInputObject, GraphQLInterface, GraphQLObject, GraphQLScalar, GraphQLUnion,
31    graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union,
32};
33
34#[doc(hidden)]
35#[macro_use]
36pub mod macros;
37mod ast;
38pub mod executor;
39mod introspection;
40pub mod parser;
41pub(crate) mod schema;
42mod types;
43mod util;
44pub mod validation;
45mod value;
46// This needs to be public until docs have support for private modules:
47// https://github.com/rust-lang/cargo/issues/1520
48pub mod http;
49pub mod integrations;
50
51#[cfg(all(test, not(feature = "expose-test-schema")))]
52mod tests;
53#[cfg(feature = "expose-test-schema")]
54pub mod tests;
55
56#[cfg(test)]
57mod executor_tests;
58
59use derive_more::with_trait::{Display, From};
60use itertools::Itertools as _;
61
62// Needs to be public because macros use it.
63pub use crate::util::to_camel_case;
64
65use crate::{
66    executor::{execute_validated_query, get_operation},
67    introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS},
68    parser::parse_document_source,
69    validation::{
70        MultiVisitorNil, ValidatorContext, rules, validate_input_values, visit as visit_rule,
71        visit_all_rules,
72    },
73};
74
75pub use crate::{
76    ast::{
77        Definition, Document, FromInputValue, InputValue, IntoInputValue, Operation, OperationType,
78        Selection, ToInputValue, Type,
79    },
80    executor::{
81        Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult,
82        FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadChildren,
83        LookAheadList, LookAheadObject, LookAheadSelection, LookAheadValue, OwnedExecutor,
84        Registry, ValuesStream, Variables,
85    },
86    introspection::IntrospectionFormat,
87    macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult},
88    parser::{ParseError, ScalarToken, Span, Spanning},
89    schema::{
90        meta,
91        model::{RootNode, SchemaType},
92    },
93    types::{
94        async_await::{GraphQLTypeAsync, GraphQLValueAsync},
95        base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
96        marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion},
97        nullable::Nullable,
98        scalars::{EmptyMutation, EmptySubscription, ID},
99        subscriptions::{
100            ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
101            SubscriptionConnection, SubscriptionCoordinator,
102        },
103    },
104    validation::RuleError,
105    value::{
106        AnyExt, DefaultScalarValue, FromScalarValue, IntoValue, Object, ParseScalarResult,
107        ParseScalarValue, Scalar, ScalarValue, ToScalarValue, TryToPrimitive, Value,
108        WrongInputScalarTypeError,
109    },
110};
111
112/// An error that prevented query execution
113#[expect(missing_docs, reason = "self-explanatory")]
114#[derive(Clone, Debug, Display, Eq, From, PartialEq)]
115pub enum GraphQLError {
116    ParseError(Spanning<ParseError>),
117    #[display("{}", _0.iter().format("\n"))]
118    ValidationError(Vec<RuleError>),
119    #[display("No operation provided")]
120    NoOperationProvided,
121    #[display("Multiple operations provided")]
122    MultipleOperationsProvided,
123    #[display("Unknown operation name")]
124    UnknownOperationName,
125    #[display("Operation is a subscription")]
126    IsSubscription,
127    #[display("Operation is not a subscription")]
128    NotSubscription,
129}
130
131impl From<RuleError> for GraphQLError {
132    fn from(value: RuleError) -> Self {
133        vec![value].into()
134    }
135}
136
137impl std::error::Error for GraphQLError {
138    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
139        match self {
140            Self::ParseError(e) => Some(e),
141            Self::ValidationError(errs) => Some(errs.first()?),
142            Self::NoOperationProvided
143            | Self::MultipleOperationsProvided
144            | Self::UnknownOperationName
145            | Self::IsSubscription
146            | Self::NotSubscription => None,
147        }
148    }
149}
150
151/// Execute a query synchronously in a provided schema
152pub fn execute_sync<'a, S, QueryT, MutationT, SubscriptionT>(
153    document_source: &'a str,
154    operation_name: Option<&str>,
155    root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
156    variables: &Variables<S>,
157    context: &QueryT::Context,
158) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
159where
160    S: ScalarValue,
161    QueryT: GraphQLType<S>,
162    MutationT: GraphQLType<S, Context = QueryT::Context>,
163    SubscriptionT: GraphQLType<S, Context = QueryT::Context>,
164{
165    let document = parse_document_source(document_source, &root_node.schema)?;
166
167    {
168        let mut ctx = ValidatorContext::new(&root_node.schema, &document);
169        visit_all_rules(&mut ctx, &document);
170        if root_node.introspection_disabled {
171            visit_rule(
172                &mut MultiVisitorNil.with(rules::disable_introspection::factory()),
173                &mut ctx,
174                &document,
175            );
176        }
177
178        let errors = ctx.into_errors();
179        if !errors.is_empty() {
180            return Err(errors.into());
181        }
182    }
183
184    let operation = get_operation(&document, operation_name)?;
185
186    {
187        let errors = validate_input_values(variables, operation, &root_node.schema);
188
189        if !errors.is_empty() {
190            return Err(errors.into());
191        }
192    }
193
194    execute_validated_query(&document, operation, root_node, variables, context)
195}
196
197/// Execute a query in a provided schema
198pub async fn execute<'a, S, QueryT, MutationT, SubscriptionT>(
199    document_source: &'a str,
200    operation_name: Option<&str>,
201    root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
202    variables: &Variables<S>,
203    context: &QueryT::Context,
204) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
205where
206    QueryT: GraphQLTypeAsync<S>,
207    QueryT::TypeInfo: Sync,
208    QueryT::Context: Sync,
209    MutationT: GraphQLTypeAsync<S, Context = QueryT::Context>,
210    MutationT::TypeInfo: Sync,
211    SubscriptionT: GraphQLType<S, Context = QueryT::Context> + Sync,
212    SubscriptionT::TypeInfo: Sync,
213    S: ScalarValue + Send + Sync,
214{
215    let document = parse_document_source(document_source, &root_node.schema)?;
216
217    {
218        let mut ctx = ValidatorContext::new(&root_node.schema, &document);
219        visit_all_rules(&mut ctx, &document);
220        if root_node.introspection_disabled {
221            visit_rule(
222                &mut MultiVisitorNil.with(rules::disable_introspection::factory()),
223                &mut ctx,
224                &document,
225            );
226        }
227
228        let errors = ctx.into_errors();
229        if !errors.is_empty() {
230            return Err(errors.into());
231        }
232    }
233
234    let operation = get_operation(&document, operation_name)?;
235
236    {
237        let errors = validate_input_values(variables, operation, &root_node.schema);
238
239        if !errors.is_empty() {
240            return Err(errors.into());
241        }
242    }
243
244    executor::execute_validated_query_async(&document, operation, root_node, variables, context)
245        .await
246}
247
248/// Resolve subscription into `ValuesStream`
249pub async fn resolve_into_stream<'a, S, QueryT, MutationT, SubscriptionT>(
250    document_source: &'a str,
251    operation_name: Option<&str>,
252    root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
253    variables: &Variables<S>,
254    context: &'a QueryT::Context,
255) -> Result<(Value<ValuesStream<'a, S>>, Vec<ExecutionError<S>>), GraphQLError>
256where
257    QueryT: GraphQLTypeAsync<S>,
258    QueryT::TypeInfo: Sync,
259    QueryT::Context: Sync,
260    MutationT: GraphQLTypeAsync<S, Context = QueryT::Context>,
261    MutationT::TypeInfo: Sync,
262    SubscriptionT: GraphQLSubscriptionType<S, Context = QueryT::Context>,
263    SubscriptionT::TypeInfo: Sync,
264    S: ScalarValue + Send + Sync,
265{
266    let document: ast::OwnedDocument<'a, S> =
267        parse_document_source(document_source, &root_node.schema)?;
268
269    {
270        let mut ctx = ValidatorContext::new(&root_node.schema, &document);
271        visit_all_rules(&mut ctx, &document);
272        if root_node.introspection_disabled {
273            visit_rule(
274                &mut MultiVisitorNil.with(rules::disable_introspection::factory()),
275                &mut ctx,
276                &document,
277            );
278        }
279
280        let errors = ctx.into_errors();
281        if !errors.is_empty() {
282            return Err(errors.into());
283        }
284    }
285
286    let operation = get_operation(&document, operation_name)?;
287
288    {
289        let errors = validate_input_values(variables, operation, &root_node.schema);
290
291        if !errors.is_empty() {
292            return Err(errors.into());
293        }
294    }
295
296    executor::resolve_validated_subscription(&document, operation, root_node, variables, context)
297        .await
298}
299
300/// Execute the reference introspection query in the provided schema
301pub fn introspect<S, QueryT, MutationT, SubscriptionT>(
302    root_node: &RootNode<QueryT, MutationT, SubscriptionT, S>,
303    context: &QueryT::Context,
304    format: IntrospectionFormat,
305) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
306where
307    S: ScalarValue,
308    QueryT: GraphQLType<S>,
309    MutationT: GraphQLType<S, Context = QueryT::Context>,
310    SubscriptionT: GraphQLType<S, Context = QueryT::Context>,
311{
312    execute_sync(
313        match format {
314            IntrospectionFormat::All => INTROSPECTION_QUERY,
315            IntrospectionFormat::WithoutDescriptions => INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS,
316        },
317        None,
318        root_node,
319        &Variables::new(),
320        context,
321    )
322}