1use 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#[derive(Debug, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct QueryPlanOptions {
25    pub auto_fragmentization: bool,
27}
28
29impl Default for QueryPlanOptions {
31    fn default() -> Self {
33        Self {
34            auto_fragmentization: false,
35        }
36    }
37}
38
39#[derive(Debug, Serialize)]
40#[serde(rename_all = "camelCase")]
41pub struct OperationalContext {
44    pub schema: String,
46    pub query: String,
48    pub operation_name: String,
50}
51
52#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq, Clone)]
60#[serde(rename_all = "camelCase")]
61pub struct PlanError {
62    pub message: Option<String>,
64    #[serde(deserialize_with = "none_only_if_value_is_null_or_empty_object")]
66    pub extensions: Option<PlanErrorExtensions>,
67    #[serde(skip_serializing, default)]
70    pub validation_error: bool,
71}
72
73fn 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)]
122pub struct PlanErrorExtensions {
124    pub code: String,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub exception: Option<ExtensionsException>,
129}
130
131#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
132pub struct ExtensionsException {
134    pub stacktrace: String,
136}
137
138impl PlanError {
140    pub fn code(&self) -> &str {
142        match self.extensions {
143            Some(ref ext) => &ext.code,
144            None => "UNKNOWN",
145        }
146    }
147}
148
149#[derive(Deserialize, Debug)]
152pub struct BridgeSetupResult<T> {
154    pub data: Option<T>,
156    pub errors: Option<Vec<PlannerError>>,
158}
159
160#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
161#[serde(rename_all = "camelCase")]
162pub struct Location {
164    pub line: u32,
166    pub column: u32,
168}
169
170#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
171#[serde(untagged)]
172pub enum PlannerError {
174    WorkerGraphQLError(WorkerGraphQLError),
176    WorkerError(WorkerError),
178}
179
180impl PlannerError {
181    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#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
218pub struct WorkerError {
219    pub message: Option<String>,
221    pub name: Option<String>,
223    pub stack: Option<String>,
225    pub extensions: Option<PlanErrorExtensions>,
227    #[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#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
250#[serde(rename_all = "camelCase")]
251pub struct WorkerGraphQLError {
252    pub name: String,
254    pub message: String,
258    #[serde(default)]
261    pub locations: Vec<Location>,
262    pub extensions: Option<PlanErrorExtensions>,
264    pub original_error: Option<Box<WorkerError>>,
266    #[serde(default)]
268    pub causes: Vec<Box<WorkerError>>,
269    #[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")]
291pub struct ReferencedFieldsForType {
294    #[serde(default)]
296    pub field_names: Vec<String>,
297    #[serde(default)]
299    pub is_interface: bool,
300}
301
302#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
303#[serde(rename_all = "camelCase")]
304pub struct UsageReporting {
307    pub stats_report_key: String,
311    #[serde(default)]
313    pub referenced_fields_by_type: HashMap<String, ReferencedFieldsForType>,
314}
315
316#[derive(Deserialize, Debug)]
317#[serde(rename_all = "camelCase")]
318pub struct PlanResult<T> {
320    pub data: Option<T>,
322    pub usage_reporting: UsageReporting,
325    pub errors: Option<Vec<PlanError>>,
327}
328
329#[derive(Debug)]
331pub struct PlanSuccess<T> {
332    pub data: T,
334    pub usage_reporting: UsageReporting,
337}
338
339#[derive(Deserialize, Debug)]
340#[serde(rename_all = "camelCase")]
341pub struct ApiSchema {
343    pub schema: String,
345}
346
347#[derive(Debug, Clone)]
349pub struct PlanErrors {
350    pub errors: Arc<Vec<PlanError>>,
352    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    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")]
403pub struct HeapStatistics {
405    pub heap_total: u64,
407    pub heap_used: u64,
409    pub external: u64,
411}
412
413pub 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    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        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    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        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    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    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    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    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    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    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        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#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
631#[serde(rename_all = "camelCase")]
632pub struct PlanOptions {
633    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")]
673pub struct QueryPlannerConfig {
675    pub incremental_delivery: Option<IncrementalDeliverySupport>,
684    pub graphql_validation: bool,
686    pub reuse_query_fragments: Option<bool>,
699
700    pub generate_query_fragments: Option<bool>,
705
706    pub debug: Option<QueryPlannerDebugConfig>,
710    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")]
737pub struct IncrementalDeliverySupport {
739    #[serde(default)]
745    pub enable_defer: Option<bool>,
746}
747
748#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq, Hash)]
749#[serde(rename_all = "camelCase")]
750pub struct QueryPlannerDebugConfig {
752    pub bypass_planner_for_single_subgraph: Option<bool>,
755
756    pub max_evaluated_plans: Option<u32>,
773
774    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    #[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                "\
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    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            "\
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    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            "{ me { id { absolutelyNotAcceptableLeaf } } }".to_string(),
1321            None,
1322        )
1323        .await;
1324    }
1325
1326    #[tokio::test]
1327    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            "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, "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            "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            "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        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    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        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        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}