diff --git a/.changesets/maint_avery_refactor_router_events.md b/.changesets/maint_avery_refactor_router_events.md new file mode 100644 index 0000000000..e3744209cd --- /dev/null +++ b/.changesets/maint_avery_refactor_router_events.md @@ -0,0 +1,5 @@ +### chore: split out router events into its own module ([PR #3235](https://github.com/apollographql/router/pull/3235)) + +Breaks down `./apollo-router/src/router.rs` into its own module `./apollo-router/src/router/mod.rs` with a sub-module `./apollo-router/src/router/event/mod.rs` that contains all of the streams that we combine to start a router (entitlement, schema, reload, configuration, shutdown, more streams to be added). This change makes adding new events/modifying existing events a bit easier since it's not in one huge giant file to rule them all. + +By [@EverlastingBugstopper](https://github.com/EverlastingBugstopper) in https://github.com/apollographql/router/pull/3235 diff --git a/Cargo.lock b/Cargo.lock index e642fa565b..aee2191171 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,9 +64,9 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead", "aes", @@ -91,7 +91,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -104,7 +104,7 @@ checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "const-random", - "getrandom 0.2.8", + "getrandom 0.2.10", "once_cell", "serde", "version_check", @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -299,7 +299,7 @@ dependencies = [ "buildstructor 0.5.2", "bytes", "ci_info", - "clap 4.3.0", + "clap 4.3.2", "console-subscriber", "dashmap", "derivative", @@ -439,7 +439,7 @@ version = "1.20.0" dependencies = [ "anyhow", "cargo-scaffold", - "clap 4.3.0", + "clap 4.3.2", "copy_dir", "regex", "str_inflector", @@ -588,9 +588,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", @@ -599,13 +599,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -616,7 +616,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -692,7 +692,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", "instant", "rand 0.8.5", ] @@ -741,9 +741,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64-simd" @@ -840,9 +840,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" dependencies = [ "memchr", "serde", @@ -873,16 +873,16 @@ dependencies = [ "proc-macro2", "quote", "str_inflector", - "syn 2.0.13", + "syn 2.0.18", "thiserror", "try_match", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecount" @@ -990,9 +990,9 @@ dependencies = [ [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", @@ -1001,15 +1001,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", @@ -1042,9 +1042,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags", "clap_lex 0.2.4", @@ -1054,9 +1054,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" dependencies = [ "clap_builder", "clap_derive", @@ -1065,9 +1065,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" dependencies = [ "anstream", "anstyle", @@ -1078,14 +1078,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -1138,9 +1138,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] @@ -1181,15 +1181,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1250,7 +1250,7 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", "once_cell", "proc-macro-hack", "tiny-keccak", @@ -1325,9 +1325,9 @@ checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -1357,7 +1357,7 @@ dependencies = [ "atty", "cast", "ciborium", - "clap 3.2.23", + "clap 3.2.25", "criterion-plot", "futures", "itertools", @@ -1387,9 +1387,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1523,15 +1523,15 @@ dependencies = [ "hashbrown", "lock_api", "once_cell", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", "serde", ] [[package]] name = "data-encoding" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "deadpool" @@ -1702,13 +1702,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -1774,9 +1774,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", @@ -1795,11 +1795,11 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys 0.4.0", + "dirs-sys 0.4.1", ] [[package]] @@ -1815,24 +1815,25 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "displaydoc" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -1843,9 +1844,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dunce" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dw" @@ -1913,7 +1914,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.6", + "digest 0.10.7", "ff", "generic-array 0.14.7", "group", @@ -1988,13 +1989,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2069,14 +2070,14 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "filetime" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if", "libc", "redox_syscall 0.2.16", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2154,7 +2155,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -2165,9 +2166,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -2290,9 +2291,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -2311,7 +2312,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -2399,9 +2400,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -2428,7 +2429,7 @@ checksum = "e77ac7b51b8e6313251737fcef4b1c01a2ea102bde68415b62c0ee9268fec357" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -2459,7 +2460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick 0.7.20", - "bstr 1.4.0", + "bstr 1.5.0", "fnv", "log", "regex", @@ -2575,9 +2576,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -2600,9 +2601,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.6" +version = "4.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "035ef95d03713f2c347a72547b7cd38cbc9af7cd51e6099fb62d586d4a6dee3a" +checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" dependencies = [ "log", "pest", @@ -2734,7 +2735,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2905,9 +2906,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2936,7 +2937,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" dependencies = [ - "console 0.15.5", + "console 0.15.7", "lazy_static", "number_prefix", "regex", @@ -2984,7 +2985,7 @@ version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a28d25139df397cbca21408bb742cf6837e04cdbebf1b07b760caf971d6a972" dependencies = [ - "console 0.15.5", + "console 0.15.7", "lazy_static", "linked-hash-map", "pest", @@ -3038,9 +3039,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -3055,14 +3056,14 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3091,9 +3092,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" @@ -3106,9 +3107,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -3157,7 +3158,7 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "pem", "ring", "serde", @@ -3278,9 +3279,9 @@ checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libssh2-sys" @@ -3298,9 +3299,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "libc", @@ -3334,20 +3335,20 @@ checksum = "279a77bf40c85a08513aca203635b96610ebf0e37a92cb0cee76e04da100a426" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -3458,7 +3459,7 @@ checksum = "4901771e1d44ddb37964565c654a3223ba41a594d02b8da471cc4464912b5cfa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -3513,14 +3514,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3662,7 +3663,7 @@ checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" dependencies = [ "byteorder", "lazy_static", - "libm 0.2.6", + "libm 0.2.7", "num-integer", "num-iter", "num-traits", @@ -3726,7 +3727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm 0.2.6", + "libm 0.2.7", ] [[package]] @@ -3747,18 +3748,18 @@ checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -3792,18 +3793,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.25.2+1.1.1t" +version = "111.26.0+1.1.1u" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320708a054ad9b3bf314688b5db87cf4d6683d64cfc835e2337924ae62bf4431" +checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.84" +version = "0.9.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" dependencies = [ "cc", "libc", @@ -3988,6 +3989,12 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.0" @@ -4067,9 +4074,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -4089,7 +4096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -4108,15 +4115,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] @@ -4145,15 +4152,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ "thiserror", "ucd-trie", @@ -4161,9 +4168,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be99c4c1d2fc2769b1d00239431d711d08f6efedcecb8b6e30707160aee99c15" +checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" dependencies = [ "pest", "pest_generator", @@ -4171,22 +4178,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56094789873daa36164de2e822b3888c6ae4b4f9da555a1103587658c805b1e" +checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] name = "pest_meta" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6733073c7cff3d8459fda0e42f13a047870242aed8b509fe98000928975f359e" +checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" dependencies = [ "once_cell", "pest", @@ -4207,22 +4214,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -4261,9 +4268,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "platforms" @@ -4429,9 +4436,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -4478,9 +4485,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c828f93f5ca4826f97fedcbd3f9a536c16b12cff3dbbb4a007f932bbad95b12" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", "heck 0.4.1", @@ -4548,9 +4555,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -4614,7 +4621,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -4706,18 +4713,18 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick 1.0.2", "memchr", "regex-syntax 0.7.2", ] @@ -4749,7 +4756,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -4917,9 +4924,9 @@ dependencies = [ [[package]] name = "router-bridge" -version = "0.2.6+v2.4.7" +version = "0.2.7+v2.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcd2822ebb7b954f2444eb6691d1e4eb354cf276eb4f6dfbe59216819859623" +checksum = "e810d1bce6761c679cd70a48a2574035e8e15e798f5f7694c7c27644e10b84d5" dependencies = [ "anyhow", "async-channel", @@ -4973,7 +4980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" dependencies = [ "byteorder", - "digest 0.10.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-iter", @@ -5036,9 +5043,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -5066,16 +5073,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.7" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -5120,7 +5127,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] @@ -5249,9 +5256,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags", "core-foundation", @@ -5262,9 +5269,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -5317,7 +5324,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -5434,7 +5441,7 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -5454,7 +5461,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -5471,7 +5478,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -5513,7 +5520,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -5523,7 +5530,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -5543,7 +5550,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf644ad016b75129f01a34a355dcb8d66a5bc803e417c7a77cc5d5ee9fa0f18" dependencies = [ - "console 0.15.5", + "console 0.15.7", "similar", ] @@ -5709,9 +5716,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "supergraph_sdl" @@ -5737,9 +5744,9 @@ dependencies = [ [[package]] name = "supports-hyperlinks" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4806e0b03b9906e76b018a5d821ebf198c8e9dc0829ed3328eeeb5094aed60" +checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" dependencies = [ "is-terminal", ] @@ -5766,9 +5773,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -5793,15 +5800,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -5930,7 +5938,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -6006,9 +6014,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", "serde", @@ -6018,15 +6026,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -6103,7 +6111,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] @@ -6178,15 +6186,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", "toml_datetime", @@ -6237,7 +6245,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.0", + "base64 0.21.2", "bytes", "futures-core", "futures-util", @@ -6354,20 +6362,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -6422,9 +6430,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term 0.46.0", @@ -6611,9 +6619,9 @@ checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-linebreak" @@ -6648,9 +6656,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle", @@ -6673,9 +6681,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -6714,7 +6722,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", "serde", "wasm-bindgen", ] @@ -6817,9 +6825,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -6827,24 +6835,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -6854,9 +6862,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6864,22 +6872,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "wasm-streams" @@ -6896,9 +6904,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -7126,9 +7134,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.1" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] @@ -7150,7 +7158,7 @@ checksum = "c6f71803d3a1c80377a06221e0530be02035d5b3e854af56c6ece7ac20ac441d" dependencies = [ "assert-json-diff", "async-trait", - "base64 0.21.0", + "base64 0.21.2", "deadpool", "futures", "futures-timer", @@ -7214,7 +7222,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.18", ] [[package]] diff --git a/apollo-router/src/router.rs b/apollo-router/src/router.rs deleted file mode 100644 index 6a887797a8..0000000000 --- a/apollo-router/src/router.rs +++ /dev/null @@ -1,1394 +0,0 @@ -// With regards to ELv2 licensing, this entire file is license key functionality -#![allow(missing_docs)] // FIXME -#![allow(deprecated)] // Note: Required to prevents complaints on enum declaration - -use std::fmt::Debug; -use std::fmt::Formatter; -use std::net::IpAddr; -use std::path::Path; -use std::path::PathBuf; -use std::pin::Pin; -use std::str::FromStr; -use std::sync::Arc; -use std::sync::Mutex; -use std::task::Context; -use std::task::Poll; -use std::time::Duration; - -use derivative::Derivative; -use derive_more::Display; -use derive_more::From; -use displaydoc::Display as DisplayDoc; -#[cfg(test)] -use futures::channel::mpsc; -#[cfg(test)] -use futures::channel::mpsc::SendError; -use futures::channel::oneshot; -use futures::prelude::*; -use futures::FutureExt; -use http_body::Body as _; -use hyper::Body; -use thiserror::Error; -#[cfg(test)] -use tokio::sync::Notify; -use tokio::sync::RwLock; -use tokio::task::spawn; -use tokio_util::time::DelayQueue; -use tower::BoxError; -use tower::ServiceExt; -use tracing_futures::WithSubscriber; -use url::Url; - -use self::Event::NoMoreConfiguration; -use self::Event::NoMoreSchema; -use self::Event::Reload; -use self::Event::Shutdown; -use self::Event::UpdateConfiguration; -use self::Event::UpdateSchema; -use crate::axum_factory::make_axum_router; -use crate::axum_factory::AxumHttpServerFactory; -use crate::axum_factory::ListenAddrAndRouter; -use crate::configuration::Configuration; -use crate::configuration::ListenAddr; -use crate::orbiter::OrbiterRouterSuperServiceFactory; -use crate::plugin::DynPlugin; -use crate::router::Event::NoMoreEntitlement; -use crate::router::Event::UpdateEntitlement; -use crate::router_factory::RouterFactory; -use crate::router_factory::RouterSuperServiceFactory; -use crate::router_factory::YamlRouterFactory; -use crate::services::router; -use crate::state_machine::ListenAddresses; -use crate::state_machine::StateMachine; -use crate::uplink::entitlement::Entitlement; -use crate::uplink::entitlement::EntitlementState; -use crate::uplink::entitlement_stream::EntitlementQuery; -use crate::uplink::entitlement_stream::EntitlementStreamExt; -use crate::uplink::schema_stream::SupergraphSdlQuery; -use crate::uplink::stream_from_uplink; -use crate::uplink::Endpoints; - -// For now this is unused: -// TODO: Check with simon once the refactor is complete -#[allow(unused)] -// Later we might add a public API for this (probably a builder similar to `test_harness.rs`), -// see https://github.com/apollographql/router/issues/1496. -// In the meantime keeping this function helps make sure it still compiles. -async fn make_router_service( - schema: &str, - configuration: Arc, - extra_plugins: Vec<(String, Box)>, - entitlement: EntitlementState, -) -> Result { - let service_factory = YamlRouterFactory - .create( - configuration.clone(), - schema.to_string(), - None, - Some(extra_plugins), - ) - .await?; - let web_endpoints = service_factory.web_endpoints(); - let routers = make_axum_router(service_factory, &configuration, web_endpoints, entitlement)?; - let ListenAddrAndRouter(_listener, router) = routers.main; - - Ok(router - .map_request(|req: router::Request| req.router_request) - .map_err(|error| match error {}) - .map_response(|res| { - res.map(|body| { - // Axum makes this `body` have type: - // https://docs.rs/http-body/0.4.5/http_body/combinators/struct.UnsyncBoxBody.html - let mut body = Box::pin(body); - // We make a stream based on its `poll_data` method - // in order to create a `hyper::Body`. - Body::wrap_stream(stream::poll_fn(move |ctx| body.as_mut().poll_data(ctx))) - // … but we ignore the `poll_trailers` method: - // https://docs.rs/http-body/0.4.5/http_body/trait.Body.html#tymethod.poll_trailers - // Apparently HTTP/2 trailers are like headers, except after the response body. - // I (Simon) believe nothing in the Apollo Router uses trailers as of this writing, - // so ignoring `poll_trailers` is fine. - // If we want to use trailers, we may need remove this convertion to `hyper::Body` - // and return `UnsyncBoxBody` (a.k.a. `axum::BoxBody`) as-is. - }) - .into() - }) - .boxed_clone()) -} - -/// Error types for FederatedServer. -#[derive(Error, Debug, DisplayDoc)] -pub enum ApolloRouterError { - /// failed to start server - StartupError, - - /// failed to stop HTTP Server - HttpServerLifecycleError, - - /// no valid configuration was supplied - NoConfiguration, - - /// no valid schema was supplied - NoSchema, - - /// no valid entitlement was supplied - NoEntitlement, - - /// entitlement violation - EntitlementViolation, - - /// could not create router: {0} - ServiceCreationError(BoxError), - - /// could not create the HTTP server: {0} - ServerCreationError(std::io::Error), - - /// tried to bind {0} and {1} on port {2} - DifferentListenAddrsOnSamePort(IpAddr, IpAddr, u16), - - /// tried to register two endpoints on `{0}:{1}{2}` - SameRouteUsedTwice(IpAddr, u16, String), - - /// TLS configuration error: {0} - Rustls(rustls::Error), -} - -type SchemaStream = Pin + Send>>; - -/// The user supplied schema. Either a static string or a stream for hot reloading. -#[derive(From, Display, Derivative)] -#[derivative(Debug)] -#[non_exhaustive] -pub enum SchemaSource { - /// A static schema. - #[display(fmt = "String")] - Static { schema_sdl: String }, - - /// A stream of schema. - #[display(fmt = "Stream")] - Stream(#[derivative(Debug = "ignore")] SchemaStream), - - /// A YAML file that may be watched for changes. - #[display(fmt = "File")] - File { - /// The path of the schema file. - path: PathBuf, - - /// `true` to watch the file for changes and hot apply them. - watch: bool, - - /// When watching, the delay to wait before applying the new schema. - /// Note: This variable is deprecated and has no effect. - #[deprecated] - delay: Option, - }, - - /// Apollo managed federation. - #[display(fmt = "Registry")] - Registry { - /// The Apollo key: `` - apollo_key: String, - - /// The apollo graph reference: `@` - apollo_graph_ref: String, - - /// The endpoint polled to fetch its latest supergraph schema. - urls: Option>, - - /// The duration between polling - poll_interval: Duration, - - /// The HTTP client timeout for each poll - timeout: Duration, - }, -} - -impl From<&'_ str> for SchemaSource { - fn from(s: &'_ str) -> Self { - Self::Static { - schema_sdl: s.to_owned(), - } - } -} - -impl SchemaSource { - /// Convert this schema into a stream regardless of if is static or not. Allows for unified handling later. - fn into_stream(self) -> impl Stream { - match self { - SchemaSource::Static { schema_sdl: schema } => { - stream::once(future::ready(UpdateSchema(schema))).boxed() - } - SchemaSource::Stream(stream) => stream.map(UpdateSchema).boxed(), - #[allow(deprecated)] - SchemaSource::File { - path, - watch, - delay: _, - } => { - // Sanity check, does the schema file exists, if it doesn't then bail. - if !path.exists() { - tracing::error!( - "Schema file at path '{}' does not exist.", - path.to_string_lossy() - ); - stream::empty().boxed() - } else { - //The schema file exists try and load it - match std::fs::read_to_string(&path) { - Ok(schema) => { - if watch { - crate::files::watch(&path) - .filter_map(move |_| { - let path = path.clone(); - async move { - match tokio::fs::read_to_string(&path).await { - Ok(schema) => Some(UpdateSchema(schema)), - Err(err) => { - tracing::error!("{}", err); - None - } - } - } - }) - .boxed() - } else { - stream::once(future::ready(UpdateSchema(schema))).boxed() - } - } - Err(err) => { - tracing::error!("Failed to read schema: {}", err); - stream::empty().boxed() - } - } - } - } - SchemaSource::Registry { - apollo_key, - apollo_graph_ref, - urls, - poll_interval, - timeout, - } => { - // With regards to ELv2 licensing, the code inside this block - // is license key functionality - stream_from_uplink::( - apollo_key, - apollo_graph_ref, - urls.map(Endpoints::fallback), - poll_interval, - timeout, - ) - .filter_map(|res| { - future::ready(match res { - Ok(schema) => Some(UpdateSchema(schema)), - Err(e) => { - tracing::error!("{}", e); - None - } - }) - }) - .boxed() - } - } - .chain(stream::iter(vec![NoMoreSchema])) - } -} - -type ConfigurationStream = Pin + Send>>; - -/// The user supplied config. Either a static instance or a stream for hot reloading. -#[derive(From, Display, Derivative)] -#[derivative(Debug)] -#[non_exhaustive] -pub enum ConfigurationSource { - /// A static configuration. - /// - /// Can be created through `serde::Deserialize` from various formats, - /// or inline in Rust code with `serde_json::json!` and `serde_json::from_value`. - #[display(fmt = "Static")] - #[from(types(Configuration))] - Static(Box), - - /// A configuration stream where the server will react to new configuration. If possible - /// the configuration will be applied without restarting the internal http server. - #[display(fmt = "Stream")] - Stream(#[derivative(Debug = "ignore")] ConfigurationStream), - - /// A yaml file that may be watched for changes - #[display(fmt = "File")] - File { - /// The path of the configuration file. - path: PathBuf, - - /// `true` to watch the file for changes and hot apply them. - watch: bool, - - /// When watching, the delay to wait before applying the new configuration. - /// Note: This variable is deprecated and has no effect. - #[deprecated] - delay: Option, - }, -} - -impl Default for ConfigurationSource { - fn default() -> Self { - ConfigurationSource::Static(Default::default()) - } -} - -impl ConfigurationSource { - /// Convert this config into a stream regardless of if is static or not. Allows for unified handling later. - fn into_stream(self) -> impl Stream { - match self { - ConfigurationSource::Static(instance) => { - stream::iter(vec![UpdateConfiguration(*instance)]).boxed() - } - ConfigurationSource::Stream(stream) => stream.map(UpdateConfiguration).boxed(), - #[allow(deprecated)] - ConfigurationSource::File { - path, - watch, - delay: _, - } => { - // Sanity check, does the config file exists, if it doesn't then bail. - if !path.exists() { - tracing::error!( - "configuration file at path '{}' does not exist.", - path.to_string_lossy() - ); - stream::empty().boxed() - } else { - match ConfigurationSource::read_config(&path) { - Ok(configuration) => { - if watch { - crate::files::watch(&path) - .filter_map(move |_| { - let path = path.clone(); - async move { - match ConfigurationSource::read_config_async(&path) - .await - { - Ok(configuration) => { - Some(UpdateConfiguration(configuration)) - } - Err(err) => { - tracing::error!("{}", err); - None - } - } - } - }) - .boxed() - } else { - stream::once(future::ready(UpdateConfiguration(configuration))) - .boxed() - } - } - Err(err) => { - tracing::error!("Failed to read configuration: {}", err); - stream::empty().boxed() - } - } - } - } - } - .chain(stream::iter(vec![NoMoreConfiguration])) - .boxed() - } - - fn read_config(path: &Path) -> Result { - let config = std::fs::read_to_string(path)?; - config.parse().map_err(ReadConfigError::Validation) - } - async fn read_config_async(path: &Path) -> Result { - let config = tokio::fs::read_to_string(path).await?; - config.parse().map_err(ReadConfigError::Validation) - } -} -type EntitlementStream = Pin + Send>>; - -/// Entitlement controls availability of certain features of the Router. -/// This API experimental and is subject to change outside of semver. -#[derive(From, Display, Derivative)] -#[derivative(Debug)] -#[non_exhaustive] -pub enum EntitlementSource { - /// A static entitlement. EXPERIMENTAL and not subject to semver. - #[display(fmt = "Static")] - Static { entitlement: Entitlement }, - - /// An entitlement supplied via APOLLO_ROUTER_ENTITLEMENT. EXPERIMENTAL and not subject to semver. - #[display(fmt = "Env")] - Env, - - /// A stream of entitlement. EXPERIMENTAL and not subject to semver. - #[display(fmt = "Stream")] - Stream(#[derivative(Debug = "ignore")] EntitlementStream), - - /// A raw file that may be watched for changes. EXPERIMENTAL and not subject to semver. - #[display(fmt = "File")] - File { - /// The path of the entitlement file. - path: PathBuf, - - /// `true` to watch the file for changes and hot apply them. - watch: bool, - }, - - /// Apollo uplink. - #[display(fmt = "Registry")] - Registry { - /// The Apollo key: `` - apollo_key: String, - - /// The apollo graph reference: `@` - apollo_graph_ref: String, - - /// The endpoint polled to fetch its latest supergraph schema. - urls: Option>, - - /// The duration between polling - poll_interval: Duration, - - /// The HTTP client timeout for each poll - timeout: Duration, - }, -} - -impl Default for EntitlementSource { - fn default() -> Self { - EntitlementSource::Static { - entitlement: Default::default(), - } - } -} - -impl EntitlementSource { - /// Convert this entitlement into a stream regardless of if is static or not. Allows for unified handling later. - fn into_stream(self) -> impl Stream { - match self { - EntitlementSource::Static { entitlement } => { - stream::once(future::ready(entitlement)).boxed() - } - EntitlementSource::Stream(stream) => stream.boxed(), - EntitlementSource::File { path, watch } => { - // Sanity check, does the schema file exists, if it doesn't then bail. - if !path.exists() { - tracing::error!( - "Entitlement file at path '{}' does not exist.", - path.to_string_lossy() - ); - stream::empty().boxed() - } else { - // The entitlement file exists try and load it - match std::fs::read_to_string(&path).map(|e| e.parse()) { - Ok(Ok(entitlement)) => { - if watch { - crate::files::watch(&path) - .filter_map(move |_| { - let path = path.clone(); - async move { - let result = tokio::fs::read_to_string(&path).await; - if let Err(e) = &result { - tracing::error!( - "failed to read entitlement file, {}", - e - ); - } - result.ok() - } - }) - .filter_map(|e| async move { - let result = e.parse(); - if let Err(e) = &result { - tracing::error!( - "failed to parse entitlement file, {}", - e - ); - } - result.ok() - }) - .boxed() - } else { - stream::once(future::ready(entitlement)).boxed() - } - } - Ok(Err(err)) => { - tracing::error!("Failed to parse entitlement: {}", err); - stream::empty().boxed() - } - Err(err) => { - tracing::error!("Failed to read entitlement: {}", err); - stream::empty().boxed() - } - } - } - } - EntitlementSource::Registry { - apollo_key, - apollo_graph_ref, - urls, - poll_interval, - timeout, - } => stream_from_uplink::( - apollo_key, - apollo_graph_ref, - urls.map(Endpoints::fallback), - poll_interval, - timeout, - ) - .filter_map(|res| { - future::ready(match res { - Ok(entitlement) => Some(entitlement), - Err(e) => { - tracing::error!("{}", e); - None - } - }) - }) - .boxed(), - EntitlementSource::Env => { - // EXPERIMENTAL and not subject to semver. - match std::env::var("APOLLO_ROUTER_ENTITLEMENT").map(|e| Entitlement::from_str(&e)) - { - Ok(Ok(entitlement)) => stream::once(future::ready(entitlement)).boxed(), - Ok(Err(err)) => { - tracing::error!("Failed to parse entitlement: {}", err); - stream::empty().boxed() - } - Err(_) => stream::once(future::ready(Entitlement::default())).boxed(), - } - } - } - .expand_entitlements() - .chain(stream::iter(vec![NoMoreEntitlement])) - } -} - -#[derive(From, Display)] -enum ReadConfigError { - /// could not read configuration: {0} - Io(std::io::Error), - /// {0} - Validation(crate::configuration::ConfigurationError), -} - -#[derive(Default)] -struct ReloadSourceInner { - queue: DelayQueue<()>, - period: Option, -} - -/// Reload source is an internal event emitter for the state machine that will send reload events on SIGUP and/or on a timer. -#[derive(Clone, Default)] -pub(crate) struct ReloadSource { - inner: Arc>, -} - -impl ReloadSource { - fn set_period(&self, period: &Option) { - let mut inner = self.inner.lock().unwrap(); - // Clear the queue before setting the period - inner.queue.clear(); - inner.period = *period; - if let Some(period) = period { - inner.queue.insert((), *period); - } - } - - fn into_stream(self) -> impl Stream { - #[cfg(unix)] - let signal_stream = { - let mut signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup()) - .expect("Failed to install SIGHUP signal handler"); - - futures::stream::poll_fn(move |cx| match signal.poll_recv(cx) { - Poll::Ready(Some(_)) => Poll::Ready(Some(Event::Reload)), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - }) - .boxed() - }; - #[cfg(not(unix))] - let signal_stream = futures::stream::empty().boxed(); - - let periodic_reload = futures::stream::poll_fn(move |cx| { - let mut inner = self.inner.lock().unwrap(); - match inner.queue.poll_expired(cx) { - Poll::Ready(Some(_expired)) => { - if let Some(period) = inner.period { - inner.queue.insert((), period); - } - Poll::Ready(Some(Event::Reload)) - } - // We must return pending even if the queue is empty, otherwise the stream will never be polled again - // The waker will still be used, so this won't end up in a hot loop. - Poll::Ready(None) => Poll::Pending, - Poll::Pending => Poll::Pending, - } - }); - - futures::stream::select(signal_stream, periodic_reload) - } -} - -type ShutdownFuture = Pin + Send>>; - -/// Specifies when the Router’s HTTP server should gracefully shutdown -#[derive(Display, Derivative)] -#[derivative(Debug)] -#[non_exhaustive] -pub enum ShutdownSource { - /// No graceful shutdown - #[display(fmt = "None")] - None, - - /// A custom shutdown future. - #[display(fmt = "Custom")] - Custom(#[derivative(Debug = "ignore")] ShutdownFuture), - - /// Watch for Ctl-C signal. - #[display(fmt = "CtrlC")] - CtrlC, -} - -impl ShutdownSource { - /// Convert this shutdown hook into a future. Allows for unified handling later. - fn into_stream(self) -> impl Stream { - match self { - ShutdownSource::None => stream::pending::().boxed(), - ShutdownSource::Custom(future) => future.map(|_| Shutdown).into_stream().boxed(), - ShutdownSource::CtrlC => { - #[cfg(not(unix))] - { - async { - tokio::signal::ctrl_c() - .await - .expect("Failed to install CTRL+C signal handler"); - } - .map(|_| Shutdown) - .into_stream() - .boxed() - } - - #[cfg(unix)] - future::select( - tokio::signal::ctrl_c().map(|s| s.ok()).boxed(), - async { - tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) - .expect("Failed to install SIGTERM signal handler") - .recv() - .await - } - .boxed(), - ) - .map(|_| Shutdown) - .into_stream() - .boxed() - } - } - } -} - -/// The entry point for running the Router’s HTTP server. -/// -/// # Examples -/// -/// ``` -/// use apollo_router::RouterHttpServer; -/// use apollo_router::Configuration; -/// -/// async { -/// let configuration = serde_yaml::from_str::("Config").unwrap(); -/// let schema = "schema"; -/// RouterHttpServer::builder() -/// .configuration(configuration) -/// .schema(schema) -/// .start() -/// .await; -/// }; -/// ``` -/// -/// Shutdown via handle. -/// ``` -/// use apollo_router::RouterHttpServer; -/// use apollo_router::Configuration; -/// -/// async { -/// let configuration = serde_yaml::from_str::("Config").unwrap(); -/// let schema = "schema"; -/// let mut server = RouterHttpServer::builder() -/// .configuration(configuration) -/// .schema(schema) -/// .start(); -/// // … -/// server.shutdown().await -/// }; -/// ``` -/// -pub struct RouterHttpServer { - result: Pin> + Send>>, - listen_addresses: Arc>, - shutdown_sender: Option>, -} - -#[buildstructor::buildstructor] -impl RouterHttpServer { - /// Returns a builder to start an HTTP server in a separate Tokio task. - /// - /// Builder methods: - /// - /// * `.schema(impl Into<`[`SchemaSource`]`>)` - /// Required. - /// Specifies where to find the supergraph schema definition. - /// Some sources support hot-reloading. - /// - /// * `.configuration(impl Into<`[`ConfigurationSource`]`>)` - /// Optional. - /// Specifies where to find the router configuration. - /// If not provided, the default configuration as with an empty YAML file. - /// - /// * `.entitlement(impl Into<`[`EntitlementSource`]`>)` - /// Optional. - /// Specifies where to find the router entitlement which controls if commercial features are enabled or not. - /// If not provided then commercial features will not be enabled. - /// - /// * `.shutdown(impl Into<`[`ShutdownSource`]`>)` - /// Optional. - /// Specifies when the server should gracefully shut down. - /// If not provided, the default is [`ShutdownSource::CtrlC`]. - /// - /// * `.start()` - /// Finishes the builder, - /// starts an HTTP server in a separate Tokio task, - /// and returns a `RouterHttpServer` handle. - /// - /// The server handle can be used in multiple ways. - /// As a [`Future`], it resolves to `Result<(), `[`ApolloRouterError`]`>` - /// either when the server has finished gracefully shutting down - /// or when it encounters a fatal error that prevents it from starting. - /// - /// If the handle is dropped before being awaited as a future, - /// a graceful shutdown is triggered. - /// In order to wait until shutdown finishes, - /// use the [`shutdown`][Self::shutdown] method instead. - #[builder(visibility = "pub", entry = "builder", exit = "start")] - fn start( - schema: SchemaSource, - configuration: Option, - entitlement: Option, - shutdown: Option, - ) -> RouterHttpServer { - let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); - let event_stream = generate_event_stream( - shutdown.unwrap_or(ShutdownSource::CtrlC), - configuration.unwrap_or_default(), - schema, - entitlement.unwrap_or_default(), - shutdown_receiver, - ); - let server_factory = AxumHttpServerFactory::new(); - let router_factory = OrbiterRouterSuperServiceFactory::new(YamlRouterFactory::default()); - let state_machine = StateMachine::new(server_factory, router_factory); - let listen_addresses = state_machine.listen_addresses.clone(); - let result = spawn( - async move { state_machine.process_events(event_stream).await } - .with_current_subscriber(), - ) - .map(|r| match r { - Ok(Ok(ok)) => Ok(ok), - Ok(Err(err)) => Err(err), - Err(err) => { - tracing::error!("{}", err); - Err(ApolloRouterError::StartupError) - } - }) - .with_current_subscriber() - .boxed(); - - RouterHttpServer { - result, - shutdown_sender: Some(shutdown_sender), - listen_addresses, - } - } - - /// Returns the listen address when the router is ready to receive GraphQL requests. - /// - /// This can be useful when the `server.listen` configuration specifies TCP port 0, - /// which instructs the operating system to pick an available port number. - /// - /// Note: if configuration is dynamic, the listen address can change over time. - pub async fn listen_address(&self) -> Option { - self.listen_addresses - .read() - .await - .graphql_listen_address - .clone() - } - - /// Returns the extra listen addresses the router can receive requests to. - /// - /// Combine it with `listen_address` to have an exhaustive list - /// of all addresses used by the router. - /// Note: if configuration is dynamic, the listen address can change over time. - pub async fn extra_listen_adresses(&self) -> Vec { - self.listen_addresses - .read() - .await - .extra_listen_addresses - .clone() - } - - /// Trigger and wait for graceful shutdown - pub async fn shutdown(&mut self) -> Result<(), ApolloRouterError> { - if let Some(sender) = self.shutdown_sender.take() { - let _ = sender.send(()); - } - (&mut self.result).await - } -} - -/// Messages that are broadcast across the app. -pub(crate) enum Event { - /// The configuration was updated. - UpdateConfiguration(Configuration), - - /// There are no more updates to the configuration - NoMoreConfiguration, - - /// The schema was updated. - UpdateSchema(String), - - /// There are no more updates to the schema - NoMoreSchema, - - /// Update entitlement {} - UpdateEntitlement(EntitlementState), - - /// There were no more updates to entitlement. - NoMoreEntitlement, - - /// Artificial hot reload for chaos testing - Reload, - - /// The server should gracefully shutdown. - Shutdown, -} - -impl Debug for Event { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - UpdateConfiguration(_) => { - write!(f, "UpdateConfiguration()") - } - NoMoreConfiguration => { - write!(f, "NoMoreConfiguration") - } - UpdateSchema(_) => { - write!(f, "UpdateSchema()") - } - NoMoreSchema => { - write!(f, "NoMoreSchema") - } - UpdateEntitlement(e) => { - write!(f, "UpdateEntitlement({e:?})") - } - NoMoreEntitlement => { - write!(f, "NoMoreEntitlement") - } - Reload => { - write!(f, "ForcedHotReload") - } - Shutdown => { - write!(f, "Shutdown") - } - } - } -} - -impl Drop for RouterHttpServer { - fn drop(&mut self) { - if let Some(sender) = self.shutdown_sender.take() { - let _ = sender.send(()); - } - } -} - -impl Future for RouterHttpServer { - type Output = Result<(), ApolloRouterError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.result.poll_unpin(cx) - } -} - -/// Create the unified event stream. -/// This merges all contributing streams and sets up shutdown handling. -/// When a shutdown message is received no more events are emitted. -fn generate_event_stream( - shutdown: ShutdownSource, - configuration: ConfigurationSource, - schema: SchemaSource, - entitlement: EntitlementSource, - shutdown_receiver: oneshot::Receiver<()>, -) -> impl Stream { - let reload_source = ReloadSource::default(); - - let stream = stream::select_all(vec![ - shutdown.into_stream().boxed(), - schema.into_stream().boxed(), - entitlement.into_stream().boxed(), - reload_source.clone().into_stream().boxed(), - configuration - .into_stream() - .map(move |config_event| { - if let Event::UpdateConfiguration(config) = &config_event { - reload_source.set_period(&config.experimental_chaos.force_reload) - } - config_event - }) - .boxed(), - shutdown_receiver.into_stream().map(|_| Shutdown).boxed(), - ]) - .take_while(|msg| future::ready(!matches!(msg, Shutdown))) - // Chain is required so that the final shutdown message is sent. - .chain(stream::iter(vec![Shutdown])) - .boxed(); - stream -} - -#[cfg(test)] -struct TestRouterHttpServer { - router_http_server: RouterHttpServer, - event_sender: mpsc::UnboundedSender, - state_machine_update_notifier: Arc, -} - -#[cfg(test)] -impl TestRouterHttpServer { - fn new() -> Self { - let (event_sender, event_receiver) = mpsc::unbounded(); - let state_machine_update_notifier = Arc::new(Notify::new()); - - let server_factory = AxumHttpServerFactory::new(); - let router_factory: OrbiterRouterSuperServiceFactory = - OrbiterRouterSuperServiceFactory::new(YamlRouterFactory::default()); - let state_machine = StateMachine::for_tests( - server_factory, - router_factory, - Arc::clone(&state_machine_update_notifier), - ); - - let listen_addresses = state_machine.listen_addresses.clone(); - let result = spawn( - async move { state_machine.process_events(event_receiver).await } - .with_current_subscriber(), - ) - .map(|r| match r { - Ok(Ok(ok)) => Ok(ok), - Ok(Err(err)) => Err(err), - Err(err) => { - tracing::error!("{}", err); - Err(ApolloRouterError::StartupError) - } - }) - .with_current_subscriber() - .boxed(); - - TestRouterHttpServer { - router_http_server: RouterHttpServer { - result, - shutdown_sender: None, - listen_addresses, - }, - event_sender, - state_machine_update_notifier, - } - } - - async fn request( - &self, - request: crate::graphql::Request, - ) -> Result { - Ok(reqwest::Client::new() - .post(format!("{}/", self.listen_address().await.unwrap())) - .json(&request) - .send() - .await - .expect("couldn't send request") - .json() - .await - .expect("couldn't deserialize into json")) - } - - async fn listen_address(&self) -> Option { - self.router_http_server.listen_address().await - } - - async fn send_event(&mut self, event: Event) -> Result<(), SendError> { - let result = self.event_sender.send(event).await; - self.state_machine_update_notifier.notified().await; - result - } - - async fn shutdown(mut self) -> Result<(), ApolloRouterError> { - self.send_event(Event::Shutdown).await.unwrap(); - self.router_http_server.shutdown().await - } -} - -#[cfg(test)] -mod tests { - use std::env::temp_dir; - - use serde_json::to_string_pretty; - use test_log::test; - - use super::*; - use crate::files::tests::create_temp_file; - use crate::files::tests::write_and_flush; - use crate::graphql; - use crate::graphql::Request; - - fn init_with_server() -> RouterHttpServer { - let configuration = - Configuration::from_str(include_str!("testdata/supergraph_config.router.yaml")) - .unwrap(); - let schema = include_str!("testdata/supergraph.graphql"); - RouterHttpServer::builder() - .configuration(configuration) - .schema(schema) - .start() - } - - #[tokio::test(flavor = "multi_thread")] - async fn basic_request() { - let mut router_handle = init_with_server(); - let listen_address = router_handle - .listen_address() - .await - .expect("router failed to start"); - - assert_federated_response(&listen_address, r#"{ topProducts { name } }"#).await; - router_handle.shutdown().await.unwrap(); - } - - async fn assert_federated_response(listen_addr: &ListenAddr, request: &str) { - let request = Request::builder().query(request).build(); - let expected = query(listen_addr, &request).await.unwrap(); - - let response = to_string_pretty(&expected).unwrap(); - assert!(!response.is_empty()); - } - - async fn query( - listen_addr: &ListenAddr, - request: &graphql::Request, - ) -> Result { - Ok(reqwest::Client::new() - .post(format!("{listen_addr}/")) - .json(request) - .send() - .await - .expect("couldn't send request") - .json() - .await - .expect("couldn't deserialize into json")) - } - - #[tokio::test(flavor = "multi_thread")] - async fn config_by_file_watching() { - let (path, mut file) = create_temp_file(); - let contents = include_str!("testdata/supergraph_config.router.yaml"); - write_and_flush(&mut file, contents).await; - let mut stream = ConfigurationSource::File { - path, - watch: true, - delay: None, - } - .into_stream() - .boxed(); - - // First update is guaranteed - assert!(matches!( - stream.next().await.unwrap(), - UpdateConfiguration(_) - )); - - // Need different contents, since we won't get an event if content is the same - let contents_datadog = include_str!("testdata/datadog.router.yaml"); - // Modify the file and try again - write_and_flush(&mut file, contents_datadog).await; - assert!(matches!( - stream.next().await.unwrap(), - UpdateConfiguration(_) - )); - - // This time write garbage, there should not be an update. - write_and_flush(&mut file, ":garbage").await; - let event = stream.into_future().now_or_never(); - assert!(event.is_none() || matches!(event, Some((Some(NoMoreConfiguration), _)))); - } - - #[tokio::test(flavor = "multi_thread")] - async fn config_by_file_invalid() { - let (path, mut file) = create_temp_file(); - write_and_flush(&mut file, "Garbage").await; - let mut stream = ConfigurationSource::File { - path, - watch: true, - delay: None, - } - .into_stream(); - - // First update fails because the file is invalid. - assert!(matches!(stream.next().await.unwrap(), NoMoreConfiguration)); - } - - #[tokio::test(flavor = "multi_thread")] - async fn config_by_file_missing() { - let mut stream = ConfigurationSource::File { - path: temp_dir().join("does_not_exit"), - watch: true, - delay: None, - } - .into_stream(); - - // First update fails because the file is invalid. - assert!(matches!(stream.next().await.unwrap(), NoMoreConfiguration)); - } - - #[tokio::test(flavor = "multi_thread")] - async fn config_by_file_no_watch() { - let (path, mut file) = create_temp_file(); - let contents = include_str!("testdata/supergraph_config.router.yaml"); - write_and_flush(&mut file, contents).await; - - let mut stream = ConfigurationSource::File { - path, - watch: false, - delay: None, - } - .into_stream(); - assert!(matches!( - stream.next().await.unwrap(), - UpdateConfiguration(_) - )); - assert!(matches!(stream.next().await.unwrap(), NoMoreConfiguration)); - } - - #[test(tokio::test)] - async fn schema_by_file_watching() { - let (path, mut file) = create_temp_file(); - let schema = include_str!("testdata/supergraph.graphql"); - write_and_flush(&mut file, schema).await; - let mut stream = SchemaSource::File { - path, - watch: true, - delay: None, - } - .into_stream() - .boxed(); - - // First update is guaranteed - assert!(matches!(stream.next().await.unwrap(), UpdateSchema(_))); - - // Need different contents, since we won't get an event if content is the same - let schema_minimal = include_str!("testdata/minimal_supergraph.graphql"); - // Modify the file and try again - write_and_flush(&mut file, schema_minimal).await; - assert!(matches!(stream.next().await.unwrap(), UpdateSchema(_))); - } - - #[test(tokio::test)] - async fn schema_by_file_missing() { - let mut stream = SchemaSource::File { - path: temp_dir().join("does_not_exist"), - watch: true, - delay: None, - } - .into_stream(); - - // First update fails because the file is invalid. - assert!(matches!(stream.next().await.unwrap(), NoMoreSchema)); - } - - #[test(tokio::test)] - async fn schema_by_file_no_watch() { - let (path, mut file) = create_temp_file(); - let schema = include_str!("testdata/supergraph.graphql"); - write_and_flush(&mut file, schema).await; - - let mut stream = SchemaSource::File { - path, - watch: false, - delay: None, - } - .into_stream(); - assert!(matches!(stream.next().await.unwrap(), UpdateSchema(_))); - assert!(matches!(stream.next().await.unwrap(), NoMoreSchema)); - } - - #[tokio::test(flavor = "multi_thread")] - async fn basic_event_stream_test() { - let mut router_handle = TestRouterHttpServer::new(); - - let configuration = - Configuration::from_str(include_str!("testdata/supergraph_config.router.yaml")) - .unwrap(); - let schema = include_str!("testdata/supergraph.graphql"); - - // let's push a valid configuration to the state machine, so it can start up - router_handle - .send_event(UpdateConfiguration(configuration)) - .await - .unwrap(); - router_handle - .send_event(UpdateSchema(schema.to_string())) - .await - .unwrap(); - router_handle - .send_event(UpdateEntitlement(EntitlementState::Unentitled)) - .await - .unwrap(); - - let request = Request::builder().query(r#"{ me { username } }"#).build(); - - let response = router_handle.request(request).await.unwrap(); - assert_eq!( - "@ada", - response - .data - .unwrap() - .get("me") - .unwrap() - .get("username") - .unwrap() - ); - - // shut the router down - router_handle - .send_event(Event::NoMoreConfiguration) - .await - .unwrap(); - router_handle.send_event(Event::NoMoreSchema).await.unwrap(); - router_handle.send_event(Event::Shutdown).await.unwrap(); - } - - #[tokio::test(flavor = "multi_thread")] - async fn schema_update_test() { - let mut router_handle = TestRouterHttpServer::new(); - // let's push a valid configuration to the state machine, so it can start up - router_handle - .send_event(UpdateConfiguration( - Configuration::from_str(include_str!("testdata/supergraph_config.router.yaml")) - .unwrap(), - )) - .await - .unwrap(); - router_handle - .send_event(UpdateSchema( - include_str!("testdata/supergraph_missing_name.graphql").to_string(), - )) - .await - .unwrap(); - router_handle - .send_event(UpdateEntitlement(EntitlementState::Unentitled)) - .await - .unwrap(); - - // let's send a valid query - let request = Request::builder().query(r#"{ me { username } }"#).build(); - let response = router_handle.request(request).await.unwrap(); - - assert_eq!( - "@ada", - response - .data - .unwrap() - .get("me") - .unwrap() - .get("username") - .unwrap() - ); - - // the name field is not present yet - let request = Request::builder() - .query(r#"{ me { username name } }"#) - .build(); - let response = router_handle.request(request).await.unwrap(); - - assert_eq!( - "cannot query field 'name' on type 'User'", - response.errors[0].message - ); - assert_eq!( - "INVALID_FIELD", - response.errors[0].extensions.get("code").unwrap() - ); - - // let's update the schema to add the field - router_handle - .send_event(UpdateSchema( - include_str!("testdata/supergraph.graphql").to_string(), - )) - .await - .unwrap(); - - // the request should now make it through - let request = Request::builder() - .query(r#"{ me { username name } }"#) - .build(); - - let response = router_handle.request(request).await.unwrap(); - - assert_eq!( - "Ada Lovelace", - response - .data - .unwrap() - .get("me") - .unwrap() - .get("name") - .unwrap() - ); - - // let's go back and remove the field - router_handle - .send_event(UpdateSchema( - include_str!("testdata/supergraph_missing_name.graphql").to_string(), - )) - .await - .unwrap(); - - let request = Request::builder().query(r#"{ me { username } }"#).build(); - let response = router_handle.request(request).await.unwrap(); - - assert_eq!( - "@ada", - response - .data - .unwrap() - .get("me") - .unwrap() - .get("username") - .unwrap() - ); - - let request = Request::builder() - .query(r#"{ me { username name } }"#) - .build(); - let response = router_handle.request(request).await.unwrap(); - - assert_eq!( - "cannot query field 'name' on type 'User'", - response.errors[0].message - ); - assert_eq!( - "INVALID_FIELD", - response.errors[0].extensions.get("code").unwrap() - ); - router_handle.shutdown().await.unwrap(); - } -} diff --git a/apollo-router/src/router/error.rs b/apollo-router/src/router/error.rs new file mode 100644 index 0000000000..f6bbb772a1 --- /dev/null +++ b/apollo-router/src/router/error.rs @@ -0,0 +1,43 @@ +use std::fmt::Debug; +use std::net::IpAddr; + +use displaydoc::Display as DisplayDoc; +use thiserror::Error; +use tower::BoxError; + +/// Error types for FederatedServer. +#[derive(Error, Debug, DisplayDoc)] +pub enum ApolloRouterError { + /// failed to start server + StartupError, + + /// failed to stop HTTP Server + HttpServerLifecycleError, + + /// no valid configuration was supplied + NoConfiguration, + + /// no valid schema was supplied + NoSchema, + + /// no valid entitlement was supplied + NoEntitlement, + + /// entitlement violation + EntitlementViolation, + + /// could not create router: {0} + ServiceCreationError(BoxError), + + /// could not create the HTTP server: {0} + ServerCreationError(std::io::Error), + + /// tried to bind {0} and {1} on port {2} + DifferentListenAddrsOnSamePort(IpAddr, IpAddr, u16), + + /// tried to register two endpoints on `{0}:{1}{2}` + SameRouteUsedTwice(IpAddr, u16, String), + + /// TLS configuration error: {0} + Rustls(rustls::Error), +} diff --git a/apollo-router/src/router/event/configuration.rs b/apollo-router/src/router/event/configuration.rs new file mode 100644 index 0000000000..de6af531c0 --- /dev/null +++ b/apollo-router/src/router/event/configuration.rs @@ -0,0 +1,226 @@ +// With regards to ELv2 licensing, this entire file is license key functionality + +use std::path::Path; +use std::path::PathBuf; +use std::pin::Pin; +use std::time::Duration; + +use derivative::Derivative; +use derive_more::Display; +use derive_more::From; +use futures::prelude::*; + +use crate::router::Event; +use crate::router::Event::NoMoreConfiguration; +use crate::router::Event::UpdateConfiguration; +use crate::Configuration; + +type ConfigurationStream = Pin + Send>>; + +/// The user supplied config. Either a static instance or a stream for hot reloading. +#[derive(From, Display, Derivative)] +#[derivative(Debug)] +#[non_exhaustive] +pub enum ConfigurationSource { + /// A static configuration. + /// + /// Can be created through `serde::Deserialize` from various formats, + /// or inline in Rust code with `serde_json::json!` and `serde_json::from_value`. + #[display(fmt = "Static")] + #[from(types(Configuration))] + Static(Box), + + /// A configuration stream where the server will react to new configuration. If possible + /// the configuration will be applied without restarting the internal http server. + #[display(fmt = "Stream")] + Stream(#[derivative(Debug = "ignore")] ConfigurationStream), + + /// A yaml file that may be watched for changes + #[display(fmt = "File")] + File { + /// The path of the configuration file. + path: PathBuf, + + /// `true` to watch the file for changes and hot apply them. + watch: bool, + + /// When watching, the delay to wait before applying the new configuration. + /// Note: This variable is deprecated and has no effect. + #[deprecated] + delay: Option, + }, +} + +impl Default for ConfigurationSource { + fn default() -> Self { + ConfigurationSource::Static(Default::default()) + } +} + +impl ConfigurationSource { + /// Convert this config into a stream regardless of if is static or not. Allows for unified handling later. + pub(crate) fn into_stream(self) -> impl Stream { + match self { + ConfigurationSource::Static(instance) => { + stream::iter(vec![UpdateConfiguration(*instance)]).boxed() + } + ConfigurationSource::Stream(stream) => stream.map(UpdateConfiguration).boxed(), + #[allow(deprecated)] + ConfigurationSource::File { + path, + watch, + delay: _, + } => { + // Sanity check, does the config file exists, if it doesn't then bail. + if !path.exists() { + tracing::error!( + "configuration file at path '{}' does not exist.", + path.to_string_lossy() + ); + stream::empty().boxed() + } else { + match ConfigurationSource::read_config(&path) { + Ok(configuration) => { + if watch { + crate::files::watch(&path) + .filter_map(move |_| { + let path = path.clone(); + async move { + match ConfigurationSource::read_config_async(&path) + .await + { + Ok(configuration) => { + Some(UpdateConfiguration(configuration)) + } + Err(err) => { + tracing::error!("{}", err); + None + } + } + } + }) + .boxed() + } else { + stream::once(future::ready(UpdateConfiguration(configuration))) + .boxed() + } + } + Err(err) => { + tracing::error!("Failed to read configuration: {}", err); + stream::empty().boxed() + } + } + } + } + } + .chain(stream::iter(vec![NoMoreConfiguration])) + .boxed() + } + + fn read_config(path: &Path) -> Result { + let config = std::fs::read_to_string(path)?; + config.parse().map_err(ReadConfigError::Validation) + } + async fn read_config_async(path: &Path) -> Result { + let config = tokio::fs::read_to_string(path).await?; + config.parse().map_err(ReadConfigError::Validation) + } +} + +#[derive(From, Display)] +enum ReadConfigError { + /// could not read configuration: {0} + Io(std::io::Error), + /// {0} + Validation(crate::configuration::ConfigurationError), +} + +#[cfg(test)] +mod tests { + use std::env::temp_dir; + + use super::*; + use crate::files::tests::create_temp_file; + use crate::files::tests::write_and_flush; + + #[tokio::test(flavor = "multi_thread")] + async fn config_by_file_watching() { + let (path, mut file) = create_temp_file(); + let contents = include_str!("../../testdata/supergraph_config.router.yaml"); + write_and_flush(&mut file, contents).await; + let mut stream = ConfigurationSource::File { + path, + watch: true, + delay: None, + } + .into_stream() + .boxed(); + + // First update is guaranteed + assert!(matches!( + stream.next().await.unwrap(), + UpdateConfiguration(_) + )); + + // Need different contents, since we won't get an event if content is the same + let contents_datadog = include_str!("../../testdata/datadog.router.yaml"); + // Modify the file and try again + write_and_flush(&mut file, contents_datadog).await; + assert!(matches!( + stream.next().await.unwrap(), + UpdateConfiguration(_) + )); + + // This time write garbage, there should not be an update. + write_and_flush(&mut file, ":garbage").await; + let event = stream.into_future().now_or_never(); + assert!(event.is_none() || matches!(event, Some((Some(NoMoreConfiguration), _)))); + } + + #[tokio::test(flavor = "multi_thread")] + async fn config_by_file_missing() { + let mut stream = ConfigurationSource::File { + path: temp_dir().join("does_not_exit"), + watch: true, + delay: None, + } + .into_stream(); + + // First update fails because the file is invalid. + assert!(matches!(stream.next().await.unwrap(), NoMoreConfiguration)); + } + + #[tokio::test(flavor = "multi_thread")] + async fn config_by_file_invalid() { + let (path, mut file) = create_temp_file(); + write_and_flush(&mut file, "Garbage").await; + let mut stream = ConfigurationSource::File { + path, + watch: true, + delay: None, + } + .into_stream(); + + // First update fails because the file is invalid. + assert!(matches!(stream.next().await.unwrap(), NoMoreConfiguration)); + } + + #[tokio::test(flavor = "multi_thread")] + async fn config_by_file_no_watch() { + let (path, mut file) = create_temp_file(); + let contents = include_str!("../../testdata/supergraph_config.router.yaml"); + write_and_flush(&mut file, contents).await; + + let mut stream = ConfigurationSource::File { + path, + watch: false, + delay: None, + } + .into_stream(); + assert!(matches!( + stream.next().await.unwrap(), + UpdateConfiguration(_) + )); + assert!(matches!(stream.next().await.unwrap(), NoMoreConfiguration)); + } +} diff --git a/apollo-router/src/router/event/entitlement.rs b/apollo-router/src/router/event/entitlement.rs new file mode 100644 index 0000000000..5480ddd67f --- /dev/null +++ b/apollo-router/src/router/event/entitlement.rs @@ -0,0 +1,180 @@ +// With regards to ELv2 licensing, this entire file is license key functionality + +use std::path::PathBuf; +use std::pin::Pin; +use std::str::FromStr; +use std::time::Duration; + +use derivative::Derivative; +use derive_more::Display; +use derive_more::From; +use futures::prelude::*; +use url::Url; + +use crate::router::Event; +use crate::router::Event::NoMoreEntitlement; +use crate::uplink::entitlement::Entitlement; +use crate::uplink::entitlement_stream::EntitlementQuery; +use crate::uplink::entitlement_stream::EntitlementStreamExt; +use crate::uplink::stream_from_uplink; +use crate::uplink::Endpoints; + +type EntitlementStream = Pin + Send>>; + +/// Entitlement controls availability of certain features of the Router. +/// This API experimental and is subject to change outside of semver. +#[derive(From, Display, Derivative)] +#[derivative(Debug)] +#[non_exhaustive] +pub enum EntitlementSource { + /// A static entitlement. EXPERIMENTAL and not subject to semver. + #[display(fmt = "Static")] + Static { entitlement: Entitlement }, + + /// An entitlement supplied via APOLLO_ROUTER_ENTITLEMENT. EXPERIMENTAL and not subject to semver. + #[display(fmt = "Env")] + Env, + + /// A stream of entitlement. EXPERIMENTAL and not subject to semver. + #[display(fmt = "Stream")] + Stream(#[derivative(Debug = "ignore")] EntitlementStream), + + /// A raw file that may be watched for changes. EXPERIMENTAL and not subject to semver. + #[display(fmt = "File")] + File { + /// The path of the entitlement file. + path: PathBuf, + + /// `true` to watch the file for changes and hot apply them. + watch: bool, + }, + + /// Apollo uplink. + #[display(fmt = "Registry")] + Registry { + /// The Apollo key: `` + apollo_key: String, + + /// The apollo graph reference: `@` + apollo_graph_ref: String, + + /// The endpoint polled to fetch its latest supergraph schema. + urls: Option>, + + /// The duration between polling + poll_interval: Duration, + + /// The HTTP client timeout for each poll + timeout: Duration, + }, +} + +impl Default for EntitlementSource { + fn default() -> Self { + EntitlementSource::Static { + entitlement: Default::default(), + } + } +} + +impl EntitlementSource { + /// Convert this entitlement into a stream regardless of if is static or not. Allows for unified handling later. + pub(crate) fn into_stream(self) -> impl Stream { + match self { + EntitlementSource::Static { entitlement } => { + stream::once(future::ready(entitlement)).boxed() + } + EntitlementSource::Stream(stream) => stream.boxed(), + EntitlementSource::File { path, watch } => { + // Sanity check, does the schema file exists, if it doesn't then bail. + if !path.exists() { + tracing::error!( + "Entitlement file at path '{}' does not exist.", + path.to_string_lossy() + ); + stream::empty().boxed() + } else { + // The entitlement file exists try and load it + match std::fs::read_to_string(&path).map(|e| e.parse()) { + Ok(Ok(entitlement)) => { + if watch { + crate::files::watch(&path) + .filter_map(move |_| { + let path = path.clone(); + async move { + let result = tokio::fs::read_to_string(&path).await; + if let Err(e) = &result { + tracing::error!( + "failed to read entitlement file, {}", + e + ); + } + result.ok() + } + }) + .filter_map(|e| async move { + let result = e.parse(); + if let Err(e) = &result { + tracing::error!( + "failed to parse entitlement file, {}", + e + ); + } + result.ok() + }) + .boxed() + } else { + stream::once(future::ready(entitlement)).boxed() + } + } + Ok(Err(err)) => { + tracing::error!("Failed to parse entitlement: {}", err); + stream::empty().boxed() + } + Err(err) => { + tracing::error!("Failed to read entitlement: {}", err); + stream::empty().boxed() + } + } + } + } + EntitlementSource::Registry { + apollo_key, + apollo_graph_ref, + urls, + poll_interval, + timeout, + } => stream_from_uplink::( + apollo_key, + apollo_graph_ref, + urls.map(Endpoints::fallback), + poll_interval, + timeout, + ) + .filter_map(|res| { + future::ready(match res { + Ok(entitlement) => Some(entitlement), + Err(e) => { + tracing::error!("{}", e); + None + } + }) + }) + .boxed(), + EntitlementSource::Env => { + // EXPERIMENTAL and not subject to semver. + match std::env::var("APOLLO_ROUTER_ENTITLEMENT").map(|e| Entitlement::from_str(&e)) + { + Ok(Ok(entitlement)) => stream::once(future::ready(entitlement)).boxed(), + Ok(Err(err)) => { + tracing::error!("Failed to parse entitlement: {}", err); + stream::empty().boxed() + } + Err(_) => stream::once(future::ready(Entitlement::default())).boxed(), + } + } + } + .expand_entitlements() + .chain(stream::iter(vec![NoMoreEntitlement])) + } +} diff --git a/apollo-router/src/router/event/mod.rs b/apollo-router/src/router/event/mod.rs new file mode 100644 index 0000000000..f6e2f391dd --- /dev/null +++ b/apollo-router/src/router/event/mod.rs @@ -0,0 +1,85 @@ +// With regards to ELv2 licensing, this entire file is license key functionality + +mod configuration; +mod entitlement; +mod reload; +mod schema; +mod shutdown; + +use std::fmt::Debug; +use std::fmt::Formatter; + +pub use configuration::ConfigurationSource; +pub use entitlement::EntitlementSource; +pub(crate) use reload::ReloadSource; +pub use schema::SchemaSource; +pub use shutdown::ShutdownSource; + +use self::Event::NoMoreConfiguration; +use self::Event::NoMoreEntitlement; +use self::Event::NoMoreSchema; +use self::Event::Reload; +use self::Event::Shutdown; +use self::Event::UpdateConfiguration; +use self::Event::UpdateEntitlement; +use self::Event::UpdateSchema; +use crate::uplink::entitlement::EntitlementState; +use crate::Configuration; + +/// Messages that are broadcast across the app. +pub(crate) enum Event { + /// The configuration was updated. + UpdateConfiguration(Configuration), + + /// There are no more updates to the configuration + NoMoreConfiguration, + + /// The schema was updated. + UpdateSchema(String), + + /// There are no more updates to the schema + NoMoreSchema, + + /// Update entitlement {} + UpdateEntitlement(EntitlementState), + + /// There were no more updates to entitlement. + NoMoreEntitlement, + + /// Artificial hot reload for chaos testing + Reload, + + /// The server should gracefully shutdown. + Shutdown, +} + +impl Debug for Event { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + UpdateConfiguration(_) => { + write!(f, "UpdateConfiguration()") + } + NoMoreConfiguration => { + write!(f, "NoMoreConfiguration") + } + UpdateSchema(_) => { + write!(f, "UpdateSchema()") + } + NoMoreSchema => { + write!(f, "NoMoreSchema") + } + UpdateEntitlement(e) => { + write!(f, "UpdateEntitlement({e:?})") + } + NoMoreEntitlement => { + write!(f, "NoMoreEntitlement") + } + Reload => { + write!(f, "ForcedHotReload") + } + Shutdown => { + write!(f, "Shutdown") + } + } + } +} diff --git a/apollo-router/src/router/event/reload.rs b/apollo-router/src/router/event/reload.rs new file mode 100644 index 0000000000..1f89ca3341 --- /dev/null +++ b/apollo-router/src/router/event/reload.rs @@ -0,0 +1,70 @@ +// With regards to ELv2 licensing, this entire file is license key functionality + +use std::sync::Arc; +use std::sync::Mutex; +use std::task::Poll; +use std::time::Duration; + +use futures::prelude::*; +use tokio_util::time::DelayQueue; + +use crate::router::Event; + +#[derive(Default)] +struct ReloadSourceInner { + queue: DelayQueue<()>, + period: Option, +} + +/// Reload source is an internal event emitter for the state machine that will send reload events on SIGUP and/or on a timer. +#[derive(Clone, Default)] +pub(crate) struct ReloadSource { + inner: Arc>, +} + +impl ReloadSource { + pub(crate) fn set_period(&self, period: &Option) { + let mut inner = self.inner.lock().unwrap(); + // Clear the queue before setting the period + inner.queue.clear(); + inner.period = *period; + if let Some(period) = period { + inner.queue.insert((), *period); + } + } + + pub(crate) fn into_stream(self) -> impl Stream { + #[cfg(unix)] + let signal_stream = { + let mut signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup()) + .expect("Failed to install SIGHUP signal handler"); + + futures::stream::poll_fn(move |cx| match signal.poll_recv(cx) { + Poll::Ready(Some(_)) => Poll::Ready(Some(Event::Reload)), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + }) + .boxed() + }; + #[cfg(not(unix))] + let signal_stream = futures::stream::empty().boxed(); + + let periodic_reload = futures::stream::poll_fn(move |cx| { + let mut inner = self.inner.lock().unwrap(); + match inner.queue.poll_expired(cx) { + Poll::Ready(Some(_expired)) => { + if let Some(period) = inner.period { + inner.queue.insert((), period); + } + Poll::Ready(Some(Event::Reload)) + } + // We must return pending even if the queue is empty, otherwise the stream will never be polled again + // The waker will still be used, so this won't end up in a hot loop. + Poll::Ready(None) => Poll::Pending, + Poll::Pending => Poll::Pending, + } + }); + + futures::stream::select(signal_stream, periodic_reload) + } +} diff --git a/apollo-router/src/router/event/schema.rs b/apollo-router/src/router/event/schema.rs new file mode 100644 index 0000000000..025d319e92 --- /dev/null +++ b/apollo-router/src/router/event/schema.rs @@ -0,0 +1,222 @@ +// With regards to ELv2 licensing, this entire file is license key functionality + +use std::path::PathBuf; +use std::pin::Pin; +use std::time::Duration; + +use derivative::Derivative; +use derive_more::Display; +use derive_more::From; +use futures::prelude::*; +use url::Url; + +use crate::router::Event; +use crate::router::Event::NoMoreSchema; +use crate::router::Event::UpdateSchema; +use crate::uplink::schema_stream::SupergraphSdlQuery; +use crate::uplink::stream_from_uplink; +use crate::uplink::Endpoints; + +type SchemaStream = Pin + Send>>; + +/// The user supplied schema. Either a static string or a stream for hot reloading. +#[derive(From, Display, Derivative)] +#[derivative(Debug)] +#[non_exhaustive] +pub enum SchemaSource { + /// A static schema. + #[display(fmt = "String")] + Static { schema_sdl: String }, + + /// A stream of schema. + #[display(fmt = "Stream")] + Stream(#[derivative(Debug = "ignore")] SchemaStream), + + /// A YAML file that may be watched for changes. + #[display(fmt = "File")] + File { + /// The path of the schema file. + path: PathBuf, + + /// `true` to watch the file for changes and hot apply them. + watch: bool, + + /// When watching, the delay to wait before applying the new schema. + /// Note: This variable is deprecated and has no effect. + #[deprecated] + delay: Option, + }, + + /// Apollo managed federation. + #[display(fmt = "Registry")] + Registry { + /// The Apollo key: `` + apollo_key: String, + + /// The apollo graph reference: `@` + apollo_graph_ref: String, + + /// The endpoint polled to fetch its latest supergraph schema. + urls: Option>, + + /// The duration between polling + poll_interval: Duration, + + /// The HTTP client timeout for each poll + timeout: Duration, + }, +} + +impl From<&'_ str> for SchemaSource { + fn from(s: &'_ str) -> Self { + Self::Static { + schema_sdl: s.to_owned(), + } + } +} + +impl SchemaSource { + /// Convert this schema into a stream regardless of if is static or not. Allows for unified handling later. + pub(crate) fn into_stream(self) -> impl Stream { + match self { + SchemaSource::Static { schema_sdl: schema } => { + stream::once(future::ready(UpdateSchema(schema))).boxed() + } + SchemaSource::Stream(stream) => stream.map(UpdateSchema).boxed(), + #[allow(deprecated)] + SchemaSource::File { + path, + watch, + delay: _, + } => { + // Sanity check, does the schema file exists, if it doesn't then bail. + if !path.exists() { + tracing::error!( + "Schema file at path '{}' does not exist.", + path.to_string_lossy() + ); + stream::empty().boxed() + } else { + //The schema file exists try and load it + match std::fs::read_to_string(&path) { + Ok(schema) => { + if watch { + crate::files::watch(&path) + .filter_map(move |_| { + let path = path.clone(); + async move { + match tokio::fs::read_to_string(&path).await { + Ok(schema) => Some(UpdateSchema(schema)), + Err(err) => { + tracing::error!("{}", err); + None + } + } + } + }) + .boxed() + } else { + stream::once(future::ready(UpdateSchema(schema))).boxed() + } + } + Err(err) => { + tracing::error!("Failed to read schema: {}", err); + stream::empty().boxed() + } + } + } + } + SchemaSource::Registry { + apollo_key, + apollo_graph_ref, + urls, + poll_interval, + timeout, + } => { + // With regards to ELv2 licensing, the code inside this block + // is license key functionality + stream_from_uplink::( + apollo_key, + apollo_graph_ref, + urls.map(Endpoints::fallback), + poll_interval, + timeout, + ) + .filter_map(|res| { + future::ready(match res { + Ok(schema) => Some(UpdateSchema(schema)), + Err(e) => { + tracing::error!("{}", e); + None + } + }) + }) + .boxed() + } + } + .chain(stream::iter(vec![NoMoreSchema])) + } +} + +#[cfg(test)] +mod tests { + use std::env::temp_dir; + + use test_log::test; + + use super::*; + use crate::files::tests::create_temp_file; + use crate::files::tests::write_and_flush; + + #[test(tokio::test)] + async fn schema_by_file_watching() { + let (path, mut file) = create_temp_file(); + let schema = include_str!("../../testdata/supergraph.graphql"); + write_and_flush(&mut file, schema).await; + let mut stream = SchemaSource::File { + path, + watch: true, + delay: None, + } + .into_stream() + .boxed(); + + // First update is guaranteed + assert!(matches!(stream.next().await.unwrap(), UpdateSchema(_))); + + // Need different contents, since we won't get an event if content is the same + let schema_minimal = include_str!("../../testdata/minimal_supergraph.graphql"); + // Modify the file and try again + write_and_flush(&mut file, schema_minimal).await; + assert!(matches!(stream.next().await.unwrap(), UpdateSchema(_))); + } + + #[test(tokio::test)] + async fn schema_by_file_no_watch() { + let (path, mut file) = create_temp_file(); + let schema = include_str!("../../testdata/supergraph.graphql"); + write_and_flush(&mut file, schema).await; + + let mut stream = SchemaSource::File { + path, + watch: false, + delay: None, + } + .into_stream(); + assert!(matches!(stream.next().await.unwrap(), UpdateSchema(_))); + assert!(matches!(stream.next().await.unwrap(), NoMoreSchema)); + } + + #[test(tokio::test)] + async fn schema_by_file_missing() { + let mut stream = SchemaSource::File { + path: temp_dir().join("does_not_exist"), + watch: true, + delay: None, + } + .into_stream(); + + // First update fails because the file is invalid. + assert!(matches!(stream.next().await.unwrap(), NoMoreSchema)); + } +} diff --git a/apollo-router/src/router/event/shutdown.rs b/apollo-router/src/router/event/shutdown.rs new file mode 100644 index 0000000000..3a3a2c1a94 --- /dev/null +++ b/apollo-router/src/router/event/shutdown.rs @@ -0,0 +1,68 @@ +// With regards to ELv2 licensing, this entire file is license key functionality + +use std::pin::Pin; + +use derivative::Derivative; +use derive_more::Display; +use futures::prelude::*; + +use crate::router::Event; +use crate::router::Event::Shutdown; + +type ShutdownFuture = Pin + Send>>; + +/// Specifies when the Router’s HTTP server should gracefully shutdown +#[derive(Display, Derivative)] +#[derivative(Debug)] +#[non_exhaustive] +pub enum ShutdownSource { + /// No graceful shutdown + #[display(fmt = "None")] + None, + + /// A custom shutdown future. + #[display(fmt = "Custom")] + Custom(#[derivative(Debug = "ignore")] ShutdownFuture), + + /// Watch for Ctl-C signal. + #[display(fmt = "CtrlC")] + CtrlC, +} + +impl ShutdownSource { + /// Convert this shutdown hook into a future. Allows for unified handling later. + pub(crate) fn into_stream(self) -> impl Stream { + match self { + ShutdownSource::None => stream::pending::().boxed(), + ShutdownSource::Custom(future) => future.map(|_| Shutdown).into_stream().boxed(), + ShutdownSource::CtrlC => { + #[cfg(not(unix))] + { + async { + tokio::signal::ctrl_c() + .await + .expect("Failed to install CTRL+C signal handler"); + } + .map(|_| Shutdown) + .into_stream() + .boxed() + } + + #[cfg(unix)] + future::select( + tokio::signal::ctrl_c().map(|s| s.ok()).boxed(), + async { + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .expect("Failed to install SIGTERM signal handler") + .recv() + .await + } + .boxed(), + ) + .map(|_| Shutdown) + .into_stream() + .boxed() + } + } + } +} diff --git a/apollo-router/src/router/mod.rs b/apollo-router/src/router/mod.rs new file mode 100644 index 0000000000..5061a57662 --- /dev/null +++ b/apollo-router/src/router/mod.rs @@ -0,0 +1,612 @@ +// With regards to ELv2 licensing, this entire file is license key functionality +#![allow(missing_docs)] // FIXME +#![allow(deprecated)] // Note: Required to prevents complaints on enum declaration + +mod error; +mod event; + +use std::pin::Pin; +use std::sync::Arc; +use std::task::Context; +use std::task::Poll; + +pub use error::ApolloRouterError; +pub use event::ConfigurationSource; +pub use event::EntitlementSource; +pub(crate) use event::Event; +pub(crate) use event::ReloadSource; +pub use event::SchemaSource; +pub use event::ShutdownSource; +#[cfg(test)] +use futures::channel::mpsc; +#[cfg(test)] +use futures::channel::mpsc::SendError; +use futures::channel::oneshot; +use futures::prelude::*; +use futures::FutureExt; +use http_body::Body as _; +use hyper::Body; +#[cfg(test)] +use tokio::sync::Notify; +use tokio::sync::RwLock; +use tokio::task::spawn; +use tower::BoxError; +use tower::ServiceExt; +use tracing_futures::WithSubscriber; + +use crate::axum_factory::make_axum_router; +use crate::axum_factory::AxumHttpServerFactory; +use crate::axum_factory::ListenAddrAndRouter; +use crate::configuration::Configuration; +use crate::configuration::ListenAddr; +use crate::orbiter::OrbiterRouterSuperServiceFactory; +use crate::plugin::DynPlugin; +use crate::router_factory::RouterFactory; +use crate::router_factory::RouterSuperServiceFactory; +use crate::router_factory::YamlRouterFactory; +use crate::services::router; +use crate::state_machine::ListenAddresses; +use crate::state_machine::StateMachine; +use crate::uplink::entitlement::EntitlementState; + +// For now this is unused: +// TODO: Check with simon once the refactor is complete +#[allow(unused)] +// Later we might add a public API for this (probably a builder similar to `test_harness.rs`), +// see https://github.com/apollographql/router/issues/1496. +// In the meantime keeping this function helps make sure it still compiles. +async fn make_router_service( + schema: &str, + configuration: Arc, + extra_plugins: Vec<(String, Box)>, + entitlement: EntitlementState, +) -> Result { + let service_factory = YamlRouterFactory + .create( + configuration.clone(), + schema.to_string(), + None, + Some(extra_plugins), + ) + .await?; + let web_endpoints = service_factory.web_endpoints(); + let routers = make_axum_router(service_factory, &configuration, web_endpoints, entitlement)?; + let ListenAddrAndRouter(_listener, router) = routers.main; + + Ok(router + .map_request(|req: router::Request| req.router_request) + .map_err(|error| match error {}) + .map_response(|res| { + res.map(|body| { + // Axum makes this `body` have type: + // https://docs.rs/http-body/0.4.5/http_body/combinators/struct.UnsyncBoxBody.html + let mut body = Box::pin(body); + // We make a stream based on its `poll_data` method + // in order to create a `hyper::Body`. + Body::wrap_stream(stream::poll_fn(move |ctx| body.as_mut().poll_data(ctx))) + // … but we ignore the `poll_trailers` method: + // https://docs.rs/http-body/0.4.5/http_body/trait.Body.html#tymethod.poll_trailers + // Apparently HTTP/2 trailers are like headers, except after the response body. + // I (Simon) believe nothing in the Apollo Router uses trailers as of this writing, + // so ignoring `poll_trailers` is fine. + // If we want to use trailers, we may need remove this convertion to `hyper::Body` + // and return `UnsyncBoxBody` (a.k.a. `axum::BoxBody`) as-is. + }) + .into() + }) + .boxed_clone()) +} +/// The entry point for running the Router’s HTTP server. +/// +/// # Examples +/// +/// ``` +/// use apollo_router::RouterHttpServer; +/// use apollo_router::Configuration; +/// +/// async { +/// let configuration = serde_yaml::from_str::("Config").unwrap(); +/// let schema = "schema"; +/// RouterHttpServer::builder() +/// .configuration(configuration) +/// .schema(schema) +/// .start() +/// .await; +/// }; +/// ``` +/// +/// Shutdown via handle. +/// ``` +/// use apollo_router::RouterHttpServer; +/// use apollo_router::Configuration; +/// +/// async { +/// let configuration = serde_yaml::from_str::("Config").unwrap(); +/// let schema = "schema"; +/// let mut server = RouterHttpServer::builder() +/// .configuration(configuration) +/// .schema(schema) +/// .start(); +/// // … +/// server.shutdown().await +/// }; +/// ``` +/// +pub struct RouterHttpServer { + result: Pin> + Send>>, + listen_addresses: Arc>, + shutdown_sender: Option>, +} + +#[buildstructor::buildstructor] +impl RouterHttpServer { + /// Returns a builder to start an HTTP server in a separate Tokio task. + /// + /// Builder methods: + /// + /// * `.schema(impl Into<`[`SchemaSource`]`>)` + /// Required. + /// Specifies where to find the supergraph schema definition. + /// Some sources support hot-reloading. + /// + /// * `.configuration(impl Into<`[`ConfigurationSource`]`>)` + /// Optional. + /// Specifies where to find the router configuration. + /// If not provided, the default configuration as with an empty YAML file. + /// + /// * `.entitlement(impl Into<`[`EntitlementSource`]`>)` + /// Optional. + /// Specifies where to find the router entitlement which controls if commercial features are enabled or not. + /// If not provided then commercial features will not be enabled. + /// + /// * `.shutdown(impl Into<`[`ShutdownSource`]`>)` + /// Optional. + /// Specifies when the server should gracefully shut down. + /// If not provided, the default is [`ShutdownSource::CtrlC`]. + /// + /// * `.start()` + /// Finishes the builder, + /// starts an HTTP server in a separate Tokio task, + /// and returns a `RouterHttpServer` handle. + /// + /// The server handle can be used in multiple ways. + /// As a [`Future`], it resolves to `Result<(), `[`ApolloRouterError`]`>` + /// either when the server has finished gracefully shutting down + /// or when it encounters a fatal error that prevents it from starting. + /// + /// If the handle is dropped before being awaited as a future, + /// a graceful shutdown is triggered. + /// In order to wait until shutdown finishes, + /// use the [`shutdown`][Self::shutdown] method instead. + #[builder(visibility = "pub", entry = "builder", exit = "start")] + fn start( + schema: SchemaSource, + configuration: Option, + entitlement: Option, + shutdown: Option, + ) -> RouterHttpServer { + let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); + let event_stream = generate_event_stream( + shutdown.unwrap_or(ShutdownSource::CtrlC), + configuration.unwrap_or_default(), + schema, + entitlement.unwrap_or_default(), + shutdown_receiver, + ); + let server_factory = AxumHttpServerFactory::new(); + let router_factory = OrbiterRouterSuperServiceFactory::new(YamlRouterFactory::default()); + let state_machine = StateMachine::new(server_factory, router_factory); + let listen_addresses = state_machine.listen_addresses.clone(); + let result = spawn( + async move { state_machine.process_events(event_stream).await } + .with_current_subscriber(), + ) + .map(|r| match r { + Ok(Ok(ok)) => Ok(ok), + Ok(Err(err)) => Err(err), + Err(err) => { + tracing::error!("{}", err); + Err(ApolloRouterError::StartupError) + } + }) + .with_current_subscriber() + .boxed(); + + RouterHttpServer { + result, + shutdown_sender: Some(shutdown_sender), + listen_addresses, + } + } + + /// Returns the listen address when the router is ready to receive GraphQL requests. + /// + /// This can be useful when the `server.listen` configuration specifies TCP port 0, + /// which instructs the operating system to pick an available port number. + /// + /// Note: if configuration is dynamic, the listen address can change over time. + pub async fn listen_address(&self) -> Option { + self.listen_addresses + .read() + .await + .graphql_listen_address + .clone() + } + + /// Returns the extra listen addresses the router can receive requests to. + /// + /// Combine it with `listen_address` to have an exhaustive list + /// of all addresses used by the router. + /// Note: if configuration is dynamic, the listen address can change over time. + pub async fn extra_listen_adresses(&self) -> Vec { + self.listen_addresses + .read() + .await + .extra_listen_addresses + .clone() + } + + /// Trigger and wait for graceful shutdown + pub async fn shutdown(&mut self) -> Result<(), ApolloRouterError> { + if let Some(sender) = self.shutdown_sender.take() { + let _ = sender.send(()); + } + (&mut self.result).await + } +} + +impl Drop for RouterHttpServer { + fn drop(&mut self) { + if let Some(sender) = self.shutdown_sender.take() { + let _ = sender.send(()); + } + } +} + +impl Future for RouterHttpServer { + type Output = Result<(), ApolloRouterError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.result.poll_unpin(cx) + } +} + +/// Create the unified event stream. +/// This merges all contributing streams and sets up shutdown handling. +/// When a shutdown message is received no more events are emitted. +fn generate_event_stream( + shutdown: ShutdownSource, + configuration: ConfigurationSource, + schema: SchemaSource, + entitlement: EntitlementSource, + shutdown_receiver: oneshot::Receiver<()>, +) -> impl Stream { + let reload_source = ReloadSource::default(); + + let stream = stream::select_all(vec![ + shutdown.into_stream().boxed(), + schema.into_stream().boxed(), + entitlement.into_stream().boxed(), + reload_source.clone().into_stream().boxed(), + configuration + .into_stream() + .map(move |config_event| { + if let Event::UpdateConfiguration(config) = &config_event { + reload_source.set_period(&config.experimental_chaos.force_reload) + } + config_event + }) + .boxed(), + shutdown_receiver + .into_stream() + .map(|_| Event::Shutdown) + .boxed(), + ]) + .take_while(|msg| future::ready(!matches!(msg, Event::Shutdown))) + // Chain is required so that the final shutdown message is sent. + .chain(stream::iter(vec![Event::Shutdown])) + .boxed(); + stream +} + +#[cfg(test)] +struct TestRouterHttpServer { + router_http_server: RouterHttpServer, + event_sender: mpsc::UnboundedSender, + state_machine_update_notifier: Arc, +} + +#[cfg(test)] +impl TestRouterHttpServer { + fn new() -> Self { + let (event_sender, event_receiver) = mpsc::unbounded(); + let state_machine_update_notifier = Arc::new(Notify::new()); + + let server_factory = AxumHttpServerFactory::new(); + let router_factory: OrbiterRouterSuperServiceFactory = + OrbiterRouterSuperServiceFactory::new(YamlRouterFactory::default()); + let state_machine = StateMachine::for_tests( + server_factory, + router_factory, + Arc::clone(&state_machine_update_notifier), + ); + + let listen_addresses = state_machine.listen_addresses.clone(); + let result = spawn( + async move { state_machine.process_events(event_receiver).await } + .with_current_subscriber(), + ) + .map(|r| match r { + Ok(Ok(ok)) => Ok(ok), + Ok(Err(err)) => Err(err), + Err(err) => { + tracing::error!("{}", err); + Err(ApolloRouterError::StartupError) + } + }) + .with_current_subscriber() + .boxed(); + + TestRouterHttpServer { + router_http_server: RouterHttpServer { + result, + shutdown_sender: None, + listen_addresses, + }, + event_sender, + state_machine_update_notifier, + } + } + + async fn request( + &self, + request: crate::graphql::Request, + ) -> Result { + Ok(reqwest::Client::new() + .post(format!("{}/", self.listen_address().await.unwrap())) + .json(&request) + .send() + .await + .expect("couldn't send request") + .json() + .await + .expect("couldn't deserialize into json")) + } + + async fn listen_address(&self) -> Option { + self.router_http_server.listen_address().await + } + + async fn send_event(&mut self, event: Event) -> Result<(), SendError> { + let result = self.event_sender.send(event).await; + self.state_machine_update_notifier.notified().await; + result + } + + async fn shutdown(mut self) -> Result<(), ApolloRouterError> { + self.send_event(Event::Shutdown).await.unwrap(); + self.router_http_server.shutdown().await + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use serde_json::to_string_pretty; + + use super::*; + use crate::graphql; + use crate::graphql::Request; + use crate::router::Event::UpdateConfiguration; + use crate::router::Event::UpdateEntitlement; + use crate::router::Event::UpdateSchema; + + fn init_with_server() -> RouterHttpServer { + let configuration = + Configuration::from_str(include_str!("../testdata/supergraph_config.router.yaml")) + .unwrap(); + let schema = include_str!("../testdata/supergraph.graphql"); + RouterHttpServer::builder() + .configuration(configuration) + .schema(schema) + .start() + } + + #[tokio::test(flavor = "multi_thread")] + async fn basic_request() { + let mut router_handle = init_with_server(); + let listen_address = router_handle + .listen_address() + .await + .expect("router failed to start"); + + assert_federated_response(&listen_address, r#"{ topProducts { name } }"#).await; + router_handle.shutdown().await.unwrap(); + } + + async fn assert_federated_response(listen_addr: &ListenAddr, request: &str) { + let request = Request::builder().query(request).build(); + let expected = query(listen_addr, &request).await.unwrap(); + + let response = to_string_pretty(&expected).unwrap(); + assert!(!response.is_empty()); + } + + async fn query( + listen_addr: &ListenAddr, + request: &graphql::Request, + ) -> Result { + Ok(reqwest::Client::new() + .post(format!("{listen_addr}/")) + .json(request) + .send() + .await + .expect("couldn't send request") + .json() + .await + .expect("couldn't deserialize into json")) + } + + #[tokio::test(flavor = "multi_thread")] + async fn basic_event_stream_test() { + let mut router_handle = TestRouterHttpServer::new(); + + let configuration = + Configuration::from_str(include_str!("../testdata/supergraph_config.router.yaml")) + .unwrap(); + let schema = include_str!("../testdata/supergraph.graphql"); + + // let's push a valid configuration to the state machine, so it can start up + router_handle + .send_event(UpdateConfiguration(configuration)) + .await + .unwrap(); + router_handle + .send_event(UpdateSchema(schema.to_string())) + .await + .unwrap(); + router_handle + .send_event(UpdateEntitlement(EntitlementState::Unentitled)) + .await + .unwrap(); + + let request = Request::builder().query(r#"{ me { username } }"#).build(); + + let response = router_handle.request(request).await.unwrap(); + assert_eq!( + "@ada", + response + .data + .unwrap() + .get("me") + .unwrap() + .get("username") + .unwrap() + ); + + // shut the router down + router_handle + .send_event(Event::NoMoreConfiguration) + .await + .unwrap(); + router_handle.send_event(Event::NoMoreSchema).await.unwrap(); + router_handle.send_event(Event::Shutdown).await.unwrap(); + } + + #[tokio::test(flavor = "multi_thread")] + async fn schema_update_test() { + let mut router_handle = TestRouterHttpServer::new(); + // let's push a valid configuration to the state machine, so it can start up + router_handle + .send_event(UpdateConfiguration( + Configuration::from_str(include_str!("../testdata/supergraph_config.router.yaml")) + .unwrap(), + )) + .await + .unwrap(); + router_handle + .send_event(UpdateSchema( + include_str!("../testdata/supergraph_missing_name.graphql").to_string(), + )) + .await + .unwrap(); + router_handle + .send_event(UpdateEntitlement(EntitlementState::Unentitled)) + .await + .unwrap(); + + // let's send a valid query + let request = Request::builder().query(r#"{ me { username } }"#).build(); + let response = router_handle.request(request).await.unwrap(); + + assert_eq!( + "@ada", + response + .data + .unwrap() + .get("me") + .unwrap() + .get("username") + .unwrap() + ); + + // the name field is not present yet + let request = Request::builder() + .query(r#"{ me { username name } }"#) + .build(); + let response = router_handle.request(request).await.unwrap(); + + assert_eq!( + "cannot query field 'name' on type 'User'", + response.errors[0].message + ); + assert_eq!( + "INVALID_FIELD", + response.errors[0].extensions.get("code").unwrap() + ); + + // let's update the schema to add the field + router_handle + .send_event(UpdateSchema( + include_str!("../testdata/supergraph.graphql").to_string(), + )) + .await + .unwrap(); + + // the request should now make it through + let request = Request::builder() + .query(r#"{ me { username name } }"#) + .build(); + + let response = router_handle.request(request).await.unwrap(); + + assert_eq!( + "Ada Lovelace", + response + .data + .unwrap() + .get("me") + .unwrap() + .get("name") + .unwrap() + ); + + // let's go back and remove the field + router_handle + .send_event(UpdateSchema( + include_str!("../testdata/supergraph_missing_name.graphql").to_string(), + )) + .await + .unwrap(); + + let request = Request::builder().query(r#"{ me { username } }"#).build(); + let response = router_handle.request(request).await.unwrap(); + + assert_eq!( + "@ada", + response + .data + .unwrap() + .get("me") + .unwrap() + .get("username") + .unwrap() + ); + + let request = Request::builder() + .query(r#"{ me { username name } }"#) + .build(); + let response = router_handle.request(request).await.unwrap(); + + assert_eq!( + "cannot query field 'name' on type 'User'", + response.errors[0].message + ); + assert_eq!( + "INVALID_FIELD", + response.errors[0].extensions.get("code").unwrap() + ); + router_handle.shutdown().await.unwrap(); + } +}