router_bridge/
planner.rs

1/*!
2 * Instantiate a QueryPlanner from a schema, and perform query planning
3*/
4use std::collections::HashMap;
5use std::fmt::Debug;
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::marker::PhantomData;
9use std::sync::Arc;
10
11use serde::de::DeserializeOwned;
12use serde::Deserialize;
13use serde::Serialize;
14use thiserror::Error;
15
16use crate::introspect::IntrospectionResponse;
17use crate::worker::JsWorker;
18
19// ------------------------------------
20
21#[derive(Debug, Serialize)]
22#[serde(rename_all = "camelCase")]
23/// Options for the query plan
24pub struct QueryPlanOptions {
25    /// Use auto fragmentation
26    pub auto_fragmentization: bool,
27}
28
29/// Default options for query planning
30impl Default for QueryPlanOptions {
31    /// Default query plan options
32    fn default() -> Self {
33        Self {
34            auto_fragmentization: false,
35        }
36    }
37}
38
39#[derive(Debug, Serialize)]
40#[serde(rename_all = "camelCase")]
41/// This is the context which provides
42/// all the information to plan a query against a schema
43pub struct OperationalContext {
44    /// The graphQL schema
45    pub schema: String,
46    /// The graphQL query
47    pub query: String,
48    /// The operation name
49    pub operation_name: String,
50}
51
52/// An error which occurred during JavaScript planning.
53///
54/// The shape of this error is meant to mimic that of the error created within
55/// JavaScript, which is a [`GraphQLError`] from the [`graphql-js`] library.
56///
57/// [`graphql-js`]: https://npm.im/graphql
58/// [`GraphQLError`]: https://github.com/graphql/graphql-js/blob/3869211/src/error/GraphQLError.js#L18-L75
59#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq, Clone)]
60#[serde(rename_all = "camelCase")]
61pub struct PlanError {
62    /// A human-readable description of the error that prevented planning.
63    pub message: Option<String>,
64    /// [`PlanErrorExtensions`]
65    #[serde(deserialize_with = "none_only_if_value_is_null_or_empty_object")]
66    pub extensions: Option<PlanErrorExtensions>,
67    /// True if the error came from GraphQL validation. The router can use this to compare
68    /// results between JS and Rust validation implementations.
69    #[serde(skip_serializing, default)]
70    pub validation_error: bool,
71}
72
73/// `none_only_if_value_is_null_or_empty_object`
74///
75/// This function returns Ok(Some(T)) if a T can be deserialized,
76///
77/// Ok(None) if data contains Null or an empty object,
78/// And fails otherwise, including if the key is missing.
79fn none_only_if_value_is_null_or_empty_object<'de, D, T>(data: D) -> Result<Option<T>, D::Error>
80where
81    D: serde::de::Deserializer<'de>,
82    T: serde::Deserialize<'de>,
83{
84    #[derive(Deserialize)]
85    #[serde(untagged)]
86    enum OptionOrValue<T> {
87        Opt(Option<T>),
88        Val(serde_json::value::Value),
89    }
90
91    let as_option_or_value: Result<OptionOrValue<T>, D::Error> =
92        serde::Deserialize::deserialize(data);
93
94    match as_option_or_value {
95        Ok(OptionOrValue::Opt(t)) => Ok(t),
96        Ok(OptionOrValue::Val(obj)) => {
97            if let serde_json::value::Value::Object(o) = &obj {
98                if o.is_empty() {
99                    return Ok(None);
100                }
101            }
102
103            Err(serde::de::Error::custom(format!(
104                "invalid neither null nor empty object: found {obj:?}"
105            )))
106        }
107        Err(e) => Err(e),
108    }
109}
110
111impl Display for PlanError {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        if let Some(msg) = &self.message {
114            f.write_fmt(format_args!("{code}: {msg}", code = self.code(), msg = msg))
115        } else {
116            f.write_str(self.code())
117        }
118    }
119}
120
121#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
122/// Error codes
123pub struct PlanErrorExtensions {
124    /// The error code
125    pub code: String,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    /// The stacktrace if we have one
128    pub exception: Option<ExtensionsException>,
129}
130
131#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
132/// stacktrace in error extensions
133pub struct ExtensionsException {
134    /// The stacktrace generated in JavaScript
135    pub stacktrace: String,
136}
137
138/// An error that was received during planning within JavaScript.
139impl PlanError {
140    /// Retrieve the error code from an error received during planning.
141    pub fn code(&self) -> &str {
142        match self.extensions {
143            Some(ref ext) => &ext.code,
144            None => "UNKNOWN",
145        }
146    }
147}
148
149// ------------------------------------
150
151#[derive(Deserialize, Debug)]
152/// The result of a router bridge invocation
153pub struct BridgeSetupResult<T> {
154    /// The data if setup happened successfully
155    pub data: Option<T>,
156    /// The errors if the query failed
157    pub errors: Option<Vec<PlannerError>>,
158}
159
160#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
161#[serde(rename_all = "camelCase")]
162/// The error location
163pub struct Location {
164    /// The line number
165    pub line: u32,
166    /// The column number
167    pub column: u32,
168}
169
170#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
171#[serde(untagged)]
172/// This contains the set of all errors that can be thrown from deno
173pub enum PlannerError {
174    /// The deno GraphQLError counterpart
175    WorkerGraphQLError(WorkerGraphQLError),
176    /// The deno Error counterpart
177    WorkerError(WorkerError),
178}
179
180impl PlannerError {
181    /// Return true if the error was a GraphQL validation error.
182    pub fn is_validation_error(&self) -> bool {
183        let PlannerError::WorkerGraphQLError(err) = self else {
184            return false;
185        };
186        err.validation_error
187    }
188}
189
190impl From<WorkerGraphQLError> for PlannerError {
191    fn from(e: WorkerGraphQLError) -> Self {
192        Self::WorkerGraphQLError(e)
193    }
194}
195
196impl From<WorkerError> for PlannerError {
197    fn from(e: WorkerError) -> Self {
198        Self::WorkerError(e)
199    }
200}
201
202impl std::fmt::Display for PlannerError {
203    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204        match self {
205            Self::WorkerGraphQLError(graphql_error) => {
206                write!(f, "{graphql_error}")
207            }
208            Self::WorkerError(error) => {
209                write!(f, "{error}")
210            }
211        }
212    }
213}
214
215/// WorkerError represents the non GraphQLErrors the deno worker can throw.
216/// We try to get as much data out of them.
217#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
218pub struct WorkerError {
219    /// The error message
220    pub message: Option<String>,
221    /// The error kind
222    pub name: Option<String>,
223    /// A stacktrace if applicable
224    pub stack: Option<String>,
225    /// [`PlanErrorExtensions`]
226    pub extensions: Option<PlanErrorExtensions>,
227    /// If an error can be associated to a particular point in the requested
228    /// GraphQL document, it should contain a list of locations.
229    #[serde(default)]
230    pub locations: Vec<Location>,
231}
232
233impl std::fmt::Display for WorkerError {
234    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
235        write!(
236            f,
237            "{}",
238            self.message
239                .clone()
240                .unwrap_or_else(|| "unknown error".to_string())
241        )
242    }
243}
244
245/// WorkerGraphQLError represents the GraphQLErrors the deno worker can throw.
246/// We try to get as much data out of them.
247/// While they mostly represent GraphQLErrors, they sometimes don't.
248/// See [`WorkerError`]
249#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
250#[serde(rename_all = "camelCase")]
251pub struct WorkerGraphQLError {
252    /// The error kind
253    pub name: String,
254    /// A short, human-readable summary of the problem that **SHOULD NOT** change
255    /// from occurrence to occurrence of the problem, except for purposes of
256    /// localization.
257    pub message: String,
258    /// If an error can be associated to a particular point in the requested
259    /// GraphQL document, it should contain a list of locations.
260    #[serde(default)]
261    pub locations: Vec<Location>,
262    /// [`PlanErrorExtensions`]
263    pub extensions: Option<PlanErrorExtensions>,
264    /// The original error thrown from a field resolver during execution.
265    pub original_error: Option<Box<WorkerError>>,
266    /// The reasons why the error was triggered (useful for schema checks)
267    #[serde(default)]
268    pub causes: Vec<Box<WorkerError>>,
269    /// Set if the error was thrown by GraphQL spec validation.
270    #[serde(default, skip_serializing)]
271    pub validation_error: bool,
272}
273
274impl std::fmt::Display for WorkerGraphQLError {
275    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
276        write!(
277            f,
278            "{}\ncaused by\n{}",
279            self.message,
280            self.causes
281                .iter()
282                .map(std::string::ToString::to_string)
283                .collect::<Vec<_>>()
284                .join("\n")
285        )
286    }
287}
288
289#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
290#[serde(rename_all = "camelCase")]
291/// A list of fields that will be resolved
292/// for a given type
293pub struct ReferencedFieldsForType {
294    /// names of the fields queried
295    #[serde(default)]
296    pub field_names: Vec<String>,
297    /// whether the field is an interface
298    #[serde(default)]
299    pub is_interface: bool,
300}
301
302#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
303#[serde(rename_all = "camelCase")]
304/// UsageReporting fields, that will be used
305/// to send stats to uplink/studio
306pub struct UsageReporting {
307    /// The `stats_report_key` is a unique identifier derived from schema and query.
308    /// Metric data  sent to Studio must be aggregated
309    /// via grouped key of (`client_name`, `client_version`, `stats_report_key`).
310    pub stats_report_key: String,
311    /// a list of all types and fields referenced in the query
312    #[serde(default)]
313    pub referenced_fields_by_type: HashMap<String, ReferencedFieldsForType>,
314}
315
316#[derive(Deserialize, Debug)]
317#[serde(rename_all = "camelCase")]
318/// The result of a router bridge plan_worker invocation
319pub struct PlanResult<T> {
320    /// The data if the query was successfully run
321    pub data: Option<T>,
322    /// Usage reporting related data such as the
323    /// operation signature and referenced fields
324    pub usage_reporting: UsageReporting,
325    /// The errors if the query failed
326    pub errors: Option<Vec<PlanError>>,
327}
328
329/// The payload if the plan_worker invocation succeeded
330#[derive(Debug)]
331pub struct PlanSuccess<T> {
332    /// The payload you're looking for
333    pub data: T,
334    /// Usage reporting related data such as the
335    /// operation signature and referenced fields
336    pub usage_reporting: UsageReporting,
337}
338
339#[derive(Deserialize, Debug)]
340#[serde(rename_all = "camelCase")]
341/// The result of a router bridge API schema invocation
342pub struct ApiSchema {
343    /// The data if the query was successfully run
344    pub schema: String,
345}
346
347/// The payload if the plan_worker invocation failed
348#[derive(Debug, Clone)]
349pub struct PlanErrors {
350    /// The errors the plan_worker invocation failed with
351    pub errors: Arc<Vec<PlanError>>,
352    /// Usage reporting related data such as the
353    /// operation signature and referenced fields
354    pub usage_reporting: UsageReporting,
355}
356
357impl std::fmt::Display for PlanErrors {
358    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359        f.write_fmt(format_args!(
360            "query validation errors: {}",
361            self.errors
362                .iter()
363                .map(|e| e
364                    .message
365                    .clone()
366                    .unwrap_or_else(|| "UNKNWON ERROR".to_string()))
367                .collect::<Vec<String>>()
368                .join(", ")
369        ))
370    }
371}
372
373impl<T> PlanResult<T>
374where
375    T: DeserializeOwned + Send + Debug + 'static,
376{
377    /// Turn a BridgeResult into an actual Result
378    pub fn into_result(self) -> Result<PlanSuccess<T>, PlanErrors> {
379        let usage_reporting = self.usage_reporting;
380        if let Some(data) = self.data {
381            Ok(PlanSuccess {
382                data,
383                usage_reporting,
384            })
385        } else {
386            let errors = Arc::new(self.errors.unwrap_or_else(|| {
387                vec![PlanError {
388                    message: Some("an unknown error occured".to_string()),
389                    extensions: None,
390                    validation_error: false,
391                }]
392            }));
393            Err(PlanErrors {
394                errors,
395                usage_reporting,
396            })
397        }
398    }
399}
400
401#[derive(Deserialize, Debug)]
402#[serde(rename_all = "camelCase")]
403/// deno's heap statistics
404pub struct HeapStatistics {
405    /// total size of the heap for V8, in bytes
406    pub heap_total: u64,
407    /// amount of the heap used for V8, in bytes
408    pub heap_used: u64,
409    /// emory, in bytes, associated with JavaScript objects outside of the JavaScript isolate
410    pub external: u64,
411}
412
413/// A Deno worker backed query Planner.
414
415pub struct Planner<T>
416where
417    T: DeserializeOwned + Send + Debug + 'static,
418{
419    worker: Arc<JsWorker>,
420    schema_id: u64,
421    t: PhantomData<T>,
422}
423
424impl<T> Debug for Planner<T>
425where
426    T: DeserializeOwned + Send + Debug + 'static,
427{
428    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
429        f.debug_struct("Planner")
430            .field("schema_id", &self.schema_id)
431            .finish()
432    }
433}
434
435impl<T> Planner<T>
436where
437    T: DeserializeOwned + Send + Debug + 'static,
438{
439    /// Instantiate a `Planner` from a schema string
440    pub async fn new(
441        schema: String,
442        config: QueryPlannerConfig,
443    ) -> Result<Self, Vec<PlannerError>> {
444        let schema_id: u64 = rand::random();
445        let worker = JsWorker::new(include_str!("../bundled/plan_worker.js"));
446        let worker_is_set_up = worker
447            .request::<PlanCmd, BridgeSetupResult<serde_json::Value>>(PlanCmd::UpdateSchema {
448                schema,
449                config,
450                schema_id,
451            })
452            .await
453            .map_err(|e| {
454                vec![WorkerError {
455                    name: Some("planner setup error".to_string()),
456                    message: Some(e.to_string()),
457                    stack: None,
458                    extensions: None,
459                    locations: Default::default(),
460                }
461                .into()]
462            });
463
464        // Both cases below the mean schema update failed.
465        // We need to pay attention here.
466        // returning early will drop the worker, which will join the jsruntime thread.
467        // however the event loop will run for ever. We need to let the worker know it needs to exit,
468        // before we drop the worker
469        match worker_is_set_up {
470            Err(setup_error) => {
471                let _ = worker
472                    .request::<PlanCmd, serde_json::Value>(PlanCmd::Exit { schema_id })
473                    .await;
474                return Err(setup_error);
475            }
476            Ok(setup) => {
477                if let Some(error) = setup.errors {
478                    let _ = worker.send(None, PlanCmd::Exit { schema_id }).await;
479                    return Err(error);
480                }
481            }
482        }
483
484        let worker = Arc::new(worker);
485
486        Ok(Self {
487            worker,
488            schema_id,
489            t: PhantomData,
490        })
491    }
492
493    /// Update `Planner` from a schema string
494    pub async fn update(
495        &self,
496        schema: String,
497        config: QueryPlannerConfig,
498    ) -> Result<Self, Vec<PlannerError>> {
499        let schema_id: u64 = rand::random();
500
501        let worker_is_set_up = self
502            .worker
503            .request::<PlanCmd, BridgeSetupResult<serde_json::Value>>(PlanCmd::UpdateSchema {
504                schema,
505                config,
506                schema_id,
507            })
508            .await
509            .map_err(|e| {
510                vec![WorkerError {
511                    name: Some("planner setup error".to_string()),
512                    message: Some(e.to_string()),
513                    stack: None,
514                    extensions: None,
515                    locations: Default::default(),
516                }
517                .into()]
518            });
519
520        // If the update failed, we keep the existing schema in place
521        match worker_is_set_up {
522            Err(setup_error) => {
523                return Err(setup_error);
524            }
525            Ok(setup) => {
526                if let Some(error) = setup.errors {
527                    return Err(error);
528                }
529            }
530        }
531
532        Ok(Self {
533            worker: self.worker.clone(),
534            schema_id,
535            t: PhantomData,
536        })
537    }
538
539    /// Plan a query against an instantiated query planner
540    pub async fn plan(
541        &self,
542        query: String,
543        operation_name: Option<String>,
544        options: PlanOptions,
545    ) -> Result<PlanResult<T>, crate::error::Error> {
546        self.worker
547            .request(PlanCmd::Plan {
548                query,
549                operation_name,
550                schema_id: self.schema_id,
551                options,
552            })
553            .await
554    }
555
556    /// Generate the API schema from the current schema
557    pub async fn api_schema(&self) -> Result<ApiSchema, crate::error::Error> {
558        self.worker
559            .request(PlanCmd::ApiSchema {
560                schema_id: self.schema_id,
561            })
562            .await
563    }
564
565    /// Generate the introspection response for this query
566    pub async fn introspect(
567        &self,
568        query: String,
569    ) -> Result<IntrospectionResponse, crate::error::Error> {
570        self.worker
571            .request(PlanCmd::Introspect {
572                query,
573                schema_id: self.schema_id,
574            })
575            .await
576    }
577
578    /// Get the operation signature for a query
579    pub async fn operation_signature(
580        &self,
581        query: String,
582        operation_name: Option<String>,
583    ) -> Result<String, crate::error::Error> {
584        self.worker
585            .request(PlanCmd::Signature {
586                query,
587                operation_name,
588                schema_id: self.schema_id,
589            })
590            .await
591    }
592
593    /// Extract the subgraph schemas from the supergraph schema
594    pub async fn subgraphs(&self) -> Result<HashMap<String, String>, crate::error::Error> {
595        self.worker
596            .request(PlanCmd::Subgraphs {
597                schema_id: self.schema_id,
598            })
599            .await
600    }
601
602    /// Get deno's heap statistics
603    pub async fn get_heap_statistics(&self) -> Result<HeapStatistics, crate::error::Error> {
604        self.worker.request(PlanCmd::GetHeapStatistics).await
605    }
606}
607
608impl<T> Drop for Planner<T>
609where
610    T: DeserializeOwned + Send + Debug + 'static,
611{
612    fn drop(&mut self) {
613        // Send a PlanCmd::Exit signal
614        let worker_clone = self.worker.clone();
615        let schema_id = self.schema_id;
616        let _ = std::thread::spawn(move || {
617            let runtime = tokio::runtime::Builder::new_current_thread()
618                .build()
619                .unwrap();
620
621            let _ = runtime.block_on(async move {
622                worker_clone.send(None, PlanCmd::Exit { schema_id }).await
623            });
624        })
625        .join();
626    }
627}
628
629/// Options for planning a query
630#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
631#[serde(rename_all = "camelCase")]
632pub struct PlanOptions {
633    /// Which labels to override during query planning
634    pub override_conditions: Vec<String>,
635}
636
637#[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash)]
638#[serde(tag = "kind")]
639enum PlanCmd {
640    #[serde(rename_all = "camelCase")]
641    UpdateSchema {
642        schema: String,
643        config: QueryPlannerConfig,
644        schema_id: u64,
645    },
646    #[serde(rename_all = "camelCase")]
647    Plan {
648        query: String,
649        operation_name: Option<String>,
650        schema_id: u64,
651        options: PlanOptions,
652    },
653    #[serde(rename_all = "camelCase")]
654    ApiSchema { schema_id: u64 },
655    #[serde(rename_all = "camelCase")]
656    Introspect { query: String, schema_id: u64 },
657    #[serde(rename_all = "camelCase")]
658    Signature {
659        query: String,
660        operation_name: Option<String>,
661        schema_id: u64,
662    },
663    #[serde(rename_all = "camelCase")]
664    Subgraphs { schema_id: u64 },
665    #[serde(rename_all = "camelCase")]
666    Exit { schema_id: u64 },
667    #[serde(rename_all = "camelCase")]
668    GetHeapStatistics,
669}
670
671#[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash)]
672#[serde(rename_all = "camelCase")]
673/// Query planner configuration
674pub struct QueryPlannerConfig {
675    //exposeDocumentNodeInFetchNode?: boolean;
676
677    // Side-note: implemented as an object instead of single boolean because we expect to add more to this soon
678    // enough. In particular, once defer-passthrough to subgraphs is implemented, the idea would be to add a
679    // new `passthroughSubgraphs` option that is the list of subgraph to which we can pass-through some @defer
680    // (and it would be empty by default). Similarly, once we support @stream, grouping the options here will
681    // make sense too.
682    /// Option for `@defer` directive support
683    pub incremental_delivery: Option<IncrementalDeliverySupport>,
684    /// Whether to validate GraphQL schema and query text
685    pub graphql_validation: bool,
686    /// Whether the query planner should try to reused the named fragments of the planned query in subgraph fetches.
687    ///
688    /// This is often a good idea as it can prevent very large subgraph queries in some cases (named fragments can
689    /// make some relatively small queries (using said fragments) expand to a very large query if all the spreads
690    /// are inline). However, due to architecture of the query planner, this optimization is done as an additional
691    /// pass on the subgraph queries of the generated plan and can thus increase the latency of building a plan.
692    /// As long as query plans are sufficiently cached, this should not be a problem, which is why this option is
693    /// enabled by default, but if the distribution of inbound queries prevents efficient caching of query plans,
694    /// this may become an undesirable trade-off and can be disabled in that case.
695    ///
696    /// Defaults to `true` in the JS query planner. Defaults to `None` here in order to defer to the JS query
697    /// planner's default.
698    pub reuse_query_fragments: Option<bool>,
699
700    /// If enabled, the query planner will extract inline fragments into
701    /// fragment definitions before sending queries to subgraphs. This can
702    /// significantly reduce the size of the query sent to subgraphs, but may
703    /// increase the time it takes to plan the query.
704    pub generate_query_fragments: Option<bool>,
705
706    /// A sub-set of configurations that are meant for debugging or testing. All the configurations in this
707    /// sub-set are provided without guarantees of stability (they may be dangerous) or continued support (they
708    /// may be removed without warning).
709    pub debug: Option<QueryPlannerDebugConfig>,
710    /// Enables type conditioned fetching.
711    /// This flag is a workaround, which may yield significant
712    /// performance degradation when computing query plans,
713    /// and increase query plan size.
714    ///
715    /// If you aren't aware of this flag, you probably don't need it.
716    /// Defaults to false.
717    pub type_conditioned_fetching: bool,
718}
719
720impl Default for QueryPlannerConfig {
721    fn default() -> Self {
722        Self {
723            incremental_delivery: Some(IncrementalDeliverySupport {
724                enable_defer: Some(false),
725            }),
726            graphql_validation: true,
727            reuse_query_fragments: None,
728            generate_query_fragments: None,
729            debug: Default::default(),
730            type_conditioned_fetching: false,
731        }
732    }
733}
734
735#[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash)]
736#[serde(rename_all = "camelCase")]
737/// Option for `@defer` directive support
738pub struct IncrementalDeliverySupport {
739    /// Enables @defer support by the query planner.
740    ///
741    /// If set, then the query plan for queries having some @defer will contains some `DeferNode` (see `QueryPlan.ts`).
742    ///
743    /// Defaults to false (meaning that the @defer are ignored).
744    #[serde(default)]
745    pub enable_defer: Option<bool>,
746}
747
748#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq, Hash)]
749#[serde(rename_all = "camelCase")]
750/// Query planner debug configuration
751pub struct QueryPlannerDebugConfig {
752    /// If used and the supergraph is built from a single subgraph, then user queries do not go through the
753    /// normal query planning and instead a fetch to the one subgraph is built directly from the input query.
754    pub bypass_planner_for_single_subgraph: Option<bool>,
755
756    /// Query planning is an exploratory process. Depending on the specificities and feature used by
757    /// subgraphs, there could exist may different theoretical valid (if not always efficient) plans
758    /// for a given query, and at a high level, the query planner generates those possible choices,
759    /// evaluate them, and return the best one. In some complex cases however, the number of
760    /// theoretically possible plans can be very large, and to keep query planning time acceptable,
761    /// the query planner cap the maximum number of plans it evaluates. This config allows to configure
762    /// that cap. Note if planning a query hits that cap, then the planner will still always return a
763    /// "correct" plan, but it may not return _the_ optimal one, so this config can be considered a
764    /// trade-off between the worst-time for query planning computation processing, and the risk of
765    /// having non-optimal query plans (impacting query runtimes).
766    ///
767    /// This value currently defaults to 10 000, but this default is considered an implementation
768    /// detail and is subject to change. We do not recommend setting this value unless it is to
769    /// debug a specific issue (with unexpectedly slow query planning for instance). Remember that
770    /// setting this value too low can negatively affect query runtime (due to the use of sub-optimal
771    /// query plans).
772    pub max_evaluated_plans: Option<u32>,
773
774    /// Before creating query plans, for each path of fields in the query we compute all the
775    /// possible options to traverse that path via the subgraphs. Multiple options can arise because
776    /// fields in the path can be provided by multiple subgraphs, and abstract types (i.e. unions
777    /// and interfaces) returned by fields sometimes require the query planner to traverse through
778    /// each constituent object type. The number of options generated in this computation can grow
779    /// large if the schema or query are sufficiently complex, and that will increase the time spent
780    /// planning.
781    ///
782    /// This config allows specifying a per-path limit to the number of options considered. If any
783    /// path's options exceeds this limit, query planning will abort and the operation will fail.
784    ///
785    /// The default value is None, which specifies no limit.
786    pub paths_limit: Option<u32>,
787}
788#[cfg(test)]
789mod tests {
790    use futures::stream;
791    use futures::stream::StreamExt;
792
793    use std::collections::BTreeMap;
794
795    use super::*;
796
797    const QUERY: &str = include_str!("testdata/query.graphql");
798    const QUERY2: &str = include_str!("testdata/query2.graphql");
799    const MULTIPLE_QUERIES: &str = include_str!("testdata/query_with_multiple_operations.graphql");
800    const NO_OPERATION: &str = include_str!("testdata/no_operation.graphql");
801    const QUERY_REUSE_QUERY_FRAGMENTS: &str =
802        include_str!("testdata/query_reuse_query_fragments.graphql");
803    const QUERY_GENERATE_QUERY_FRAGMENTS: &str =
804        include_str!("testdata/query_generate_query_fragments.graphql");
805
806    const MULTIPLE_ANONYMOUS_QUERIES: &str =
807        include_str!("testdata/query_with_multiple_anonymous_operations.graphql");
808    const NAMED_QUERY: &str = include_str!("testdata/named_query.graphql");
809    const SCHEMA: &str = include_str!("testdata/schema.graphql");
810    const SCHEMA_WITHOUT_REVIEW_BODY: &str =
811        include_str!("testdata/schema_without_review_body.graphql");
812    const SCHEMA_REUSE_QUERY_FRAGMENTS: &str =
813        include_str!("testdata/schema_reuse_query_fragments.graphql");
814    const SCHEMA_GENERATE_QUERY_FRAGMENTS: &str =
815        include_str!("testdata/schema_generate_query_fragments.graphql");
816    const CORE_IN_V0_1: &str = include_str!("testdata/core_in_v0.1.graphql");
817    const UNSUPPORTED_FEATURE: &str = include_str!("testdata/unsupported_feature.graphql");
818    const UNSUPPORTED_FEATURE_FOR_EXECUTION: &str =
819        include_str!("testdata/unsupported_feature_for_execution.graphql");
820    const UNSUPPORTED_FEATURE_FOR_SECURITY: &str =
821        include_str!("testdata/unsupported_feature_for_security.graphql");
822    const PROGRESSIVE_OVERRIDE: &str = include_str!("testdata/progressive_override.graphql");
823
824    #[tokio::test]
825    async fn anonymous_query_works() {
826        let planner =
827            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
828                .await
829                .unwrap();
830
831        let payload = planner
832            .plan(QUERY.to_string(), None, PlanOptions::default())
833            .await
834            .unwrap()
835            .into_result()
836            .unwrap();
837        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
838        insta::with_settings!({sort_maps => true}, {
839            insta::assert_json_snapshot!(payload.usage_reporting);
840        });
841    }
842
843    // This test runs the same query twice, but provides an override label on
844    // the first run. The resulting snapshots should show the difference in the
845    // query plans.
846    #[tokio::test]
847    async fn progressive_override() {
848        let query = "{ t { a } }";
849        let planner = Planner::<serde_json::Value>::new(
850            PROGRESSIVE_OVERRIDE.to_string(),
851            QueryPlannerConfig::default(),
852        )
853        .await
854        .unwrap();
855
856        let payload1 = planner
857            .plan(
858                query.to_string(),
859                None,
860                PlanOptions {
861                    override_conditions: vec!["foo".to_string()],
862                    ..Default::default()
863                },
864            )
865            .await
866            .unwrap()
867            .into_result()
868            .unwrap();
869        insta::assert_snapshot!(serde_json::to_string_pretty(&payload1.data).unwrap());
870
871        let payload2 = planner
872            .plan(query.to_string(), None, PlanOptions::default())
873            .await
874            .unwrap()
875            .into_result()
876            .unwrap();
877        insta::assert_snapshot!(serde_json::to_string_pretty(&payload2.data).unwrap());
878    }
879
880    #[tokio::test]
881    async fn named_query_works() {
882        let planner =
883            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
884                .await
885                .unwrap();
886
887        let payload = planner
888            .plan(NAMED_QUERY.to_string(), None, PlanOptions::default())
889            .await
890            .unwrap()
891            .into_result()
892            .unwrap();
893        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
894        insta::with_settings!({sort_maps => true}, {
895            insta::assert_json_snapshot!(payload.usage_reporting);
896        });
897    }
898
899    #[tokio::test]
900    async fn named_query_with_several_choices_works() {
901        let planner =
902            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
903                .await
904                .unwrap();
905
906        let payload = planner
907            .plan(
908                MULTIPLE_QUERIES.to_string(),
909                Some("MyFirstName".to_string()),
910                PlanOptions::default(),
911            )
912            .await
913            .unwrap()
914            .into_result()
915            .unwrap();
916        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
917        insta::with_settings!({sort_maps => true}, {
918            insta::assert_json_snapshot!(payload.usage_reporting);
919        });
920    }
921
922    #[tokio::test]
923    async fn named_query_with_operation_name_works() {
924        let planner =
925            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
926                .await
927                .unwrap();
928
929        let payload = planner
930            .plan(
931                NAMED_QUERY.to_string(),
932                Some("MyFirstAndLastName".to_string()),
933                PlanOptions::default(),
934            )
935            .await
936            .unwrap()
937            .into_result()
938            .unwrap();
939        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
940    }
941
942    #[tokio::test]
943    async fn reuse_query_fragments_defaults_to_true() {
944        let planner = Planner::<serde_json::Value>::new(
945            SCHEMA_REUSE_QUERY_FRAGMENTS.to_string(),
946            QueryPlannerConfig::default(),
947        )
948        .await
949        .unwrap();
950
951        let payload = planner
952            .plan(
953                QUERY_REUSE_QUERY_FRAGMENTS.to_string(),
954                None,
955                PlanOptions::default(),
956            )
957            .await
958            .unwrap()
959            .into_result()
960            .unwrap();
961        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
962    }
963
964    #[tokio::test]
965    async fn reuse_query_fragments_explicit_true() {
966        let planner = Planner::<serde_json::Value>::new(
967            SCHEMA_REUSE_QUERY_FRAGMENTS.to_string(),
968            QueryPlannerConfig {
969                generate_query_fragments: Default::default(),
970                reuse_query_fragments: Some(true),
971                ..Default::default()
972            },
973        )
974        .await
975        .unwrap();
976
977        let payload = planner
978            .plan(
979                QUERY_REUSE_QUERY_FRAGMENTS.to_string(),
980                None,
981                PlanOptions::default(),
982            )
983            .await
984            .unwrap()
985            .into_result()
986            .unwrap();
987        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
988    }
989
990    #[tokio::test]
991    async fn reuse_query_fragments_false() {
992        let planner = Planner::<serde_json::Value>::new(
993            SCHEMA_REUSE_QUERY_FRAGMENTS.to_string(),
994            QueryPlannerConfig {
995                generate_query_fragments: Default::default(),
996                reuse_query_fragments: Some(false),
997                ..Default::default()
998            },
999        )
1000        .await
1001        .unwrap();
1002
1003        let payload = planner
1004            .plan(
1005                QUERY_REUSE_QUERY_FRAGMENTS.to_string(),
1006                None,
1007                PlanOptions::default(),
1008            )
1009            .await
1010            .unwrap()
1011            .into_result()
1012            .unwrap();
1013        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
1014    }
1015
1016    #[tokio::test]
1017    async fn generate_query_fragments_defaults_to_false() {
1018        let planner = Planner::<serde_json::Value>::new(
1019            SCHEMA_GENERATE_QUERY_FRAGMENTS.to_string(),
1020            QueryPlannerConfig::default(),
1021        )
1022        .await
1023        .unwrap();
1024
1025        let payload = planner
1026            .plan(
1027                QUERY_GENERATE_QUERY_FRAGMENTS.to_string(),
1028                None,
1029                PlanOptions::default(),
1030            )
1031            .await
1032            .unwrap()
1033            .into_result()
1034            .unwrap();
1035        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
1036    }
1037
1038    #[tokio::test]
1039    async fn generate_query_fragments_explicit_false() {
1040        let planner = Planner::<serde_json::Value>::new(
1041            SCHEMA_GENERATE_QUERY_FRAGMENTS.to_string(),
1042            QueryPlannerConfig {
1043                generate_query_fragments: Some(false),
1044                ..Default::default()
1045            },
1046        )
1047        .await
1048        .unwrap();
1049
1050        let payload = planner
1051            .plan(
1052                QUERY_GENERATE_QUERY_FRAGMENTS.to_string(),
1053                None,
1054                PlanOptions::default(),
1055            )
1056            .await
1057            .unwrap()
1058            .into_result()
1059            .unwrap();
1060        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
1061    }
1062
1063    #[tokio::test]
1064    async fn generate_query_fragments_true() {
1065        let planner = Planner::<serde_json::Value>::new(
1066            SCHEMA_GENERATE_QUERY_FRAGMENTS.to_string(),
1067            QueryPlannerConfig {
1068                generate_query_fragments: Some(true),
1069                ..Default::default()
1070            },
1071        )
1072        .await
1073        .unwrap();
1074
1075        let payload = planner
1076            .plan(
1077                QUERY_GENERATE_QUERY_FRAGMENTS.to_string(),
1078                None,
1079                PlanOptions::default(),
1080            )
1081            .await
1082            .unwrap()
1083            .into_result()
1084            .unwrap();
1085        insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
1086    }
1087
1088    #[tokio::test]
1089    async fn parse_errors_return_the_right_usage_reporting_data() {
1090        let planner =
1091            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1092                .await
1093                .unwrap();
1094
1095        let payload = planner
1096            .plan(
1097                "this query will definitely not parse".to_string(),
1098                None,
1099                PlanOptions::default(),
1100            )
1101            .await
1102            .unwrap()
1103            .into_result()
1104            .unwrap_err();
1105
1106        assert_eq!(
1107            "Syntax Error: Unexpected Name \"this\".",
1108            payload.errors[0].message.as_ref().unwrap()
1109        );
1110        assert_eq!(
1111            "## GraphQLParseFailure\n",
1112            payload.usage_reporting.stats_report_key
1113        );
1114    }
1115
1116    #[tokio::test]
1117    async fn validation_errors_return_the_right_usage_reporting_data() {
1118        let planner =
1119            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1120                .await
1121                .unwrap();
1122
1123        let payload = planner
1124            .plan(
1125                // These two fragments will spread themselves into a cycle, which is invalid per NoFragmentCyclesRule.
1126                "\
1127            fragment thatUserFragment1 on User {
1128                id
1129                ...thatUserFragment2
1130            }
1131            fragment thatUserFragment2 on User {
1132                id
1133                ...thatUserFragment1
1134            }
1135            query { me { id ...thatUserFragment1 } }"
1136                    .to_string(),
1137                None,
1138                PlanOptions::default(),
1139            )
1140            .await
1141            .unwrap()
1142            .into_result()
1143            .unwrap_err();
1144
1145        assert_eq!(
1146            "Cannot spread fragment \"thatUserFragment1\" within itself via \"thatUserFragment2\".",
1147            payload.errors[0].message.as_ref().unwrap()
1148        );
1149        assert_eq!(
1150            "## GraphQLValidationFailure\n",
1151            payload.usage_reporting.stats_report_key
1152        );
1153    }
1154
1155    #[tokio::test]
1156    async fn unknown_operation_name_errors_return_the_right_usage_reporting_data() {
1157        let planner =
1158            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1159                .await
1160                .unwrap();
1161
1162        let payload = planner
1163            .plan(
1164                QUERY.to_string(),
1165                Some("ThisOperationNameDoesntExist".to_string()),
1166                PlanOptions::default(),
1167            )
1168            .await
1169            .unwrap()
1170            .into_result()
1171            .unwrap_err();
1172
1173        assert_eq!(
1174            "Unknown operation named \"ThisOperationNameDoesntExist\"",
1175            payload.errors[0].message.as_ref().unwrap()
1176        );
1177        assert_eq!(
1178            "## GraphQLUnknownOperationName\n",
1179            payload.usage_reporting.stats_report_key
1180        );
1181    }
1182
1183    #[tokio::test]
1184    async fn must_provide_operation_name_errors_return_the_right_usage_reporting_data() {
1185        let planner =
1186            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1187                .await
1188                .unwrap();
1189
1190        let payload = planner
1191            .plan(MULTIPLE_QUERIES.to_string(), None, PlanOptions::default())
1192            .await
1193            .unwrap()
1194            .into_result()
1195            .unwrap_err();
1196
1197        assert_eq!(
1198            "Must provide operation name if query contains multiple operations.",
1199            payload.errors[0].message.as_ref().unwrap()
1200        );
1201        assert_eq!(
1202            "## GraphQLUnknownOperationName\n",
1203            payload.usage_reporting.stats_report_key
1204        );
1205    }
1206
1207    #[tokio::test]
1208    async fn multiple_anonymous_queries_return_the_expected_usage_reporting_data() {
1209        let planner =
1210            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1211                .await
1212                .unwrap();
1213
1214        let payload = planner
1215            .plan(
1216                MULTIPLE_ANONYMOUS_QUERIES.to_string(),
1217                None,
1218                PlanOptions::default(),
1219            )
1220            .await
1221            .unwrap()
1222            .into_result()
1223            .unwrap_err();
1224
1225        assert_eq!(
1226            "This anonymous operation must be the only defined operation.",
1227            payload.errors[0].message.as_ref().unwrap()
1228        );
1229        assert_eq!(
1230            "## GraphQLValidationFailure\n",
1231            payload.usage_reporting.stats_report_key
1232        );
1233    }
1234
1235    #[tokio::test]
1236    async fn no_operation_in_document() {
1237        let planner =
1238            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1239                .await
1240                .unwrap();
1241
1242        let payload = planner
1243            .plan(NO_OPERATION.to_string(), None, PlanOptions::default())
1244            .await
1245            .unwrap()
1246            .into_result()
1247            .unwrap_err();
1248
1249        assert_eq!(
1250            "Fragment \"thatUserFragment1\" is never used.",
1251            payload.errors[0].message.as_ref().unwrap()
1252        );
1253        assert_eq!(
1254            "## GraphQLValidationFailure\n",
1255            payload.usage_reporting.stats_report_key
1256        );
1257    }
1258
1259    #[tokio::test]
1260    // A series of queries that should fail graphql-js's validate function.  The federation
1261    // query planning logic automatically does some validation in order to do its duties.
1262    // Some, but not all, of that validation is also handled by the graphql-js validator.
1263    // However, we are trying to assert that we are testing graphql-js validation, not
1264    // Federation's query planner validation.  So we run a few validations which we do not
1265    // expect to every show up in Federation's query planner validation.
1266    // This one is for the NoFragmentCyclesRule in graphql/validate
1267    async fn invalid_graphql_validation_1_is_caught() {
1268        let errors= vec![PlanError {
1269                message: Some("Cannot spread fragment \"thatUserFragment1\" within itself via \"thatUserFragment2\".".to_string()),
1270                extensions: Some(PlanErrorExtensions {
1271                    code: String::from("GRAPHQL_VALIDATION_FAILED"),
1272                    exception: None,
1273                }),
1274                validation_error: true,
1275            }];
1276
1277        assert_errors(
1278            errors,
1279            // These two fragments will spread themselves into a cycle, which is invalid per NoFragmentCyclesRule.
1280            "\
1281        fragment thatUserFragment1 on User {
1282            id
1283            ...thatUserFragment2
1284        }
1285        fragment thatUserFragment2 on User {
1286            id
1287            ...thatUserFragment1
1288        }
1289        query { me { id ...thatUserFragment1 } }"
1290                .to_string(),
1291            None,
1292        )
1293        .await;
1294    }
1295
1296    #[tokio::test]
1297    // A series of queries that should fail graphql-js's validate function.  The federation
1298    // query planning logic automatically does some validation in order to do its duties.
1299    // Some, but not all, of that validation is also handled by the graphql-js validator.
1300    // However, we are trying to assert that we are testing graphql-js validation, not
1301    // Federation's query planner validation.  So we run a few validations which we do not
1302    // expect to every show up in Federation's query planner validation.
1303    // This one is for the ScalarLeafsRule in graphql/validate
1304    async fn invalid_graphql_validation_2_is_caught() {
1305        let errors = vec![PlanError {
1306            message: Some(
1307                "Field \"id\" must not have a selection since type \"ID!\" has no subfields."
1308                    .to_string(),
1309            ),
1310            extensions: Some(PlanErrorExtensions {
1311                code: String::from("GRAPHQL_VALIDATION_FAILED"),
1312                exception: None,
1313            }),
1314            validation_error: true,
1315        }];
1316
1317        assert_errors(
1318            errors,
1319            // This Book resolver requires a selection set, per the schema.
1320            "{ me { id { absolutelyNotAcceptableLeaf } } }".to_string(),
1321            None,
1322        )
1323        .await;
1324    }
1325
1326    #[tokio::test]
1327    // A series of queries that should fail graphql-js's validate function.  The federation
1328    // query planning logic automatically does some validation in order to do its duties.
1329    // Some, but not all, of that validation is also handled by the graphql-js validator.
1330    // However, we are trying to assert that we are testing graphql-js validation, not
1331    // Federation's query planner validation.  So we run a few validations which we do not
1332    // expect to every show up in Federation's query planner validation.
1333    // This one is for NoUnusedFragmentsRule in graphql/validate
1334    async fn invalid_graphql_validation_3_is_caught() {
1335        let errors = vec![PlanError {
1336            message: Some("Fragment \"UnusedTestFragment\" is never used.".to_string()),
1337            extensions: Some(PlanErrorExtensions {
1338                code: String::from("GRAPHQL_VALIDATION_FAILED"),
1339                exception: None,
1340            }),
1341            validation_error: true,
1342        }];
1343
1344        assert_errors(
1345            errors,
1346            // This Book resolver requires a selection set, per the schema.
1347            "fragment UnusedTestFragment on User { id } query { me { id } }".to_string(),
1348            None,
1349        )
1350        .await;
1351    }
1352
1353    #[tokio::test]
1354    async fn invalid_federation_validation_is_caught() {
1355        let errors = vec![PlanError {
1356            message: Some(
1357                "Must provide operation name if query contains multiple operations.".to_string(),
1358            ),
1359            extensions: Some(PlanErrorExtensions {
1360                code: "GRAPHQL_VALIDATION_FAILED".to_string(),
1361                exception: None,
1362            }),
1363            validation_error: false,
1364        }];
1365
1366        assert_errors(
1367            errors, // This requires passing an operation name (because there are multiple operations)
1368            // but we have not done that! Therefore, we expect a validation error from planning.
1369            "query Operation1 { me { id } } query Operation2 { me { id } }".to_string(),
1370            None,
1371        )
1372        .await;
1373    }
1374
1375    #[tokio::test]
1376    async fn invalid_schema_is_caught() {
1377        let expected_errors: Vec<PlannerError> = vec![WorkerGraphQLError {
1378            name: "GraphQLError".to_string(),
1379            message: "Syntax Error: Unexpected Name \"Garbage\".".to_string(),
1380            extensions: None,
1381            locations: vec![Location { line: 1, column: 1 }],
1382            original_error: None,
1383            causes: vec![],
1384            validation_error: false,
1385        }
1386        .into()];
1387
1388        let actual_error =
1389            Planner::<serde_json::Value>::new("Garbage".to_string(), QueryPlannerConfig::default())
1390                .await
1391                .unwrap_err();
1392
1393        assert_eq!(expected_errors, actual_error);
1394    }
1395
1396    #[tokio::test]
1397    async fn syntactically_incorrect_query_is_caught() {
1398        let errors = vec![PlanError {
1399            message: Some("Syntax Error: Unexpected Name \"Garbage\".".to_string()),
1400            extensions: Some(PlanErrorExtensions {
1401                code: String::from("GRAPHQL_PARSE_FAILED"),
1402                exception: None,
1403            }),
1404            validation_error: false,
1405        }];
1406
1407        assert_errors(errors, "Garbage".to_string(), None).await;
1408    }
1409
1410    #[tokio::test]
1411    async fn query_missing_subfields() {
1412        let expected_error_message = r#"Field "reviews" of type "[Review]" must have a selection of subfields. Did you mean "reviews { ... }"?"#;
1413
1414        let errors = vec![PlanError {
1415            message: Some(expected_error_message.to_string()),
1416            extensions: Some(PlanErrorExtensions {
1417                code: String::from("GRAPHQL_VALIDATION_FAILED"),
1418                exception: None,
1419            }),
1420            validation_error: true,
1421        }];
1422
1423        assert_errors(
1424            errors,
1425            // This query contains reviews, which requires subfields
1426            "query ExampleQuery { me { id reviews } }".to_string(),
1427            None,
1428        )
1429        .await;
1430    }
1431
1432    #[tokio::test]
1433    async fn query_field_that_doesnt_exist() {
1434        let expected_error_message = r#"Cannot query field "thisDoesntExist" on type "Query"."#;
1435        let errors = vec![PlanError {
1436            message: Some(expected_error_message.to_string()),
1437            extensions: Some(PlanErrorExtensions {
1438                code: String::from("GRAPHQL_VALIDATION_FAILED"),
1439                exception: None,
1440            }),
1441            validation_error: true,
1442        }];
1443
1444        assert_errors(
1445            errors,
1446            // This query contains reviews, which requires subfields
1447            "query ExampleQuery { thisDoesntExist }".to_string(),
1448            None,
1449        )
1450        .await;
1451    }
1452
1453    async fn assert_errors(
1454        expected_errors: Vec<PlanError>,
1455        query: String,
1456        operation_name: Option<String>,
1457    ) {
1458        let planner =
1459            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1460                .await
1461                .unwrap();
1462
1463        let actual = planner
1464            .plan(query, operation_name, PlanOptions::default())
1465            .await
1466            .unwrap();
1467
1468        assert_eq!(expected_errors, actual.errors.unwrap());
1469    }
1470
1471    #[tokio::test]
1472    async fn it_doesnt_race() {
1473        let planner =
1474            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1475                .await
1476                .unwrap();
1477
1478        let query_1_response = planner
1479            .plan(QUERY.to_string(), None, PlanOptions::default())
1480            .await
1481            .unwrap()
1482            .data
1483            .unwrap();
1484
1485        let query_2_response = planner
1486            .plan(QUERY2.to_string(), None, PlanOptions::default())
1487            .await
1488            .unwrap()
1489            .data
1490            .unwrap();
1491
1492        let all_futures = stream::iter((0..1000).map(|i| {
1493            let (query, fut) = if i % 2 == 0 {
1494                (
1495                    QUERY,
1496                    planner.plan(QUERY.to_string(), None, PlanOptions::default()),
1497                )
1498            } else {
1499                (
1500                    QUERY2,
1501                    planner.plan(QUERY2.to_string(), None, PlanOptions::default()),
1502                )
1503            };
1504
1505            async move { (query, fut.await.unwrap()) }
1506        }));
1507
1508        all_futures
1509            .for_each_concurrent(None, |fut| async {
1510                let (query, plan_result) = fut.await;
1511                if query == QUERY {
1512                    assert_eq!(query_1_response, plan_result.data.unwrap());
1513                } else {
1514                    assert_eq!(query_2_response, plan_result.data.unwrap());
1515                }
1516            })
1517            .await;
1518    }
1519
1520    #[tokio::test]
1521    async fn error_on_core_in_v0_1() {
1522        let expected_errors: Vec<PlannerError> = vec![
1523            WorkerGraphQLError {
1524                name: "GraphQLError".to_string(),
1525                message: r#"one or more checks failed. Caused by:
1526the `for:` argument is unsupported by version v0.1 of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).
1527
1528GraphQL request:2:1
15291 | schema
15302 | @core(feature: "https://specs.apollo.dev/core/v0.1")
1531  | ^
15323 | @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
1533
1534GraphQL request:3:1
15352 | @core(feature: "https://specs.apollo.dev/core/v0.1")
15363 | @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
1537  | ^
15384 | @core(
1539
1540GraphQL request:4:1
15413 | @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
15424 | @core(
1543  | ^
15445 |     feature: "https://specs.apollo.dev/something-unsupported/v0.1"
1545
1546feature https://specs.apollo.dev/something-unsupported/v0.1 is for: SECURITY but is unsupported
1547
1548GraphQL request:4:1
15493 | @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
15504 | @core(
1551  | ^
15525 |     feature: "https://specs.apollo.dev/something-unsupported/v0.1""#.to_string(),
1553                locations: Default::default(),
1554                extensions: Some(PlanErrorExtensions {
1555                    code: "CheckFailed".to_string(),
1556                    exception: None
1557                }),
1558                original_error: None,
1559                causes: vec![
1560                    Box::new(WorkerError {
1561                        message: Some("the `for:` argument is unsupported by version v0.1 of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).".to_string()),
1562                        name: None,
1563                        stack: None,
1564                        extensions: Some(PlanErrorExtensions { code: "UNSUPPORTED_LINKED_FEATURE".to_string(), exception: None }),
1565                        locations: vec![Location { line: 2, column: 1 }, Location { line: 3, column: 1 }, Location { line: 4, column: 1 }]
1566                    }),
1567                    Box::new(WorkerError {
1568                        message: Some("feature https://specs.apollo.dev/something-unsupported/v0.1 is for: SECURITY but is unsupported".to_string()),
1569                        name: None,
1570                        stack: None,
1571                        extensions: Some(PlanErrorExtensions { code: "UNSUPPORTED_LINKED_FEATURE".to_string(), exception: None }),
1572                        locations: vec![Location { line: 4, column: 1 }]
1573                    })
1574                ],
1575                validation_error: false,
1576            }.into()
1577        ];
1578        let actual_errors = Planner::<serde_json::Value>::new(
1579            CORE_IN_V0_1.to_string(),
1580            QueryPlannerConfig::default(),
1581        )
1582        .await
1583        .unwrap_err();
1584
1585        pretty_assertions::assert_eq!(expected_errors, actual_errors);
1586    }
1587
1588    #[tokio::test]
1589    async fn unsupported_feature_without_for() {
1590        // this should not return an error
1591        // see gateway test "it doesn't throw errors when using unsupported features which have no `for:` argument"
1592        Planner::<serde_json::Value>::new(
1593            UNSUPPORTED_FEATURE.to_string(),
1594            QueryPlannerConfig::default(),
1595        )
1596        .await
1597        .unwrap();
1598    }
1599
1600    #[tokio::test]
1601    async fn unsupported_feature_for_execution() {
1602        let expected_errors: Vec<PlannerError> = vec![
1603            WorkerGraphQLError {
1604                name: "GraphQLError".to_string(),
1605                message: r#"one or more checks failed. Caused by:
1606feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: EXECUTION but is unsupported
1607
1608GraphQL request:4:9
16093 |         @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
16104 |         @core(
1611  |         ^
16125 |           feature: "https://specs.apollo.dev/unsupported-feature/v0.1""#.to_string(),
1613                locations: Default::default(),
1614                extensions: Some(PlanErrorExtensions {
1615                    code: "CheckFailed".to_string(),
1616                    exception: None
1617                }),
1618                original_error: None,
1619                causes: vec![
1620                    Box::new(WorkerError {
1621                        message: Some("feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: EXECUTION but is unsupported".to_string()),
1622                        name: None,
1623                        stack: None,
1624                        extensions: Some(PlanErrorExtensions { code: "UNSUPPORTED_LINKED_FEATURE".to_string(), exception: None }),
1625                        locations: vec![Location { line: 4, column: 9 }]
1626                    }),
1627                ],
1628                validation_error: false,
1629            }.into()
1630        ];
1631        let actual_errors = Planner::<serde_json::Value>::new(
1632            UNSUPPORTED_FEATURE_FOR_EXECUTION.to_string(),
1633            QueryPlannerConfig::default(),
1634        )
1635        .await
1636        .unwrap_err();
1637
1638        pretty_assertions::assert_eq!(expected_errors, actual_errors);
1639    }
1640
1641    #[tokio::test]
1642    async fn unsupported_feature_for_security() {
1643        let expected_errors: Vec<PlannerError> = vec![WorkerGraphQLError {
1644            name:"GraphQLError".into(),
1645            message: r#"one or more checks failed. Caused by:
1646feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: SECURITY but is unsupported
1647
1648GraphQL request:4:9
16493 |         @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
16504 |         @core(
1651  |         ^
16525 |           feature: "https://specs.apollo.dev/unsupported-feature/v0.1""#.to_string(),
1653            locations: vec![],
1654            extensions: Some(PlanErrorExtensions {
1655                code: "CheckFailed".to_string(),
1656                exception: None
1657            }),
1658            original_error: None,
1659            causes: vec![Box::new(WorkerError {
1660                message: Some("feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: SECURITY but is unsupported".to_string()),
1661                extensions: Some(PlanErrorExtensions {
1662                    code: "UNSUPPORTED_LINKED_FEATURE".to_string(),
1663                    exception: None
1664                }),
1665                name: None,
1666                stack: None,
1667                locations: vec![Location { line: 4, column: 9 }]
1668            })],
1669            validation_error: false,
1670        }
1671        .into()];
1672        let actual_errors = Planner::<serde_json::Value>::new(
1673            UNSUPPORTED_FEATURE_FOR_SECURITY.to_string(),
1674            QueryPlannerConfig::default(),
1675        )
1676        .await
1677        .unwrap_err();
1678
1679        pretty_assertions::assert_eq!(expected_errors, actual_errors);
1680    }
1681
1682    #[tokio::test]
1683    async fn api_schema() {
1684        let planner =
1685            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1686                .await
1687                .unwrap();
1688
1689        let api_schema = planner.api_schema().await.unwrap();
1690        insta::assert_snapshot!(api_schema.schema);
1691    }
1692    // This string is the result of calling getIntrospectionQuery() from the 'graphql' js package.
1693    static INTROSPECTION: &str = r#"
1694query IntrospectionQuery {
1695__schema {
1696    queryType {
1697        name
1698    }
1699    mutationType {
1700        name
1701    }
1702    subscriptionType {
1703        name
1704    }
1705    types {
1706        ...FullType
1707    }
1708    directives {
1709        name
1710        description
1711        locations
1712        args {
1713            ...InputValue
1714        }
1715    }
1716}
1717}
1718
1719fragment FullType on __Type {
1720kind
1721name
1722description
1723
1724fields(includeDeprecated: true) {
1725    name
1726    description
1727    args {
1728        ...InputValue
1729    }
1730    type {
1731        ...TypeRef
1732    }
1733    isDeprecated
1734    deprecationReason
1735}
1736inputFields {
1737    ...InputValue
1738}
1739interfaces {
1740    ...TypeRef
1741}
1742enumValues(includeDeprecated: true) {
1743    name
1744    description
1745    isDeprecated
1746    deprecationReason
1747}
1748possibleTypes {
1749    ...TypeRef
1750}
1751}
1752
1753fragment InputValue on __InputValue {
1754name
1755description
1756type {
1757    ...TypeRef
1758}
1759defaultValue
1760}
1761
1762fragment TypeRef on __Type {
1763kind
1764name
1765ofType {
1766    kind
1767    name
1768    ofType {
1769        kind
1770        name
1771        ofType {
1772            kind
1773            name
1774                ofType {
1775                kind
1776                name
1777                ofType {
1778                    kind
1779                    name
1780                        ofType {
1781                        kind
1782                        name
1783                        ofType {
1784                            kind
1785                            name
1786                        }
1787                    }
1788                }
1789            }
1790        }
1791    }
1792}
1793}
1794"#;
1795    #[tokio::test]
1796    async fn introspect() {
1797        let planner =
1798            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1799                .await
1800                .unwrap();
1801
1802        let introspection_response = planner.introspect(INTROSPECTION.to_string()).await.unwrap();
1803        insta::assert_json_snapshot!(serde_json::to_value(introspection_response).unwrap());
1804    }
1805
1806    #[tokio::test]
1807    async fn planner_update() {
1808        let query = "{ me { id name {first } reviews { id author { name { first } } body } } }";
1809        let planner = Planner::<serde_json::Value>::new(
1810            SCHEMA_WITHOUT_REVIEW_BODY.to_string(),
1811            QueryPlannerConfig::default(),
1812        )
1813        .await
1814        .unwrap();
1815        let query_plan1 = planner
1816            .plan(query.to_string(), None, PlanOptions::default())
1817            .await
1818            .unwrap()
1819            .into_result()
1820            .unwrap_err();
1821        insta::assert_snapshot!(&format!("{query_plan1:#?}"));
1822        let api_schema1 = planner.api_schema().await.unwrap();
1823        insta::assert_snapshot!(api_schema1.schema);
1824        let introspected_schema1 = planner.introspect(INTROSPECTION.to_string()).await.unwrap();
1825
1826        let updated_planner = planner
1827            .update(SCHEMA.to_string(), QueryPlannerConfig::default())
1828            .await
1829            .unwrap();
1830        let query_plan2 = updated_planner
1831            .plan(query.to_string(), None, PlanOptions::default())
1832            .await
1833            .unwrap()
1834            .into_result()
1835            .unwrap();
1836        insta::assert_snapshot!(serde_json::to_string_pretty(&query_plan2.data).unwrap());
1837        let api_schema2 = updated_planner.api_schema().await.unwrap();
1838        insta::assert_snapshot!(api_schema2.schema);
1839
1840        // we should still be able to call the old planner, and it must have kept the same schema
1841        assert_eq!(
1842            planner.introspect(INTROSPECTION.to_string()).await.unwrap(),
1843            introspected_schema1
1844        );
1845
1846        let introspected_schema2 = updated_planner
1847            .introspect(INTROSPECTION.to_string())
1848            .await
1849            .unwrap();
1850        assert_ne!(introspected_schema1, introspected_schema2);
1851
1852        // Now we drop the old planner. The updated planner should still work
1853        drop(planner);
1854
1855        assert_eq!(
1856            query_plan2.data,
1857            updated_planner
1858                .plan(query.to_string(), None, PlanOptions::default())
1859                .await
1860                .unwrap()
1861                .into_result()
1862                .unwrap()
1863                .data
1864        );
1865    }
1866
1867    #[tokio::test]
1868    async fn get_operation_signature() {
1869        let planner =
1870            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1871                .await
1872                .unwrap();
1873
1874        let signature = planner
1875            .operation_signature(NAMED_QUERY.to_string(), None)
1876            .await
1877            .unwrap();
1878        insta::assert_snapshot!(signature);
1879    }
1880
1881    #[tokio::test]
1882    async fn subgraphs() {
1883        let planner =
1884            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1885                .await
1886                .unwrap();
1887
1888        let subgraphs = planner.subgraphs().await.unwrap();
1889        let subgraphs: BTreeMap<String, String> = subgraphs.into_iter().collect();
1890        for schema in subgraphs.values() {
1891            insta::assert_snapshot!(schema);
1892        }
1893    }
1894
1895    #[tokio::test]
1896    async fn heap_statistics() {
1897        let planner =
1898            Planner::<serde_json::Value>::new(SCHEMA.to_string(), QueryPlannerConfig::default())
1899                .await
1900                .unwrap();
1901
1902        let _subgraphs = planner.subgraphs().await.unwrap();
1903        let statistics = planner.get_heap_statistics().await.unwrap();
1904
1905        println!("statistics: {statistics:?}");
1906    }
1907}
1908
1909#[cfg(test)]
1910mod planning_error {
1911    use std::collections::HashMap;
1912
1913    use crate::planner::PlanError;
1914    use crate::planner::PlanErrorExtensions;
1915    use crate::planner::ReferencedFieldsForType;
1916    use crate::planner::UsageReporting;
1917
1918    #[test]
1919    #[should_panic(
1920        expected = "Result::unwrap()` on an `Err` value: Error(\"missing field `extensions`\", line: 1, column: 2)"
1921    )]
1922    fn deserialize_empty_planning_error() {
1923        let raw = "{}";
1924        serde_json::from_str::<PlanError>(raw).unwrap();
1925    }
1926
1927    #[test]
1928    #[should_panic(
1929        expected = "Result::unwrap()` on an `Err` value: Error(\"missing field `extensions`\", line: 1, column: 44)"
1930    )]
1931    fn deserialize_planning_error_missing_extension() {
1932        let raw = r#"{ "message": "something terrible happened" }"#;
1933        serde_json::from_str::<PlanError>(raw).unwrap();
1934    }
1935
1936    #[test]
1937    fn deserialize_planning_error_with_extension() {
1938        let raw = r#"{
1939            "message": "something terrible happened",
1940            "extensions": {
1941                "code": "E_TEST_CASE"
1942            }
1943        }"#;
1944
1945        let expected = PlanError {
1946            message: Some("something terrible happened".to_string()),
1947            extensions: Some(PlanErrorExtensions {
1948                code: "E_TEST_CASE".to_string(),
1949                exception: None,
1950            }),
1951            validation_error: false,
1952        };
1953
1954        assert_eq!(expected, serde_json::from_str(raw).unwrap());
1955    }
1956
1957    #[test]
1958    fn deserialize_planning_error_with_empty_object_extension() {
1959        let raw = r#"{
1960            "extensions": {}
1961        }"#;
1962        let expected = PlanError {
1963            message: None,
1964            extensions: None,
1965            validation_error: false,
1966        };
1967
1968        assert_eq!(expected, serde_json::from_str(raw).unwrap());
1969    }
1970
1971    #[test]
1972    fn deserialize_planning_error_with_null_extension() {
1973        let raw = r#"{
1974            "extensions": null
1975        }"#;
1976        let expected = PlanError {
1977            message: None,
1978            extensions: None,
1979            validation_error: false,
1980        };
1981
1982        assert_eq!(expected, serde_json::from_str(raw).unwrap());
1983    }
1984
1985    #[test]
1986    fn deserialize_referenced_fields_for_type_defaults() {
1987        let raw = r#"{}"#;
1988        let expected = ReferencedFieldsForType {
1989            field_names: Vec::new(),
1990            is_interface: false,
1991        };
1992
1993        assert_eq!(expected, serde_json::from_str(raw).unwrap());
1994    }
1995
1996    #[test]
1997    fn deserialize_usage_reporting_with_defaults() {
1998        let raw = r#"{
1999            "statsReportKey": "thisIsAtest"
2000        }"#;
2001        let expected = UsageReporting {
2002            stats_report_key: "thisIsAtest".to_string(),
2003            referenced_fields_by_type: HashMap::new(),
2004        };
2005
2006        assert_eq!(expected, serde_json::from_str(raw).unwrap());
2007    }
2008}
2009
2010#[cfg(test)]
2011mod error_display {
2012    use super::*;
2013
2014    #[test]
2015    fn error_on_core_in_v0_1_display() {
2016        let expected = r#"one or more checks failed
2017caused by
2018the `for:` argument is unsupported by version v0.1 of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).
2019feature https://specs.apollo.dev/something-unsupported/v0.1 is for: SECURITY but is unsupported"#;
2020
2021        let error_to_display: PlannerError = WorkerGraphQLError {
2022            name: "CheckFailed".to_string(),
2023            message: "one or more checks failed".to_string(),
2024            locations: Default::default(),
2025            extensions: Some(PlanErrorExtensions {
2026                code: "CheckFailed".to_string(),
2027                exception: None
2028            }),
2029            original_error: None,
2030            causes: vec![
2031                Box::new(WorkerError {
2032                    message: Some("the `for:` argument is unsupported by version v0.1 of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).".to_string()),
2033                    name: None,
2034                    stack: None,
2035                    extensions: Some(PlanErrorExtensions { code: "ForUnsupported".to_string(), exception: None }),
2036                    locations: vec![Location { line: 2, column: 1 }, Location { line: 3, column: 1 }, Location { line: 4, column: 1 }]
2037                }),
2038                Box::new(WorkerError {
2039                    message: Some("feature https://specs.apollo.dev/something-unsupported/v0.1 is for: SECURITY but is unsupported".to_string()),
2040                    name: None,
2041                    stack: None,
2042                    extensions: Some(PlanErrorExtensions { code: "UnsupportedFeature".to_string(), exception: None }),
2043                    locations: vec![Location { line: 4, column: 1 }]
2044                })
2045            ],
2046            validation_error: false,
2047        }.into();
2048
2049        assert_eq!(expected.to_string(), error_to_display.to_string());
2050    }
2051
2052    #[test]
2053    fn unsupported_feature_for_execution_display() {
2054        let expected = r#"one or more checks failed
2055caused by
2056feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: EXECUTION but is unsupported"#;
2057
2058        let error_to_display: PlannerError = WorkerGraphQLError {
2059            name: "CheckFailed".to_string(),
2060            message: "one or more checks failed".to_string(),
2061            locations: Default::default(),
2062            extensions: Some(PlanErrorExtensions {
2063                code: "CheckFailed".to_string(),
2064                exception: None
2065            }),
2066            original_error: None,
2067            causes: vec![
2068                Box::new(WorkerError {
2069                    message: Some("feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: EXECUTION but is unsupported".to_string()),
2070                    name: None,
2071                    stack: None,
2072                    extensions: Some(PlanErrorExtensions { code: "UnsupportedFeature".to_string(), exception: None }),
2073                    locations: vec![Location { line: 4, column: 9 }]
2074                }),
2075            ],
2076            validation_error: false,
2077        }.into();
2078
2079        assert_eq!(expected.to_string(), error_to_display.to_string());
2080    }
2081
2082    #[test]
2083    fn unsupported_feature_for_security_display() {
2084        let expected = r#"one or more checks failed
2085caused by
2086feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: SECURITY but is unsupported"#;
2087
2088        let error_to_display: PlannerError = WorkerGraphQLError {
2089            name: "CheckFailed".into(),
2090            message: "one or more checks failed".to_string(),
2091            locations: vec![],
2092            extensions: Some(PlanErrorExtensions {
2093                code: "CheckFailed".to_string(),
2094                exception: None
2095            }),
2096            original_error: None,
2097            causes: vec![Box::new(WorkerError {
2098                message: Some("feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: SECURITY but is unsupported".to_string()),
2099                extensions: Some(PlanErrorExtensions {
2100                    code: "UnsupportedFeature".to_string(),
2101                    exception: None
2102                }),
2103                name: None,
2104                stack: None,
2105                locations: vec![Location { line: 4, column: 9 }]
2106            })],
2107            validation_error: false,
2108        }
2109        .into();
2110
2111        assert_eq!(expected.to_string(), error_to_display.to_string());
2112    }
2113
2114    #[tokio::test]
2115    async fn defer_with_fragment() {
2116        let schema = r#"
2117        schema
2118          @link(url: "https://specs.apollo.dev/link/v1.0")
2119          @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION)
2120        {
2121          query: Query
2122        }
2123        
2124        directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2125        directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2126        directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2127        directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2128        directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2129                
2130        scalar link__Import
2131        enum link__Purpose {
2132          SECURITY
2133          EXECUTION
2134        }
2135
2136        type Computer
2137          @join__type(graph: COMPUTERS)
2138        {
2139          id: ID!
2140          errorField: String
2141          nonNullErrorField: String!
2142        }
2143        
2144        scalar join__FieldSet
2145        
2146        enum join__Graph {
2147          COMPUTERS @join__graph(name: "computers", url: "http://localhost:4001/")
2148        }
2149
2150
2151        type Query
2152          @join__type(graph: COMPUTERS)
2153        {
2154          computer(id: ID!): Computer
2155        }"#;
2156
2157        let planner = Planner::<serde_json::Value>::new(
2158            schema.to_string(),
2159            QueryPlannerConfig {
2160                generate_query_fragments: Default::default(),
2161                incremental_delivery: Some(IncrementalDeliverySupport {
2162                    enable_defer: Some(true),
2163                }),
2164                graphql_validation: true,
2165                reuse_query_fragments: None,
2166                debug: Default::default(),
2167                type_conditioned_fetching: false,
2168            },
2169        )
2170        .await
2171        .unwrap();
2172
2173        let plan_response = planner
2174            .plan(
2175                r#"query { 
2176                        computer(id: "Computer1") {   
2177                        id
2178                        ...ComputerErrorField @defer
2179                        }
2180                    }
2181                    fragment ComputerErrorField on Computer {
2182                        errorField
2183                    }"#
2184                .to_string(),
2185                None,
2186                PlanOptions::default(),
2187            )
2188            .await
2189            .unwrap()
2190            .data
2191            .unwrap();
2192
2193        insta::assert_snapshot!(serde_json::to_string_pretty(&plan_response).unwrap());
2194    }
2195
2196    #[tokio::test]
2197    async fn defer_query_plan() {
2198        let schema = r#"schema
2199                @core(feature: "https://specs.apollo.dev/core/v0.1")
2200                @core(feature: "https://specs.apollo.dev/join/v0.1")
2201                @core(feature: "https://specs.apollo.dev/inaccessible/v0.1")
2202                {
2203                query: Query
2204        }
2205        directive @core(feature: String!) repeatable on SCHEMA
2206        directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet) on FIELD_DEFINITION
2207        directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE
2208        directive @join__owner(graph: join__Graph!) on OBJECT | INTERFACE
2209        directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2210        directive @inaccessible on OBJECT | FIELD_DEFINITION | INTERFACE | UNION
2211        scalar join__FieldSet
2212        enum join__Graph {
2213            USER @join__graph(name: "user", url: "http://localhost:4001/graphql")
2214            ORGA @join__graph(name: "orga", url: "http://localhost:4002/graphql")
2215        }
2216        type Query {
2217            currentUser: User @join__field(graph: USER)
2218        }
2219        type User
2220        @join__owner(graph: USER)
2221        @join__type(graph: ORGA, key: "id")
2222        @join__type(graph: USER, key: "id"){
2223            id: ID!
2224            name: String
2225            activeOrganization: Organization
2226        }
2227        type Organization
2228        @join__owner(graph: ORGA)
2229        @join__type(graph: ORGA, key: "id")
2230        @join__type(graph: USER, key: "id") {
2231            id: ID
2232            creatorUser: User
2233            name: String
2234            nonNullId: ID!
2235            suborga: [Organization]
2236        }"#;
2237
2238        let planner = Planner::<serde_json::Value>::new(
2239            schema.to_string(),
2240            QueryPlannerConfig {
2241                generate_query_fragments: Default::default(),
2242                incremental_delivery: Some(IncrementalDeliverySupport {
2243                    enable_defer: Some(true),
2244                }),
2245                graphql_validation: true,
2246                reuse_query_fragments: None,
2247                debug: Default::default(),
2248                type_conditioned_fetching: false,
2249            },
2250        )
2251        .await
2252        .unwrap();
2253
2254        insta::assert_snapshot!(serde_json::to_string_pretty(&planner
2255            .plan(
2256                "query { currentUser { activeOrganization { id  suborga { id ...@defer { nonNullId } } } } }"
2257                .to_string(),
2258                None,
2259                PlanOptions::default(),
2260            )
2261            .await
2262            .unwrap()
2263        .data
2264        .unwrap()).unwrap());
2265    }
2266
2267    #[tokio::test]
2268    async fn propagate_internal_qp_errors() {
2269        let schema = r#"
2270        schema
2271          @link(url: "https://specs.apollo.dev/link/v1.0")
2272          @link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION)
2273        {
2274          query: Query
2275          subscription: Subscription
2276        }
2277        
2278        directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
2279        directive @join__graph(name: String!, url: String!) on ENUM_VALUE
2280        directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
2281        directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
2282        directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
2283                
2284        scalar link__Import
2285        enum link__Purpose {
2286          SECURITY
2287          EXECUTION
2288        }
2289
2290        type Computer
2291          @join__type(graph: COMPUTERS)
2292        {
2293          id: ID!
2294          errorField: String
2295          nonNullErrorField: String!
2296          gpus: [GPU]
2297        }
2298
2299        type GPU @join__type(graph: COMPUTERS) @join__type(graph: GPUS) {
2300          id: ID!
2301          wattage: Int! @join__field(graph: GPUS)
2302        }
2303        
2304        scalar join__FieldSet
2305        
2306        enum join__Graph {
2307          COMPUTERS @join__graph(name: "computers", url: "http://localhost:4001/")
2308          GPUS @join__graph(name: "gpus", url: "http://localhost:4002/")
2309        }
2310
2311
2312        type Query
2313          @join__type(graph: COMPUTERS)
2314        {
2315          computer(id: ID!): Computer
2316        }
2317        type Subscription @join__type(graph: COMPUTERS) {
2318          computer(id: ID!): Computer
2319        }"#;
2320
2321        let planner = Planner::<serde_json::Value>::new(
2322            schema.to_string(),
2323            QueryPlannerConfig {
2324                incremental_delivery: Some(IncrementalDeliverySupport {
2325                    enable_defer: Some(true),
2326                }),
2327                graphql_validation: true,
2328                reuse_query_fragments: None,
2329                generate_query_fragments: None,
2330                debug: Default::default(),
2331                type_conditioned_fetching: false,
2332            },
2333        )
2334        .await
2335        .unwrap();
2336
2337        insta::assert_snapshot!(serde_json::to_string_pretty(
2338            &planner
2339                .plan(
2340                    "subscription { computer(id: 1) { ... @defer { gpus { wattage } } } }"
2341                        .to_string(),
2342                    None,
2343                    PlanOptions::default(),
2344                )
2345                .await
2346                .unwrap()
2347                .errors
2348                .unwrap()
2349        )
2350        .unwrap());
2351    }
2352
2353    static TYPED_CONDITION_SCHEMA: &str = include_str!("testdata/typed_conditions.graphql");
2354
2355    #[tokio::test]
2356    async fn typed_condition_field_merging_disabled() {
2357        let planner = Planner::<serde_json::Value>::new(
2358            TYPED_CONDITION_SCHEMA.to_string(),
2359            QueryPlannerConfig {
2360                generate_query_fragments: Default::default(),
2361                incremental_delivery: Some(IncrementalDeliverySupport {
2362                    enable_defer: Some(true),
2363                }),
2364                graphql_validation: true,
2365                reuse_query_fragments: None,
2366                debug: Default::default(),
2367                type_conditioned_fetching: false,
2368            },
2369        )
2370        .await
2371        .unwrap();
2372
2373        insta::assert_snapshot!(serde_json::to_string_pretty(
2374            &planner
2375                .plan(
2376                    "query Search($movieParams: String, $articleParams: String) {
2377                    search {
2378                      __typename
2379                      ... on MovieResult {
2380                        id
2381                        sections {
2382                          ... on EntityCollectionSection {
2383                            id
2384                            artwork(params: $movieParams)
2385                          }
2386                        }
2387                      }
2388                      ... on ArticleResult {
2389                        id
2390                        sections {
2391                          ... on EntityCollectionSection {
2392                            id
2393                            artwork(params: $articleParams)
2394                            title
2395                          }
2396                        }
2397                      }
2398                    }
2399                  }"
2400                    .to_string(),
2401                    None,
2402                    PlanOptions::default(),
2403                )
2404                .await
2405                .unwrap()
2406                .data
2407                .unwrap()
2408        )
2409        .unwrap());
2410    }
2411    #[tokio::test]
2412    async fn typed_condition_field_merging_enabled() {
2413        let planner = Planner::<serde_json::Value>::new(
2414            TYPED_CONDITION_SCHEMA.to_string(),
2415            QueryPlannerConfig {
2416                generate_query_fragments: Default::default(),
2417                incremental_delivery: Some(IncrementalDeliverySupport {
2418                    enable_defer: Some(true),
2419                }),
2420                graphql_validation: true,
2421                reuse_query_fragments: None,
2422                debug: Default::default(),
2423                type_conditioned_fetching: true,
2424            },
2425        )
2426        .await
2427        .unwrap();
2428
2429        insta::assert_snapshot!(serde_json::to_string_pretty(
2430            &planner
2431                .plan(
2432                    "query Search($movieParams: String, $articleParams: String) {
2433                    search {
2434                      __typename
2435                      ... on MovieResult {
2436                        id
2437                        sections {
2438                          ... on EntityCollectionSection {
2439                            id
2440                            artwork(params: $movieParams)
2441                          }
2442                        }
2443                      }
2444                      ... on ArticleResult {
2445                        id
2446                        sections {
2447                          ... on EntityCollectionSection {
2448                            id
2449                            artwork(params: $articleParams)
2450                            title
2451                          }
2452                        }
2453                      }
2454                    }
2455                  }"
2456                    .to_string(),
2457                    None,
2458                    PlanOptions {
2459                        ..Default::default()
2460                    },
2461                )
2462                .await
2463                .unwrap()
2464                .data
2465                .unwrap()
2466        )
2467        .unwrap());
2468    }
2469
2470    #[tokio::test]
2471    async fn extracts_cost_directives() {
2472        let schema = include_str!("testdata/custom_cost_schema.graphql");
2473        let planner = Planner::<serde_json::Value>::new(schema.to_string(), Default::default())
2474            .await
2475            .expect("can create planner");
2476        let subgraphs = planner.subgraphs().await.expect("can extract subgraphs");
2477
2478        for (name, schema) in subgraphs {
2479            insta::assert_snapshot!(name, schema);
2480        }
2481    }
2482}