diff --git a/Cargo.lock b/Cargo.lock index 6ae2ccbcd..958d7d8c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" +checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767" [[package]] name = "arrayref" @@ -204,9 +204,9 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "byteorder" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" @@ -216,9 +216,9 @@ checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] name = "camino" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd065703998b183ed0b348a22555691373a9345a1431141e5778b48bb17e4703" +checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" dependencies = [ "serde", ] @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "ci_info" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11dc72f80a224a348bb597d5a01785728a1d4828375c771504ad3cb3b604187b" +checksum = "61eed4218981025780de67d238868a4e3f4e18ac84ace81041c4ccc74eb94c49" dependencies = [ "envmnt", "serde", @@ -326,9 +326,9 @@ dependencies = [ [[package]] name = "console" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" dependencies = [ "encode_unicode", "lazy_static", @@ -400,6 +400,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "deno_core" version = "0.73.0" @@ -502,9 +512,9 @@ dependencies = [ [[package]] name = "envmnt" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d328fc287c61314c4a61af7cfdcbd7e678e39778488c7cb13ec133ce0f4059" +checksum = "dbfac51e9996e41d78a943227b7f313efcebf545b21584a0e213b956a062e11e" dependencies = [ "fsio", "indexmap", @@ -574,9 +584,9 @@ dependencies = [ [[package]] name = "fsio" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" +checksum = "a50045aa8931ae01afbc5d72439e8f57f326becb8c70d07dfc816778eff3d167" [[package]] name = "futures" @@ -780,6 +790,16 @@ dependencies = [ "failure", ] +[[package]] +name = "graphql-parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1abd4ce5247dfc04a03ccde70f87a048458c9356c7e41d21ad8c407b3dde6f2" +dependencies = [ + "combine", + "thiserror", +] + [[package]] name = "graphql_client" version = "0.9.0" @@ -800,7 +820,7 @@ checksum = "8e304c223c809b3bff4614018f8e6d9edb176b31d64ed9ea48b6ae8b1a03abb9" dependencies = [ "failure", "graphql-introspection-query", - "graphql-parser", + "graphql-parser 0.2.3", "heck", "lazy_static", "proc-macro2", @@ -905,12 +925,13 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" +checksum = "5dfb77c123b4e2f72a2069aeae0b4b4949cc7e966df277813fc16347e7549737" dependencies = [ "bytes", "http", + "pin-project-lite", ] [[package]] @@ -1007,6 +1028,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" +dependencies = [ + "unindent", +] + [[package]] name = "instant" version = "0.1.9" @@ -1039,9 +1069,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.48" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" +checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" dependencies = [ "wasm-bindgen", ] @@ -1054,9 +1084,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" +checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" [[package]] name = "libgit2-sys" @@ -1161,9 +1191,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.9" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5dede4e2065b3842b8b0af444119f3aa331cc7cc2dd20388bfb0f5d5a38823a" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" dependencies = [ "libc", "log", @@ -1174,11 +1204,10 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "socket2", "winapi", ] @@ -1282,15 +1311,15 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.32" +version = "0.10.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" +checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" dependencies = [ "bitflags", "cfg-if", "foreign-types", - "lazy_static", "libc", + "once_cell", "openssl-sys", ] @@ -1311,9 +1340,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.60" +version = "0.9.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" +checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" dependencies = [ "autocfg", "cc", @@ -1343,6 +1372,15 @@ dependencies = [ "regex", ] +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -1456,6 +1494,18 @@ dependencies = [ "treeline", ] +[[package]] +name = "pretty_assertions" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +dependencies = [ + "ansi_term 0.11.0", + "ctor", + "difference", + "output_vt100", +] + [[package]] name = "prettytable-rs" version = "0.8.0" @@ -1602,14 +1652,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.3" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] @@ -1624,9 +1673,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.22" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "remove_dir_all" @@ -1639,9 +1688,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0460542b551950620a3648c6aa23318ac6b3cd779114bd873209e6e8b5eb1c34" +checksum = "bf12057f289428dbf5c591c74bf10392e4a8003f993405a902f20117019022d4" dependencies = [ "base64", "bytes", @@ -1699,7 +1748,7 @@ dependencies = [ "binstall", "camino", "chrono", - "console 0.14.0", + "console 0.14.1", "git-url-parse", "git2", "harmonizer", @@ -1715,6 +1764,7 @@ dependencies = [ "robot-panic", "rover-client", "rustversion", + "sdl-encoder", "semver", "serde", "serde_json", @@ -1737,12 +1787,16 @@ dependencies = [ "anyhow", "camino", "chrono", + "graphql-parser 0.3.0", "graphql_client", "houston", "http", + "indoc", "online", + "pretty_assertions", "regex", "reqwest", + "sdl-encoder", "serde", "serde_json", "thiserror", @@ -1818,6 +1872,14 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sdl-encoder" +version = "0.1.0" +dependencies = [ + "indoc", + "pretty_assertions", +] + [[package]] name = "security-framework" version = "2.1.2" @@ -1862,18 +1924,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2", "quote", @@ -2064,9 +2126,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.62" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123a78a3596b24fee53a6464ce52d8ecbf62241e6294c7e7fe12086cd161f512" +checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" dependencies = [ "proc-macro2", "quote", @@ -2223,9 +2285,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a" +checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" dependencies = [ "autocfg", "bytes", @@ -2248,9 +2310,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b" +checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f" dependencies = [ "bytes", "futures-core", @@ -2289,9 +2351,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a9bd1db7706f2373a190b0d067146caa39350c486f3d455b0e33b431f94c07" +checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" dependencies = [ "proc-macro2", "quote", @@ -2330,9 +2392,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ab8966ac3ca27126141f7999361cc97dd6fb4b71da04c02044fa9045d98bb96" +checksum = "705096c6f83bf68ea5d357a6aa01829ddbdac531b357b45abeca842938085baa" dependencies = [ "ansi_term 0.12.1", "chrono", @@ -2365,9 +2427,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "typenum" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "ucd-trie" @@ -2411,6 +2473,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unindent" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" + [[package]] name = "unreachable" version = "1.0.0" @@ -2457,9 +2525,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "void" @@ -2478,9 +2546,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", @@ -2511,9 +2579,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" +checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" dependencies = [ "cfg-if", "serde", @@ -2523,9 +2591,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" +checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" dependencies = [ "bumpalo", "lazy_static", @@ -2538,9 +2606,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e67a5806118af01f0d9045915676b22aaebecf4178ae7021bc171dab0b897ab" +checksum = "73157efb9af26fb564bb59a009afd1c7c334a44db171d280690d0c3faaec3468" dependencies = [ "cfg-if", "js-sys", @@ -2550,9 +2618,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" +checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2560,9 +2628,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" +checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" dependencies = [ "proc-macro2", "quote", @@ -2573,15 +2641,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.71" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" +checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" [[package]] name = "web-sys" -version = "0.3.48" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" +checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 55879feaf..f4be881d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,18 @@ [package] -name = "rover" -version = "0.0.3" authors = ["Apollo Developers "] +build = "build.rs" +categories = ["command-line-interface"] description = """ Rover is a tool for working with the Apollo GraphQL Registry. """ documentation = "https://go.apollo.dev/r/docs" -repository = "https://github.com/apollographql/rover/" -readme = "README.md" +edition = "2018" keywords = ["graphql", "cli", "apollo", "graph", "registry"] -categories = ["command-line-interface"] license = "MIT" -build = "build.rs" -edition = "2018" +name = "rover" +readme = "README.md" +repository = "https://github.com/apollographql/rover/" +version = "0.0.3" [[bin]] name = "rover" @@ -24,47 +24,48 @@ members = [".", "crates/*", "installers/binstall"] [dependencies] # workspace deps -binstall = { path = "./installers/binstall" } -houston = { path = "./crates/houston" } -robot-panic = { path = "./crates/robot-panic" } -rover-client = { path = "./crates/rover-client" } -sputnik = { path = "./crates/sputnik" } -timber = { path = "./crates/timber" } +binstall = {path = "./installers/binstall"} +houston = {path = "./crates/houston"} +robot-panic = {path = "./crates/robot-panic"} +rover-client = {path = "./crates/rover-client"} +sdl-encoder = {path = "./crates/sdl-encoder"} +sputnik = {path = "./crates/sputnik"} +timber = {path = "./crates/timber"} # crates.io deps +ansi_term = "0.12.1" anyhow = "1.0.38" atty = "0.2.14" -ansi_term = "0.12.1" -camino = { version = "1.0.2", features = ["serde1"] } -billboard = { git = "https://github.com/EverlastingBugstopper/billboard.git", branch = "main" } +billboard = {git = "https://github.com/EverlastingBugstopper/billboard.git", branch = "main"} +camino = {version = "1.0.2", features = ["serde1"]} chrono = "0.4" console = "0.14.0" -git2 = "0.13.17" git-url-parse = "0.3.1" +git2 = "0.13.17" harmonizer = "0.1.2" heck = "0.3.2" humantime = "2.1.0" opener = "0.4.1" os_info = "3.0" prettytable-rs = "0.8.0" +regex = "1" +semver = "0.11" serde = "1.0" -strsim = "0.10" serde_json = "1.0" +serde_yaml = "0.8" +strsim = "0.10" structopt = "0.3.21" -tracing = "0.1.22" toml = "0.5" -regex = "1" +tracing = "0.1.22" url = "2.2.0" -semver = "0.11" -serde_yaml = "0.8" [dev-dependencies] assert_cmd = "1.0.1" assert_fs = "1.0.0" +predicates = "1.0.5" reqwest = "0.11.1" rustversion = "1.0.4" serial_test = "0.5.0" -predicates = "1.0.5" [build-dependencies] anyhow = "1" diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index 633c84218..489cfb87e 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -1,30 +1,36 @@ [package] -name = "rover-client" -description = "an http client for making graphql requests for the rover CLI" -version = "0.0.0" authors = ["Apollo Developers "] +description = "an http client for making graphql requests for the rover CLI" edition = "2018" +name = "rover-client" +version = "0.0.0" [dependencies] # workspace deps -houston = { path = "../houston" } +houston = {path = "../houston"} # crates.io deps anyhow = "1" camino = "1" +chrono = "0.4" +graphql-parser = "0.3.0" graphql_client = "0.9" http = "0.2" -reqwest = { version = "0.11", features = ["json", "blocking", "native-tls-vendored"] } +regex = "1.4.5" +reqwest = {version = "0.11", features = ["json", "blocking", "native-tls-vendored"]} +sdl-encoder = {path = "../sdl-encoder"} serde = "1" serde_json = "1" thiserror = "1" tracing = "0.1" -chrono = "0.4" -regex = "1" [build-dependencies] camino = "1" online = "0.2.2" -reqwest = { version = "0.11", features = ["blocking", "native-tls-vendored"] } -uuid = { version = "0.8", features = ["v4"] } +reqwest = {version = "0.11", features = ["blocking", "native-tls-vendored"]} +uuid = {version = "0.8", features = ["v4"]} + +[dev-dependencies] +indoc = "1.0.3" +pretty_assertions = "0.6.1" diff --git a/crates/rover-client/src/error.rs b/crates/rover-client/src/error.rs index bfe017d43..175ddf011 100644 --- a/crates/rover-client/src/error.rs +++ b/crates/rover-client/src/error.rs @@ -10,6 +10,13 @@ pub enum RoverClientError { msg: String, }, + /// Failed to parse Introspection Response coming from server. + #[error("{msg}")] + IntrospectionError { + /// Introspection Error coming from schema encoder. + msg: String, + }, + /// Tried to build a [HeaderMap] with an invalid header name. #[error("invalid header name")] InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName), diff --git a/crates/rover-client/src/introspection/mod.rs b/crates/rover-client/src/introspection/mod.rs new file mode 100644 index 000000000..dd9d7b111 --- /dev/null +++ b/crates/rover-client/src/introspection/mod.rs @@ -0,0 +1,2 @@ +mod schema; +pub use schema::Schema; diff --git a/crates/rover-client/src/introspection/schema.rs b/crates/rover-client/src/introspection/schema.rs new file mode 100644 index 000000000..c5744f894 --- /dev/null +++ b/crates/rover-client/src/introspection/schema.rs @@ -0,0 +1,271 @@ +//! Schema encoding module used to work with Introspection result. +//! +//! More information on Schema Definition language(SDL) can be found in [this +//! documentation](https://www.apollographql.com/docs/apollo-server/schema/schema/). +//! +use crate::query::graph::introspect; +use sdl_encoder::{ + Directive, EnumDef, EnumValue, Field, InputField, InputObjectDef, InputValue, InterfaceDef, + ObjectDef, ScalarDef, Schema as SDL, SchemaDef, Type_, UnionDef, +}; +use serde::Deserialize; +use std::convert::TryFrom; + +pub type FullTypeField = introspect::introspection_query::FullTypeFields; +pub type FullTypeInputField = introspect::introspection_query::FullTypeInputFields; +pub type FullTypeFieldArg = introspect::introspection_query::FullTypeFieldsArgs; +pub type IntrospectionResult = introspect::introspection_query::ResponseData; +pub type SchemaMutationType = introspect::introspection_query::IntrospectionQuerySchemaMutationType; +pub type SchemaQueryType = introspect::introspection_query::IntrospectionQuerySchemaQueryType; +pub type SchemaType = introspect::introspection_query::IntrospectionQuerySchemaTypes; +pub type SchemaDirective = introspect::introspection_query::IntrospectionQuerySchemaDirectives; +pub type SchemaSubscriptionType = + introspect::introspection_query::IntrospectionQuerySchemaSubscriptionType; +pub type __TypeKind = introspect::introspection_query::__TypeKind; + +// Represents GraphQL types we will not be encoding to SDL. +const GRAPHQL_NAMED_TYPES: [&str; 12] = [ + "__Schema", + "__Type", + "__TypeKind", + "__Field", + "__InputValue", + "__EnumValue", + "__DirectiveLocation", + "__Directive", + "Boolean", + "String", + "Int", + "ID", +]; + +// Represents GraphQL directives we will not be encoding to SDL. +const SPECIFIED_DIRECTIVES: [&str; 3] = ["skip", "include", "deprecated"]; + +/// A representation of a GraphQL Schema. +/// +/// Contains Schema Types and Directives. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Schema { + types: Vec, + directives: Vec, + mutation_type: Option, + query_type: SchemaQueryType, + subscription_type: Option, +} + +impl Schema { + /// Encode Schema into an SDL. + pub fn encode(self) -> String { + let mut sdl = SDL::new(); + + // When we have a defined mutation and subscription, we record + // everything to Schema Definition. + // https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/ + if self.mutation_type.is_some() | self.subscription_type.is_some() { + let mut schema_def = SchemaDef::new(); + if let Some(mutation_type) = self.mutation_type { + schema_def.mutation(mutation_type.name.unwrap()); + } + if let Some(subscription_type) = self.subscription_type { + schema_def.subscription(subscription_type.name.unwrap()); + } + if let Some(name) = self.query_type.name { + schema_def.query(name); + } + sdl.schema(schema_def); + } else if let Some(name) = self.query_type.name { + // If we don't have a mutation or a subscription, but do have a + // query type, only create a Schema Definition when it's something + // other than `Query`. + if name != "Query" { + let mut schema_def = SchemaDef::new(); + schema_def.query(name); + sdl.schema(schema_def); + } + } + + // Exclude GraphQL directives like 'skip' and 'include' before encoding directives. + self.directives + .into_iter() + .filter(|directive| !SPECIFIED_DIRECTIVES.contains(&directive.name.as_str())) + .for_each(|directive| Self::encode_directives(directive, &mut sdl)); + + // Exclude GraphQL named types like __Schema before encoding full type. + self.types + .into_iter() + .filter(|type_| match type_.full_type.name.as_deref() { + Some(name) => !GRAPHQL_NAMED_TYPES.contains(&name), + None => false, + }) + .for_each(|type_| Self::encode_full_type(type_, &mut sdl)); + + sdl.finish() + } + + fn encode_directives(directive: SchemaDirective, sdl: &mut SDL) { + let mut directive_ = Directive::new(directive.name); + directive_.description(directive.description); + for location in directive.locations { + // Location is of a __DirectiveLocation enum that doesn't implement + // Display (meaning we can't just do .to_string). This next line + // just forces it into a String with format! debug. + directive_.location(format!("{:?}", location)); + } + + sdl.directive(directive_) + } + + fn encode_full_type(type_: SchemaType, sdl: &mut SDL) { + let ty = type_.full_type; + + match ty.kind { + __TypeKind::OBJECT => { + let mut object_def = ObjectDef::new(ty.name.unwrap_or_else(String::new)); + object_def.description(ty.description); + if let Some(interfaces) = ty.interfaces { + for interface in interfaces { + object_def.interface(interface.type_ref.name.unwrap_or_else(String::new)); + } + } + if let Some(field) = ty.fields { + for f in field { + let field_def = Self::encode_field(f); + object_def.field(field_def); + } + sdl.object(object_def); + } + } + __TypeKind::INPUT_OBJECT => { + let mut input_def = InputObjectDef::new(ty.name.unwrap_or_else(String::new)); + input_def.description(ty.description); + if let Some(field) = ty.input_fields { + for f in field { + let input_field_def = Self::encode_input_field(f); + input_def.field(input_field_def); + } + sdl.input(input_def); + } + } + __TypeKind::INTERFACE => { + let mut interface_def = InterfaceDef::new(ty.name.unwrap_or_else(String::new)); + interface_def.description(ty.description); + if let Some(interfaces) = ty.interfaces { + for interface in interfaces { + interface_def + .interface(interface.type_ref.name.unwrap_or_else(String::new)); + } + } + if let Some(field) = ty.fields { + for f in field { + let field_def = Self::encode_field(f); + interface_def.field(field_def); + } + sdl.interface(interface_def); + } + } + __TypeKind::SCALAR => { + let mut scalar_def = ScalarDef::new(ty.name.unwrap_or_else(String::new)); + scalar_def.description(ty.description); + sdl.scalar(scalar_def); + } + __TypeKind::UNION => { + let mut union_def = UnionDef::new(ty.name.unwrap_or_else(String::new)); + union_def.description(ty.description); + if let Some(possible_types) = ty.possible_types { + for possible_type in possible_types { + union_def.member(possible_type.type_ref.name.unwrap_or_else(String::new)); + } + } + sdl.union(union_def); + } + __TypeKind::ENUM => { + let mut enum_def = EnumDef::new(ty.name.unwrap_or_else(String::new)); + if let Some(enums) = ty.enum_values { + for enum_ in enums { + let mut enum_value = EnumValue::new(enum_.name); + enum_value.description(enum_.description); + + if enum_.is_deprecated { + enum_value.deprecated(enum_.deprecation_reason); + } + + enum_def.value(enum_value); + } + } + sdl.enum_(enum_def); + } + _ => (), + } + } + + fn encode_field(field: FullTypeField) -> Field { + let ty = Self::encode_type(field.type_.type_ref); + let mut field_def = Field::new(field.name, ty); + + for value in field.args { + let field_value = Self::encode_arg(value); + field_def.arg(field_value); + } + + if field.is_deprecated { + field_def.deprecated(field.deprecation_reason); + } + field_def.description(field.description); + field_def + } + + fn encode_input_field(field: FullTypeInputField) -> InputField { + let ty = Self::encode_type(field.input_value.type_.type_ref); + let mut field_def = InputField::new(field.input_value.name, ty); + + field_def.default(field.input_value.default_value); + field_def.description(field.input_value.description); + field_def + } + + fn encode_arg(value: FullTypeFieldArg) -> InputValue { + let ty = Self::encode_type(value.input_value.type_.type_ref); + let mut value_def = InputValue::new(value.input_value.name, ty); + + value_def.default(value.input_value.default_value); + value_def.description(value.input_value.description); + value_def + } + + fn encode_type(ty: impl introspect::OfType) -> Type_ { + use introspect::introspection_query::__TypeKind::*; + match ty.kind() { + SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT => Type_::NamedType { + name: ty.name().unwrap().to_string(), + }, + NON_NULL => { + let ty = Self::encode_type(ty.of_type().unwrap()); + Type_::NonNull { ty: Box::new(ty) } + } + LIST => { + let ty = Self::encode_type(ty.of_type().unwrap()); + Type_::List { ty: Box::new(ty) } + } + Other(ty) => panic!("Unknown type: {}", ty), + } + } +} + +impl TryFrom for Schema { + type Error = &'static str; + + fn try_from(src: IntrospectionResult) -> Result { + match src.schema { + Some(s) => Ok(Self { + types: s.types, + directives: s.directives, + mutation_type: s.mutation_type, + query_type: s.query_type, + subscription_type: s.subscription_type, + }), + None => Err("Schema not found in Introspection Result."), + } + } +} diff --git a/crates/rover-client/src/lib.rs b/crates/rover-client/src/lib.rs index 2a41722a6..b220f0cc2 100644 --- a/crates/rover-client/src/lib.rs +++ b/crates/rover-client/src/lib.rs @@ -9,6 +9,9 @@ mod error; /// Module related to constructing request headers. pub mod headers; +/// Module related to building an SDL from an introspection response. +pub mod introspection; + /// Module for client related errors. pub use error::RoverClientError; diff --git a/crates/rover-client/src/query/graph/introspect.rs b/crates/rover-client/src/query/graph/introspect.rs new file mode 100644 index 000000000..d10cfe5b7 --- /dev/null +++ b/crates/rover-client/src/query/graph/introspect.rs @@ -0,0 +1,129 @@ +use std::{collections::HashMap, convert::TryFrom}; + +use crate::blocking::Client; +use crate::introspection::Schema; +use crate::RoverClientError; +use graphql_client::*; + +#[derive(GraphQLQuery)] +#[graphql( + query_path = "src/query/graph/introspect_query.graphql", + schema_path = "src/query/graph/introspect_schema.graphql", + response_derives = "PartialEq, Debug, Serialize, Deserialize", + deprecated = "warn" +)] + +/// This struct is used to generate the module containing `Variables` and +/// `ResponseData` structs. +/// Snake case of this name is the mod name. i.e. introspection_query +pub struct IntrospectionQuery; + +#[derive(Debug, PartialEq)] +pub struct IntrospectionResponse { + pub result: String, +} + +/// The main function to be used from this module. This function fetches a +/// schema from apollo studio and returns it in either sdl (default) or json format +pub fn run( + client: &Client, + headers: &HashMap, +) -> Result { + let variables = introspection_query::Variables {}; + let response_data = client.post::(variables, headers)?; + build_response(response_data) +} + +fn build_response( + response: introspection_query::ResponseData, +) -> Result { + match Schema::try_from(response) { + Ok(schema) => Ok(IntrospectionResponse { + result: schema.encode(), + }), + Err(msg) => Err(RoverClientError::IntrospectionError { msg: msg.into() }), + } +} + +/// This trait is used to be able to iterate over ofType fields in +/// IntrospectionResponse. +pub trait OfType { + type TypeRef: OfType; + + fn kind(&self) -> &introspection_query::__TypeKind; + fn name(&self) -> Option<&str>; + fn of_type(self) -> Option; +} + +macro_rules! impl_of_type { + ($target:ty, $assoc:ty) => { + impl OfType for $target { + type TypeRef = $assoc; + + fn kind(&self) -> &introspection_query::__TypeKind { + &self.kind + } + + fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + fn of_type(self) -> Option { + self.of_type + } + } + }; +} + +impl_of_type!( + introspection_query::TypeRef, + introspection_query::TypeRefOfType +); + +impl_of_type!( + introspection_query::TypeRefOfType, + introspection_query::TypeRefOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType +); + +impl_of_type!( + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfType, + introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType +); + +// NOTE(lrlna): This is a **hack**. This makes sure that the last possible +// generated ofType by graphql_client can return a None for of_type method. +impl OfType for introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType { + type TypeRef = introspection_query::TypeRefOfTypeOfTypeOfTypeOfTypeOfTypeOfTypeOfType; + + fn kind(&self) -> &introspection_query::__TypeKind { + &self.kind + } + + fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + fn of_type(self) -> Option { + None + } +} diff --git a/crates/rover-client/src/query/graph/introspect_query.graphql b/crates/rover-client/src/query/graph/introspect_query.graphql new file mode 100644 index 000000000..996742faf --- /dev/null +++ b/crates/rover-client/src/query/graph/introspect_query.graphql @@ -0,0 +1,99 @@ +query IntrospectionQuery { + __schema { + queryType { + name + } + mutationType { + name + } + subscriptionType { + name + } + types { + ...FullType + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ...TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/rover-client/src/query/graph/introspect_schema.graphql b/crates/rover-client/src/query/graph/introspect_schema.graphql new file mode 100644 index 000000000..805ad4486 --- /dev/null +++ b/crates/rover-client/src/query/graph/introspect_schema.graphql @@ -0,0 +1,101 @@ +schema { + query: Query +} + +type Query { + __schema: __Schema +} + +type __Schema { + types: [__Type!]! + queryType: __Type! + mutationType: __Type + subscriptionType: __Type + directives: [__Directive!]! +} + +type __Type { + kind: __TypeKind! + name: String + description: String + + # OBJECT and INTERFACE only + fields(includeDeprecated: Boolean = false): [__Field!] + + # OBJECT only + interfaces: [__Type!] + + # INTERFACE and UNION only + possibleTypes: [__Type!] + + # ENUM only + enumValues(includeDeprecated: Boolean = false): [__EnumValue!] + + # INPUT_OBJECT only + inputFields: [__InputValue!] + + # NON_NULL and LIST only + ofType: __Type +} + +type __Field { + name: String! + description: String + args: [__InputValue!]! + type: __Type! + isDeprecated: Boolean! + deprecationReason: String +} + +type __InputValue { + name: String! + description: String + type: __Type! + defaultValue: String +} + +type __EnumValue { + name: String! + description: String + isDeprecated: Boolean! + deprecationReason: String +} + +enum __TypeKind { + SCALAR + OBJECT + INTERFACE + UNION + ENUM + INPUT_OBJECT + LIST + NON_NULL +} + +type __Directive { + name: String! + description: String + locations: [__DirectiveLocation!]! + args: [__InputValue!]! +} + +enum __DirectiveLocation { + QUERY + MUTATION + SUBSCRIPTION + FIELD + FRAGMENT_DEFINITION + FRAGMENT_SPREAD + INLINE_FRAGMENT + SCHEMA + SCALAR + OBJECT + FIELD_DEFINITION + ARGUMENT_DEFINITION + INTERFACE + UNION + ENUM + ENUM_VALUE + INPUT_OBJECT + INPUT_FIELD_DEFINITION +} \ No newline at end of file diff --git a/crates/rover-client/src/query/graph/mod.rs b/crates/rover-client/src/query/graph/mod.rs index 6eda56b8a..ecbc72219 100644 --- a/crates/rover-client/src/query/graph/mod.rs +++ b/crates/rover-client/src/query/graph/mod.rs @@ -6,3 +6,6 @@ pub mod publish; /// "graph check" command exeuction pub mod check; + +/// "graph introspect" command exeuction +pub mod introspect; diff --git a/crates/rover-client/tests/fixtures/interfaces.json b/crates/rover-client/tests/fixtures/interfaces.json new file mode 100644 index 000000000..982901742 --- /dev/null +++ b/crates/rover-client/tests/fixtures/interfaces.json @@ -0,0 +1,1880 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "topProducts", + "description": "Fetch a simple list of products with an offset", + "args": [ + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "5" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "INTERFACE", + "name": "Product", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `products` instead" + }, + { + "name": "products", + "description": "Fetch a paginated list of products based on a filter type.", + "args": [ + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "5" + }, + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "0" + }, + { + "name": "type", + "description": null, + "type": { + "kind": "ENUM", + "name": "ProductType", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "ProductConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "me", + "description": "The currently authenticated user root. All nodes off of this\nroot will be authenticated as the current user", + "args": [], + "type": { + "kind": "OBJECT", + "name": "User", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INTERFACE", + "name": "Product", + "description": "The Product type represents all products within the system", + "fields": [ + { + "name": "upc", + "description": "The primary identifier of products in the graph", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The display name of the product", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "price", + "description": "A simple integer price of the product in US dollars", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "weight", + "description": "How much the product weighs in kg", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": true, + "deprecationReason": "Not all product's have a weight" + }, + { + "name": "reviews", + "description": "A simple list of all reviews for a product", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Review", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "The `reviews` field on product is deprecated to roll over the return\ntype from a simple list to a paginated list. The easiest way to fix your\noperations is to alias the new field `reviewList` to `review`:\n \n {\n ... on Product {\n reviews: reviewList {\n edges {\n review {\n body\n }\n }\n }\n }\n }\n\nOnce all clients have updated, we will roll over this field and deprecate\n`reviewList` in favor of the field name `reviews` again" + }, + { + "name": "reviewList", + "description": "A paginated list of reviews. This field naming is temporary while all clients\nmigrate off of the un-paginated version of this field call reviews. To ease this migration,\nalias your usage of `reviewList` to `reviews` so that after the roll over is finished, you\ncan remove the alias and use the final field name:\n\n {\n ... on Product {\n reviews: reviewList {\n edges {\n review {\n body\n }\n }\n }\n }\n }", + "args": [ + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "5" + }, + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "0" + } + ], + "type": { + "kind": "OBJECT", + "name": "ReviewConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Book", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Furniture", + "ofType": null + } + ] + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Review", + "description": "A review is any feedback about products across the graph", + "fields": [ + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "body", + "description": "The plain text version of the review", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "author", + "description": "The user who authored the review", + "args": [], + "type": { + "kind": "OBJECT", + "name": "User", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "product", + "description": "The product which this review is about", + "args": [], + "type": { + "kind": "INTERFACE", + "name": "Product", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "User", + "description": "The base User in Acephei", + "fields": [ + { + "name": "id", + "description": "A globally unique id for the user", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The users full name as provided", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "username", + "description": "The account username of the user", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "reviews", + "description": "A list of all reviews by the user", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Review", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ReviewConnection", + "description": "A connection wrapper for lists of reviews", + "fields": [ + { + "name": "pageInfo", + "description": "Helpful metadata about the connection", + "args": [], + "type": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "List of reviews returned by the search", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ReviewEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PageInfo", + "description": "The PageInfo type provides pagination helpers for determining\nif more data can be fetched from the list", + "fields": [ + { + "name": "hasNextPage", + "description": "More items exist in the list", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hasPreviousPage", + "description": "Items earlier in the list exist", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ReviewEdge", + "description": "A connection edge for the Review type", + "fields": [ + { + "name": "review", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "Review", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "ProductType", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "LATEST", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "TRENDING", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ProductConnection", + "description": "A connection wrapper for lists of products", + "fields": [ + { + "name": "pageInfo", + "description": "Helpful metadata about the connection", + "args": [], + "type": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "List of products returned by the search", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ProductEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ProductEdge", + "description": "A connection edge for the Product type", + "fields": [ + { + "name": "product", + "description": null, + "args": [], + "type": { + "kind": "INTERFACE", + "name": "Product", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "description": "Location adjacent to a variable definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Book", + "description": "The basic book in the graph", + "fields": [ + { + "name": "isbn", + "description": "All books can be found by an isbn", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "title", + "description": "The title of the book", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "year", + "description": "The year the book was published", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "similarBooks", + "description": "A simple list of similar books", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Book", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "reviews", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Review", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "reviewList", + "description": null, + "args": [ + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "5" + }, + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "0" + } + ], + "type": { + "kind": "OBJECT", + "name": "ReviewConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "relatedReviews", + "description": "relatedReviews for a book use the knowledge of `similarBooks` from the books\nservice to return related reviews that may be of interest to the user", + "args": [ + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "5" + }, + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "0" + } + ], + "type": { + "kind": "OBJECT", + "name": "ReviewConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "upc", + "description": "Since books are now products, we can also use their upc as a primary id", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The name of a book is the book's title + year published", + "args": [ + { + "name": "delimeter", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\" \"" + } + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "price", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "weight", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Product", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Amazon", + "description": "Information about the brand Amazon", + "fields": [ + { + "name": "referrer", + "description": "The url of a referrer for a product", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "UNION", + "name": "Brand", + "description": "A union of all brands represented within the store", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Ikea", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Amazon", + "ofType": null + } + ] + }, + { + "kind": "OBJECT", + "name": "Ikea", + "description": "Information about the brand Ikea", + "fields": [ + { + "name": "asile", + "description": "Which asile to find an item", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Furniture", + "description": "The Furniture type represents all products which are items\nof furniture.", + "fields": [ + { + "name": "upc", + "description": "The modern primary identifier for furniture", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sku", + "description": "The SKU field is how furniture was previously stored, and still exists in some legacy systems", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "price", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "brand", + "description": "The brand of furniture", + "args": [], + "type": { + "kind": "UNION", + "name": "Brand", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "weight", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "reviews", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Review", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "reviewList", + "description": null, + "args": [ + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "5" + }, + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "0" + } + ], + "type": { + "kind": "OBJECT", + "name": "ReviewConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Product", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": [ + "FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/crates/rover-client/tests/fixtures/simple.json b/crates/rover-client/tests/fixtures/simple.json new file mode 100644 index 000000000..f9b201c7c --- /dev/null +++ b/crates/rover-client/tests/fixtures/simple.json @@ -0,0 +1,1211 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "hello", + "description": "A simple type for getting started!", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cats", + "description": null, + "args": [ + { + "name": "cat", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "defaultValue": "[\"Nori\"]" + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "BooleanQueryOperatorInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "eq", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "ne", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "in", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "nin", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "CacheControlScope", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "PUBLIC", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PRIVATE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Upload", + "description": "The `Upload` scalar type represents a file upload.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByUrl`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "specifiedByUrl", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isRepeatable", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "description": "Location adjacent to a variable definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + } + ], + "directives": [ + { + "name": "cacheControl", + "description": null, + "locations": [ + "FIELD_DEFINITION", + "OBJECT", + "INTERFACE" + ], + "args": [ + { + "name": "maxAge", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "scope", + "description": null, + "type": { + "kind": "ENUM", + "name": "CacheControlScope", + "ofType": null + }, + "defaultValue": null + } + ] + }, + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": [ + "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INPUT_FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + }, + { + "name": "specifiedBy", + "description": "Exposes a URL that specifies the behaviour of this scalar.", + "locations": [ + "SCALAR" + ], + "args": [ + { + "name": "url", + "description": "The URL that specifies the behaviour of this scalar.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/crates/rover-client/tests/fixtures/swapi.json b/crates/rover-client/tests/fixtures/swapi.json new file mode 100644 index 000000000..d6c30ee44 --- /dev/null +++ b/crates/rover-client/tests/fixtures/swapi.json @@ -0,0 +1,5960 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Root" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Root", + "description": null, + "fields": [ + { + "name": "allFilms", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "FilmsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "film", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "filmID", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "allPeople", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PeopleConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "person", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "personID", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "allPlanets", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PlanetsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "planet", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "planetID", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "allSpecies", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "SpeciesConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "species", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "speciesID", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Species", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "allStarships", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "StarshipsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "starship", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "starshipID", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "allVehicles", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "VehiclesConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "vehicle", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "vehicleID", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "node", + "description": "Fetches an object given its ID", + "args": [ + { + "name": "id", + "description": "The ID of an object", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "FilmsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "films", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PageInfo", + "description": "Information about pagination in a connection.", + "fields": [ + { + "name": "hasNextPage", + "description": "When paginating forwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hasPreviousPage", + "description": "When paginating backwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "startCursor", + "description": "When paginating backwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "endCursor", + "description": "When paginating forwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Film", + "description": "A single film.", + "fields": [ + { + "name": "title", + "description": "The title of this film.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "episodeID", + "description": "The episode number of this film.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "openingCrawl", + "description": "The opening paragraphs at the beginning of this film.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "director", + "description": "The name of the director of this film.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "producers", + "description": "The name(s) of the producer(s) of this film.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "releaseDate", + "description": "The ISO 8601 date format of film release at original creator country.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "speciesConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "FilmSpeciesConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "starshipConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "FilmStarshipsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "vehicleConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "FilmVehiclesConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "characterConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "FilmCharactersConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "planetConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "FilmPlanetsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "created", + "description": "The ISO 8601 date format of the time that this resource was created.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edited", + "description": "The ISO 8601 date format of the time that this resource was edited.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INTERFACE", + "name": "Node", + "description": "An object with an ID", + "fields": [ + { + "name": "id", + "description": "The id of the object.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Species", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + } + ] + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmSpeciesConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "FilmSpeciesEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "species", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Species", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmSpeciesEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Species", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Species", + "description": "A type of person or character within the Star Wars Universe.", + "fields": [ + { + "name": "name", + "description": "The name of this species.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "classification", + "description": "The classification of this species, such as \"mammal\" or \"reptile\".", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "designation", + "description": "The designation of this species, such as \"sentient\".", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "averageHeight", + "description": "The average height of this species in centimeters.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "averageLifespan", + "description": "The average lifespan of this species in years, null if unknown.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "eyeColors", + "description": "Common eye colors for this species, null if this species does not typically\nhave eyes.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hairColors", + "description": "Common hair colors for this species, null if this species does not typically\nhave hair.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "skinColors", + "description": "Common skin colors for this species, null if this species does not typically\nhave skin.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "language", + "description": "The language commonly spoken by this species.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "homeworld", + "description": "A planet that this species originates from.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "personConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "SpeciesPeopleConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filmConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "SpeciesFilmsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "created", + "description": "The ISO 8601 date format of the time that this resource was created.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edited", + "description": "The ISO 8601 date format of the time that this resource was edited.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Float", + "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Planet", + "description": "A large mass, planet or planetoid in the Star Wars Universe, at the time of\n0 ABY.", + "fields": [ + { + "name": "name", + "description": "The name of this planet.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "diameter", + "description": "The diameter of this planet in kilometers.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "rotationPeriod", + "description": "The number of standard hours it takes for this planet to complete a single\nrotation on its axis.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "orbitalPeriod", + "description": "The number of standard days it takes for this planet to complete a single orbit\nof its local star.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "gravity", + "description": "A number denoting the gravity of this planet, where \"1\" is normal or 1 standard\nG. \"2\" is twice or 2 standard Gs. \"0.5\" is half or 0.5 standard Gs.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "population", + "description": "The average population of sentient beings inhabiting this planet.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "climates", + "description": "The climates of this planet.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "terrains", + "description": "The terrains of this planet.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "surfaceWater", + "description": "The percentage of the planet surface that is naturally occuring water or bodies\nof water.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "residentConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PlanetResidentsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filmConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PlanetFilmsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "created", + "description": "The ISO 8601 date format of the time that this resource was created.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edited", + "description": "The ISO 8601 date format of the time that this resource was edited.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PlanetResidentsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PlanetResidentsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "residents", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PlanetResidentsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Person", + "description": "An individual person or character within the Star Wars universe.", + "fields": [ + { + "name": "name", + "description": "The name of this person.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "birthYear", + "description": "The birth year of the person, using the in-universe standard of BBY or ABY -\nBefore the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is\na battle that occurs at the end of Star Wars episode IV: A New Hope.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "eyeColor", + "description": "The eye color of this person. Will be \"unknown\" if not known or \"n/a\" if the\nperson does not have an eye.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "gender", + "description": "The gender of this person. Either \"Male\", \"Female\" or \"unknown\",\n\"n/a\" if the person does not have a gender.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hairColor", + "description": "The hair color of this person. Will be \"unknown\" if not known or \"n/a\" if the\nperson does not have hair.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "height", + "description": "The height of the person in centimeters.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mass", + "description": "The mass of the person in kilograms.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "skinColor", + "description": "The skin color of this person.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "homeworld", + "description": "A planet that this person was born on or inhabits.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filmConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PersonFilmsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "species", + "description": "The species that this person belongs to, or null if unknown.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Species", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "starshipConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PersonStarshipsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "vehicleConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PersonVehiclesConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "created", + "description": "The ISO 8601 date format of the time that this resource was created.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edited", + "description": "The ISO 8601 date format of the time that this resource was edited.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PersonFilmsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PersonFilmsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "films", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PersonFilmsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PersonStarshipsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PersonStarshipsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "starships", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PersonStarshipsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Starship", + "description": "A single transport craft that has hyperdrive capability.", + "fields": [ + { + "name": "name", + "description": "The name of this starship. The common name, such as \"Death Star\".", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "model", + "description": "The model or official name of this starship. Such as \"T-65 X-wing\" or \"DS-1\nOrbital Battle Station\".", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "starshipClass", + "description": "The class of this starship, such as \"Starfighter\" or \"Deep Space Mobile\nBattlestation\"", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "manufacturers", + "description": "The manufacturers of this starship.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "costInCredits", + "description": "The cost of this starship new, in galactic credits.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "length", + "description": "The length of this starship in meters.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "crew", + "description": "The number of personnel needed to run or pilot this starship.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "passengers", + "description": "The number of non-essential people this starship can transport.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "maxAtmospheringSpeed", + "description": "The maximum speed of this starship in atmosphere. null if this starship is\nincapable of atmosphering flight.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hyperdriveRating", + "description": "The class of this starships hyperdrive.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MGLT", + "description": "The Maximum number of Megalights this starship can travel in a standard hour.\nA \"Megalight\" is a standard unit of distance and has never been defined before\nwithin the Star Wars universe. This figure is only really useful for measuring\nthe difference in speed of starships. We can assume it is similar to AU, the\ndistance between our Sun (Sol) and Earth.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cargoCapacity", + "description": "The maximum number of kilograms that this starship can transport.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "consumables", + "description": "The maximum length of time that this starship can provide consumables for its\nentire crew without having to resupply.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pilotConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "StarshipPilotsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filmConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "StarshipFilmsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "created", + "description": "The ISO 8601 date format of the time that this resource was created.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edited", + "description": "The ISO 8601 date format of the time that this resource was edited.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "StarshipPilotsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "StarshipPilotsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pilots", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "StarshipPilotsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "StarshipFilmsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "StarshipFilmsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "films", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "StarshipFilmsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PersonVehiclesConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PersonVehiclesEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "vehicles", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PersonVehiclesEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Vehicle", + "description": "A single transport craft that does not have hyperdrive capability", + "fields": [ + { + "name": "name", + "description": "The name of this vehicle. The common name, such as \"Sand Crawler\" or \"Speeder\nbike\".", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "model", + "description": "The model or official name of this vehicle. Such as \"All-Terrain Attack\nTransport\".", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "vehicleClass", + "description": "The class of this vehicle, such as \"Wheeled\" or \"Repulsorcraft\".", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "manufacturers", + "description": "The manufacturers of this vehicle.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "costInCredits", + "description": "The cost of this vehicle new, in Galactic Credits.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "length", + "description": "The length of this vehicle in meters.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "crew", + "description": "The number of personnel needed to run or pilot this vehicle.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "passengers", + "description": "The number of non-essential people this vehicle can transport.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "maxAtmospheringSpeed", + "description": "The maximum speed of this vehicle in atmosphere.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cargoCapacity", + "description": "The maximum number of kilograms that this vehicle can transport.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "consumables", + "description": "The maximum length of time that this vehicle can provide consumables for its\nentire crew without having to resupply.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pilotConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "VehiclePilotsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filmConnection", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "VehicleFilmsConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "created", + "description": "The ISO 8601 date format of the time that this resource was created.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edited", + "description": "The ISO 8601 date format of the time that this resource was edited.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "VehiclePilotsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "VehiclePilotsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pilots", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "VehiclePilotsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "VehicleFilmsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "VehicleFilmsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "films", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "VehicleFilmsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PlanetFilmsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PlanetFilmsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "films", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PlanetFilmsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SpeciesPeopleConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "SpeciesPeopleEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "people", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SpeciesPeopleEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SpeciesFilmsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "SpeciesFilmsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "films", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SpeciesFilmsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Film", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmStarshipsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "FilmStarshipsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "starships", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmStarshipsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmVehiclesConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "FilmVehiclesEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "vehicles", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmVehiclesEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmCharactersConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "FilmCharactersEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "characters", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmCharactersEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmPlanetsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "FilmPlanetsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "planets", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "FilmPlanetsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PeopleConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PeopleEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "people", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PeopleEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Person", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PlanetsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PlanetsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "planets", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PlanetsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Planet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SpeciesConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "SpeciesEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "species", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Species", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SpeciesEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Species", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "StarshipsConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "StarshipsEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "starships", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "StarshipsEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Starship", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "VehiclesConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "VehiclesEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "vehicles", + "description": "A list of all of the objects returned in the connection. This is a convenience\nfield provided for quickly exploring the API; rather than querying for\n\"{ edges { node } }\" when no edge data is needed, this field can be be used\ninstead. Note that when clients like Relay need to fetch the \"cursor\" field on\nthe edge to enable efficient pagination, this shortcut cannot be used, and the\nfull \"{ edges { node } }\" version should be used instead.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "VehiclesEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Vehicle", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "description": "Location adjacent to a variable definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": [ + "FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/crates/rover-client/tests/schema.rs b/crates/rover-client/tests/schema.rs new file mode 100644 index 000000000..4678ec852 --- /dev/null +++ b/crates/rover-client/tests/schema.rs @@ -0,0 +1,1194 @@ +use graphql_client::Response; +use rover_client::introspection::Schema; +use std::convert::TryFrom; +use std::fs::File; + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + use rover_client::query::graph::introspect; + + pub type IntrospectionResult = introspect::introspection_query::ResponseData; + #[test] + fn it_builds_simple_schema() { + let file = File::open("tests/fixtures/simple.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); + + let data = res.data.unwrap(); + let schema = Schema::try_from(data).unwrap(); + assert_eq!( + schema.encode(), + indoc! { r#" + directive @cacheControl on FIELD_DEFINITION | OBJECT | INTERFACE + """Exposes a URL that specifies the behaviour of this scalar.""" + directive @specifiedBy on SCALAR + type Query { + """A simple type for getting started!""" + hello: String + cats(cat: [String]! = ["Nori"]): [String]! + } + input BooleanQueryOperatorInput { + eq: Boolean + ne: Boolean + in: [Boolean] + nin: [Boolean] + } + enum CacheControlScope { + PUBLIC + PRIVATE + } + """The `Upload` scalar type represents a file upload.""" + scalar Upload + "#} + ) + } + + #[test] + fn it_builds_swapi_schema() { + let file = File::open("tests/fixtures/swapi.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); + + let data = res.data.unwrap(); + let schema = Schema::try_from(data).unwrap(); + assert_eq!( + schema.encode(), + indoc! { r#" + schema { + query: Root + } + type Root { + allFilms(after: String, first: Int, before: String, last: Int): FilmsConnection + film(id: ID, filmID: ID): Film + allPeople(after: String, first: Int, before: String, last: Int): PeopleConnection + person(id: ID, personID: ID): Person + allPlanets(after: String, first: Int, before: String, last: Int): PlanetsConnection + planet(id: ID, planetID: ID): Planet + allSpecies(after: String, first: Int, before: String, last: Int): SpeciesConnection + species(id: ID, speciesID: ID): Species + allStarships(after: String, first: Int, before: String, last: Int): StarshipsConnection + starship(id: ID, starshipID: ID): Starship + allVehicles(after: String, first: Int, before: String, last: Int): VehiclesConnection + vehicle(id: ID, vehicleID: ID): Vehicle + """Fetches an object given its ID""" + node("""The ID of an object""" id: ID!): Node + } + """A connection to a list of items.""" + type FilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [FilmsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + """Information about pagination in a connection.""" + type PageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + """When paginating backwards, the cursor to continue.""" + startCursor: String + """When paginating forwards, the cursor to continue.""" + endCursor: String + } + """An edge in a connection.""" + type FilmsEdge { + """The item at the end of the edge""" + node: Film + """A cursor for use in pagination""" + cursor: String! + } + """A single film.""" + type Film implements Node { + """The title of this film.""" + title: String + """The episode number of this film.""" + episodeID: Int + """The opening paragraphs at the beginning of this film.""" + openingCrawl: String + """The name of the director of this film.""" + director: String + """The name(s) of the producer(s) of this film.""" + producers: [String] + """The ISO 8601 date format of film release at original creator country.""" + releaseDate: String + speciesConnection(after: String, first: Int, before: String, last: Int): FilmSpeciesConnection + starshipConnection(after: String, first: Int, before: String, last: Int): FilmStarshipsConnection + vehicleConnection(after: String, first: Int, before: String, last: Int): FilmVehiclesConnection + characterConnection(after: String, first: Int, before: String, last: Int): FilmCharactersConnection + planetConnection(after: String, first: Int, before: String, last: Int): FilmPlanetsConnection + """The ISO 8601 date format of the time that this resource was created.""" + created: String + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + """The ID of an object""" + id: ID! + } + """An object with an ID""" + interface Node { + """The id of the object.""" + id: ID! + } + """A connection to a list of items.""" + type FilmSpeciesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [FilmSpeciesEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + species: [Species] + } + """An edge in a connection.""" + type FilmSpeciesEdge { + """The item at the end of the edge""" + node: Species + """A cursor for use in pagination""" + cursor: String! + } + """A type of person or character within the Star Wars Universe.""" + type Species implements Node { + """The name of this species.""" + name: String + """The classification of this species, such as "mammal" or "reptile".""" + classification: String + """The designation of this species, such as "sentient".""" + designation: String + """The average height of this species in centimeters.""" + averageHeight: Float + """The average lifespan of this species in years, null if unknown.""" + averageLifespan: Int + """ + Common eye colors for this species, null if this species does not typically + have eyes. + """ + eyeColors: [String] + """ + Common hair colors for this species, null if this species does not typically + have hair. + """ + hairColors: [String] + """ + Common skin colors for this species, null if this species does not typically + have skin. + """ + skinColors: [String] + """The language commonly spoken by this species.""" + language: String + """A planet that this species originates from.""" + homeworld: Planet + personConnection(after: String, first: Int, before: String, last: Int): SpeciesPeopleConnection + filmConnection(after: String, first: Int, before: String, last: Int): SpeciesFilmsConnection + """The ISO 8601 date format of the time that this resource was created.""" + created: String + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + """The ID of an object""" + id: ID! + } + """The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).""" + scalar Float + """ + A large mass, planet or planetoid in the Star Wars Universe, at the time of + 0 ABY. + """ + type Planet implements Node { + """The name of this planet.""" + name: String + """The diameter of this planet in kilometers.""" + diameter: Int + """ + The number of standard hours it takes for this planet to complete a single + rotation on its axis. + """ + rotationPeriod: Int + """ + The number of standard days it takes for this planet to complete a single orbit + of its local star. + """ + orbitalPeriod: Int + """ + A number denoting the gravity of this planet, where "1" is normal or 1 standard + G. "2" is twice or 2 standard Gs. "0.5" is half or 0.5 standard Gs. + """ + gravity: String + """The average population of sentient beings inhabiting this planet.""" + population: Float + """The climates of this planet.""" + climates: [String] + """The terrains of this planet.""" + terrains: [String] + """ + The percentage of the planet surface that is naturally occuring water or bodies + of water. + """ + surfaceWater: Float + residentConnection(after: String, first: Int, before: String, last: Int): PlanetResidentsConnection + filmConnection(after: String, first: Int, before: String, last: Int): PlanetFilmsConnection + """The ISO 8601 date format of the time that this resource was created.""" + created: String + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + """The ID of an object""" + id: ID! + } + """A connection to a list of items.""" + type PlanetResidentsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [PlanetResidentsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + residents: [Person] + } + """An edge in a connection.""" + type PlanetResidentsEdge { + """The item at the end of the edge""" + node: Person + """A cursor for use in pagination""" + cursor: String! + } + """An individual person or character within the Star Wars universe.""" + type Person implements Node { + """The name of this person.""" + name: String + """ + The birth year of the person, using the in-universe standard of BBY or ABY - + Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is + a battle that occurs at the end of Star Wars episode IV: A New Hope. + """ + birthYear: String + """ + The eye color of this person. Will be "unknown" if not known or "n/a" if the + person does not have an eye. + """ + eyeColor: String + """ + The gender of this person. Either "Male", "Female" or "unknown", + "n/a" if the person does not have a gender. + """ + gender: String + """ + The hair color of this person. Will be "unknown" if not known or "n/a" if the + person does not have hair. + """ + hairColor: String + """The height of the person in centimeters.""" + height: Int + """The mass of the person in kilograms.""" + mass: Float + """The skin color of this person.""" + skinColor: String + """A planet that this person was born on or inhabits.""" + homeworld: Planet + filmConnection(after: String, first: Int, before: String, last: Int): PersonFilmsConnection + """The species that this person belongs to, or null if unknown.""" + species: Species + starshipConnection(after: String, first: Int, before: String, last: Int): PersonStarshipsConnection + vehicleConnection(after: String, first: Int, before: String, last: Int): PersonVehiclesConnection + """The ISO 8601 date format of the time that this resource was created.""" + created: String + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + """The ID of an object""" + id: ID! + } + """A connection to a list of items.""" + type PersonFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [PersonFilmsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + """An edge in a connection.""" + type PersonFilmsEdge { + """The item at the end of the edge""" + node: Film + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type PersonStarshipsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [PersonStarshipsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + } + """An edge in a connection.""" + type PersonStarshipsEdge { + """The item at the end of the edge""" + node: Starship + """A cursor for use in pagination""" + cursor: String! + } + """A single transport craft that has hyperdrive capability.""" + type Starship implements Node { + """The name of this starship. The common name, such as "Death Star".""" + name: String + """ + The model or official name of this starship. Such as "T-65 X-wing" or "DS-1 + Orbital Battle Station". + """ + model: String + """ + The class of this starship, such as "Starfighter" or "Deep Space Mobile + Battlestation" + """ + starshipClass: String + """The manufacturers of this starship.""" + manufacturers: [String] + """The cost of this starship new, in galactic credits.""" + costInCredits: Float + """The length of this starship in meters.""" + length: Float + """The number of personnel needed to run or pilot this starship.""" + crew: String + """The number of non-essential people this starship can transport.""" + passengers: String + """ + The maximum speed of this starship in atmosphere. null if this starship is + incapable of atmosphering flight. + """ + maxAtmospheringSpeed: Int + """The class of this starships hyperdrive.""" + hyperdriveRating: Float + """ + The Maximum number of Megalights this starship can travel in a standard hour. + A "Megalight" is a standard unit of distance and has never been defined before + within the Star Wars universe. This figure is only really useful for measuring + the difference in speed of starships. We can assume it is similar to AU, the + distance between our Sun (Sol) and Earth. + """ + MGLT: Int + """The maximum number of kilograms that this starship can transport.""" + cargoCapacity: Float + """ + The maximum length of time that this starship can provide consumables for its + entire crew without having to resupply. + """ + consumables: String + pilotConnection(after: String, first: Int, before: String, last: Int): StarshipPilotsConnection + filmConnection(after: String, first: Int, before: String, last: Int): StarshipFilmsConnection + """The ISO 8601 date format of the time that this resource was created.""" + created: String + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + """The ID of an object""" + id: ID! + } + """A connection to a list of items.""" + type StarshipPilotsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [StarshipPilotsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + pilots: [Person] + } + """An edge in a connection.""" + type StarshipPilotsEdge { + """The item at the end of the edge""" + node: Person + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type StarshipFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [StarshipFilmsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + """An edge in a connection.""" + type StarshipFilmsEdge { + """The item at the end of the edge""" + node: Film + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type PersonVehiclesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [PersonVehiclesEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] + } + """An edge in a connection.""" + type PersonVehiclesEdge { + """The item at the end of the edge""" + node: Vehicle + """A cursor for use in pagination""" + cursor: String! + } + """A single transport craft that does not have hyperdrive capability""" + type Vehicle implements Node { + """ + The name of this vehicle. The common name, such as "Sand Crawler" or "Speeder + bike". + """ + name: String + """ + The model or official name of this vehicle. Such as "All-Terrain Attack + Transport". + """ + model: String + """The class of this vehicle, such as "Wheeled" or "Repulsorcraft".""" + vehicleClass: String + """The manufacturers of this vehicle.""" + manufacturers: [String] + """The cost of this vehicle new, in Galactic Credits.""" + costInCredits: Float + """The length of this vehicle in meters.""" + length: Float + """The number of personnel needed to run or pilot this vehicle.""" + crew: String + """The number of non-essential people this vehicle can transport.""" + passengers: String + """The maximum speed of this vehicle in atmosphere.""" + maxAtmospheringSpeed: Int + """The maximum number of kilograms that this vehicle can transport.""" + cargoCapacity: Float + """ + The maximum length of time that this vehicle can provide consumables for its + entire crew without having to resupply. + """ + consumables: String + pilotConnection(after: String, first: Int, before: String, last: Int): VehiclePilotsConnection + filmConnection(after: String, first: Int, before: String, last: Int): VehicleFilmsConnection + """The ISO 8601 date format of the time that this resource was created.""" + created: String + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + """The ID of an object""" + id: ID! + } + """A connection to a list of items.""" + type VehiclePilotsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [VehiclePilotsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + pilots: [Person] + } + """An edge in a connection.""" + type VehiclePilotsEdge { + """The item at the end of the edge""" + node: Person + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type VehicleFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [VehicleFilmsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + """An edge in a connection.""" + type VehicleFilmsEdge { + """The item at the end of the edge""" + node: Film + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type PlanetFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [PlanetFilmsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + """An edge in a connection.""" + type PlanetFilmsEdge { + """The item at the end of the edge""" + node: Film + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type SpeciesPeopleConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [SpeciesPeopleEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + people: [Person] + } + """An edge in a connection.""" + type SpeciesPeopleEdge { + """The item at the end of the edge""" + node: Person + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type SpeciesFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [SpeciesFilmsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + """An edge in a connection.""" + type SpeciesFilmsEdge { + """The item at the end of the edge""" + node: Film + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type FilmStarshipsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [FilmStarshipsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + } + """An edge in a connection.""" + type FilmStarshipsEdge { + """The item at the end of the edge""" + node: Starship + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type FilmVehiclesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [FilmVehiclesEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] + } + """An edge in a connection.""" + type FilmVehiclesEdge { + """The item at the end of the edge""" + node: Vehicle + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type FilmCharactersConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [FilmCharactersEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + characters: [Person] + } + """An edge in a connection.""" + type FilmCharactersEdge { + """The item at the end of the edge""" + node: Person + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type FilmPlanetsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [FilmPlanetsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + planets: [Planet] + } + """An edge in a connection.""" + type FilmPlanetsEdge { + """The item at the end of the edge""" + node: Planet + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type PeopleConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [PeopleEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + people: [Person] + } + """An edge in a connection.""" + type PeopleEdge { + """The item at the end of the edge""" + node: Person + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type PlanetsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [PlanetsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + planets: [Planet] + } + """An edge in a connection.""" + type PlanetsEdge { + """The item at the end of the edge""" + node: Planet + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type SpeciesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [SpeciesEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + species: [Species] + } + """An edge in a connection.""" + type SpeciesEdge { + """The item at the end of the edge""" + node: Species + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type StarshipsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [StarshipsEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + } + """An edge in a connection.""" + type StarshipsEdge { + """The item at the end of the edge""" + node: Starship + """A cursor for use in pagination""" + cursor: String! + } + """A connection to a list of items.""" + type VehiclesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + """A list of edges.""" + edges: [VehiclesEdge] + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] + } + """An edge in a connection.""" + type VehiclesEdge { + """The item at the end of the edge""" + node: Vehicle + """A cursor for use in pagination""" + cursor: String! + } + "#} + ) + } + + #[test] + fn it_builds_schema_with_interfaces() { + let file = File::open("tests/fixtures/interfaces.json").unwrap(); + let res: Response = serde_json::from_reader(file).unwrap(); + + let data = res.data.unwrap(); + let schema = Schema::try_from(data).unwrap(); + assert_eq!( + schema.encode(), + indoc! { r#" + type Query { + """Fetch a simple list of products with an offset""" + topProducts(first: Int = 5): [Product] @deprecated(reason: "Use `products` instead") + """Fetch a paginated list of products based on a filter type.""" + products(first: Int = 5, after: Int = 0, type: ProductType): ProductConnection + """ + The currently authenticated user root. All nodes off of this + root will be authenticated as the current user + """ + me: User + } + """The Product type represents all products within the system""" + interface Product { + """The primary identifier of products in the graph""" + upc: String! + """The display name of the product""" + name: String + """A simple integer price of the product in US dollars""" + price: Int + """How much the product weighs in kg""" + weight: Int @deprecated(reason: "Not all product's have a weight") + """A simple list of all reviews for a product""" + reviews: [Review] @deprecated(reason: "The `reviews` field on product is deprecated to roll over the return + type from a simple list to a paginated list. The easiest way to fix your + operations is to alias the new field `reviewList` to `review`: + + { + ... on Product { + reviews: reviewList { + edges { + review { + body + } + } + } + } + } + + Once all clients have updated, we will roll over this field and deprecate + `reviewList` in favor of the field name `reviews` again") + """ + A paginated list of reviews. This field naming is temporary while all clients + migrate off of the un-paginated version of this field call reviews. To ease this migration, + alias your usage of `reviewList` to `reviews` so that after the roll over is finished, you + can remove the alias and use the final field name: + + { + ... on Product { + reviews: reviewList { + edges { + review { + body + } + } + } + } + } + """ + reviewList(first: Int = 5, after: Int = 0): ReviewConnection + } + """A review is any feedback about products across the graph""" + type Review { + id: ID! + """The plain text version of the review""" + body: String + """The user who authored the review""" + author: User + """The product which this review is about""" + product: Product + } + """The base User in Acephei""" + type User { + """A globally unique id for the user""" + id: ID! + """The users full name as provided""" + name: String + """The account username of the user""" + username: String + """A list of all reviews by the user""" + reviews: [Review] + } + """A connection wrapper for lists of reviews""" + type ReviewConnection { + """Helpful metadata about the connection""" + pageInfo: PageInfo + """List of reviews returned by the search""" + edges: [ReviewEdge] + } + """ + The PageInfo type provides pagination helpers for determining + if more data can be fetched from the list + """ + type PageInfo { + """More items exist in the list""" + hasNextPage: Boolean + """Items earlier in the list exist""" + hasPreviousPage: Boolean + } + """A connection edge for the Review type""" + type ReviewEdge { + review: Review + } + enum ProductType { + LATEST + TRENDING + } + """A connection wrapper for lists of products""" + type ProductConnection { + """Helpful metadata about the connection""" + pageInfo: PageInfo + """List of products returned by the search""" + edges: [ProductEdge] + } + """A connection edge for the Product type""" + type ProductEdge { + product: Product + } + """The basic book in the graph""" + type Book implements Product { + """All books can be found by an isbn""" + isbn: String! + """The title of the book""" + title: String + """The year the book was published""" + year: Int + """A simple list of similar books""" + similarBooks: [Book] + reviews: [Review] + reviewList(first: Int = 5, after: Int = 0): ReviewConnection + """ + relatedReviews for a book use the knowledge of `similarBooks` from the books + service to return related reviews that may be of interest to the user + """ + relatedReviews(first: Int = 5, after: Int = 0): ReviewConnection + """Since books are now products, we can also use their upc as a primary id""" + upc: String! + """The name of a book is the book's title + year published""" + name(delimeter: String = " "): String + price: Int + weight: Int + } + """Information about the brand Amazon""" + type Amazon { + """The url of a referrer for a product""" + referrer: String + } + """A union of all brands represented within the store""" + union Brand = Ikea | Amazon + """Information about the brand Ikea""" + type Ikea { + """Which asile to find an item""" + asile: Int + } + """ + The Furniture type represents all products which are items + of furniture. + """ + type Furniture implements Product { + """The modern primary identifier for furniture""" + upc: String! + """The SKU field is how furniture was previously stored, and still exists in some legacy systems""" + sku: String! + name: String + price: Int + """The brand of furniture""" + brand: Brand + weight: Int + reviews: [Review] + reviewList(first: Int = 5, after: Int = 0): ReviewConnection + } + "#} + ) + } +} diff --git a/crates/sdl-encoder/Cargo.toml b/crates/sdl-encoder/Cargo.toml new file mode 100644 index 000000000..c01acd89b --- /dev/null +++ b/crates/sdl-encoder/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["lrlna "] +edition = "2018" +name = "sdl-encoder" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[dev-dependencies] +pretty_assertions = "0.6.1" +indoc = "1.0.3" diff --git a/crates/sdl-encoder/src/directive_def.rs b/crates/sdl-encoder/src/directive_def.rs new file mode 100644 index 000000000..6ef9bf951 --- /dev/null +++ b/crates/sdl-encoder/src/directive_def.rs @@ -0,0 +1,170 @@ +use crate::InputValue; +use std::fmt::{self, Display}; + +/// The `__Directive` type represents a Directive that a service supports. +/// +/// *DirectiveDefinition*: +/// Descriptionopt **directive @** Name Arguments Definitionopt **repeatable**opt **on** DirectiveLocations +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-Type-System.Directives). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Directive}; +/// use indoc::indoc; +/// +/// let mut directive = Directive::new("infer".to_string()); +/// directive.description(Some("Infer field types\nfrom field values.".to_string())); +/// directive.location("OBJECT".to_string()); +/// directive.location("FIELD_DEFINITION".to_string()); +/// directive.location("INPUT_FIELD_DEFINITION".to_string()); +/// +/// assert_eq!( +/// directive.to_string(), +/// r#"""" +/// Infer field types +/// from field values. +/// """ +/// directive @infer on OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION +/// "# +/// ); +/// ``` +#[derive(Debug)] +pub struct Directive { + // Name must return a String. + name: String, + // Description may return a String or null. + description: Option, + // Args returns a Vector of __InputValue representing the arguments this + // directive accepts. + args: Vec, + // Locations returns a List of __DirectiveLocation representing the valid + // locations this directive may be placed. + locations: Vec, +} + +impl Directive { + /// Create a new instance of Directive definition. + pub fn new(name: String) -> Self { + Self { + name, + description: None, + args: Vec::new(), + locations: Vec::new(), + } + } + + /// Set the Directive's description. + pub fn description(&mut self, description: Option) { + self.description = description; + } + + /// Set the Directive's location. + pub fn location(&mut self, location: String) { + self.locations.push(location); + } + + /// Set the Directive's args. + pub fn arg(&mut self, arg: InputValue) { + self.args.push(arg); + } +} + +impl Display for Directive { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + + write!(f, "directive @{}", self.name)?; + + if !self.args.is_empty() { + for (i, arg) in self.args.iter().enumerate() { + match i { + 0 => write!(f, "({}", arg)?, + _ => write!(f, ", {}", arg)?, + } + } + write!(f, ")")?; + } + + for (i, location) in self.locations.iter().enumerate() { + match i { + 0 => write!(f, " on {}", location)?, + _ => write!(f, " | {}", location)?, + } + } + + // append a new line at the end + writeln!(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + // use indoc::indoc; + use crate::Type_; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_directives_for_a_single_location() { + let mut directive = Directive::new("infer".to_string()); + directive.description(Some("Infer field types from field values.".to_string())); + directive.location("OBJECT".to_string()); + + assert_eq!( + directive.to_string(), + r#""""Infer field types from field values.""" +directive @infer on OBJECT +"# + ); + } + + #[test] + fn it_encodes_directives_for_multiple_location() { + let mut directive = Directive::new("infer".to_string()); + directive.description(Some("Infer field types\nfrom field values.".to_string())); + directive.location("OBJECT".to_string()); + directive.location("FIELD_DEFINITION".to_string()); + directive.location("INPUT_FIELD_DEFINITION".to_string()); + + assert_eq!( + directive.to_string(), + r#"""" +Infer field types +from field values. +""" +directive @infer on OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION +"# + ); + } + + #[test] + fn it_encodes_directives_with_arguments() { + let mut directive = Directive::new("infer".to_string()); + directive.description(Some("Infer field types from field values.".to_string())); + directive.location("OBJECT".to_string()); + + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let arg = InputValue::new("cat".to_string(), ty_2); + directive.arg(arg); + + assert_eq!( + directive.to_string(), + r#""""Infer field types from field values.""" +directive @infer(cat: [SpaceProgram]) on OBJECT +"# + ); + } +} diff --git a/crates/sdl-encoder/src/enum_def.rs b/crates/sdl-encoder/src/enum_def.rs new file mode 100644 index 000000000..59bdff003 --- /dev/null +++ b/crates/sdl-encoder/src/enum_def.rs @@ -0,0 +1,143 @@ +use crate::EnumValue; +use std::fmt::{self, Display}; + +/// Enums are special scalars that can only have a defined set of values. +/// +/// *EnumTypeDefinition*: +/// Descriptionopt **enum** Name Directives\[Const\] opt EnumValuesDefinition opt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-Enums). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{EnumValue, EnumDef}; +/// +/// let mut enum_ty_1 = EnumValue::new("CAT_TREE".to_string()); +/// enum_ty_1.description(Some("Top bunk of a cat tree.".to_string())); +/// let enum_ty_2 = EnumValue::new("BED".to_string()); +/// let mut enum_ty_3 = EnumValue::new("CARDBOARD_BOX".to_string()); +/// enum_ty_3.deprecated(Some("Box was recycled.".to_string())); +/// +/// let mut enum_ = EnumDef::new("NapSpots".to_string()); +/// enum_.description(Some("Favourite cat nap spots.".to_string())); +/// enum_.value(enum_ty_1); +/// enum_.value(enum_ty_2); +/// enum_.value(enum_ty_3); +/// +/// assert_eq!( +/// enum_.to_string(), +/// r#""""Favourite cat nap spots.""" +/// enum NapSpots { +/// """Top bunk of a cat tree.""" +/// CAT_TREE +/// BED +/// CARDBOARD_BOX @deprecated(reason: "Box was recycled.") +/// } +/// "# +/// ); +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct EnumDef { + // Name must return a String. + name: String, + // Description may return a String or null. + description: Option, + // A vector of EnumValue. There must be at least one and they must have + // unique names. + values: Vec, +} + +impl EnumDef { + /// Create a new instance of Enum Definition. + pub fn new(name: String) -> Self { + Self { + name, + description: None, + values: Vec::new(), + } + } + + /// Set the Enum Definition's description. + pub fn description(&mut self, description: Option) { + self.description = description; + } + + /// Set the Enum Definitions's values. + pub fn value(&mut self, value: EnumValue) { + self.values.push(value) + } +} + +impl Display for EnumDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + + write!(f, "enum {} {{", self.name)?; + for value in &self.values { + write!(f, "\n{}", value)?; + } + writeln!(f, "\n}}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_a_simple_enum() { + let enum_ty_1 = EnumValue::new("CAT_TREE".to_string()); + let enum_ty_2 = EnumValue::new("BED".to_string()); + let enum_ty_3 = EnumValue::new("CARDBOARD_BOX".to_string()); + + let mut enum_ = EnumDef::new("NapSpots".to_string()); + enum_.value(enum_ty_1); + enum_.value(enum_ty_2); + enum_.value(enum_ty_3); + + assert_eq!( + enum_.to_string(), + r#"enum NapSpots { + CAT_TREE + BED + CARDBOARD_BOX +} +"# + ); + } + #[test] + fn it_encodes_enum_with_descriptions() { + let mut enum_ty_1 = EnumValue::new("CAT_TREE".to_string()); + enum_ty_1.description(Some("Top bunk of a cat tree.".to_string())); + let enum_ty_2 = EnumValue::new("BED".to_string()); + let mut enum_ty_3 = EnumValue::new("CARDBOARD_BOX".to_string()); + enum_ty_3.deprecated(Some("Box was recycled.".to_string())); + + let mut enum_ = EnumDef::new("NapSpots".to_string()); + enum_.description(Some("Favourite cat nap spots.".to_string())); + enum_.value(enum_ty_1); + enum_.value(enum_ty_2); + enum_.value(enum_ty_3); + + assert_eq!( + enum_.to_string(), + r#""""Favourite cat nap spots.""" +enum NapSpots { + """Top bunk of a cat tree.""" + CAT_TREE + BED + CARDBOARD_BOX @deprecated(reason: "Box was recycled.") +} +"# + ); + } +} diff --git a/crates/sdl-encoder/src/enum_value.rs b/crates/sdl-encoder/src/enum_value.rs new file mode 100644 index 000000000..2c542a6a2 --- /dev/null +++ b/crates/sdl-encoder/src/enum_value.rs @@ -0,0 +1,122 @@ +use std::fmt::{self, Display}; + +/// The __EnumValue type represents one of possible values of an enum. +/// +/// *EnumValueDefinition*: +/// Descriptionopt EnumValue Directives\[Const\] opt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-The-__EnumValue-Type). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{EnumValue}; +/// +/// let mut enum_ty = EnumValue::new("CARDBOARD_BOX".to_string()); +/// enum_ty.description(Some("Box nap spot.".to_string())); +/// enum_ty.deprecated(Some("Box was recycled.".to_string())); +/// +/// assert_eq!( +/// enum_ty.to_string(), +/// r#" """Box nap spot.""" +/// CARDBOARD_BOX @deprecated(reason: "Box was recycled.")"# +/// ); +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct EnumValue { + // Name must return a String. + name: String, + // Description may return a String or null. + description: Option, + // Deprecated returns true if this enum value should no longer be used, otherwise false. + is_deprecated: bool, + // Deprecation reason optionally provides a reason why this enum value is deprecated. + deprecation_reason: Option, +} + +impl EnumValue { + /// Create a new instance of EnumValue. + pub fn new(name: String) -> Self { + Self { + name, + is_deprecated: false, + description: None, + deprecation_reason: None, + } + } + + /// Set the Enum Value's description. + pub fn description(&mut self, description: Option) { + self.description = description; + } + + /// Set the Enum Value's deprecation properties. + pub fn deprecated(&mut self, reason: Option) { + self.is_deprecated = true; + self.deprecation_reason = reason; + } +} + +impl Display for EnumValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, " \"\"\"\n {}\n \"\"\"", description)?, + false => writeln!(f, " \"\"\"{}\"\"\"", description)?, + } + } + + write!(f, " {}", self.name)?; + + if self.is_deprecated { + write!(f, " @deprecated")?; + // Just in case deprecated directive is ever used without a reason, + // let's properly unwrap this Option. + if let Some(reason) = &self.deprecation_reason { + write!(f, "(reason: \"{}\")", reason)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_an_enum_value() { + let enum_ty = EnumValue::new("CAT_TREE".to_string()); + assert_eq!(enum_ty.to_string(), " CAT_TREE"); + } + + #[test] + fn it_encodes_an_enum_value_with_desciption() { + let mut enum_ty = EnumValue::new("CAT_TREE".to_string()); + enum_ty.description(Some("Top bunk of a cat tree.".to_string())); + assert_eq!( + enum_ty.to_string(), + r#" """Top bunk of a cat tree.""" + CAT_TREE"# + ); + } + #[test] + fn it_encodes_an_enum_value_with_deprecated() { + let mut enum_ty = EnumValue::new("CARDBOARD_BOX".to_string()); + enum_ty.description(Some("Box nap\nspot.".to_string())); + enum_ty.deprecated(Some("Box was recycled.".to_string())); + + assert_eq!( + enum_ty.to_string(), + r#" """ + Box nap +spot. + """ + CARDBOARD_BOX @deprecated(reason: "Box was recycled.")"# + ); + } +} diff --git a/crates/sdl-encoder/src/field.rs b/crates/sdl-encoder/src/field.rs new file mode 100644 index 000000000..a4cff95e9 --- /dev/null +++ b/crates/sdl-encoder/src/field.rs @@ -0,0 +1,205 @@ +use crate::{InputValue, Type_}; +use std::fmt::{self, Display}; +/// The __Field type represents each field in an Object or Interface type. +/// +/// *FieldDefinition*: +/// Descriptionopt Name ArgumentsDefinitionopt **:** TypeDirectives\[Const\] opt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-The-__Field-Type). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Type_, Field, InputValue}; +/// +/// let ty_1 = Type_::NamedType { +/// name: "CatBreed".to_string(), +/// }; +/// +/// let mut field = Field::new("cat".to_string(), ty_1); +/// +/// let value_1 = Type_::NamedType { +/// name: "CatBreed".to_string(), +/// }; +/// +/// let arg = InputValue::new("breed".to_string(), value_1); +/// +/// field.arg(arg); +/// +/// assert_eq!( +/// field.to_string(), +/// r#" cat(breed: CatBreed): CatBreed"# +/// ); +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct Field { + // Name must return a String. + name: String, + // Description may return a String. + description: Option, + // Args returns a List of __InputValue representing the arguments this field accepts. + args: Vec, + // Type must return a __Type that represents the type of value returned by this field. + type_: Type_, + // Deprecated returns true if this field should no longer be used, otherwise false. + is_deprecated: bool, + // Deprecation reason optionally provides a reason why this field is deprecated. + deprecation_reason: Option, +} + +impl Field { + /// Create a new instance of Field. + pub fn new(name: String, type_: Type_) -> Self { + Self { + description: None, + name, + type_, + args: Vec::new(), + is_deprecated: false, + deprecation_reason: None, + } + } + + /// Set the Field's description. + pub fn description(&mut self, description: Option) { + self.description = description; + } + + /// Set the Field's deprecation properties. + pub fn deprecated(&mut self, reason: Option) { + self.is_deprecated = true; + self.deprecation_reason = reason; + } + + /// Set the Field's arguments. + pub fn arg(&mut self, arg: InputValue) { + self.args.push(arg); + } +} + +impl Display for Field { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // Let's indent description on a field level for now, as all fields + // are always on the same level and are indented by 2 spaces. + // + // We are also determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, " \"\"\"\n {}\n \"\"\"", description)?, + false => writeln!(f, " \"\"\"{}\"\"\"", description)?, + } + } + + write!(f, " {}", self.name)?; + + if !self.args.is_empty() { + for (i, arg) in self.args.iter().enumerate() { + match i { + 0 => write!(f, "({}", arg)?, + _ => write!(f, ", {}", arg)?, + } + } + write!(f, ")")?; + } + + write!(f, ": {}", self.type_)?; + + if self.is_deprecated { + write!(f, " @deprecated")?; + // Just in case deprecated field is ever used without a reason, + // let's properly unwrap this Option. + if let Some(reason) = &self.deprecation_reason { + write!(f, "(reason: \"{}\")", reason)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_simple_fields() { + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let ty_3 = Type_::NonNull { ty: Box::new(ty_2) }; + let field = Field::new("spaceCat".to_string(), ty_3); + + assert_eq!(field.to_string(), r#" spaceCat: [SpaceProgram]!"#); + } + + #[test] + fn it_encodes_fields_with_deprecation() { + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let mut field = Field::new("cat".to_string(), ty_2); + field.description(Some("Very good cats".to_string())); + field.deprecated(Some("Cats are no longer sent to space.".to_string())); + + assert_eq!( + field.to_string(), + r#" """Very good cats""" + cat: [SpaceProgram] @deprecated(reason: "Cats are no longer sent to space.")"# + ); + } + + #[test] + fn it_encodes_fields_with_description() { + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::NonNull { ty: Box::new(ty_1) }; + let ty_3 = Type_::List { ty: Box::new(ty_2) }; + let ty_4 = Type_::NonNull { ty: Box::new(ty_3) }; + let mut field = Field::new("spaceCat".to_string(), ty_4); + field.description(Some("Very good space cats".to_string())); + + assert_eq!( + field.to_string(), + r#" """Very good space cats""" + spaceCat: [SpaceProgram!]!"# + ); + } + + #[test] + fn it_encodes_fields_with_valueuments() { + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::NonNull { ty: Box::new(ty_1) }; + let ty_3 = Type_::List { ty: Box::new(ty_2) }; + let ty_4 = Type_::NonNull { ty: Box::new(ty_3) }; + let mut field = Field::new("spaceCat".to_string(), ty_4); + field.description(Some("Very good space cats".to_string())); + + let value_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let value_2 = Type_::List { + ty: Box::new(value_1), + }; + let mut arg = InputValue::new("cat".to_string(), value_2); + arg.deprecated(Some("Cats are no longer sent to space.".to_string())); + field.arg(arg); + + assert_eq!( + field.to_string(), + r#" """Very good space cats""" + spaceCat(cat: [SpaceProgram] @deprecated(reason: "Cats are no longer sent to space.")): [SpaceProgram!]!"# + ); + } +} diff --git a/crates/sdl-encoder/src/field_value.rs b/crates/sdl-encoder/src/field_value.rs new file mode 100644 index 000000000..4c067b978 --- /dev/null +++ b/crates/sdl-encoder/src/field_value.rs @@ -0,0 +1,120 @@ +use std::fmt::{self, Display}; + +/// Convenience Type_ implementation used when creating a Field. +/// Can be a `NamedType`, a `NonNull` or a `List`. +/// +/// This enum is resposible for encoding creating values such as `String!`, `[[[[String]!]!]!]!`, etc. +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Type_}; +/// +/// let field_ty = Type_::NamedType { +/// name: "String".to_string(), +/// }; +/// +/// let list = Type_::List { +/// ty: Box::new(field_ty), +/// }; +/// +/// let non_null = Type_::NonNull { ty: Box::new(list) }; +/// +/// assert_eq!(non_null.to_string(), "[String]!"); +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub enum Type_ { + /// The Non-Null field type. + NonNull { + /// Null inner type. + ty: Box, + }, + /// The List field type. + List { + /// List inner type. + ty: Box, + }, + /// The Named field type. + NamedType { + /// NamedType type. + name: String, + }, +} + +impl Display for Type_ { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type_::List { ty } => { + write!(f, "[{}]", ty) + } + Type_::NonNull { ty } => { + write!(f, "{}!", ty) + } + Type_::NamedType { name } => write!(f, "{}", name), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn encodes_simple_field_value() { + let field_ty = Type_::NamedType { + name: "String".to_string(), + }; + + assert_eq!(field_ty.to_string(), "String"); + } + + #[test] + fn encodes_list_field_value() { + let field_ty = Type_::NamedType { + name: "String".to_string(), + }; + + let list = Type_::List { + ty: Box::new(field_ty), + }; + + assert_eq!(list.to_string(), "[String]"); + } + + #[test] + fn encodes_non_null_list_field_value() { + let field_ty = Type_::NamedType { + name: "String".to_string(), + }; + + let list = Type_::List { + ty: Box::new(field_ty), + }; + + let non_null = Type_::NonNull { ty: Box::new(list) }; + + assert_eq!(non_null.to_string(), "[String]!"); + } + #[test] + fn encodes_non_null_list_non_null_list_field_value() { + let field_ty = Type_::NamedType { + name: "String".to_string(), + }; + + let list = Type_::List { + ty: Box::new(field_ty), + }; + + let non_null = Type_::NonNull { ty: Box::new(list) }; + + let list_2 = Type_::List { + ty: Box::new(non_null), + }; + + let non_null_2 = Type_::NonNull { + ty: Box::new(list_2), + }; + + assert_eq!(non_null_2.to_string(), "[[String]!]!"); + } +} diff --git a/crates/sdl-encoder/src/input_field.rs b/crates/sdl-encoder/src/input_field.rs new file mode 100644 index 000000000..c034b3a42 --- /dev/null +++ b/crates/sdl-encoder/src/input_field.rs @@ -0,0 +1,96 @@ +use crate::Type_; +use std::fmt::{self, Display}; + +#[derive(Debug, PartialEq, Clone)] +/// Input Field in a given Input Object. +/// A GraphQL Input Object defines a set of input fields; the input fields are +/// either scalars, enums, or other input objects. Input fields are similar to +/// Fields, but can have a default value. +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Type_, InputField}; +/// +/// let ty_1 = Type_::NamedType { +/// name: "CatBreed".to_string(), +/// }; +/// +/// let mut field = InputField::new("cat".to_string(), ty_1); +/// field.default(Some("\"Norwegian Forest\"".to_string())); +/// +/// assert_eq!(field.to_string(), r#" cat: CatBreed = "Norwegian Forest""#); +/// ``` +pub struct InputField { + // Name must return a String. + name: String, + // Description may return a String. + description: Option, + // Type must return a __Type that represents the type of value returned by this field. + type_: Type_, + // Default value for this input field. + default_value: Option, +} + +impl InputField { + /// Create a new instance of InputField. + pub fn new(name: String, type_: Type_) -> Self { + Self { + description: None, + name, + type_, + default_value: None, + } + } + + /// Set the InputField's description. + pub fn description(&mut self, description: Option) { + self.description = description; + } + + /// Set the InputField's default value. + pub fn default(&mut self, default: Option) { + self.default_value = default; + } +} + +impl Display for InputField { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // Let's indent description on a field level for now, as all fields + // are always on the same level and are indented by 2 spaces. + // + // We are also determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, " \"\"\"\n {}\n \"\"\"", description)?, + false => writeln!(f, " \"\"\"{}\"\"\"", description)?, + } + } + + write!(f, " {}: {}", self.name, self.type_)?; + if let Some(default) = &self.default_value { + write!(f, " = {}", default)?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_fields_with_defaults() { + let ty_1 = Type_::NamedType { + name: "CatBreed".to_string(), + }; + + let mut field = InputField::new("cat".to_string(), ty_1); + field.default(Some("\"Norwegian Forest\"".to_string())); + + assert_eq!(field.to_string(), r#" cat: CatBreed = "Norwegian Forest""#); + } +} diff --git a/crates/sdl-encoder/src/input_object_def.rs b/crates/sdl-encoder/src/input_object_def.rs new file mode 100644 index 000000000..5edb2de06 --- /dev/null +++ b/crates/sdl-encoder/src/input_object_def.rs @@ -0,0 +1,172 @@ +use crate::InputField; +use std::fmt::{self, Display}; + +/// Input objects are composite types used as inputs into queries defined as a list of named input values.. +/// +/// InputObjectTypeDefinition +/// Descriptionopt **input** Name Directives\[Const\] opt FieldsDefinitionopt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-Input-Objects). +/// +/// **Note**: At the moment InputObjectTypeDefinition differs slightly from the +/// spec. Instead of accepting InputValues as `field` parameter, we accept +/// InputField. +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Type_, InputField, InputObjectDef}; +/// use indoc::indoc; +/// +/// let ty_1 = Type_::NamedType { +/// name: "DanglerPoleToys".to_string(), +/// }; +/// +/// let ty_2 = Type_::List { ty: Box::new(ty_1) }; +/// let mut field = InputField::new("toys".to_string(), ty_2); +/// field.default(Some("\"Cat Dangler Pole Bird\"".to_string())); +/// let ty_3 = Type_::NamedType { +/// name: "FavouriteSpots".to_string(), +/// }; +/// let mut field_2 = InputField::new("playSpot".to_string(), ty_3); +/// field_2.description(Some("Best playime spots, e.g. tree, bed.".to_string())); +/// +/// let mut input_def = InputObjectDef::new("PlayTime".to_string()); +/// input_def.field(field); +/// input_def.field(field_2); +/// input_def.description(Some("Cat playtime input".to_string())); +/// +/// assert_eq!( +/// input_def.to_string(), +/// indoc! { r#" +/// """Cat playtime input""" +/// input PlayTime { +/// toys: [DanglerPoleToys] = "Cat Dangler Pole Bird" +/// """Best playime spots, e.g. tree, bed.""" +/// playSpot: FavouriteSpots +/// } +/// "#} +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct InputObjectDef { + // Name must return a String. + name: String, + // Description may return a String or null. + description: Option, + // A vector of fields + fields: Vec, +} + +impl InputObjectDef { + /// Create a new instance of ObjectDef with a name. + pub fn new(name: String) -> Self { + Self { + name, + description: None, + fields: Vec::new(), + } + } + + /// Set the InputObjectDef's description field. + pub fn description(&mut self, description: Option) { + self.description = description + } + + /// Push a Field to InputObjectDef's fields vector. + pub fn field(&mut self, field: InputField) { + self.fields.push(field) + } +} + +impl Display for InputObjectDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + + write!(f, "input {} {{", &self.name)?; + + for field in &self.fields { + write!(f, "\n{}", field)?; + } + writeln!(f, "\n}}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{InputField, Type_}; + use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_input_object() { + let ty_1 = Type_::NamedType { + name: "DanglerPoleToys".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let mut field = InputField::new("toys".to_string(), ty_2); + field.default(Some("\"Cat Dangler Pole Bird\"".to_string())); + let ty_3 = Type_::NamedType { + name: "FavouriteSpots".to_string(), + }; + let mut field_2 = InputField::new("playSpot".to_string(), ty_3); + field_2.description(Some("Best playime spots, e.g. tree, bed.".to_string())); + + let mut input_def = InputObjectDef::new("PlayTime".to_string()); + input_def.field(field); + input_def.field(field_2); + + assert_eq!( + input_def.to_string(), + indoc! { r#" + input PlayTime { + toys: [DanglerPoleToys] = "Cat Dangler Pole Bird" + """Best playime spots, e.g. tree, bed.""" + playSpot: FavouriteSpots + } + "#} + ); + } + + #[test] + fn it_encodes_input_object_with_description() { + let ty_1 = Type_::NamedType { + name: "DanglerPoleToys".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let mut field = InputField::new("toys".to_string(), ty_2); + field.default(Some("\"Cat Dangler Pole Bird\"".to_string())); + let ty_3 = Type_::NamedType { + name: "FavouriteSpots".to_string(), + }; + let mut field_2 = InputField::new("playSpot".to_string(), ty_3); + field_2.description(Some("Best playime spots, e.g. tree, bed.".to_string())); + + let mut input_def = InputObjectDef::new("PlayTime".to_string()); + input_def.field(field); + input_def.field(field_2); + input_def.description(Some("Cat playtime input".to_string())); + + assert_eq!( + input_def.to_string(), + indoc! { r#" + """Cat playtime input""" + input PlayTime { + toys: [DanglerPoleToys] = "Cat Dangler Pole Bird" + """Best playime spots, e.g. tree, bed.""" + playSpot: FavouriteSpots + } + "#} + ); + } +} diff --git a/crates/sdl-encoder/src/input_value.rs b/crates/sdl-encoder/src/input_value.rs new file mode 100644 index 000000000..9bf20efca --- /dev/null +++ b/crates/sdl-encoder/src/input_value.rs @@ -0,0 +1,183 @@ +use crate::Type_; +use std::fmt::{self, Display}; + +// NOTE(@lrlna): __InputValue is also meant to be used for InputFields on an +// InputObject. We currently do not differentiate between InputFields and +// Fields, so this is not applied directly to InputObjects. Once we are able to +// walk an AST to encode a schema, we will want to make sure this struct is used +// directly: InputObject --> InputField --> InputValue + +/// The __InputValue type represents field and directive arguments. +/// +/// *InputValueDefinition*: +/// Descriptionopt Name **:** Type DefaultValueopt Directives\[Const\] opt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-The-__InputValue-Type). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Type_, InputValue}; +/// +/// let ty_1 = Type_::NamedType { +/// name: "SpaceProgram".to_string(), +/// }; +/// +/// let ty_2 = Type_::List { ty: Box::new(ty_1) }; +/// let mut value = InputValue::new("cat".to_string(), ty_2); +/// value.description(Some("Very good cats".to_string())); +/// value.deprecated(Some("Cats are no longer sent to space.".to_string())); +/// +/// assert_eq!( +/// value.to_string(), +/// r#""""Very good cats""" cat: [SpaceProgram] @deprecated(reason: "Cats are no longer sent to space.")"# +/// ); +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct InputValue { + // Name must return a String. + name: String, + // Description may return a String. + description: Option, + // Type must return a __Type that represents the type this input value expects. + type_: Type_, + // Default may return a String encoding (using the GraphQL language) of + // the default value used by this input value in the condition a value is + // not provided at runtime. If this input value has no default value, + // returns null. + default: Option, + // Deprecated returns true if this field should no longer be used, otherwise false. + is_deprecated: bool, + // Deprecation reason optionally provides a reason why this field is deprecated. + deprecation_reason: Option, +} + +impl InputValue { + /// Create a new instance of InputValue. + pub fn new(name: String, type_: Type_) -> Self { + Self { + description: None, + name, + type_, + is_deprecated: false, + deprecation_reason: None, + default: None, + } + } + + /// Set the InputValue's description. + pub fn description(&mut self, description: Option) { + self.description = description; + } + + /// Set the InputValue's default value. + pub fn default(&mut self, default: Option) { + self.default = default; + } + + /// Set the InputValue's deprecation properties. + pub fn deprecated(&mut self, reason: Option) { + self.is_deprecated = true; + self.deprecation_reason = reason; + } +} + +impl Display for InputValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => write!(f, "\"\"\"\n{}\n\"\"\" ", description)?, + false => write!(f, "\"\"\"{}\"\"\" ", description)?, + } + } + + write!(f, "{}: {}", self.name, self.type_)?; + + if let Some(default) = &self.default { + write!(f, " = {}", default)?; + } + + if self.is_deprecated { + write!(f, " @deprecated")?; + // Just in case deprecated field is ever used without a reason, + // let's properly unwrap this Option. + if let Some(reason) = &self.deprecation_reason { + write!(f, "(reason: \"{}\")", reason)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_simple_values() { + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let ty_3 = Type_::NonNull { ty: Box::new(ty_2) }; + let value = InputValue::new("spaceCat".to_string(), ty_3); + + assert_eq!(value.to_string(), r#"spaceCat: [SpaceProgram]!"#); + } + + #[test] + fn it_encodes_input_values_with_default() { + let ty_1 = Type_::NamedType { + name: "Breed".to_string(), + }; + + let ty_2 = Type_::NonNull { ty: Box::new(ty_1) }; + let mut value = InputValue::new("spaceCat".to_string(), ty_2); + value.default(Some("\"Norwegian Forest\"".to_string())); + + assert_eq!( + value.to_string(), + r#"spaceCat: Breed! = "Norwegian Forest""# + ); + } + + #[test] + fn it_encodes_value_with_deprecation() { + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let mut value = InputValue::new("cat".to_string(), ty_2); + value.description(Some("Very good cats".to_string())); + value.deprecated(Some("Cats are no longer sent to space.".to_string())); + + assert_eq!( + value.to_string(), + r#""""Very good cats""" cat: [SpaceProgram] @deprecated(reason: "Cats are no longer sent to space.")"# + ); + } + + #[test] + fn it_encodes_valueuments_with_description() { + let ty_1 = Type_::NamedType { + name: "SpaceProgram".to_string(), + }; + + let ty_2 = Type_::NonNull { ty: Box::new(ty_1) }; + let ty_3 = Type_::List { ty: Box::new(ty_2) }; + let ty_4 = Type_::NonNull { ty: Box::new(ty_3) }; + let mut value = InputValue::new("spaceCat".to_string(), ty_4); + value.description(Some("Very good space cats".to_string())); + + assert_eq!( + value.to_string(), + r#""""Very good space cats""" spaceCat: [SpaceProgram!]!"# + ); + } +} diff --git a/crates/sdl-encoder/src/interface_def.rs b/crates/sdl-encoder/src/interface_def.rs new file mode 100644 index 000000000..4ddc774ed --- /dev/null +++ b/crates/sdl-encoder/src/interface_def.rs @@ -0,0 +1,194 @@ +use crate::Field; +use std::fmt::{self, Display}; + +/// InterfaceDefs are an abstract type where there are common fields declared. +/// +/// Any type that implements an interface must define all the fields with names +/// and types exactly matching. The implementations of this interface are +/// explicitly listed out in possibleTypes. +/// +/// *InterfaceDefTypeDefinition*: +/// Descriptionopt **interface** Name ImplementsInterfaceDefsopt Directives\[Const\] opt FieldsDefinitionopt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-InterfaceDef). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Type_, Field, InterfaceDef}; +/// use indoc::indoc; +/// +/// let ty_1 = Type_::NamedType { +/// name: "String".to_string(), +/// }; +/// +/// let ty_2 = Type_::NamedType { +/// name: "String".to_string(), +/// }; +/// +/// let ty_3 = Type_::NonNull { ty: Box::new(ty_2) }; +/// let ty_4 = Type_::List { ty: Box::new(ty_3) }; +/// let ty_5 = Type_::NonNull { ty: Box::new(ty_4) }; +/// +/// let ty_6 = Type_::NamedType { +/// name: "Boolean".to_string(), +/// }; +/// +/// let mut field_1 = Field::new("main".to_string(), ty_1); +/// field_1.description(Some("Cat's main dish of a meal.".to_string())); +/// +/// let mut field_2 = Field::new("snack".to_string(), ty_5); +/// field_2.description(Some("Cat's post meal snack.".to_string())); +/// +/// let mut field_3 = Field::new("pats".to_string(), ty_6); +/// field_3.description(Some("Does cat get a pat after meal?".to_string())); +/// +/// // a schema definition +/// let mut interface = InterfaceDef::new("Meal".to_string()); +/// interface.description(Some( +/// "Meal interface for various meals during the day.".to_string(), +/// )); +/// interface.field(field_1); +/// interface.field(field_2); +/// interface.field(field_3); +/// +/// assert_eq!( +/// interface.to_string(), +/// indoc! { r#" +/// """Meal interface for various meals during the day.""" +/// interface Meal { +/// """Cat's main dish of a meal.""" +/// main: String +/// """Cat's post meal snack.""" +/// snack: [String!]! +/// """Does cat get a pat after meal?""" +/// pats: Boolean +/// } +/// "# } +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct InterfaceDef { + // Name must return a String. + name: String, + // Description may return a String or null. + description: Option, + // The vector of interfaces that this interface implements. + interfaces: Vec, + // The vector of fields required by this interface. + fields: Vec, +} + +impl InterfaceDef { + /// Create a new instance of InterfaceDef. + pub fn new(name: String) -> Self { + Self { + name, + description: None, + fields: Vec::new(), + interfaces: Vec::new(), + } + } + + /// Set the schema def's description. + pub fn description(&mut self, description: Option) { + self.description = description + } + + /// Set the interfaces ObjectDef implements. + pub fn interface(&mut self, interface: String) { + self.interfaces.push(interface) + } + + /// Push a Field to schema def's fields vector. + pub fn field(&mut self, field: Field) { + self.fields.push(field) + } +} + +impl Display for InterfaceDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + + write!(f, "interface {}", &self.name)?; + for (i, interface) in self.interfaces.iter().enumerate() { + match i { + 0 => write!(f, " implements {}", interface)?, + _ => write!(f, "& {}", interface)?, + } + } + write!(f, " {{")?; + + for field in &self.fields { + write!(f, "\n{}", field)?; + } + writeln!(f, "\n}}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Type_; + use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_interfaces() { + let ty_1 = Type_::NamedType { + name: "String".to_string(), + }; + + let ty_2 = Type_::NamedType { + name: "String".to_string(), + }; + + let ty_3 = Type_::NonNull { ty: Box::new(ty_2) }; + let ty_4 = Type_::List { ty: Box::new(ty_3) }; + let ty_5 = Type_::NonNull { ty: Box::new(ty_4) }; + + let ty_6 = Type_::NamedType { + name: "Boolean".to_string(), + }; + + let mut field_1 = Field::new("main".to_string(), ty_1); + field_1.description(Some("Cat's main dish of a meal.".to_string())); + + let mut field_2 = Field::new("snack".to_string(), ty_5); + field_2.description(Some("Cat's post meal snack.".to_string())); + + let mut field_3 = Field::new("pats".to_string(), ty_6); + field_3.description(Some("Does cat get a pat after meal?".to_string())); + + // a schema definition + let mut interface = InterfaceDef::new("Meal".to_string()); + interface.description(Some( + "Meal interface for various meals during the day.".to_string(), + )); + interface.field(field_1); + interface.field(field_2); + interface.field(field_3); + + assert_eq!( + interface.to_string(), + indoc! { r#" + """Meal interface for various meals during the day.""" + interface Meal { + """Cat's main dish of a meal.""" + main: String + """Cat's post meal snack.""" + snack: [String!]! + """Does cat get a pat after meal?""" + pats: Boolean + } + "# } + ); + } +} diff --git a/crates/sdl-encoder/src/lib.rs b/crates/sdl-encoder/src/lib.rs new file mode 100644 index 000000000..8a885c47b --- /dev/null +++ b/crates/sdl-encoder/src/lib.rs @@ -0,0 +1,105 @@ +//! SDL Encoder provides methods to serialise a GraphQL Schema. +//! +//! For mor information on GraphQL Schema Types, please refer to [official +//! documentation](https://graphql.org/learn/schema/). +//! +//! ## Example +//! ```rust +//! use sdl_encoder::{Schema, Field, UnionDef, EnumValue, Directive, EnumDef, Type_}; +//! use indoc::indoc; +//! +//! let mut schema = Schema::new(); +//! +//! // Create a Directive Definition. +//! let mut directive = Directive::new("provideTreat".to_string()); +//! directive.description(Some("Ensures cats get treats.".to_string())); +//! directive.location("OBJECT".to_string()); +//! directive.location("FIELD_DEFINITION".to_string()); +//! directive.location("INPUT_FIELD_DEFINITION".to_string()); +//! schema.directive(directive); + +//! // Create an Enum Definition +//! let mut enum_ty_1 = EnumValue::new("CatTree".to_string()); +//! enum_ty_1.description(Some("Top bunk of a cat tree.".to_string())); +//! let enum_ty_2 = EnumValue::new("Bed".to_string()); +//! let mut enum_ty_3 = EnumValue::new("CardboardBox".to_string()); +//! enum_ty_3.deprecated(Some("Box was recycled.".to_string())); +//! +//! let mut enum_def = EnumDef::new("NapSpots".to_string()); +//! enum_def.description(Some("Favourite cat nap spots.".to_string())); +//! enum_def.value(enum_ty_1); +//! enum_def.value(enum_ty_2); +//! enum_def.value(enum_ty_3); +//! schema.enum_(enum_def); +//! // Union Definition +//! let mut union_def = UnionDef::new("Cat".to_string()); +//! union_def.description(Some( +//! "A union of all cats represented within a household.".to_string(), +//! )); +//! union_def.member("NORI".to_string()); +//! union_def.member("CHASHU".to_string()); +//! schema.union(union_def); +//! +//! assert_eq!( +//! schema.finish(), +//! indoc! { r#" +//! """Ensures cats get treats.""" +//! directive @provideTreat on OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION +//! """Favourite cat nap spots.""" +//! enum NapSpots { +//! """Top bunk of a cat tree.""" +//! CatTree +//! Bed +//! CardboardBox @deprecated(reason: "Box was recycled.") +//! } +//! """A union of all cats represented within a household.""" +//! union Cat = NORI | CHASHU +//! "# } +//! ); +//! ``` + +#![forbid(unsafe_code)] +#![deny(missing_debug_implementations, nonstandard_style)] +#![warn(missing_docs, future_incompatible, unreachable_pub, rust_2018_idioms)] + +mod schema; +pub use schema::Schema; + +mod field; +pub use field::Field; + +mod field_value; +pub use field_value::Type_; + +mod input_object_def; +pub use input_object_def::InputObjectDef; + +mod input_value; +pub use input_value::InputValue; + +mod input_field; +pub use input_field::InputField; + +mod enum_def; +pub use enum_def::EnumDef; + +mod enum_value; +pub use enum_value::EnumValue; + +mod object_def; +pub use object_def::ObjectDef; + +mod scalar_def; +pub use scalar_def::ScalarDef; + +mod union_def; +pub use union_def::UnionDef; + +mod directive_def; +pub use directive_def::Directive; + +mod interface_def; +pub use interface_def::InterfaceDef; + +mod schema_def; +pub use schema_def::SchemaDef; diff --git a/crates/sdl-encoder/src/object_def.rs b/crates/sdl-encoder/src/object_def.rs new file mode 100644 index 000000000..0b90b6f6a --- /dev/null +++ b/crates/sdl-encoder/src/object_def.rs @@ -0,0 +1,190 @@ +use crate::Field; +use std::fmt::{self, Display}; +/// Object types represent concrete instantiations of sets of fields. +/// +/// The introspection types (e.g. `__Type`, `__Field`, etc) are examples of +/// objects. +/// +/// *ObjectTypeDefinition*: +/// Descriptionopt **type** Name ImplementsInterfacesopt Directives\[Const\] opt FieldsDefinitionopt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-Object). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Type_, Field, ObjectDef}; +/// use indoc::indoc; +/// +/// let ty_1 = Type_::NamedType { +/// name: "DanglerPoleToys".to_string(), +/// }; +/// +/// let ty_2 = Type_::List { ty: Box::new(ty_1) }; +/// let mut field = Field::new("toys".to_string(), ty_2); +/// field.deprecated(Some("Cats are too spoiled".to_string())); +/// let ty_3 = Type_::NamedType { +/// name: "FoodType".to_string(), +/// }; +/// let mut field_2 = Field::new("food".to_string(), ty_3); +/// field_2.description(Some("Dry or wet food?".to_string())); +/// +/// let ty_4 = Type_::NamedType { +/// name: "Boolean".to_string(), +/// }; +/// let field_3 = Field::new("catGrass".to_string(), ty_4); +/// +/// let mut object_def = ObjectDef::new("PetStoreTrip".to_string()); +/// object_def.field(field); +/// object_def.field(field_2); +/// object_def.field(field_3); +/// object_def.interface("ShoppingTrip".to_string()); +/// +/// assert_eq!( +/// object_def.to_string(), +/// indoc! { r#" +/// type PetStoreTrip implements ShoppingTrip { +/// toys: [DanglerPoleToys] @deprecated(reason: "Cats are too spoiled") +/// """Dry or wet food?""" +/// food: FoodType +/// catGrass: Boolean +/// } +/// "#} +/// ); +/// ``` +#[derive(Debug)] +pub struct ObjectDef { + // Name must return a String. + name: String, + // Description may return a String or null. + description: Option, + // The vector of interfaces that an object implements. + interfaces: Vec, + // The vector of fields query‐able on this type. + fields: Vec, +} + +impl ObjectDef { + /// Create a new instance of ObjectDef with a name. + pub fn new(name: String) -> Self { + Self { + name, + description: None, + interfaces: Vec::new(), + fields: Vec::new(), + } + } + + /// Set the ObjectDef's description field. + pub fn description(&mut self, description: Option) { + self.description = description + } + + /// Set the interfaces ObjectDef implements. + pub fn interface(&mut self, interface: String) { + self.interfaces.push(interface) + } + + /// Push a Field to ObjectDef's fields vector. + pub fn field(&mut self, field: Field) { + self.fields.push(field) + } +} + +impl Display for ObjectDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + + write!(f, "type {}", &self.name)?; + for (i, interface) in self.interfaces.iter().enumerate() { + match i { + 0 => write!(f, " implements {}", interface)?, + _ => write!(f, "& {}", interface)?, + } + } + write!(f, " {{")?; + + for field in &self.fields { + write!(f, "\n{}", field)?; + } + writeln!(f, "\n}}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Field, Type_}; + use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_object_with_description() { + let ty_1 = Type_::NamedType { + name: "DanglerPoleToys".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let field = Field::new("toys".to_string(), ty_2); + + let mut object_def = ObjectDef::new("PetStoreTrip".to_string()); + object_def.field(field); + object_def.description(Some("What to get at Fressnapf?".to_string())); + + assert_eq!( + object_def.to_string(), + indoc! { r#" + """What to get at Fressnapf?""" + type PetStoreTrip { + toys: [DanglerPoleToys] + } + "#} + ); + } + + #[test] + fn it_encodes_object_with_interface() { + let ty_1 = Type_::NamedType { + name: "DanglerPoleToys".to_string(), + }; + + let ty_2 = Type_::List { ty: Box::new(ty_1) }; + let mut field = Field::new("toys".to_string(), ty_2); + field.deprecated(Some("Cats are too spoiled".to_string())); + let ty_3 = Type_::NamedType { + name: "FoodType".to_string(), + }; + let mut field_2 = Field::new("food".to_string(), ty_3); + field_2.description(Some("Dry or wet food?".to_string())); + + let ty_4 = Type_::NamedType { + name: "Boolean".to_string(), + }; + let field_3 = Field::new("catGrass".to_string(), ty_4); + + let mut object_def = ObjectDef::new("PetStoreTrip".to_string()); + object_def.field(field); + object_def.field(field_2); + object_def.field(field_3); + object_def.interface("ShoppingTrip".to_string()); + + assert_eq!( + object_def.to_string(), + indoc! { r#" + type PetStoreTrip implements ShoppingTrip { + toys: [DanglerPoleToys] @deprecated(reason: "Cats are too spoiled") + """Dry or wet food?""" + food: FoodType + catGrass: Boolean + } + "#} + ); + } +} diff --git a/crates/sdl-encoder/src/scalar_def.rs b/crates/sdl-encoder/src/scalar_def.rs new file mode 100644 index 000000000..d78b10700 --- /dev/null +++ b/crates/sdl-encoder/src/scalar_def.rs @@ -0,0 +1,94 @@ +use std::fmt::{self, Display}; + +/// Represents scalar types such as Int, String, and Boolean. +/// Scalars cannot have fields. +/// +/// *ScalarTypeDefinition*: +/// Descriptionopt **scalar** Name Directives\[Const\]opt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-Scalar). +/// ### Example +/// ```rust +/// use sdl_encoder::ScalarDef; +/// +/// let mut scalar = ScalarDef::new("NumberOfTreatsPerDay".to_string()); +/// scalar.description(Some( +/// "Int representing number of treats received.".to_string(), +/// )); +/// +/// assert_eq!( +/// scalar.to_string(), +/// r#""""Int representing number of treats received.""" +/// scalar NumberOfTreatsPerDay +/// "# +/// ); +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct ScalarDef { + // Name must return a String. + name: String, + // Description may return a String or null. + description: Option, +} + +impl ScalarDef { + /// Create a new instance of Scalar Definition. + pub fn new(name: String) -> Self { + Self { + name, + description: None, + } + } + + /// Set the ScalarDef's description. + pub fn description(&mut self, description: Option) { + self.description = description; + } +} + +impl Display for ScalarDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + + writeln!(f, "scalar {}", self.name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_scalar() { + let scalar = ScalarDef::new("NumberOfTreatsPerDay".to_string()); + assert_eq!( + scalar.to_string(), + r#"scalar NumberOfTreatsPerDay +"# + ); + } + + #[test] + fn it_encodes_scalar_with_description() { + let mut scalar = ScalarDef::new("NumberOfTreatsPerDay".to_string()); + scalar.description(Some( + "Int representing number of treats received.".to_string(), + )); + + assert_eq!( + scalar.to_string(), + r#""""Int representing number of treats received.""" +scalar NumberOfTreatsPerDay +"# + ); + } +} diff --git a/crates/sdl-encoder/src/schema.rs b/crates/sdl-encoder/src/schema.rs new file mode 100644 index 000000000..ff7af0aa0 --- /dev/null +++ b/crates/sdl-encoder/src/schema.rs @@ -0,0 +1,246 @@ +use crate::*; + +/// GraphQLSchema represented in Schema Definition Language. +/// +/// SDL is used as a human-readable format for a given schema to help define and +/// store schema as a string. +/// More information about SDL can be read in this [documentation](https://www.apollographql.com/docs/apollo-server/schema/schema/) +/// +/// The `Schema` struct provides method to encode various types to a schema. +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{Schema, Field, UnionDef, EnumValue, Directive, EnumDef, Type_}; +/// use indoc::indoc; +/// +/// let mut schema = Schema::new(); +/// +/// let mut union_def = UnionDef::new("Cat".to_string()); +/// union_def.description(Some( +/// "A union of all cats represented within a household.".to_string(), +/// )); +/// union_def.member("NORI".to_string()); +/// union_def.member("CHASHU".to_string()); +/// schema.union(union_def); +/// assert_eq!( +/// schema.finish(), +/// indoc! { r#" +/// """A union of all cats represented within a household.""" +/// union Cat = NORI | CHASHU +/// "# } +/// ); +/// ``` +#[derive(Debug)] +pub struct Schema { + buf: String, +} + +impl Schema { + /// Creates a new instance of Schema Encoder. + pub fn new() -> Self { + Self { buf: String::new() } + } + + /// Adds a new Directive Definition. + pub fn directive(&mut self, directive: Directive) { + self.buf.push_str(&directive.to_string()); + } + + /// Adds a new Type Definition. + pub fn object(&mut self, object: ObjectDef) { + self.buf.push_str(&object.to_string()); + } + + /// Adds a new Schema Definition. + /// + /// The schema type is only used when the root GraphQL type is different + /// from default GraphQL types. + pub fn schema(&mut self, schema: SchemaDef) { + self.buf.push_str(&schema.to_string()); + } + + /// Adds a new Input Object Definition. + pub fn input(&mut self, input: InputObjectDef) { + self.buf.push_str(&input.to_string()); + } + + /// Adds a new Enum Definition. + pub fn enum_(&mut self, enum_: EnumDef) { + self.buf.push_str(&enum_.to_string()); + } + + /// Adds a new Scalar Definition. + pub fn scalar(&mut self, scalar: ScalarDef) { + self.buf.push_str(&scalar.to_string()); + } + + /// Adds a new Union Definition. + pub fn union(&mut self, union_: UnionDef) { + self.buf.push_str(&union_.to_string()); + } + + /// Adds a new Interface Definition. + pub fn interface(&mut self, interface: InterfaceDef) { + self.buf.push_str(&interface.to_string()); + } + + /// Return the encoded SDL string after all types have been processed. + pub fn finish(self) -> String { + self.buf + } +} + +impl Default for Schema { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn smoke_test() { + let mut schema = Schema::new(); + + // create a directive + let mut directive = Directive::new("provideTreat".to_string()); + directive.description(Some("Ensures cats get treats.".to_string())); + directive.location("OBJECT".to_string()); + directive.location("FIELD_DEFINITION".to_string()); + directive.location("INPUT_FIELD_DEFINITION".to_string()); + schema.directive(directive); + + // a schema definition + let mut schema_def = SchemaDef::new(); + schema_def.query("TryingToFindCatQuery".to_string()); + schema.schema(schema_def); + + // create a field + let field_value = Type_::NamedType { + name: "String".to_string(), + }; + + let null_field = Type_::NonNull { + ty: Box::new(field_value), + }; + + let mut field = Field::new("cat".to_string(), null_field); + field.description(Some("Very good cats".to_string())); + + // Union Definition + let mut union_def = UnionDef::new("Pet".to_string()); + union_def.description(Some("A union of all animals in a household.".to_string())); + union_def.member("Cat".to_string()); + union_def.member("Dog".to_string()); + schema.union(union_def); + + // Object Definition. + let object_value = Type_::NamedType { + name: "DanglerPoleToys".to_string(), + }; + + let object_value_2 = Type_::List { + ty: Box::new(object_value), + }; + + let mut object_field = Field::new("toys".to_string(), object_value_2); + object_field.deprecated(Some("Cats are too spoiled".to_string())); + + let object_value_2 = Type_::NamedType { + name: "FoodType".to_string(), + }; + + let mut object_field_2 = Field::new("food".to_string(), object_value_2); + object_field_2.description(Some("Dry or wet food?".to_string())); + + let object_field_3 = Type_::NamedType { + name: "Boolean".to_string(), + }; + let object_field_3 = Field::new("catGrass".to_string(), object_field_3); + + let mut object_def = ObjectDef::new("PetStoreTrip".to_string()); + object_def.field(object_field); + object_def.field(object_field_2); + object_def.field(object_field_3); + object_def.interface("ShoppingTrip".to_string()); + schema.object(object_def); + + // Enum definition + let mut enum_ty_1 = EnumValue::new("CAT_TREE".to_string()); + enum_ty_1.description(Some("Top bunk of a cat tree.".to_string())); + let enum_ty_2 = EnumValue::new("BED".to_string()); + let mut enum_ty_3 = EnumValue::new("CARDBOARD_BOX".to_string()); + enum_ty_3.deprecated(Some("Box was recycled.".to_string())); + + let mut enum_def = EnumDef::new("NapSpots".to_string()); + enum_def.description(Some("Favourite cat nap spots.".to_string())); + enum_def.value(enum_ty_1); + enum_def.value(enum_ty_2); + enum_def.value(enum_ty_3); + schema.enum_(enum_def); + + let mut scalar = ScalarDef::new("NumberOfTreatsPerDay".to_string()); + scalar.description(Some( + "Int representing number of treats received.".to_string(), + )); + schema.scalar(scalar); + + // input definition + let input_value = Type_::NamedType { + name: "DanglerPoleToys".to_string(), + }; + + let input_value_2 = Type_::List { + ty: Box::new(input_value), + }; + let mut input_field = InputField::new("toys".to_string(), input_value_2); + input_field.default(Some("\"Cat Dangler Pole Bird\"".to_string())); + let input_value_3 = Type_::NamedType { + name: "FavouriteSpots".to_string(), + }; + let mut input_value_2 = InputField::new("playSpot".to_string(), input_value_3); + input_value_2.description(Some("Best playime spots, e.g. tree, bed.".to_string())); + + let mut input_def = InputObjectDef::new("PlayTime".to_string()); + input_def.field(input_field); + input_def.field(input_value_2); + schema.input(input_def); + + assert_eq!( + schema.finish(), + indoc! { r#" + """Ensures cats get treats.""" + directive @provideTreat on OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION + schema { + query: TryingToFindCatQuery + } + """A union of all animals in a household.""" + union Pet = Cat | Dog + type PetStoreTrip implements ShoppingTrip { + toys: [DanglerPoleToys] @deprecated(reason: "Cats are too spoiled") + """Dry or wet food?""" + food: FoodType + catGrass: Boolean + } + """Favourite cat nap spots.""" + enum NapSpots { + """Top bunk of a cat tree.""" + CAT_TREE + BED + CARDBOARD_BOX @deprecated(reason: "Box was recycled.") + } + """Int representing number of treats received.""" + scalar NumberOfTreatsPerDay + input PlayTime { + toys: [DanglerPoleToys] = "Cat Dangler Pole Bird" + """Best playime spots, e.g. tree, bed.""" + playSpot: FavouriteSpots + } + "# } + ); + } +} diff --git a/crates/sdl-encoder/src/schema_def.rs b/crates/sdl-encoder/src/schema_def.rs new file mode 100644 index 000000000..19905380e --- /dev/null +++ b/crates/sdl-encoder/src/schema_def.rs @@ -0,0 +1,133 @@ +use std::fmt::{self, Display}; + +/// A GraphQL service’s collective type system capabilities are referred to as that service’s “schema”. +/// +/// *SchemaDefinition*: +/// Descriptionopt **schema** Directives\[Const\] opt **{** RootOperationTypeDefinitionlist **}** +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-Schema). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{SchemaDef}; +/// use indoc::indoc; +/// +/// let mut schema_def = SchemaDef::new(); +/// schema_def.query("TryingToFindCatQuery".to_string()); +/// schema_def.mutation("MyMutation".to_string()); +/// schema_def.subscription("MySubscription".to_string()); +/// +/// assert_eq!( +/// schema_def.to_string(), +/// indoc! { r#" +/// schema { +/// query: TryingToFindCatQuery +/// mutation: MyMutation +/// subscription: MySubscription +/// } +/// "#} +/// ); +/// ``` + +#[derive(Debug, Clone)] +pub struct SchemaDef { + // Description may be a String. + description: Option, + // The vector of fields in a schema to represent root operation type + // definition. + query: Option, + mutation: Option, + subscription: Option, +} + +impl SchemaDef { + /// Create a new instance of SchemaDef. + pub fn new() -> Self { + Self { + description: None, + query: None, + mutation: None, + subscription: None, + } + } + + /// Set the SchemaDef's description. + pub fn description(&mut self, description: Option) { + self.description = description + } + + /// Set the schema def's query type. + pub fn query(&mut self, query: String) { + self.query = Some(query); + } + + /// Set the schema def's mutation type. + pub fn mutation(&mut self, mutation: String) { + self.mutation = Some(mutation); + } + + /// Set the schema def's subscription type. + pub fn subscription(&mut self, subscription: String) { + self.subscription = Some(subscription); + } +} + +impl Default for SchemaDef { + fn default() -> Self { + Self::new() + } +} + +impl Display for SchemaDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + writeln!(f, "schema {{")?; + if let Some(query) = &self.query { + writeln!(f, " query: {}", query)?; + } + + if let Some(mutation) = &self.mutation { + writeln!(f, " mutation: {}", mutation)?; + } + + if let Some(subscription) = &self.subscription { + writeln!(f, " subscription: {}", subscription)?; + } + + writeln!(f, "}}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_schema_with_mutation_and_subscription() { + let mut schema_def = SchemaDef::new(); + schema_def.query("TryingToFindCatQuery".to_string()); + schema_def.mutation("MyMutation".to_string()); + schema_def.subscription("MySubscription".to_string()); + + assert_eq!( + schema_def.to_string(), + indoc! { r#" + schema { + query: TryingToFindCatQuery + mutation: MyMutation + subscription: MySubscription + } + "#} + ); + } +} diff --git a/crates/sdl-encoder/src/union_def.rs b/crates/sdl-encoder/src/union_def.rs new file mode 100644 index 000000000..7a4e98247 --- /dev/null +++ b/crates/sdl-encoder/src/union_def.rs @@ -0,0 +1,100 @@ +use std::fmt::{self, Display}; + +/// UnionDefs are an abstract type where no common fields are declared. +/// +/// *UnionDefTypeDefinition*: +/// Descriptionopt **union** Name Directives\[Const\] opt UnionDefMemberTypesopt +/// +/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/draft/#sec-UnionDef). +/// +/// ### Example +/// ```rust +/// use sdl_encoder::{UnionDef}; +/// +/// let mut union_ = UnionDef::new("Pet".to_string()); +/// union_.member("Cat".to_string()); +/// union_.member("Dog".to_string()); +/// +/// assert_eq!( +/// union_.to_string(), +/// r#"union Pet = Cat | Dog +/// "# +/// ); +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct UnionDef { + // Name must return a String. + name: String, + // Description may return a String. + description: Option, + // The vector of members that can be represented within this union. + members: Vec, +} + +impl UnionDef { + /// Create a new instance of a UnionDef. + pub fn new(name: String) -> Self { + Self { + name, + description: None, + members: Vec::new(), + } + } + + /// Set the UnionDefs description. + pub fn description(&mut self, description: Option) { + self.description = description; + } + + /// Set a UnionDef member. + pub fn member(&mut self, member: String) { + self.members.push(member); + } +} + +impl Display for UnionDef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(description) = &self.description { + // We are determing on whether to have description formatted as + // a multiline comment based on whether or not it already includes a + // \n. + match description.contains('\n') { + true => writeln!(f, "\"\"\"\n{}\n\"\"\"", description)?, + false => writeln!(f, "\"\"\"{}\"\"\"", description)?, + } + } + + write!(f, "union {} = ", self.name)?; + + for (i, member) in self.members.iter().enumerate() { + match i { + 0 => write!(f, "{}", member)?, + _ => write!(f, " | {}", member)?, + } + } + + writeln!(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + // use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn it_encodes_union_with_description() { + let mut union_ = UnionDef::new("Pet".to_string()); + union_.description(Some("A union of all animals in a household.".to_string())); + union_.member("Cat".to_string()); + union_.member("Dog".to_string()); + + assert_eq!( + union_.to_string(), + r#""""A union of all animals in a household.""" +union Pet = Cat | Dog +"# + ); + } +} diff --git a/src/command/graph/introspect.rs b/src/command/graph/introspect.rs new file mode 100644 index 000000000..5c18bc8a0 --- /dev/null +++ b/src/command/graph/introspect.rs @@ -0,0 +1,46 @@ +use crate::Result; +use serde::Serialize; +use std::collections::HashMap; +use structopt::StructOpt; +use url::Url; + +use rover_client::{blocking::Client, query::graph::introspect}; + +use crate::command::RoverStdout; +use crate::utils::parsers::{parse_header, parse_url}; + +#[derive(Debug, Serialize, StructOpt)] +pub struct Introspect { + /// The endpoint of the graph to introspect + #[structopt(parse(try_from_str = parse_url))] + #[serde(skip_serializing)] + pub endpoint: Url, + + /// headers to pass to the endpoint. Values must be key:value pairs. + /// If a value has a space in it, use quotes around the pair, + /// ex. -H "Auth:some key" + + // The `name` here is for the help text and error messages, to print like + // --header rather than the plural field name --header + #[structopt(name="key:value", multiple=true, long="header", short="H", parse(try_from_str = parse_header))] + #[serde(skip_serializing)] + pub headers: Option>, +} + +impl Introspect { + pub fn run(&self) -> Result { + let client = Client::new(&self.endpoint.to_string()); + + // add the flag headers to a hashmap to pass along to rover-client + let mut headers = HashMap::new(); + if self.headers.is_some() { + for (key, value) in self.headers.clone().unwrap() { + headers.insert(key, value); + } + } + + let introspection_response = introspect::run(&client, &headers)?; + + Ok(RoverStdout::Introspection(introspection_response.result)) + } +} diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index a655ea68a..92e9a3c86 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -1,5 +1,6 @@ mod check; mod fetch; +mod introspect; mod publish; use serde::Serialize; @@ -26,6 +27,9 @@ pub enum Command { /// Publish an updated graph schema to the Apollo graph registry Publish(publish::Publish), + + /// Introspect current graph schema. + Introspect(introspect::Introspect), } impl Graph { @@ -38,6 +42,7 @@ impl Graph { Command::Check(command) => command.run(client_config, git_context), Command::Fetch(command) => command.run(client_config), Command::Publish(command) => command.run(client_config, git_context), + Command::Introspect(command) => command.run(), } } } diff --git a/src/command/output.rs b/src/command/output.rs index 4eb673853..bd57e6fb4 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -23,6 +23,7 @@ pub enum RoverStdout { SubgraphList(ListDetails), VariantList(Vec), Profiles(Vec), + Introspection(String), None, } @@ -101,6 +102,10 @@ impl RoverStdout { println!("{}", profile); } } + RoverStdout::Introspection(introspection_response) => { + eprintln!("Introspection Response:"); + println!("{}", &introspection_response); + } RoverStdout::None => (), } } diff --git a/src/error/metadata/mod.rs b/src/error/metadata/mod.rs index 05a50ab71..b23f19749 100644 --- a/src/error/metadata/mod.rs +++ b/src/error/metadata/mod.rs @@ -71,9 +71,9 @@ impl From<&mut anyhow::Error> for Metadata { RoverClientError::NoService { graph: _ } => { (Some(Suggestion::CheckGraphNameAndAuth), None) } - RoverClientError::AdhocError { msg: _ } | RoverClientError::GraphQL { msg: _ } => { - (None, None) - } + RoverClientError::AdhocError { msg: _ } + | RoverClientError::GraphQL { msg: _ } + | RoverClientError::IntrospectionError { msg: _ } => (None, None), RoverClientError::InvalidKey => (Some(Suggestion::CheckKey), None), RoverClientError::MalformedKey => (Some(Suggestion::ProperKey), None), RoverClientError::UnparseableReleaseVersion => { diff --git a/src/utils/parsers.rs b/src/utils/parsers.rs index 8fa29bd8e..01b0bfb95 100644 --- a/src/utils/parsers.rs +++ b/src/utils/parsers.rs @@ -5,6 +5,7 @@ use serde::Serialize; use std::{convert::TryInto, fmt}; use crate::{error::RoverError, Result}; +use url::Url; #[derive(Debug, PartialEq)] pub enum SchemaSource { @@ -125,6 +126,26 @@ pub fn parse_query_percentage_threshold(threshold: &str) -> Result { } } +/// Parse Urls from the command line. +// TODO: @lrlna return error for parse url +pub fn parse_url(url: &str) -> Result { + let res = Url::parse(url)?; + Ok(res) +} + +/// Parses a key:value pair from a string and returns a tuple of key:value. +/// If a full key:value can't be parsed, it will error. +pub fn parse_header(header: &str) -> Result<(String, String)> { + // only split once, a header's value may have a ":" in it, but not a key. Right? + let pair: Vec<&str> = header.splitn(2, ':').collect(); + if pair.len() < 2 { + let msg = format!("Could not parse \"key:value\" pair for provided header: \"{}\". Headers must be provided in key:value pairs, with quotes around the pair if there are any spaces in the key or value.", header); + Err(RoverError::parse_error(msg)) + } else { + Ok((pair[0].to_string(), pair[1].to_string())) + } +} + #[cfg(test)] mod tests { use super::{parse_graph_ref, parse_schema_source, GraphRef, SchemaSource};