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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
//! Execution of a GraphQL operation that passed parsing and validation.
use std::{
borrow::Cow,
collections::HashMap,
fmt,
sync::{Arc, RwLock},
};
use crate::{
GraphQLError,
ast::{Definition, Fragment, Operation, OperationType},
parser::{Spanning, parse_document_source},
schema::model::{RootNode, TypeType},
types::{
async_await::GraphQLTypeAsync, base::GraphQLType, subscriptions::GraphQLSubscriptionType,
},
validation::{
MultiVisitorNil, ValidatorContext, rules, validate_input_values, visit as visit_rule,
visit_all_rules,
},
value::{DefaultScalarValue, ScalarValue, Value},
};
use super::{ExecutionError, Executor, FieldPath, ValuesStream, Variables};
/// GraphQL operation that passed parsing and validation.
///
/// The execution methods validate the variables and execute the operation without parsing and
/// validating the document again. A [`ValidatedOperation`] can execute more than once. Each
/// execution takes its own variables and context.
pub struct ValidatedOperation<'a, QueryT, MutationT, SubscriptionT, S = DefaultScalarValue>
where
QueryT: GraphQLType<S>,
MutationT: GraphQLType<S>,
SubscriptionT: GraphQLType<S>,
S: ScalarValue,
{
/// Operation to execute.
operation: Spanning<Operation<'a, S>>,
/// Fragments of the document by name.
fragments: HashMap<&'a str, Fragment<'a, S>>,
/// Schema of the document.
root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
}
impl<'a, QueryT, MutationT, SubscriptionT, S>
ValidatedOperation<'a, QueryT, MutationT, SubscriptionT, S>
where
QueryT: GraphQLType<S>,
MutationT: GraphQLType<S>,
SubscriptionT: GraphQLType<S>,
S: ScalarValue,
{
/// Parses and validates the `document_source` in the schema of the `root_node`.
///
/// `operation_name` selects the operation of a document with more than one operation. A server
/// can inspect the [`OperationType`] of the operation before execution. The server can then
/// route subscriptions to [`ValidatedOperation::resolve_into_stream()`] and other operations to
/// [`ValidatedOperation::execute()`].
///
/// # Errors
///
/// - [`GraphQLError::ParseError`] if the document fails to parse or exceeds the document
/// limits of the `root_node`.
/// - [`GraphQLError::ValidationError`] if the document fails validation.
/// - [`GraphQLError::MultipleOperationsProvided`] if the document has more than one operation
/// and `operation_name` is [`None`].
/// - [`GraphQLError::UnknownOperationName`] if the document has no operation with the
/// `operation_name`.
///
/// # Example
///
/// ```rust
/// # use juniper::{
/// # EmptyMutation, EmptySubscription, GraphQLError, OperationType, RootNode,
/// # ValidatedOperation, graphql_object, graphql_value, graphql_vars,
/// # };
/// #
/// struct Query;
///
/// #[graphql_object]
/// impl Query {
/// fn ping() -> bool {
/// true
/// }
/// }
///
/// type Schema = RootNode<Query, EmptyMutation, EmptySubscription>;
///
/// let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
///
/// let operation = ValidatedOperation::new("{ ping }", None, &schema)?;
/// assert_eq!(operation.operation_type(), OperationType::Query);
///
/// let (data, errors) = operation.execute_sync(&graphql_vars! {}, &())?;
/// assert_eq!(data, graphql_value!({"ping": true}));
/// assert!(errors.is_empty());
/// # Ok::<_, GraphQLError>(())
/// ```
pub fn new(
document_source: &'a str,
operation_name: Option<&str>,
root_node: &'a RootNode<QueryT, MutationT, SubscriptionT, S>,
) -> Result<Self, GraphQLError> {
let document = parse_document_source(document_source, &root_node.schema)?;
let mut validator = ValidatorContext::new(&root_node.schema, &document);
visit_all_rules(&mut validator, &document);
if root_node.introspection_disabled {
visit_rule(
&mut MultiVisitorNil.with(rules::disable_introspection::factory()),
&mut validator,
&document,
);
}
let errors = validator.into_errors();
if !errors.is_empty() {
return Err(errors.into());
}
let mut operation = None;
let mut fragments = HashMap::new();
for definition in document {
match definition {
Definition::Operation(candidate) => {
if operation_name.is_none() && operation.is_some() {
return Err(GraphQLError::MultipleOperationsProvided);
}
if operation_name.is_none()
|| candidate.item.name.as_ref().map(|name| name.item) == operation_name
{
operation = Some(candidate);
}
}
Definition::Fragment(fragment) => {
fragments.insert(fragment.item.name.item, fragment.item);
}
}
}
Ok(Self {
operation: operation.ok_or(GraphQLError::UnknownOperationName)?,
fragments,
root_node,
})
}
/// Returns the [`OperationType`] of this [`ValidatedOperation`].
#[must_use]
pub fn operation_type(&self) -> OperationType {
self.operation.item.operation_type
}
/// Executes this [`ValidatedOperation`] synchronously with the provided `variables` and
/// `context`.
///
/// # Errors
///
/// - [`GraphQLError::ValidationError`] if the `variables` do not match the variable definitions
/// of the operation.
/// - [`GraphQLError::IsSubscription`] for a subscription.
/// - [`GraphQLError::NotSupported`] for a mutation if the schema has no mutation root type.
pub fn execute_sync(
&self,
variables: &Variables<S>,
context: &QueryT::Context,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
where
MutationT: GraphQLType<S, Context = QueryT::Context>,
SubscriptionT: GraphQLType<S, Context = QueryT::Context>,
{
self.validate_variables(variables)?;
let variables = self.with_default_values(variables);
let errors = RwLock::new(Vec::new());
let root_node = self.root_node;
let value = match self.operation_type() {
OperationType::Query => self
.executor(&variables, context, &errors)?
.resolve_into_value(&root_node.query_info, &root_node),
OperationType::Mutation => self
.executor(&variables, context, &errors)?
.resolve_into_value(&root_node.mutation_info, &root_node.mutation_type),
OperationType::Subscription => return Err(GraphQLError::IsSubscription),
};
Ok((value, sorted(errors)))
}
/// Executes this [`ValidatedOperation`] asynchronously with the provided `variables` and
/// `context`.
///
/// # Errors
///
/// - [`GraphQLError::ValidationError`] if the `variables` do not match the variable definitions
/// of the operation.
/// - [`GraphQLError::IsSubscription`] for a subscription.
/// - [`GraphQLError::NotSupported`] for a mutation if the schema has no mutation root type.
pub async fn execute(
&self,
variables: &Variables<S>,
context: &QueryT::Context,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError>
where
QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync,
QueryT::Context: Sync,
MutationT: GraphQLTypeAsync<S, Context = QueryT::Context>,
MutationT::TypeInfo: Sync,
SubscriptionT: GraphQLType<S, Context = QueryT::Context> + Sync,
SubscriptionT::TypeInfo: Sync,
S: Send + Sync,
{
self.validate_variables(variables)?;
let variables = self.with_default_values(variables);
let errors = RwLock::new(Vec::new());
let root_node = self.root_node;
let value = match self.operation_type() {
OperationType::Query => {
self.executor(&variables, context, &errors)?
.resolve_into_value_async(&root_node.query_info, &root_node)
.await
}
OperationType::Mutation => {
self.executor(&variables, context, &errors)?
.resolve_into_value_async(&root_node.mutation_info, &root_node.mutation_type)
.await
}
OperationType::Subscription => return Err(GraphQLError::IsSubscription),
};
Ok((value, sorted(errors)))
}
/// Resolves this subscription [`ValidatedOperation`] into a [`ValuesStream`] with the provided
/// `variables` and `context`.
///
/// # Errors
///
/// - [`GraphQLError::ValidationError`] if the `variables` do not match the variable definitions
/// of the operation.
/// - [`GraphQLError::NotSubscription`] for a query or a mutation.
/// - [`GraphQLError::NotSupported`] if the schema has no subscription root type.
pub async fn resolve_into_stream(
&self,
variables: &Variables<S>,
context: &'a QueryT::Context,
) -> Result<(Value<ValuesStream<'a, S>>, Vec<ExecutionError<S>>), GraphQLError>
where
QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync,
QueryT::Context: Sync,
MutationT: GraphQLTypeAsync<S, Context = QueryT::Context>,
MutationT::TypeInfo: Sync,
SubscriptionT: GraphQLSubscriptionType<S, Context = QueryT::Context>,
SubscriptionT::TypeInfo: Sync,
S: Send + Sync,
{
self.validate_variables(variables)?;
if self.operation_type() != OperationType::Subscription {
return Err(GraphQLError::NotSubscription);
}
let variables = self.with_default_values(variables);
let errors = RwLock::new(Vec::new());
let root_node = self.root_node;
let value = self
.executor(&variables, context, &errors)?
.resolve_into_stream(&root_node.subscription_info, &root_node.subscription_type)
.await;
Ok((value, sorted(errors)))
}
/// Validates the `variables` against the variable definitions of the operation.
fn validate_variables(&self, variables: &Variables<S>) -> Result<(), GraphQLError> {
let errors = validate_input_values(variables, &self.operation, &self.root_node.schema);
if errors.is_empty() {
Ok(())
} else {
Err(errors.into())
}
}
/// Adds the default values of the operation for the variables missing from the `variables`.
///
/// Copies the `variables` only if a default value applies.
fn with_default_values<'v>(&self, variables: &'v Variables<S>) -> Cow<'v, Variables<S>> {
let mut variables = Cow::Borrowed(variables);
let Some(definitions) = &self.operation.item.variables_definition else {
return variables;
};
for (name, definition) in &definitions.item.items {
let Some(default) = &definition.default_value else {
continue;
};
if !variables.contains_key(name.item) {
variables
.to_mut()
.insert(name.item.into(), default.item.clone());
}
}
variables
}
/// Creates an [`Executor`] for the root selection set of the operation.
///
/// # Errors
///
/// [`GraphQLError::NotSupported`] if the schema has no root type for the operation type.
fn executor<'r, 'x>(
&'r self,
variables: &'r Variables<S>,
context: &'x QueryT::Context,
errors: &'r RwLock<Vec<ExecutionError<S>>>,
) -> Result<Executor<'r, 'x, QueryT::Context, S>, GraphQLError>
where
'a: 'x,
{
Ok(Executor {
fragments: &self.fragments,
variables,
current_selection_set: Some(&self.operation.item.selection_set),
parent_selection_set: None,
current_type: self.root_type()?,
schema: &self.root_node.schema,
context,
errors,
field_path: Arc::new(FieldPath::Root(self.operation.span.start)),
serial: self.operation_type() == OperationType::Mutation,
})
}
/// Returns the root type of the operation in the schema.
///
/// # Errors
///
/// [`GraphQLError::NotSupported`] if the schema has no root type for the operation type. An
/// [`EmptyMutation`] or [`EmptySubscription`] root has no type in the schema.
///
/// [`EmptyMutation`]: crate::EmptyMutation
/// [`EmptySubscription`]: crate::EmptySubscription
fn root_type(&self) -> Result<TypeType<'a, S>, GraphQLError> {
let schema = &self.root_node.schema;
let operation_type = self.operation_type();
match operation_type {
OperationType::Query => Some(schema.query_type()),
OperationType::Mutation => schema.mutation_type(),
OperationType::Subscription => schema.subscription_type(),
}
.ok_or(GraphQLError::NotSupported(operation_type))
}
}
impl<QueryT, MutationT, SubscriptionT, S> fmt::Debug
for ValidatedOperation<'_, QueryT, MutationT, SubscriptionT, S>
where
QueryT: GraphQLType<S>,
MutationT: GraphQLType<S>,
SubscriptionT: GraphQLType<S>,
S: ScalarValue,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ValidatedOperation")
.field("operation", &self.operation)
.field("fragments", &self.fragments)
.finish_non_exhaustive()
}
}
/// Returns the `errors` of an execution in the order of their positions in the document.
fn sorted<S: ScalarValue>(errors: RwLock<Vec<ExecutionError<S>>>) -> Vec<ExecutionError<S>> {
let mut errors = errors.into_inner().unwrap();
errors.sort();
errors
}