sudograph_generate/
lib.rs

1// TODO I might be able to use traits, methods, impls whatever to make a lot of the generation
2// TODO simpler per inputobject
3// TODO once we have those implemented we can start really testing from the playground
4// TODO then we can add update and delete resolvers
5// TODO once all of those basics are working, we can start adding more functionality
6// TODO once we have a baseline of functionality, we should add tests
7// TODO after we add tests we can continue to add functionality, refactor, and then start
8// TODO working on multi-canister functionality possibly
9// TODO we might want to prioritize Motoko interop...since many newcomers seem to really be moving toward Motoko
10
11mod structs {
12    pub mod object_type;
13    pub mod create_input;
14    pub mod read_input;
15    pub mod read_boolean_input;
16    pub mod read_date_input;
17    pub mod read_float_input;
18    pub mod read_id_input;
19    pub mod read_int_input;
20    pub mod read_string_input;
21    pub mod read_enum_input;
22    pub mod read_relation_input;
23    pub mod read_json_input;
24    pub mod read_blob_input;
25    pub mod order_input;
26    pub mod update_input;
27    pub mod delete_input;
28    pub mod upsert_input;
29}
30mod query_resolvers {
31    pub mod read;
32}
33mod mutation_resolvers {
34    pub mod create;
35    pub mod update;
36    pub mod delete;
37    pub mod upsert;
38    pub mod init;
39}
40mod settings {
41    pub mod generate_settings;
42}
43mod custom_resolvers {
44    pub mod generate_custom_query_struct;
45    pub mod generate_custom_mutation_struct;
46    pub mod utilities;
47}
48mod enums {
49    pub mod enum_type;
50}
51
52use proc_macro::TokenStream;
53use quote::quote;
54use syn::{
55    parse_macro_input,
56    LitStr
57};
58use std::{
59    fs
60};
61use graphql_parser::schema::{
62    parse_schema,
63    Definition,
64    TypeDefinition,
65    ObjectType,
66    Type,
67    Document,
68    Field,
69    EnumType
70};
71use structs::object_type::generate_object_type_structs;
72use structs::create_input::generate_create_input_rust_structs;
73use structs::read_input::generate_read_input_rust_structs;
74use structs::read_boolean_input::get_read_boolean_input_rust_struct;
75use structs::read_date_input::get_read_date_input_rust_struct;
76use structs::read_float_input::get_read_float_input_rust_struct;
77use structs::read_id_input::get_read_id_input_rust_struct;
78use structs::read_int_input::get_read_int_input_rust_struct;
79use structs::read_string_input::get_read_string_input_rust_struct;
80use structs::read_enum_input::get_read_enum_input_rust_struct;
81use structs::read_relation_input::get_read_relation_input_rust_struct;
82use structs::read_json_input::get_read_json_input_rust_struct;
83use structs::read_blob_input::get_read_blob_input_rust_struct;
84use structs::order_input::generate_order_input_rust_structs;
85use structs::update_input::generate_update_input_rust_structs;
86use structs::delete_input::generate_delete_input_rust_structs;
87use structs::upsert_input::generate_upsert_input_rust_structs;
88use enums::enum_type::generate_enums;
89use query_resolvers::read::generate_read_query_resolvers;
90use mutation_resolvers::create::generate_create_mutation_resolvers;
91use mutation_resolvers::update::generate_update_mutation_resolvers;
92use mutation_resolvers::delete::generate_delete_mutation_resolvers;
93use mutation_resolvers::upsert::generate_upsert_mutation_resolvers;
94use mutation_resolvers::init::generate_init_mutation_resolvers;
95use settings::generate_settings::{
96    generate_export_generated_query_function_attribute,
97    generate_export_generated_mutation_function_attribute,
98    generate_export_generated_init_function_attribute,
99    generate_export_generated_post_upgrade_function_attribute,
100    generate_clear_mutation
101};
102use custom_resolvers::{
103    generate_custom_query_struct::{
104        generate_merged_query_object_names,
105        generate_custom_query_struct
106    },
107    generate_custom_mutation_struct::{
108        generate_merged_mutation_object_names,
109        generate_custom_mutation_struct
110    }
111};
112
113#[proc_macro]
114pub fn graphql_database(schema_file_path_token_stream: TokenStream) -> TokenStream {
115    let schema_file_path_string_literal = parse_macro_input!(schema_file_path_token_stream as LitStr);
116    let schema_file_path_string_value = schema_file_path_string_literal.value();
117
118    // TODO some of this cwd strangeness is here just so that the canister is forced to recompile when the GraphQL schema file changes
119    // TODO Hopefully this issue will help solve this more elegantly:https://users.rust-lang.org/t/logging-file-dependency-like-include-bytes-in-custom-macro/57441
120    // TODO more information: https://github.com/rust-lang/rust/pull/24423
121    // TODO more information: https://stackoverflow.com/questions/58768109/proper-way-to-handle-a-compile-time-relevant-text-file-passed-to-a-procedural-ma
122    // TODO const temp: &str = include_str!(#schema_absolute_file_path_string); below is related to this as well
123    // TODO whenever the schema file changes, include_str! somehow makes it so that the create will recompile, which is what we want!
124    // TODO it would be nice if there were a simpler or more standard way to accomplish this
125    let cwd = std::env::current_dir().expect("graphql_database::cwd");
126    let schema_absolute_file_path = cwd.join(&schema_file_path_string_value);
127    let schema_absolute_file_path_string_option = schema_absolute_file_path.to_str();
128    let schema_absolute_file_path_string = schema_absolute_file_path_string_option.unwrap();
129
130    let schema_file_contents = fs::read_to_string(&schema_absolute_file_path_string).unwrap();
131
132    let graphql_ast = parse_schema::<String>(&schema_file_contents).unwrap();
133
134    let all_object_types = get_object_types(
135        &graphql_ast
136    );
137
138    let sudograph_settings_option = all_object_types.iter().find(|object_type| {
139        return object_type.name == "SudographSettings";
140    });
141
142    let export_generated_query_function_attribute = generate_export_generated_query_function_attribute(sudograph_settings_option);
143    let export_generated_mutation_function_attribute = generate_export_generated_mutation_function_attribute(sudograph_settings_option);
144    let export_generated_init_function_attribute = generate_export_generated_init_function_attribute(sudograph_settings_option);
145    let export_generated_post_upgrade_function_attribute = generate_export_generated_post_upgrade_function_attribute(sudograph_settings_option);
146
147    let clear_mutation = generate_clear_mutation(sudograph_settings_option);
148
149    let query_object_option = all_object_types.iter().find(|object_type| {
150        return object_type.name == "Query";
151    });
152
153    let mutation_object_option = all_object_types.iter().find(|object_type| {
154        return object_type.name == "Mutation";
155    });
156
157    let generated_custom_query_struct = generate_custom_query_struct(query_object_option);
158    let generated_merged_query_object_names = generate_merged_query_object_names(query_object_option);
159
160    let generated_custom_mutation_struct = generate_custom_mutation_struct(mutation_object_option);
161    let generated_merged_mutation_object_names = generate_merged_mutation_object_names(mutation_object_option);
162
163    let object_types = all_object_types.into_iter().filter(|object_type| {
164        return
165            object_type.name != "SudographSettings" &&
166            object_type.name != "Query" &&
167            object_type.name != "Mutation";
168    }).collect();
169
170    let generated_object_type_structs = generate_object_type_structs(
171        &graphql_ast,
172        &object_types
173    );
174
175    let generated_create_input_structs = generate_create_input_rust_structs(
176        &graphql_ast,
177        &object_types
178    );
179
180    let generated_read_input_structs = generate_read_input_rust_structs(
181        &graphql_ast,
182        &object_types
183    );
184
185    let read_boolean_input_rust_struct = get_read_boolean_input_rust_struct();
186    let read_date_input_rust_struct = get_read_date_input_rust_struct();
187    let read_float_input_rust_struct = get_read_float_input_rust_struct();
188    let read_id_input_rust_struct = get_read_id_input_rust_struct();
189    let read_int_input_rust_struct = get_read_int_input_rust_struct();
190    let read_string_input_rust_struct = get_read_string_input_rust_struct();
191    let read_enum_input_rust_struct = get_read_enum_input_rust_struct();
192    let read_relation_input_rust_struct = get_read_relation_input_rust_struct();
193    let read_json_input_rust_struct = get_read_json_input_rust_struct();
194    let read_blob_input_rust_struct = get_read_blob_input_rust_struct();
195
196    let generated_order_input_structs = generate_order_input_rust_structs(
197        &graphql_ast,
198        &object_types
199    );
200
201    let generated_update_input_structs = generate_update_input_rust_structs(
202        &graphql_ast,
203        &object_types
204    );
205
206    let generated_delete_input_structs = generate_delete_input_rust_structs(&object_types);
207
208    let generated_upsert_input_structs = generate_upsert_input_rust_structs(
209        &graphql_ast,
210        &object_types
211    );
212
213    let enum_types = get_enum_types(&graphql_ast);
214
215    let generated_enums = generate_enums(&enum_types);
216
217    let generated_query_resolvers = generate_read_query_resolvers(&object_types);
218
219    let generated_create_mutation_resolvers = generate_create_mutation_resolvers(&object_types);
220    let generated_update_mutation_resolvers = generate_update_mutation_resolvers(&object_types);
221    let generated_delete_mutation_resolvers = generate_delete_mutation_resolvers(&object_types);
222
223    let generated_upsert_mutation_resolvers = generate_upsert_mutation_resolvers(
224        &graphql_ast,
225        &object_types
226    );
227
228    let generated_init_mutation_resolvers = generate_init_mutation_resolvers(
229        &graphql_ast,
230        &object_types
231    );
232
233    let generated_init_mutations = object_types.iter().fold(String::from(""), |result, object_type| {
234        let object_type_name = &object_type.name;
235        
236        let init_function_name = String::from("init") + object_type_name;
237
238        return result + &init_function_name + "\n";
239    });
240
241    let gen = quote! {
242        use sudograph::serde::{
243            Deserialize,
244            Serialize,
245            self
246        };
247        use sudograph::async_graphql;
248        use sudograph::async_graphql::{
249            SimpleObject,
250            InputObject,
251            Object,
252            MaybeUndefined,
253            Schema,
254            EmptySubscription,
255            scalar,
256            Variables,
257            Request,
258            Enum,
259            MergedObject,
260            Scalar
261        };
262        use sudograph::sudodb::{
263            ObjectTypeStore,
264            create,
265            read,
266            update,
267            delete,
268            init_object_type,
269            FieldTypeInput,
270            FieldType,
271            FieldInput,
272            FieldValue,
273            FieldValueScalar,
274            FieldValueRelationMany,
275            FieldValueRelationOne,
276            ReadInput,
277            ReadInputType,
278            ReadInputOperation,
279            FieldTypeRelationInfo,
280            SelectionSet,
281            SelectionSetInfo,
282            OrderInput,
283            UpdateOperation
284        };
285        use sudograph::serde_json::from_str;
286        use sudograph::ic_cdk;
287        use sudograph::ic_cdk::storage;
288        use sudograph::to_json_string;
289        use sudograph::ic_print;
290        use sudograph::ic_cdk_macros::{
291            query,
292            update,
293            init,
294            post_upgrade,
295            import
296        };
297        use std::error::Error;
298        use std::collections::{
299            BTreeMap,
300            HashMap
301        };
302        use sudograph::rand::{
303            Rng,
304            SeedableRng,
305            rngs::StdRng
306        };
307        // use sudograph::ic_cdk::export::candid::CandidType; // TODO reenable https://github.com/sudograph/sudograph/issues/123
308
309        // TODO this is just to test out storing a source of randomness per update call
310        // TODO the best way I believe would to somehow
311        // TODO use the standard randomness ways of getting randomness
312        // TODO used in the random crates...I think we would have to implement some
313        // TODO random trait or something for the IC architecture
314        // TODO second best would be if DFINITY were to implement a synchronous way of getting
315        // TODO raw randomness from the IC environment
316        // TODO third best is to use an async call to get randomness from the management canister
317        // TODO but for now there are issues with asynchronous calls from within graphql resolvers
318        type RandStore = BTreeMap<String, StdRng>;
319
320        const temp: &str = include_str!(#schema_absolute_file_path_string);
321
322        // We are creating our own custom ID scalar so that we can derive the Default trait
323        // Default traits are needed so that serde has default values when the selection set
324        // Does not provide all required values
325        #[derive(
326            Serialize,
327            Deserialize,
328            Default,
329            Clone,
330            Debug,
331            // CandidType // TODO reenable https://github.com/sudograph/sudograph/issues/123
332        )]
333        // #[candid_path("::sudograph::ic_cdk::export::candid")] TODO reenable once https://github.com/dfinity/candid/issues/248 is released
334        #[serde(crate="self::serde")]
335        struct ID(String);
336
337        impl ID {
338            fn to_string(&self) -> String {
339                return String::from(&self.0);
340            }
341        }
342
343        scalar!(ID);
344
345        #[derive(
346            Serialize,
347            Deserialize,
348            Default,
349            Clone,
350            Debug,
351            // CandidType // TODO reenable https://github.com/sudograph/sudograph/issues/123
352        )]
353        // #[candid_path("::sudograph::ic_cdk::export::candid")] TODO reenable once https://github.com/dfinity/candid/issues/248 is released
354        #[serde(crate="self::serde")]
355        struct Date(String);
356
357        scalar!(Date);
358
359        #[derive(
360            Serialize,
361            Deserialize,
362            Default,
363            Clone,
364            Debug,
365            // CandidType // TODO reenable https://github.com/sudograph/sudograph/issues/123
366        )]
367        // #[candid_path("::sudograph::ic_cdk::export::candid")] TODO reenable once https://github.com/dfinity/candid/issues/248 is released
368        #[serde(crate="self::serde")]
369        struct Blob(Vec<u8>);
370
371        #[Scalar]
372        impl sudograph::async_graphql::ScalarType for Blob {
373            fn parse(value: sudograph::async_graphql::Value) -> sudograph::async_graphql::InputValueResult<Self> {
374                match value {
375                    sudograph::async_graphql::Value::String(value_string) => {
376                        return Ok(Blob(value_string.into_bytes()));
377                    },
378                    sudograph::async_graphql::Value::List(value_list) => {
379                        return Ok(Blob(value_list.iter().map(|item| {
380                            match item {
381                                // sudograph::async_graphql::Value::String(item_string) => {
382                                    // TODO should we implement this too?
383                                // },
384                                sudograph::async_graphql::Value::Number(item_number) => {
385                                    return item_number.as_u64().expect("should be a u64") as u8; // TODO potentially unsafe conversion here
386                                },
387                                _ => panic!("incorrect value") // TODO return an error explaining that a utf-8 encoded string is the only acceptable input
388                            };
389                        }).collect()));
390                    },
391                    _ => panic!("incorrect value") // TODO return an error explaining that a utf-8 encoded string is the only acceptable input
392                };
393            }
394
395            fn to_value(&self) -> sudograph::async_graphql::Value {
396                return sudograph::async_graphql::Value::List((&self.0).iter().map(|item_u8| {
397                    return sudograph::async_graphql::Value::Number(sudograph::async_graphql::Number::from_f64(*item_u8 as f64).expect("should be able to convert to f64"));
398                }).collect());
399            }
400        }
401
402        // TODO each object type and each field will probably need their own relation inputs
403        // TODO the relation inputs are going to have connect, disconnect, create, update, delete, etc
404        #[derive(InputObject, Default, Debug)]
405        struct CreateRelationManyInput {
406            connect: Vec<ID>
407        }
408
409        #[derive(InputObject, Default, Debug)]
410        struct CreateRelationOneInput {
411            connect: ID
412        }
413
414        #[derive(InputObject)]
415        struct UpdateRelationManyInput {
416            connect: Option<Vec<ID>>,
417            disconnect: Option<Vec<ID>>
418        }
419
420        #[derive(InputObject)]
421        struct UpdateNullableRelationOneInput {
422            connect: Option<ID>,
423            disconnect: Option<bool>
424        }
425
426        #[derive(InputObject)]
427        struct UpdateNonNullableRelationOneInput {
428            connect: ID
429        }
430
431        #[derive(Enum, Copy, Clone, Eq, PartialEq)]
432        enum OrderDirection {
433            ASC,
434            DESC
435        }
436
437        #[derive(InputObject)]
438        struct UpdateBlobInput {
439            replace: MaybeUndefined<Blob>,
440            append: Option<Blob>
441            // prepend: Option<Blob> // TODO going to wait on implementing this for now
442        }
443
444        #read_boolean_input_rust_struct
445        #read_date_input_rust_struct
446        #read_float_input_rust_struct
447        #read_id_input_rust_struct
448        #read_int_input_rust_struct
449        #read_string_input_rust_struct
450        #read_enum_input_rust_struct
451        #read_relation_input_rust_struct
452        #read_json_input_rust_struct
453        #read_blob_input_rust_struct
454
455        #(#generated_object_type_structs)*
456        #(#generated_create_input_structs)*
457        #(#generated_read_input_structs)*
458        #(#generated_order_input_structs)*
459        #(#generated_update_input_structs)*
460        #(#generated_delete_input_structs)*
461        // #(#generated_upsert_input_structs)*
462
463        #(#generated_enums)*
464
465        // TODO consider renaming this to something besides serialize
466        trait SudoSerialize {
467            fn sudo_serialize(&self) -> FieldValue;
468        }
469
470        impl SudoSerialize for bool {
471            fn sudo_serialize(&self) -> FieldValue {
472                return FieldValue::Scalar(Some(FieldValueScalar::Boolean(self.clone())));
473            }
474        }
475
476        impl SudoSerialize for f32 {
477            fn sudo_serialize(&self) -> FieldValue {
478                return FieldValue::Scalar(Some(FieldValueScalar::Float(self.clone())));
479            }
480        }
481
482        impl SudoSerialize for ID {
483            fn sudo_serialize(&self) -> FieldValue {
484                // TODO I do not think we actually need the to_string method anymore, ID is a tuple struct I believe
485                return FieldValue::Scalar(Some(FieldValueScalar::String(self.to_string())));
486            }
487        }
488
489        impl SudoSerialize for Date {
490            fn sudo_serialize(&self) -> FieldValue {
491                return FieldValue::Scalar(Some(FieldValueScalar::Date(String::from(&self.0))));
492            }
493        }
494
495        impl SudoSerialize for i32 {
496            fn sudo_serialize(&self) -> FieldValue {
497                return FieldValue::Scalar(Some(FieldValueScalar::Int(self.clone())));
498            }
499        }
500
501        impl SudoSerialize for String {
502            fn sudo_serialize(&self) -> FieldValue {
503                return FieldValue::Scalar(Some(FieldValueScalar::String(self.clone())));
504            }
505        }
506
507        impl SudoSerialize for sudograph::serde_json::Value {
508            fn sudo_serialize(&self) -> FieldValue {
509                return FieldValue::Scalar(Some(FieldValueScalar::JSON(self.to_string())));
510            }
511        }
512
513        impl SudoSerialize for Blob {
514            fn sudo_serialize(&self) -> FieldValue {
515                return FieldValue::Scalar(Some(FieldValueScalar::Blob((&self.0).to_vec())));
516            }
517        }
518
519        impl<T: SudoSerialize> SudoSerialize for Option<T> {
520            fn sudo_serialize(&self) -> FieldValue {
521                match self {
522                    Some(value) => {
523                        return value.sudo_serialize();
524                    },
525                    None => {
526                        return FieldValue::Scalar(None);
527                    }
528                }
529            }
530        }
531
532        // TODO we might want to make sure we explicitly path everything...I am not quite sure
533        // TODO why Default here is able to be used, becuase I believe it come from async-graphql
534        // TODO and I am not importing it
535        #[derive(Default)]
536        pub struct GeneratedQuery;
537
538        #[Object]
539        impl GeneratedQuery {
540            #(#generated_query_resolvers)*
541        }
542
543        #generated_custom_query_struct
544
545        #[derive(MergedObject, Default)]
546        struct Query(
547            #(#generated_merged_query_object_names),*
548        );
549
550        #[derive(Default)]
551        pub struct GeneratedMutation;
552
553        #[Object]
554        impl GeneratedMutation {
555            #(#generated_create_mutation_resolvers)*
556            #(#generated_update_mutation_resolvers)*
557            #(#generated_delete_mutation_resolvers)*
558            // #(#generated_upsert_mutation_resolvers)*
559            #(#generated_init_mutation_resolvers)*
560            #clear_mutation
561
562            // TODO obviously this is an extremely horrible and dangerous thing
563            // TODO perhaps only enable it in testing, or at least
564            // TODO create a Sudograph setting that you must explicitly enable to allow this
565            // async fn clear(&self) -> std::result::Result<bool, sudograph::async_graphql::Error> {
566            //     let object_store = storage::get_mut::<ObjectTypeStore>();
567
568            //     sudograph::sudodb::clear(object_store);
569
570            //     return Ok(true);
571            // }
572        }
573
574        #generated_custom_mutation_struct
575
576        #[derive(MergedObject, Default)]
577        struct Mutation(
578            #(#generated_merged_mutation_object_names),*
579        );
580
581        #export_generated_query_function_attribute
582        async fn graphql_query(query_string: String, variables_json_string: String) -> String {
583            
584            // TODO create a sudograph setting for logs
585            // ic_cdk::println!("query_string: {}", query_string);
586            // ic_cdk::println!("variables_json_string: {}", variables_json_string);
587
588            // TODO figure out how to create global variable to store the schema in
589            // TODO we can probably just store this in a map or something with ic storage
590            let schema = Schema::new(
591                Query::default(),
592                Mutation::default(),
593                EmptySubscription
594            );
595
596            // ic_cdk::println!("{}", schema.federation_sdl());
597
598            // TODO sudosettings should turn these on and off
599            // TODO it would be nice to print these out prettily
600            // TODO also, it would be nice to turn off these kinds of logs
601            // TODO I am thinking about having directives on the type Query set global things
602            // ic_cdk::println!("query_string: {:?}", query_string);
603            // ic_cdk::println!("variables_json_string: {:?}", variables_json_string);
604
605            let request = Request::new(query_string).variables(Variables::from_json(sudograph::serde_json::from_str(&variables_json_string).expect("This should work")));
606
607            let response = schema.execute(request).await;
608
609            let json_result = to_json_string(&response);
610
611            return json_result.expect("This should work");
612        }
613
614        #export_generated_mutation_function_attribute
615        async fn graphql_mutation(mutation_string: String, variables_json_string: String) -> String {
616            // TODO create a sudograph setting for logs
617            // ic_cdk::println!("mutation_string: {}", mutation_string);
618            // ic_cdk::println!("variables_json_string: {}", variables_json_string);
619
620            let rand_store = storage::get_mut::<RandStore>();
621
622            let rng_option = rand_store.get("RNG");
623
624            if rng_option.is_none() {
625                // TODO it seems it would be best to just do this once in the init function, but there is an error: https://forum.dfinity.org/t/cant-do-cross-canister-call-in-init-function/5187
626                // TODO I think this cross-canister call is making the mutations take forever
627                // TODO once the async types are fixed in ic_cdk, update and we should be able to move the randomness into the
628                // TODO create resolver itself, so only it will need to do this call and take forever to do so
629                // TODO and we should be able to get it to be only the first create
630                let call_result: Result<(Vec<u8>,), _> = ic_cdk::api::call::call(ic_cdk::export::Principal::management_canister(), "raw_rand", ()).await;
631    
632                if let Ok(result) = call_result {
633                    let rand_store = storage::get_mut::<RandStore>();
634    
635                    let randomness = result.0;
636    
637                    let mut rng: StdRng = SeedableRng::from_seed(randomness_vector_to_array(randomness));
638    
639                    rand_store.insert(String::from("RNG"), rng);
640                }
641            }
642
643            // TODO figure out how to create global variable to store the schema in
644            let schema = Schema::new(
645                Query::default(),
646                Mutation::default(),
647                EmptySubscription
648            );
649
650            ic_print("graphql_mutation");
651
652            let request = Request::new(mutation_string).variables(Variables::from_json(sudograph::serde_json::from_str(&variables_json_string).expect("This should work")));
653
654            let response = schema.execute(request).await;
655
656            // TODO create a sudograph setting for logs
657            // ic_cdk::println!("response: {:?}", response);
658
659            let json_result = to_json_string(&response).expect("This should work");
660
661            // TODO create a sudograph setting for logs
662            // ic_cdk::println!("json_result {}", &json_result);
663
664            return json_result;
665        }
666
667        #export_generated_init_function_attribute
668        async fn init() {
669            initialize_database_entities().await;
670        }
671
672        #export_generated_post_upgrade_function_attribute
673        async fn post_upgrade() {
674            initialize_database_entities().await;
675        }
676
677        async fn initialize_database_entities() {
678            let schema = Schema::new(
679                Query::default(),
680                Mutation::default(),
681                EmptySubscription
682            );
683
684            let response = schema.execute(format!("
685                    mutation {{
686                        {generated_init_mutations}
687                    }}
688                ",
689                generated_init_mutations = #generated_init_mutations
690            )).await;
691
692            // TODO make this error print prettily
693            if response.errors.len() > 0 {
694                panic!("{:?}", response.errors);
695            }
696        }
697
698        // TODO double-check the math
699        // TODO there is no protection on lengths here...the IC will give us 32 bytes, so a vector of length 32 with u8 values
700        fn randomness_vector_to_array(randomness: Vec<u8>) -> [u8; 32] {
701            let mut array = [0u8; 32];
702
703            for i in 0..randomness.len() {
704                // if i > array.len() {
705                //     break;
706                // }
707
708                array[i] = randomness[i];
709            }
710
711            return array;
712        }
713
714        fn convert_selection_field_to_selection_set(
715            object_type_name: &str,
716            selection_field: sudograph::async_graphql::context::SelectionField<'_>,
717            selection_set: SelectionSet
718        ) -> SelectionSet {
719            let selection_fields: Vec<sudograph::async_graphql::context::SelectionField<'_>> = selection_field.selection_set().collect();
720
721            if selection_fields.len() == 0 {
722                return selection_set;
723            }
724
725            // TODO we should probably also put this at the top level of the resolvers so that we do not parse it so many times
726            // TODO But I need to figure out how to get the schema_file_contents down to the resolvers
727            // TODO best way might be to use context data from the top level functions
728            let graphql_ast = sudograph::graphql_parser::schema::parse_schema::<String>(#schema_file_contents).unwrap();
729
730            let mut hash_map = HashMap::new();
731
732            for selection_field in selection_fields {
733                // TODO this is not exactly the object type name in all cases, but if the field is a scalar
734                // TODO I am thinking it should not matter
735                let child_type_name = get_type_name_for_object_type_name_and_field_name(
736                    &graphql_ast,
737                    object_type_name,
738                    selection_field.name()
739                );
740
741                let child_selection_set = convert_selection_field_to_selection_set(
742                    &child_type_name,
743                    selection_field,
744                    SelectionSet(None)
745                );
746
747                let child_selection_set_info = SelectionSetInfo {
748                    selection_set: child_selection_set,
749                    search_inputs: get_search_inputs_from_selection_field(
750                        &graphql_ast,
751                        object_type_name,
752                        selection_field
753                    ),
754                    limit_option: get_limit_option_from_selection_field(selection_field),
755                    offset_option: get_offset_option_from_selection_field(selection_field),
756                    order_inputs: get_order_inputs_from_selection_field(selection_field)
757                };
758
759                hash_map.insert(String::from(selection_field.name()), child_selection_set_info);
760            }
761
762            return SelectionSet(Some(hash_map));
763        }
764
765        fn get_search_inputs_from_selection_field(
766            graphql_ast: &sudograph::graphql_parser::schema::Document<String>,
767            object_type_name: &str,
768            selection_field: sudograph::async_graphql::context::SelectionField<'_>
769        ) -> Vec<ReadInput> {            
770            match selection_field.arguments() {
771                Ok(arguments) => {
772                    let search_argument_option = arguments.iter().find(|argument| {
773                        return argument.0.as_str() == "search";
774                    });
775
776                    match search_argument_option {
777                        Some(search_argument) => {
778                            let relation_object_type_name = get_type_name_for_object_type_name_and_field_name(
779                                graphql_ast,
780                                object_type_name,
781                                selection_field.name()
782                            );
783
784                            return get_search_inputs_from_value(
785                                graphql_ast,
786                                &relation_object_type_name,
787                                &search_argument.1
788                            )
789                            .into_iter()
790                            .flatten()
791                            .collect();
792                        },
793                        None => {
794                            return vec![];
795                        }
796                    };
797                },
798                _ => {
799                    // TODO we might want to return the err result up here
800                    return vec![];
801                }
802            };
803        }
804
805        // TODO not sure if this should be from an object value in particular or just a value
806        fn get_search_inputs_from_value(
807            graphql_ast: &sudograph::graphql_parser::schema::Document<String>,
808            object_type_name: &str,
809            value: &sudograph::async_graphql::Value
810        ) -> Vec<Vec<ReadInput>> {
811            match value {
812                sudograph::async_graphql::Value::Object(object) => {
813                    let search_inputs = object.keys().fold(vec![], |mut result, object_key| {
814                        let object_value = object.get(object_key).expect("get_search_inputs_from_value::object_value");
815
816                        if object_key == "and" {
817                            result.push(vec![ReadInput {
818                                input_type: ReadInputType::Scalar,
819                                input_operation: ReadInputOperation::Equals,
820                                field_name: String::from("and"),
821                                field_value: FieldValue::Scalar(None),
822                                relation_object_type_name: String::from(""),
823                                relation_read_inputs: vec![],
824                                and: match object_value {
825                                    sudograph::async_graphql::Value::List(list) => list.iter().flat_map(|value| { get_search_inputs_from_value(
826                                        graphql_ast,
827                                        object_type_name,
828                                        value
829                                    )
830                                    .into_iter()
831                                    .flatten()
832                                    .collect::<Vec<ReadInput>>() }).collect(),
833                                    sudograph::async_graphql::Value::Object(_) => {
834                                        get_search_inputs_from_value(
835                                            graphql_ast,
836                                            object_type_name,
837                                            object_value
838                                        )
839                                        .into_iter()
840                                        .flatten()
841                                        .collect()
842                                    },
843                                    _ => panic!("panic for and")
844                                },
845                                or: vec![]
846                            }]);
847
848                            return result;
849                        }
850
851                        if object_key == "or" {
852                            match object_value {
853                                sudograph::async_graphql::Value::List(list) => {
854                                    for value in list {
855                                        result.push(
856                                            vec![
857                                                ReadInput {
858                                                    input_type: ReadInputType::Scalar,
859                                                    input_operation: ReadInputOperation::Equals,
860                                                    field_name: String::from("or"),
861                                                    field_value: FieldValue::Scalar(None),
862                                                    relation_object_type_name: String::from(""),
863                                                    relation_read_inputs: vec![],
864                                                    and: vec![],
865                                                    or: get_search_inputs_from_value(
866                                                            graphql_ast,
867                                                            object_type_name,
868                                                            value
869                                                        )
870                                                        .into_iter()
871                                                        .map(|read_inputs| {
872                                                            return ReadInput {
873                                                                input_type: ReadInputType::Scalar,
874                                                                input_operation: ReadInputOperation::Equals,
875                                                                field_name: String::from("and"),
876                                                                field_value: FieldValue::Scalar(None),
877                                                                relation_object_type_name: String::from(""),
878                                                                relation_read_inputs: vec![],
879                                                                and: read_inputs,
880                                                                or: vec![]
881                                                            };
882                                                        })
883                                                        .collect()
884                                                }
885                                            ]
886                                        );
887                                    }
888                                },
889                                sudograph::async_graphql::Value::Object(_) => {
890                                    result.push(
891                                        vec![
892                                            ReadInput {
893                                                input_type: ReadInputType::Scalar,
894                                                input_operation: ReadInputOperation::Equals,
895                                                field_name: String::from("or"),
896                                                field_value: FieldValue::Scalar(None),
897                                                relation_object_type_name: String::from(""),
898                                                relation_read_inputs: vec![],
899                                                and: vec![],
900                                                or: get_search_inputs_from_value(
901                                                        graphql_ast,
902                                                        object_type_name,
903                                                        object_value
904                                                    )
905                                                    .into_iter()
906                                                    .map(|read_inputs| {
907                                                        return ReadInput {
908                                                            input_type: ReadInputType::Scalar,
909                                                            input_operation: ReadInputOperation::Equals,
910                                                            field_name: String::from("and"),
911                                                            field_value: FieldValue::Scalar(None),
912                                                            relation_object_type_name: String::from(""),
913                                                            relation_read_inputs: vec![],
914                                                            and: read_inputs,
915                                                            or: vec![]
916                                                        };
917                                                    })
918                                                    .collect()
919                                            }
920                                        ]
921                                    );
922                                },
923                                _ => panic!()
924                            };
925
926
927                            return result;
928                        }
929
930                        let field = get_field_for_object_type_name_and_field_name(
931                            graphql_ast,
932                            object_type_name,
933                            object_key
934                        );
935
936                        if
937                            is_graphql_type_a_relation_many(
938                                graphql_ast,
939                                &field.field_type
940                            ) == true ||
941                            is_graphql_type_a_relation_one(
942                                graphql_ast,
943                                &field.field_type
944                            ) == true
945                        {
946                            let relation_object_type_name = get_field_type_name(&field);
947
948                            result.push(vec![ReadInput {
949                                input_type: ReadInputType::Relation,
950                                input_operation: ReadInputOperation::Equals,
951                                field_name: object_key.to_string(),
952                                field_value: FieldValue::Scalar(None),
953                                relation_object_type_name: String::from(&relation_object_type_name),
954                                relation_read_inputs: get_search_inputs_from_value(
955                                    graphql_ast,
956                                    &relation_object_type_name,
957                                    object_value
958                                )
959                                .into_iter()
960                                .flatten()
961                                .collect(),
962                                and: vec![],
963                                or: vec![]
964                            }]);
965
966                            return result;
967                        }
968                        else {
969                            match object_value {
970                                sudograph::async_graphql::Value::Object(scalar_object) => {
971                                    let scalar_search_inputs: Vec<ReadInput> = scalar_object.keys().map(|scalar_object_key| {
972                                        let scalar_object_value = scalar_object.get(scalar_object_key).unwrap();
973                                        
974                                        let input_operation = match scalar_object_key.as_str() {
975                                            "eq" => ReadInputOperation::Equals,
976                                            "gt" => ReadInputOperation::GreaterThan,
977                                            "gte" => ReadInputOperation::GreaterThanOrEqualTo,
978                                            "lt" => ReadInputOperation::LessThan,
979                                            "lte" => ReadInputOperation::LessThanOrEqualTo,
980                                            "contains" => ReadInputOperation::Contains,
981                                            "startsWith" => ReadInputOperation::StartsWith,
982                                            "endsWith" => ReadInputOperation::EndsWith,
983                                            _ => panic!()
984                                        };
985
986                                        let graphql_type_name = get_graphql_type_name(&field.field_type);
987
988                                        // TODO this will get more difficult once we introduce custom scalars
989                                        let field_value = match graphql_type_name.as_str() {
990                                            "Blob" => match scalar_object_value {
991                                                sudograph::async_graphql::Value::String(value_string) => FieldValue::Scalar(Some(FieldValueScalar::Blob(value_string.clone().into_bytes()))),
992                                                sudograph::async_graphql::Value::List(value_list) => FieldValue::Scalar(Some(FieldValueScalar::Blob(value_list.iter().map(|item| {
993                                                    match item {
994                                                        // sudograph::async_graphql::Value::String(item_string) => {
995                                                            // TODO should we implement this too?
996                                                        // },
997                                                        sudograph::async_graphql::Value::Number(item_number) => {
998                                                            return item_number.as_u64().expect("should be a u64") as u8; // TODO potentially unsafe conversion here
999                                                        },
1000                                                        _ => panic!("incorrect value") // TODO return an error explaining that a utf-8 encoded string is the only acceptable input
1001                                                    };
1002                                                }).collect()))),
1003                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1004                                                _ => panic!() // TODO return an error explaining that a utf-8 encoded string is the only acceptable input
1005                                            },
1006                                            "Boolean" => match scalar_object_value {
1007                                                sudograph::async_graphql::Value::Boolean(boolean) => FieldValue::Scalar(Some(FieldValueScalar::Boolean(boolean.clone()))),
1008                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1009                                                _ => panic!()
1010                                            },
1011                                            "Date" => match scalar_object_value {
1012                                                sudograph::async_graphql::Value::String(date_string) => FieldValue::Scalar(Some(FieldValueScalar::Date(date_string.to_string()))),
1013                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1014                                                _ => panic!()
1015                                            },
1016                                            "Float" => match scalar_object_value {
1017                                                sudograph::async_graphql::Value::Number(number) => FieldValue::Scalar(Some(FieldValueScalar::Float(number.as_f64().unwrap() as f32))),
1018                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1019                                                _ => panic!()
1020                                            },
1021                                            "ID" => match scalar_object_value {
1022                                                sudograph::async_graphql::Value::String(id_string) => FieldValue::Scalar(Some(FieldValueScalar::String(id_string.to_string()))),
1023                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1024                                                _ => panic!()
1025                                            },
1026                                            "Int" => match scalar_object_value {
1027                                                sudograph::async_graphql::Value::Number(number) => FieldValue::Scalar(Some(FieldValueScalar::Int(number.as_i64().unwrap() as i32))),
1028                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1029                                                _ => panic!()
1030                                            },
1031                                            "JSON" => match scalar_object_value {
1032                                                sudograph::async_graphql::Value::String(string) => FieldValue::Scalar(Some(FieldValueScalar::JSON(string.to_string()))),
1033                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1034                                                _ => panic!()
1035                                            },
1036                                            "String" => match scalar_object_value {
1037                                                sudograph::async_graphql::Value::String(string) => FieldValue::Scalar(Some(FieldValueScalar::String(string.to_string()))),
1038                                                sudograph::async_graphql::Value::Null => FieldValue::Scalar(None),
1039                                                _ => panic!()
1040                                            },
1041                                            _ => panic!("this scalar is not defined")
1042                                        };
1043
1044                                        return ReadInput {
1045                                            input_type: ReadInputType::Scalar,
1046                                            input_operation: input_operation,
1047                                            field_name: object_key.to_string(),
1048                                            field_value,
1049                                            relation_object_type_name: String::from(""),
1050                                            relation_read_inputs: vec![],
1051                                            and: vec![],
1052                                            or: vec![]
1053                                        };
1054                                    }).collect();
1055
1056                                    result.push(scalar_search_inputs);
1057
1058                                    return result;
1059                                },
1060                                _ => {
1061                                    panic!();
1062                                }
1063                            };
1064                        }
1065                    });
1066
1067                    return search_inputs;
1068                },
1069                _ => {
1070                    panic!(); // TODO probably return a result instead, I am getting really lazy with this
1071                }
1072            }
1073        }
1074
1075        fn get_type_name_for_object_type_name_and_field_name(
1076            graphql_ast: &sudograph::graphql_parser::schema::Document<String>,
1077            object_type_name: &str,
1078            field_name: &str
1079        ) -> String {
1080            let object_type = get_object_type(
1081                graphql_ast,
1082                object_type_name
1083            );
1084            let field = get_field(
1085                &object_type,
1086                field_name
1087            );
1088            let field_type_name = get_field_type_name(&field);
1089
1090            return field_type_name;
1091        }
1092
1093        fn get_field_for_object_type_name_and_field_name<'a>(
1094            graphql_ast: &sudograph::graphql_parser::schema::Document<'a, String>,
1095            object_type_name: &str,
1096            field_name: &str
1097        ) -> sudograph::graphql_parser::schema::Field<'a, String> {
1098            let object_type = get_object_type(
1099                graphql_ast,
1100                object_type_name
1101            );
1102            let field = get_field(
1103                &object_type,
1104                field_name
1105            );
1106
1107            return field;
1108        }
1109
1110        fn get_object_types<'a>(graphql_ast: &sudograph::graphql_parser::schema::Document<'a, String>) -> Vec<sudograph::graphql_parser::schema::ObjectType<'a, String>> {
1111            let type_definitions: Vec<sudograph::graphql_parser::schema::TypeDefinition<String>> = graphql_ast.definitions.iter().filter_map(|definition| {
1112                match definition {
1113                    sudograph::graphql_parser::schema::Definition::TypeDefinition(type_definition) => {
1114                        return Some(type_definition.clone());
1115                    },
1116                    _ => {
1117                        return None;
1118                    }
1119                };
1120            }).collect();
1121        
1122            let object_types = type_definitions.into_iter().filter_map(|type_definition| {
1123                match type_definition {
1124                    sudograph::graphql_parser::schema::TypeDefinition::Object(object_type) => {
1125                        return Some(object_type);
1126                    },
1127                    _ => {
1128                        return None;
1129                    }
1130                }
1131            }).collect();
1132        
1133            return object_types;
1134        }
1135
1136        fn get_object_type<'a>(
1137            graphql_ast: &sudograph::graphql_parser::schema::Document<'a, String>,
1138            object_type_name: &str
1139        ) -> sudograph::graphql_parser::schema::ObjectType<'a, String> {
1140            let object_types = get_object_types(graphql_ast);
1141            let object_type = object_types.iter().find(|object_type| {
1142                return object_type.name == object_type_name;
1143            }).expect("get_object_type::object_type");
1144
1145            return object_type.clone();
1146        }
1147
1148        fn get_field<'a>(
1149            object_type: &sudograph::graphql_parser::schema::ObjectType<'a, String>,
1150            field_name: &str
1151        ) -> sudograph::graphql_parser::schema::Field<'a, String> {
1152            // ic_cdk::println!("object_type {:?}", object_type);
1153            // ic_cdk::println!("object_type {}", field_name);
1154            return object_type.fields.iter().find(|field| {
1155                return field.name == field_name;
1156            }).expect("get_field").clone(); // TODO instead of returning these types of clones, returning references might be better since the AST stuff is read-only
1157        }
1158
1159        fn get_field_type_name(
1160            field: &sudograph::graphql_parser::schema::Field<String>
1161        ) -> String {
1162            return get_graphql_type_name(&field.field_type);
1163        }
1164
1165        // TODO this is now copied inside and outside of the quote
1166        fn get_graphql_type_name(graphql_type: &sudograph::graphql_parser::schema::Type<String>) -> String {
1167            match graphql_type {
1168                sudograph::graphql_parser::schema::Type::NamedType(named_type) => {
1169                    return String::from(named_type);
1170                },
1171                sudograph::graphql_parser::schema::Type::NonNullType(non_null_type) => {
1172                    return get_graphql_type_name(non_null_type);
1173                },
1174                sudograph::graphql_parser::schema::Type::ListType(list_type) => {
1175                    return get_graphql_type_name(list_type);
1176                }
1177            };
1178        }
1179
1180        fn get_limit_option_from_selection_field(selection_field: sudograph::async_graphql::context::SelectionField<'_>) -> Option<u32> {
1181            match selection_field.arguments() {
1182                Ok(arguments) => {
1183                    let limit_argument_option = arguments.iter().find(|argument| {
1184                        return argument.0.as_str() == "limit";
1185                    });
1186
1187                    match limit_argument_option {
1188                        Some(limit_argument) => {
1189                            match &limit_argument.1 {
1190                                sudograph::async_graphql::Value::Number(number) => {
1191                                    match number.as_u64() {
1192                                        Some(number_u64) => {
1193                                            return Some(number_u64 as u32);
1194                                        },
1195                                        None => {
1196                                            return None;
1197                                        }
1198                                    };
1199                                },
1200                                _ => {
1201                                    return None; // TODO we should probably return an error here
1202                                }
1203                            };
1204                        },
1205                        None => {
1206                            return None;
1207                        }
1208                    };
1209                },
1210                _ => {
1211                    // TODO should we panic or something here?
1212                    // TODO we should probably return the result up the chain
1213                    return None;
1214                }
1215            };
1216        }
1217
1218        fn get_offset_option_from_selection_field(selection_field: sudograph::async_graphql::context::SelectionField<'_>) -> Option<u32> {
1219            match selection_field.arguments() {
1220                Ok(arguments) => {
1221                    let limit_argument_option = arguments.iter().find(|argument| {
1222                        return argument.0.as_str() == "offset";
1223                    });
1224
1225                    match limit_argument_option {
1226                        Some(limit_argument) => {
1227                            match &limit_argument.1 {
1228                                sudograph::async_graphql::Value::Number(number) => {
1229                                    match number.as_u64() {
1230                                        Some(number_u64) => {
1231                                            return Some(number_u64 as u32);
1232                                        },
1233                                        None => {
1234                                            return None;
1235                                        }
1236                                    };
1237                                },
1238                                _ => {
1239                                    return None; // TODO we should probably return an error here
1240                                }
1241                            };
1242                        },
1243                        None => {
1244                            return None;
1245                        }
1246                    };
1247                },
1248                _ => {
1249                    // TODO should we panic or something here?
1250                    // TODO we should probably return the result up the chain
1251                    return None;
1252                }
1253            };
1254        }
1255
1256        fn get_order_inputs_from_selection_field(selection_field: sudograph::async_graphql::context::SelectionField<'_>) -> Vec<sudograph::sudodb::OrderInput> {
1257            match selection_field.arguments() {
1258                Ok(arguments) => {
1259                    let order_argument_option = arguments.iter().find(|argument| {
1260                        return argument.0.as_str() == "order";
1261                    });
1262
1263                    match order_argument_option {
1264                        Some(order_argument) => {
1265                            match &order_argument.1 {
1266                                sudograph::async_graphql::Value::Object(object) => {
1267                                    return object.keys().map(|key| {
1268                                        let value = object.get(key).expect("get_order_inputs_from_selection_field::value"); // TODO be better
1269
1270                                        return sudograph::sudodb::OrderInput {
1271                                            field_name: String::from(key.as_str()),
1272                                            order_direction: match value {
1273                                                sudograph::async_graphql::Value::Enum(name) => {
1274                                                    if name.as_str() == "ASC" {
1275                                                        sudograph::sudodb::OrderDirection::ASC
1276                                                    }
1277                                                    // TODO to be really sure we should have an explicit branch for "DESC"
1278                                                    else {
1279                                                        sudograph::sudodb::OrderDirection::DESC
1280                                                    }
1281                                                },
1282                                                _ => panic!("bad")
1283                                            }
1284                                        };
1285                                    }).collect();
1286                                },
1287                                _ => {
1288                                    return vec![]; // TODO we should probably return an error here
1289                                }
1290                            };
1291                        },
1292                        None => {
1293                            return vec![];
1294                        }
1295                    };
1296                },
1297                _ => {
1298                    // TODO we might want to return the err result up here
1299                    return vec![];
1300                }
1301            };
1302        }
1303
1304        fn get_field_arguments(
1305            context: &sudograph::async_graphql::Context<'_>,
1306            field_name: &str
1307        ) -> sudograph::async_graphql::ServerResult<Vec<(sudograph::async_graphql::Name, sudograph::async_graphql::Value)>> {
1308            let selection_field_option = context.field().selection_set().find(|selection_field| {
1309                return selection_field.name() == field_name;
1310            });
1311
1312            match selection_field_option {
1313                Some(selection_field) => {
1314                    return selection_field.arguments();
1315                },
1316                None => {
1317                    return Ok(vec![]);
1318                }
1319            };
1320        }
1321
1322        fn is_graphql_type_a_relation_many(
1323            graphql_ast: &sudograph::graphql_parser::schema::Document<String>,
1324            graphql_type: &sudograph::graphql_parser::schema::Type<String>
1325        ) -> bool {
1326            let object_types = get_object_types(graphql_ast);
1327            let graphql_type_name = get_graphql_type_name(graphql_type);
1328        
1329            let graphql_type_is_a_relation = object_types.iter().any(|object_type| {
1330                return object_type.name == graphql_type_name;
1331            });
1332        
1333            let graphql_type_is_a_list_type = is_graphql_type_a_list_type(
1334                graphql_ast,
1335                graphql_type
1336            );
1337        
1338            return 
1339                graphql_type_is_a_relation == true &&
1340                graphql_type_is_a_list_type == true
1341            ;
1342        }
1343
1344        fn is_graphql_type_a_relation_one(
1345            graphql_ast: &sudograph::graphql_parser::schema::Document<String>,
1346            graphql_type: &sudograph::graphql_parser::schema::Type<String>
1347        ) -> bool {
1348            let object_types = get_object_types(graphql_ast);
1349            let graphql_type_name = get_graphql_type_name(graphql_type);
1350        
1351            let graphql_type_is_a_relation = object_types.iter().any(|object_type| {
1352                return object_type.name == graphql_type_name;
1353            });
1354        
1355            let graphql_type_is_a_list_type = is_graphql_type_a_list_type(
1356                graphql_ast,
1357                graphql_type
1358            );
1359        
1360            return 
1361                graphql_type_is_a_relation == true &&
1362                graphql_type_is_a_list_type == false
1363            ;
1364        }
1365
1366        fn is_graphql_type_a_list_type(
1367            graphql_ast: &sudograph::graphql_parser::schema::Document<String>,
1368            graphql_type: &sudograph::graphql_parser::schema::Type<String>
1369        ) -> bool {
1370            match graphql_type {
1371                sudograph::graphql_parser::schema::Type::NamedType(_) => {
1372                    return false;
1373                },
1374                sudograph::graphql_parser::schema::Type::NonNullType(non_null_type) => {
1375                    return is_graphql_type_a_list_type(
1376                        graphql_ast,
1377                        non_null_type
1378                    );
1379                },
1380                sudograph::graphql_parser::schema::Type::ListType(_) => {
1381                    return true;
1382                }
1383            };
1384        }
1385    };
1386
1387    return gen.into();
1388}
1389
1390// TODO this is now copied inside and outside of the quote
1391// TODO many of the functions are copied, we need to organize this better
1392fn get_graphql_type_name(graphql_type: &Type<String>) -> String {
1393    match graphql_type {
1394        Type::NamedType(named_type) => {
1395            return String::from(named_type);
1396        },
1397        Type::NonNullType(non_null_type) => {
1398            return get_graphql_type_name(non_null_type);
1399        },
1400        Type::ListType(list_type) => {
1401            return get_graphql_type_name(list_type);
1402        }
1403    };
1404}
1405
1406fn is_graphql_type_nullable(graphql_type: &Type<String>) -> bool {
1407    match graphql_type {
1408        Type::NonNullType(_) => {
1409            return false;
1410        },
1411        _ => {
1412            return true;
1413        }
1414    };
1415}
1416
1417fn is_field_a_relation(
1418    graphql_ast: &Document<String>,
1419    field: &Field<String>
1420) -> bool {
1421    return
1422        is_graphql_type_a_relation_many(
1423            graphql_ast,
1424            &field.field_type
1425        ) == true ||
1426        is_graphql_type_a_relation_one(
1427            graphql_ast,
1428            &field.field_type
1429        ) == true;
1430}
1431
1432fn is_graphql_type_a_relation_many(
1433    graphql_ast: &Document<String>,
1434    graphql_type: &Type<String>
1435) -> bool {
1436    let object_types = get_object_types(graphql_ast);
1437    let graphql_type_name = get_graphql_type_name(graphql_type);
1438
1439    let graphql_type_is_a_relation = object_types.iter().any(|object_type| {
1440        return object_type.name == graphql_type_name;
1441    });
1442
1443    let graphql_type_is_a_list_type = is_graphql_type_a_list_type(
1444        graphql_ast,
1445        graphql_type
1446    );
1447
1448    return 
1449        graphql_type_is_a_relation == true &&
1450        graphql_type_is_a_list_type == true
1451    ;
1452}
1453
1454fn is_graphql_type_a_relation_one(
1455    graphql_ast: &Document<String>,
1456    graphql_type: &Type<String>
1457) -> bool {
1458    let object_types = get_object_types(graphql_ast);
1459    let graphql_type_name = get_graphql_type_name(graphql_type);
1460
1461    let graphql_type_is_a_relation = object_types.iter().any(|object_type| {
1462        return object_type.name == graphql_type_name;
1463    });
1464
1465    let graphql_type_is_a_list_type = is_graphql_type_a_list_type(
1466        graphql_ast,
1467        graphql_type
1468    );
1469
1470    return 
1471        graphql_type_is_a_relation == true &&
1472        graphql_type_is_a_list_type == false
1473    ;
1474}
1475
1476fn is_graphql_type_an_enum(
1477    graphql_ast: &Document<String>,
1478    graphql_type: &Type<String>
1479) -> bool {
1480    let enum_types = get_enum_types(graphql_ast);
1481    let graphql_type_name = get_graphql_type_name(graphql_type);
1482
1483    let graphql_type_is_an_enum = enum_types.iter().any(|enum_type| {
1484        return enum_type.name == graphql_type_name;
1485    });
1486
1487    return graphql_type_is_an_enum;
1488}
1489
1490fn is_graphql_type_a_blob(graphql_type: &Type<String>) -> bool {
1491    let graphql_type_name = get_graphql_type_name(graphql_type);
1492    
1493    return graphql_type_name == "Blob";
1494}
1495
1496fn is_graphql_type_a_list_type(
1497    graphql_ast: &Document<String>,
1498    graphql_type: &Type<String>
1499) -> bool {
1500    match graphql_type {
1501        Type::NamedType(_) => {
1502            return false;
1503        },
1504        Type::NonNullType(non_null_type) => {
1505            return is_graphql_type_a_list_type(
1506                graphql_ast,
1507                non_null_type
1508            );
1509        },
1510        Type::ListType(_) => {
1511            return true;
1512        }
1513    };
1514}
1515
1516fn get_object_types<'a>(graphql_ast: &Document<'a, String>) -> Vec<ObjectType<'a, String>> {
1517    let type_definitions: Vec<TypeDefinition<String>> = graphql_ast.definitions.iter().filter_map(|definition| {
1518        match definition {
1519            Definition::TypeDefinition(type_definition) => {
1520                return Some(type_definition.clone());
1521            },
1522            _ => {
1523                return None;
1524            }
1525        };
1526    }).collect();
1527
1528    let object_types: Vec<ObjectType<String>> = type_definitions.into_iter().filter_map(|type_definition| {
1529        match type_definition {
1530            TypeDefinition::Object(object_type) => {
1531                return Some(object_type);
1532            },
1533            _ => {
1534                return None;
1535            }
1536        }
1537    }).collect();
1538
1539    return object_types;
1540}
1541
1542fn get_enum_types<'a>(graphql_ast: &Document<'a, String>) -> Vec<EnumType<'a, String>> {
1543    let type_definitions: Vec<TypeDefinition<String>> = graphql_ast.definitions.iter().filter_map(|definition| {
1544        match definition {
1545            Definition::TypeDefinition(type_definition) => {
1546                return Some(type_definition.clone());
1547            },
1548            _ => {
1549                return None;
1550            }
1551        };
1552    }).collect();
1553
1554    let enum_types: Vec<EnumType<String>> = type_definitions.into_iter().filter_map(|type_definition| {
1555        match type_definition {
1556            TypeDefinition::Enum(enum_type) => {
1557                return Some(enum_type);
1558            },
1559            _ => {
1560                return None;
1561            }
1562        }
1563    }).collect();
1564
1565    return enum_types;
1566}
1567
1568// TODO this search needs to exclude the relation's own entity field...
1569// TODO you could have a relation to your same type, but you need to skip your original field
1570fn get_opposing_relation_field<'a>(
1571    graphql_ast: &'a Document<'a, String>,
1572    relation_field: &Field<String>
1573) -> Option<Field<'a, String>> {
1574    let relation_name = get_directive_argument_value_from_field(
1575        relation_field,
1576        "relation",
1577        "name"
1578    )?;
1579
1580    let opposing_object_type_name = get_graphql_type_name(&relation_field.field_type);
1581    
1582    let object_types = get_object_types(graphql_ast);
1583
1584    return object_types.iter().filter(|object_type| {
1585        return object_type.name == opposing_object_type_name; // TODO a find might make more sense than a filter
1586    }).fold(None, |_, object_type| {
1587        return object_type.fields.iter().fold(None, |result, field| {
1588            if result != None {
1589                return result;
1590            }
1591
1592            let opposing_relation_name = get_directive_argument_value_from_field(
1593                field,
1594                "relation",
1595                "name"
1596            )?;
1597
1598            if opposing_relation_name == relation_name {
1599                return Some(field.clone());
1600            }
1601            else {
1602                return result;
1603            }
1604        });
1605    });
1606}
1607
1608fn get_directive_argument_value_from_field(
1609    field: &Field<String>,
1610    directive_name: &str,
1611    argument_name: &str
1612) -> Option<String> {
1613    let directive = field.directives.iter().find(|directive| {
1614        return directive.name == directive_name;
1615    })?;
1616
1617    let argument = directive.arguments.iter().find(|argument| {
1618        return argument.0 == argument_name;
1619    })?;
1620
1621    return Some(argument.1.to_string());
1622}
1623
1624fn get_object_type_from_field<'a>(
1625    graphql_ast: &Document<'a, String>,
1626    field: &Field<String>
1627) -> Option<ObjectType<'a, String>> {
1628    let object_type_name = get_graphql_type_name(&field.field_type);
1629
1630    let object_types = get_object_types(graphql_ast);
1631
1632    return object_types.into_iter().find(|object_type| {
1633        return object_type.name == object_type_name;
1634    }).clone();
1635}
1636
1637fn get_enum_type_from_field<'a>(
1638    graphql_ast: &Document<'a, String>,
1639    field: &Field<String>
1640) -> Option<EnumType<'a, String>> {
1641    let enum_type_name = get_graphql_type_name(&field.field_type);
1642
1643    let enum_types = get_enum_types(graphql_ast);
1644
1645    return enum_types.into_iter().find(|enum_type| {
1646        return enum_type.name == enum_type_name;
1647    }).clone();
1648}
1649
1650fn get_scalar_fields<'a>(
1651    graphql_ast: &Document<String>,
1652    object_type: &ObjectType<'a, String>
1653) -> Vec<Field<'a, String>> {
1654    return object_type.fields.iter().cloned().filter(|field| {            
1655        return 
1656            is_graphql_type_a_relation_many(
1657                graphql_ast,
1658                &field.field_type
1659            ) == false &&
1660            is_graphql_type_a_relation_one(
1661                graphql_ast,
1662                &field.field_type
1663            ) == false;
1664    }).collect();
1665}
1666
1667fn get_relation_fields<'a>(
1668    graphql_ast: &Document<String>,
1669    object_type: &ObjectType<'a, String>
1670) -> Vec<Field<'a, String>> {
1671    return object_type.fields.iter().cloned().filter(|field| {            
1672        return 
1673            is_graphql_type_a_relation_many(
1674                graphql_ast,
1675                &field.field_type
1676            ) == true ||
1677            is_graphql_type_a_relation_one(
1678                graphql_ast,
1679                &field.field_type
1680            ) == true;
1681    }).collect();
1682}