diff --git a/.changeset/nice-birds-mate.md b/.changeset/nice-birds-mate.md deleted file mode 100644 index 342463d76a..0000000000 --- a/.changeset/nice-birds-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'hive': patch ---- - -Move information about target and organization to logger's metadata in usage service diff --git a/.changeset/pretty-planes-jog.md b/.changeset/pretty-planes-jog.md deleted file mode 100644 index 7de8ee9362..0000000000 --- a/.changeset/pretty-planes-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'hive': patch ---- - -Make optional properties optional or nullable in usage report v2 diff --git a/.changeset/unlucky-pianos-hide.md b/.changeset/unlucky-pianos-hide.md deleted file mode 100644 index a8846f5802..0000000000 --- a/.changeset/unlucky-pianos-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'hive': patch ---- - -Fix a missing @join\_\_field on a query field where @override is used, but not in all subgraphs. diff --git a/configs/cargo/Cargo.lock b/configs/cargo/Cargo.lock index 6f5220e7a4..bd4e4af86d 100644 --- a/configs/cargo/Cargo.lock +++ b/configs/cargo/Cargo.lock @@ -170,7 +170,7 @@ dependencies = [ "apollo-compiler", "apollo-federation", "arc-swap", - "async-channel", + "async-channel 1.9.0", "async-compression", "async-trait", "aws-config", @@ -220,7 +220,7 @@ dependencies = [ "itoa", "jsonpath-rust", "jsonpath_lib", - "jsonschema", + "jsonschema 0.17.1", "jsonwebtoken", "lazy_static", "libc", @@ -247,7 +247,7 @@ dependencies = [ "opentelemetry-zipkin", "opentelemetry_api", "opentelemetry_sdk", - "parking_lot", + "parking_lot 0.12.3", "paste", "pin-project-lite", "prometheus", @@ -333,6 +333,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -343,6 +352,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -350,10 +369,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-compression" version = "0.4.6" @@ -368,6 +399,138 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.0.0", + "futures-lite 2.0.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite 2.0.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.0.0", + "parking", + "polling", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-object-pool" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel 2.3.1", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.1", + "futures-lite 2.0.0", + "rustix 0.38.34", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.34", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.0.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -390,6 +553,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.77" @@ -401,6 +570,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -800,6 +975,17 @@ dependencies = [ "vsimd", ] +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "basic-toml" version = "0.1.9" @@ -815,7 +1001,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -824,6 +1019,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -845,17 +1046,36 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.0.0", + "piper", +] + [[package]] name = "bloomfilter" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b64d54e47a7f4fd723f082e8f11429f3df6ba8adaeca355a76556f9f0602bbcf" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", "getrandom 0.2.10", - "siphasher", + "siphasher 1.0.0", ] +[[package]] +name = "borrow-or-share" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" + [[package]] name = "brotli" version = "3.5.0" @@ -971,7 +1191,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -1085,9 +1305,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1283,7 +1503,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.8", "serde", ] @@ -1358,7 +1578,7 @@ dependencies = [ "libc", "log", "once_cell", - "parking_lot", + "parking_lot 0.12.3", "pin-project", "serde", "serde_json", @@ -1488,6 +1708,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.4.1" @@ -1500,6 +1730,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.4" @@ -1535,6 +1776,24 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1603,16 +1862,48 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + [[package]] name = "fancy-regex" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ - "bit-set", + "bit-set 0.5.3", "regex", ] +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +dependencies = [ + "bit-set 0.8.0", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1666,6 +1957,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fluent-uri" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1697,6 +1999,16 @@ dependencies = [ "num", ] +[[package]] +name = "fraction" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +dependencies = [ + "lazy_static", + "num", +] + [[package]] name = "fragile" version = "2.0.0" @@ -1717,7 +2029,7 @@ dependencies = [ "futures", "lazy_static", "log", - "parking_lot", + "parking_lot 0.12.3", "rand 0.8.5", "redis-protocol", "rustls 0.21.12", @@ -1816,6 +2128,21 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-lite" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1155db57329dca6d018b61e76b1488ce9a2e5e44028cac420a5898f4fcef63" +dependencies = [ + "fastrand 2.0.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -1927,6 +2254,18 @@ dependencies = [ "regex", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "graphql-introspection-query" version = "0.2.0" @@ -2116,6 +2455,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -2161,7 +2506,7 @@ dependencies = [ "ipconfig", "lru-cache", "once_cell", - "parking_lot", + "parking_lot 0.12.3", "rand 0.8.5", "resolv-conf", "smallvec", @@ -2181,10 +2526,16 @@ dependencies = [ "graphql-parser-hive-fork", "graphql-tools", "http 0.2.11", + "httpmock", + "hyper 0.14.31", + "jsonschema 0.26.1", + "lazy_static", "lru", "md5", "rand 0.8.5", "reqwest 0.12.5", + "reqwest-middleware", + "reqwest-retry", "schemars", "serde", "serde_json", @@ -2303,9 +2654,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" dependencies = [ "anyhow", - "async-channel", + "async-channel 1.9.0", "base64 0.13.1", - "futures-lite", + "futures-lite 1.13.0", "http 0.2.11", "infer", "pin-project-lite", @@ -2329,6 +2680,34 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.31", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + [[package]] name = "humantime" version = "2.1.0" @@ -2696,6 +3075,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2750,7 +3132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.1", - "rustix 0.38.28", + "rustix 0.38.34", "windows-sys 0.48.0", ] @@ -2765,9 +3147,18 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] @@ -2848,15 +3239,15 @@ dependencies = [ "anyhow", "base64 0.21.7", "bytecount", - "fancy-regex", - "fraction", + "fancy-regex 0.11.0", + "fraction 0.13.1", "getrandom 0.2.10", "iso8601", "itoa", "memchr", "num-cmp", "once_cell", - "parking_lot", + "parking_lot 0.12.3", "percent-encoding", "regex", "serde", @@ -2866,6 +3257,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "jsonschema" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "893d6229c7315763ca0df9b29ab7661ee419f286577a02847c5521b462e071af" +dependencies = [ + "ahash", + "base64 0.22.1", + "bytecount", + "email_address", + "fancy-regex 0.14.0", + "fraction 0.15.3", + "idna", + "itoa", + "num-cmp", + "once_cell", + "percent-encoding", + "referencing", + "regex-syntax 0.8.5", + "serde", + "serde_json", + "uuid-simd", +] + [[package]] name = "jsonwebtoken" version = "9.3.0" @@ -2901,6 +3316,46 @@ dependencies = [ "libc", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set 0.5.3", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.8.5", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.9", +] + [[package]] name = "lazy-regex" version = "2.5.0" @@ -2926,9 +3381,15 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "levenshtein" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" @@ -3008,6 +3469,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", + "value-bag", ] [[package]] @@ -3192,6 +3654,12 @@ dependencies = [ "serde", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nom" version = "7.1.3" @@ -3240,9 +3708,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -3254,11 +3722,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", "rand 0.8.5", @@ -3272,9 +3739,9 @@ checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" [[package]] name = "num-complex" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -3287,19 +3754,18 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -3308,11 +3774,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -3357,9 +3822,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl-probe" @@ -3575,6 +4040,17 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -3582,7 +4058,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -3676,6 +4166,21 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.0" @@ -3708,6 +4213,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.0.0", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -3725,6 +4241,21 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3737,6 +4268,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "3.1.2" @@ -3802,7 +4339,7 @@ dependencies = [ "fnv", "lazy_static", "memchr", - "parking_lot", + "parking_lot 0.12.3", "protobuf", "thiserror", ] @@ -4090,6 +4627,39 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "referencing" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb853437e467c693ac1dc8c1520105a31b8c2588544ff2f3cfa5a7c706c6c069" +dependencies = [ + "ahash", + "fluent-uri", + "once_cell", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" version = "1.11.1" @@ -4228,6 +4798,43 @@ dependencies = [ "winreg 0.52.0", ] +[[package]] +name = "reqwest-middleware" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3" +dependencies = [ + "anyhow", + "async-trait", + "http 1.0.0", + "reqwest 0.12.5", + "serde", + "thiserror", + "tower-service", +] + +[[package]] +name = "reqwest-retry" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "getrandom 0.2.10", + "http 1.0.0", + "hyper 1.4.1", + "parking_lot 0.11.2", + "reqwest 0.12.5", + "reqwest-middleware", + "retry-policies", + "thiserror", + "tokio", + "tracing", + "wasm-timer", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -4244,6 +4851,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "rhai" version = "1.19.0" @@ -4320,7 +4936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bcc6f2aa0c619a4fb74ce271873a500f5640c257ca2e7aa8ea6be6226262855" dependencies = [ "anyhow", - "async-channel", + "async-channel 1.9.0", "deno_console", "deno_core", "deno_url", @@ -4437,9 +5053,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.4.0", "errno", @@ -4746,6 +5362,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4883,6 +5509,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "siphasher" version = "1.0.0" @@ -4978,6 +5610,19 @@ dependencies = [ "regex", ] +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot 0.12.3", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.11.1" @@ -5132,6 +5777,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termtree" version = "0.4.1" @@ -5303,7 +5959,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -5776,6 +6432,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unreachable" version = "1.0.0" @@ -5857,6 +6519,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "uuid", + "vsimd", +] + [[package]] name = "v8" version = "0.74.3" @@ -5875,6 +6548,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "version_check" version = "0.9.4" @@ -5901,9 +6584,9 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -6009,6 +6692,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.64" @@ -6043,7 +6741,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.28", + "rustix 0.38.34", ] [[package]] @@ -6089,7 +6787,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -6131,7 +6829,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -6166,17 +6873,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -6193,9 +6901,9 @@ checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -6211,9 +6919,9 @@ checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -6229,9 +6937,15 @@ checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -6247,9 +6961,9 @@ checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -6265,9 +6979,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -6283,9 +6997,9 @@ checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -6301,9 +7015,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" diff --git a/deployment/CHANGELOG.md b/deployment/CHANGELOG.md index 7ae3c460ae..824bdcfe44 100644 --- a/deployment/CHANGELOG.md +++ b/deployment/CHANGELOG.md @@ -1,5 +1,24 @@ # hive +## 1.2.3 + +### Patch Changes + +- [#6115](https://github.com/graphql-hive/console/pull/6115) + [`0d7ce02`](https://github.com/graphql-hive/console/commit/0d7ce02082a5ac02111b888132209ee0ef34c831) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Move information about target and + organization to logger's metadata in usage service + +- [#6121](https://github.com/graphql-hive/console/pull/6121) + [`6d78547`](https://github.com/graphql-hive/console/commit/6d78547a0f29a732713052d33d207396144e0998) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Make optional properties optional or + nullable in usage report v2 + +- [#6111](https://github.com/graphql-hive/console/pull/6111) + [`cffd08a`](https://github.com/graphql-hive/console/commit/cffd08a53d7e5a53bb59fa68e940b693e9102485) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix a missing @join\_\_field on a query + field where @override is used, but not in all subgraphs. + ## 1.2.2 ### Patch Changes diff --git a/deployment/package.json b/deployment/package.json index 61b505878c..b88c924ed2 100644 --- a/deployment/package.json +++ b/deployment/package.json @@ -1,6 +1,6 @@ { "name": "hive", - "version": "1.2.2", + "version": "1.2.3", "private": true, "scripts": { "generate": "tsx generate.ts", diff --git a/packages/libraries/router/CHANGELOG.md b/packages/libraries/router/CHANGELOG.md index 267a0ede9d..d1b7d18dcd 100644 --- a/packages/libraries/router/CHANGELOG.md +++ b/packages/libraries/router/CHANGELOG.md @@ -1,5 +1,21 @@ # 16.10.2024 +## 1.1.0 + +### Minor Changes + +- [#5732](https://github.com/graphql-hive/console/pull/5732) + [`1d3c566`](https://github.com/graphql-hive/console/commit/1d3c566ddcf5eb31c68545931da32bcdf4b8a047) + Thanks [@dotansimha](https://github.com/dotansimha)! - Updated Apollo-Router custom plugin for + Hive to use Usage reporting spec v2. + [Learn more](https://the-guild.dev/graphql/hive/docs/specs/usage-reports) + +- [#5732](https://github.com/graphql-hive/console/pull/5732) + [`1d3c566`](https://github.com/graphql-hive/console/commit/1d3c566ddcf5eb31c68545931da32bcdf4b8a047) + Thanks [@dotansimha](https://github.com/dotansimha)! - Add support for persisted documents using + Hive App Deployments. + [Learn more](https://the-guild.dev/graphql/hive/product-updates/2024-07-30-persisted-documents-app-deployments-preview) + ## 1.0.1 ### Patch Changes diff --git a/packages/libraries/router/Cargo.toml b/packages/libraries/router/Cargo.toml index e2b491b79a..990e7ca2bd 100644 --- a/packages/libraries/router/Cargo.toml +++ b/packages/libraries/router/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/graphql-hive/console/" edition = "2021" license = "MIT" publish = true -version = "1.0.1" +version = "1.1.0" description = "Apollo-Router Plugin for Hive" [[bin]] @@ -18,11 +18,18 @@ path = "src/lib.rs" [dependencies] apollo-router = { version = "^1.13.0" } -thiserror = "1.0.57" -reqwest = { version = "0.12.0", default-features = false, features = ["rustls-tls", "blocking", "json"] } +thiserror = "1.0.62" +reqwest = { version = "0.12.0", default-features = false, features = [ + "rustls-tls", + "blocking", + "json", +] } +reqwest-retry = "0.7.0" +reqwest-middleware = "0.4.0" sha2 = { version = "0.10.8", features = ["std"] } anyhow = "1" tracing = "0.1" +hyper = { version = "0.14.28", features = ["server", "client", "stream"] } async-trait = "0.1.77" futures = { version = "0.3.30", features = ["thread-pool"] } schemars = { version = "0.8", features = ["url"] } @@ -32,7 +39,16 @@ tokio = { version = "1.36.0", features = ["full"] } tower = { version = "0.4.13", features = ["full"] } http = "0.2" graphql-parser = { version = "0.5.0", package = "graphql-parser-hive-fork" } -graphql-tools = { version = "0.4.0", features = ["graphql_parser_fork"], default-features = false } +graphql-tools = { version = "0.4.0", features = [ + "graphql_parser_fork", +], default-features = false } lru = "^0.12.1" md5 = "0.7.0" rand = "0.8.5" + +[dev-dependencies] +httpmock = "0.7.0" +jsonschema = { version = "0.26.1", default-features = false, features = [ + "resolve-file", +] } +lazy_static = "1.5.0" diff --git a/packages/libraries/router/README.md b/packages/libraries/router/README.md index 3b81a1928a..f2440f6584 100644 --- a/packages/libraries/router/README.md +++ b/packages/libraries/router/README.md @@ -13,6 +13,8 @@ At the moment, the following are implemented: - [Fetching Supergraph from Hive CDN](https://the-guild.dev/graphql/hive/docs/high-availability-cdn) - [Sending usage information](https://the-guild.dev/graphql/hive/docs/schema-registry/usage-reporting) from a running Apollo Router instance to Hive +- Persisted Operations using Hive's + [App Deployments](https://the-guild.dev/graphql/hive/docs/schema-registry/app-deployments) This project is constructed as a Rust project that implements Apollo-Router plugin interface. diff --git a/packages/libraries/router/package.json b/packages/libraries/router/package.json index a308bf6cf8..78b6c5196e 100644 --- a/packages/libraries/router/package.json +++ b/packages/libraries/router/package.json @@ -1,6 +1,6 @@ { "name": "hive-apollo-router-plugin", - "version": "1.0.1", + "version": "1.1.0", "private": true, "scripts": { "sync-cargo-file": "./sync-cargo-file.sh" diff --git a/packages/libraries/router/router.yaml b/packages/libraries/router/router.yaml index 8053a916a8..615c01d511 100644 --- a/packages/libraries/router/router.yaml +++ b/packages/libraries/router/router.yaml @@ -7,3 +7,4 @@ supergraph: introspection: true plugins: hive.usage: {} + hive.persisted_documents: {} diff --git a/packages/libraries/router/src/agent.rs b/packages/libraries/router/src/agent.rs index 1f63b99d53..c183cdd88a 100644 --- a/packages/libraries/router/src/agent.rs +++ b/packages/libraries/router/src/agent.rs @@ -23,6 +23,7 @@ pub struct Report { #[derive(Serialize, Debug)] struct OperationMapRecord { operation: String, + #[serde(skip_serializing_if = "Option::is_none")] operationName: Option, fields: Vec, } @@ -33,7 +34,10 @@ struct Operation { operationMapKey: String, timestamp: u64, execution: Execution, + #[serde(skip_serializing_if = "Option::is_none")] metadata: Option, + #[serde(skip_serializing_if = "Option::is_none")] + persistedDocumentHash: Option, } #[allow(non_snake_case)] @@ -46,12 +50,15 @@ struct Execution { #[derive(Serialize, Debug)] struct Metadata { + #[serde(skip_serializing_if = "Option::is_none")] client: Option, } #[derive(Serialize, Debug)] struct ClientInfo { + #[serde(skip_serializing_if = "Option::is_none")] name: Option, + #[serde(skip_serializing_if = "Option::is_none")] version: Option, } @@ -65,6 +72,7 @@ pub struct ExecutionReport { pub errors: usize, pub operation_body: String, pub operation_name: Option, + pub persisted_document_hash: Option, } #[derive(Debug, Clone)] @@ -145,6 +153,7 @@ impl UsageAgent { connect_timeout: u64, request_timeout: u64, accept_invalid_certs: bool, + flush_interval: Duration, ) -> Self { let schema = parse_schema::(&schema) .expect("Failed to parse schema") @@ -173,7 +182,7 @@ impl UsageAgent { tokio::task::spawn(async move { loop { - tokio::time::sleep(Duration::from_secs(5)).await; + tokio::time::sleep(flush_interval).await; let agent_ref = agent_for_interval.lock().await.clone(); tokio::task::spawn(async move { @@ -197,21 +206,13 @@ impl UsageAgent { let operation = self .processor .lock() - .map_err(|e| { - AgentError::Lock( - e.to_string(), - ) - })? + .map_err(|e| AgentError::Lock(e.to_string()))? .process( &op.operation_body, &self .state .lock() - .map_err(|e| { - AgentError::Lock( - e.to_string(), - ) - })? + .map_err(|e| AgentError::Lock(e.to_string()))? .schema, ); match operation { @@ -230,6 +231,21 @@ impl UsageAgent { match operation { Some(operation) => { let hash = operation.hash; + + let client_name = non_empty_string(op.client_name); + let client_version = non_empty_string(op.client_version); + + let metadata: Option = + if client_name.is_some() || client_version.is_some() { + Some(Metadata { + client: Some(ClientInfo { + name: client_name, + version: client_version, + }), + }) + } else { + None + }; report.operations.push(Operation { operationMapKey: hash.clone(), timestamp: op.timestamp, @@ -238,12 +254,8 @@ impl UsageAgent { duration: op.duration.as_nanos(), errorsTotal: op.errors, }, - metadata: Some(Metadata { - client: Some(ClientInfo { - name: non_empty_string(op.client_name), - version: non_empty_string(op.client_version), - }), - }), + persistedDocumentHash: op.persisted_document_hash, + metadata, }); if !report.map.contains_key(&hash) { report.map.insert( @@ -272,9 +284,7 @@ impl UsageAgent { let size = self .state .lock() - .map_err(|e| { - AgentError::Lock(e.to_string()) - })? + .map_err(|e| AgentError::Lock(e.to_string()))? .push(execution_report); self.flush_if_full(size)?; @@ -285,13 +295,14 @@ impl UsageAgent { pub async fn send_report(&self, report: Report) -> Result<(), AgentError> { const DELAY_BETWEEN_TRIES: Duration = Duration::from_millis(500); const MAX_TRIES: u8 = 3; - let mut error_message = "unexpected error".to_string(); for _ in 0..MAX_TRIES { + // Based on https://the-guild.dev/graphql/hive/docs/specs/usage-reports#data-structure let resp = self .client .post(self.endpoint.clone()) + .header("X-Usage-API-Version", "2") .header( reqwest::header::AUTHORIZATION, format!("Bearer {}", self.token.clone()), diff --git a/packages/libraries/router/src/graphql.rs b/packages/libraries/router/src/graphql.rs index a526c6a87c..9ef413802c 100644 --- a/packages/libraries/router/src/graphql.rs +++ b/packages/libraries/router/src/graphql.rs @@ -236,7 +236,6 @@ impl<'a> OperationVisitor<'a, SchemaCoordinatesContext> for SchemaCoordinatesVis match arg_value { Value::Enum(value) => { let value_str = value.to_string(); - println!("Coordinate: {input_type_name}.{value_str}"); ctx.schema_coordinates .insert(format!("{input_type_name}.{value_str}").to_string()); } diff --git a/packages/libraries/router/src/lib.rs b/packages/libraries/router/src/lib.rs index dc568edf2c..89395180c9 100644 --- a/packages/libraries/router/src/lib.rs +++ b/packages/libraries/router/src/lib.rs @@ -1,5 +1,6 @@ mod agent; mod graphql; +pub mod persisted_documents; pub mod registry; pub mod registry_logger; pub mod usage; diff --git a/packages/libraries/router/src/main.rs b/packages/libraries/router/src/main.rs index bcf083a3ae..a0543cd19b 100644 --- a/packages/libraries/router/src/main.rs +++ b/packages/libraries/router/src/main.rs @@ -1,16 +1,25 @@ // Specify the modules our binary should include -- https://twitter.com/YassinEldeeb7/status/1468680104243077128 mod agent; mod graphql; +mod persisted_documents; mod registry; mod registry_logger; mod usage; +use apollo_router::register_plugin; +use persisted_documents::PersistedDocumentsPlugin; use registry::HiveRegistry; -use usage::register; +use usage::UsagePlugin; + +// Register the Hive plugin +pub fn register_plugins() { + register_plugin!("hive", "usage", UsagePlugin); + register_plugin!("hive", "persisted_documents", PersistedDocumentsPlugin); +} fn main() { - // Register the usage reporting plugin - register(); + // Register the Hive plugins in Apollo Router + register_plugins(); // Initialize the Hive Registry and start the Apollo Router match HiveRegistry::new(None).and(apollo_router::main()) { diff --git a/packages/libraries/router/src/persisted_documents.rs b/packages/libraries/router/src/persisted_documents.rs new file mode 100644 index 0000000000..2f54e31cbc --- /dev/null +++ b/packages/libraries/router/src/persisted_documents.rs @@ -0,0 +1,782 @@ +use apollo_router::graphql; +use apollo_router::graphql::Error; +use apollo_router::layers::ServiceBuilderExt; +use apollo_router::plugin::Plugin; +use apollo_router::plugin::PluginInit; +use apollo_router::services::router; +use apollo_router::services::router::Body; +use apollo_router::Context; +use core::ops::Drop; +use std::env; +use futures::FutureExt; +use http::StatusCode; +use lru::LruCache; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::num::NonZeroUsize; +use std::ops::ControlFlow; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::Mutex; +use tower::{BoxError, ServiceBuilder, ServiceExt}; +use tracing::{debug, info, warn}; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; + +pub struct PersistedDocumentsPlugin { + persisted_documents_manager: Arc, + configuration: Config, +} + +pub(crate) static PERSISTED_DOCUMENT_HASH_KEY: &str = "hive::persisted_document_hash"; + +#[derive(Clone, Debug, Deserialize, JsonSchema, Default)] +pub struct Config { + enabled: Option, + /// GraphQL Hive persisted documents CDN endpoint URL. + endpoint: Option, + /// GraphQL Hive persisted documents CDN access token. + key: Option, + /// Whether arbitrary documents should be allowed along-side persisted documents. + /// default: false + allow_arbitrary_documents: Option, + /// A timeout for only the connect phase of a request to GraphQL Hive + /// Unit: seconds + /// Default: 5 + connect_timeout: Option, + /// Retry count for the request to CDN request + /// Default: 3 + retry_count: Option, + /// A timeout for the entire request to GraphQL Hive + /// Unit: seconds + /// Default: 15 + request_timeout: Option, + /// Accept invalid SSL certificates + /// default: false + accept_invalid_certs: Option, + /// Configuration for the size of the in-memory caching of persisted documents. + /// Default: 1000 + cache_size: Option, +} + +impl PersistedDocumentsPlugin { + #[cfg(test)] + fn new(config: Config) -> Self { + PersistedDocumentsPlugin { + configuration: config.clone(), + persisted_documents_manager: Arc::new(PersistedDocumentsManager::new(&config)), + } + } +} + +#[async_trait::async_trait] +impl Plugin for PersistedDocumentsPlugin { + type Config = Config; + + async fn new(init: PluginInit) -> Result { + let mut config = init.config.clone(); + if init.config.endpoint.is_none() { + if let Ok(endpoint) = env::var("HIVE_CDN_ENDPOINT") { + config.endpoint = Some(str::replace(&endpoint, "/supergraph", "")); + } + } + + if init.config.key.is_none() { + if let Ok(key) = env::var("HIVE_CDN_KEY") { + config.key = Some(key); + } + } + + Ok(PersistedDocumentsPlugin { + configuration: config.clone(), + persisted_documents_manager: Arc::new(PersistedDocumentsManager::new(&config)), + }) + } + + fn router_service(&self, service: router::BoxService) -> router::BoxService { + let enabled = self.configuration.enabled.unwrap_or(true); + let allow_arbitrary_documents = self + .configuration + .allow_arbitrary_documents + .unwrap_or(false); + let mgr_ref = self.persisted_documents_manager.clone(); + + if enabled { + ServiceBuilder::new() + .oneshot_checkpoint_async(move |req: router::Request| { + let mgr = mgr_ref.clone(); + + async move { + let (parts, body) = req.router_request.into_parts(); + let bytes: hyper::body::Bytes = hyper::body::to_bytes(body) + .await + .map_err(PersistedDocumentsError::FailedToReadBody)?; + + let payload = PersistedDocumentsManager::extract_document_id(&bytes); + + let mut payload = match payload { + Ok(payload) => payload, + Err(e) => { + return Ok(ControlFlow::Break(e.to_router_response(req.context))); + } + }; + + if payload.original_req.query.is_some() { + if allow_arbitrary_documents { + let roll_req: router::Request = ( + http::Request::::from_parts(parts, bytes.into()), + req.context, + ) + .into(); + + return Ok(ControlFlow::Continue(roll_req)); + } else { + return Ok(ControlFlow::Break( + PersistedDocumentsError::PersistedDocumentRequired + .to_router_response(req.context), + )); + } + } + + if payload.document_id.is_none() { + return Ok(ControlFlow::Break( + PersistedDocumentsError::KeyNotFound + .to_router_response(req.context), + )); + } + + match payload.document_id.as_ref() { + None => { + return Ok(ControlFlow::Break( + PersistedDocumentsError::PersistedDocumentRequired + .to_router_response(req.context), + )); + } + Some(document_id) => match mgr.resolve_document(document_id).await { + Ok(document) => { + info!("Document found in persisted documents: {}", document); + + if req + .context + .insert(PERSISTED_DOCUMENT_HASH_KEY, document_id.clone()) + .is_err() + { + warn!("failed to extend router context with persisted document hash key"); + } + + payload.original_req.query = Some(document); + + let mut bytes: Vec = Vec::new(); + serde_json::to_writer(&mut bytes, &payload).unwrap(); + + let roll_req: router::Request = ( + http::Request::::from_parts(parts, bytes.into()), + req.context, + ) + .into(); + + Ok(ControlFlow::Continue(roll_req)) + } + Err(e) => { + return Ok(ControlFlow::Break( + e.to_router_response(req.context), + )); + } + }, + } + } + .boxed() + }) + .service(service) + .boxed() + } else { + service + } + } +} + +impl Drop for PersistedDocumentsPlugin { + fn drop(&mut self) { + debug!("PersistedDocumentsPlugin has been dropped!"); + } +} + +#[derive(Debug)] +struct PersistedDocumentsManager { + agent: ClientWithMiddleware, + cache: Arc>>, + config: Config, +} + +#[derive(Debug, thiserror::Error)] +pub enum PersistedDocumentsError { + #[error("Failed to read body: {0}")] + FailedToReadBody(hyper::Error), + #[error("Failed to parse body: {0}")] + FailedToParseBody(serde_json::Error), + #[error("Persisted document not found.")] + DocumentNotFound, + #[error("Failed to locate the persisted document key in request.")] + KeyNotFound, + #[error("Failed to validate persisted document")] + FailedToFetchFromCDN(reqwest_middleware::Error), + #[error("Failed to read CDN response body")] + FailedToReadCDNResponse(reqwest::Error), + #[error("No persisted document provided, or document id cannot be resolved.")] + PersistedDocumentRequired, +} + +impl PersistedDocumentsError { + fn message(&self) -> String { + self.to_string() + } + + fn code(&self) -> String { + match self { + PersistedDocumentsError::FailedToReadBody(_) => "FAILED_TO_READ_BODY".into(), + PersistedDocumentsError::FailedToParseBody(_) => "FAILED_TO_PARSE_BODY".into(), + PersistedDocumentsError::DocumentNotFound => "PERSISTED_DOCUMENT_NOT_FOUND".into(), + PersistedDocumentsError::KeyNotFound => "PERSISTED_DOCUMENT_KEY_NOT_FOUND".into(), + PersistedDocumentsError::FailedToFetchFromCDN(_) => "FAILED_TO_FETCH_FROM_CDN".into(), + PersistedDocumentsError::FailedToReadCDNResponse(_) => "FAILED_TO_READ_CDN_RESPONSE".into(), + PersistedDocumentsError::PersistedDocumentRequired => { + "PERSISTED_DOCUMENT_REQUIRED".into() + } + } + } + + fn to_router_response(&self, ctx: Context) -> router::Response { + let errors = vec![Error::builder() + .message(self.message()) + .extension_code(self.code()) + .build()]; + + router::Response::error_builder() + .errors(errors) + .status_code(StatusCode::OK) + .context(ctx) + .build() + .unwrap() + } +} + +impl PersistedDocumentsManager { + fn new(config: &Config) -> Self { + let retry_policy = ExponentialBackoff::builder().build_with_max_retries(config.retry_count.unwrap_or(3)); + + let reqwest_agent = reqwest::Client::builder() + .danger_accept_invalid_certs(config.accept_invalid_certs.unwrap_or(false)) + .connect_timeout(Duration::from_secs(config.connect_timeout.unwrap_or(5))) + .timeout(Duration::from_secs(config.request_timeout.unwrap_or(15))).build().expect("Failed to create reqwest client"); + let agent = ClientBuilder::new(reqwest_agent).with(RetryTransientMiddleware::new_with_policy(retry_policy)) + .build(); + + let cache_size = config.cache_size.unwrap_or(1000); + let cache = Arc::new(Mutex::new(LruCache::::new( + NonZeroUsize::new(cache_size).unwrap(), + ))); + + Self { + agent, + cache, + config: config.clone(), + } + } + + /// Extracts the document id from the request body. + /// In case of a parsing error, it returns the error. + /// This will also try to parse other GraphQL-related (see `original_req`) fields in order to + /// pass it to the next layer. + fn extract_document_id( + body: &hyper::body::Bytes, + ) -> Result { + serde_json::from_slice::(&body) + .map_err(PersistedDocumentsError::FailedToParseBody) + } + + /// Resolves the document from the cache, or from the CDN + async fn resolve_document(&self, document_id: &str) -> Result { + let cached_record = self.cache.lock().await.get(document_id).cloned(); + + match cached_record { + Some(document) => { + debug!("Document {} found in cache: {}", document_id, document); + + return Ok(document); + } + None => { + debug!( + "Document {} not found in cache. Fetching from CDN", + document_id + ); + let cdn_document_id = str::replace(document_id, "~", "/"); + let cdn_artifact_url = format!("{}/apps/{}", self.config.endpoint.as_ref().unwrap(), cdn_document_id); + info!( + "Fetching document {} from CDN: {}", + document_id, cdn_artifact_url + ); + let cdn_response = self + .agent + .get(cdn_artifact_url) + .header("X-Hive-CDN-Key", self.config.key.as_ref().unwrap()) + .send() + .await; + + match cdn_response { + Ok(response) => { + if response.status().is_success() { + let document = response.text().await.map_err(PersistedDocumentsError::FailedToReadCDNResponse)?; + debug!( + "Document fetched from CDN: {}, storing in local cache", + document + ); + self.cache + .lock() + .await + .put(document_id.into(), document.clone()); + + return Ok(document); + } + + warn!( + "Document fetch from CDN failed: HTTP {}, Body: {:?}", + response.status(), + response.text().await.unwrap_or_else(|_| "Unavailable".to_string()) + ); + + return Err(PersistedDocumentsError::DocumentNotFound); + } + Err(e) => { + warn!("Failed to fetch document from CDN: {:?}", e); + + return Err(PersistedDocumentsError::FailedToFetchFromCDN(e)); + } + } + } + } + } +} + +/// Expected body structure for the router incoming requests +/// This is used to extract the document id and the original request as-is (see `flatten` attribute) +#[derive(Debug, Serialize, Deserialize, Clone)] +struct ExpectedBodyStructure { + /// This field is set to optional in order to prevent parsing errors + /// At runtime later, the plugin will double check the value. + #[serde(rename = "documentId")] + #[serde(skip_serializing)] + document_id: Option, + /// The rest of the GraphQL request, flattened to keep the original structure. + #[serde(flatten)] + original_req: graphql::Request, +} + +/// To test this plugin, we do the following: +/// 1. Create the plugin instance +/// 2. Link it to a mocked router service that reflects +/// back the body (to validate that the plugin is working and passes the body correctly) +/// 3. Run HTTP mock to create a mock Hive CDN server +#[cfg(test)] +mod hive_persisted_documents_tests { + use apollo_router::plugin::test::MockRouterService; + use futures::executor::block_on; + use http::Method; + use httpmock::{Method::GET, Mock, MockServer}; + use serde_json::json; + + use super::*; + + /// Creates a regular GraphQL request with a very simple GraphQL query: + /// { "query": "query { __typename }" } + fn create_regular_request() -> router::Request { + let mut r = graphql::Request::default(); + + r.query = Some("query { __typename }".into()); + + router::Request::fake_builder() + .method(Method::POST) + .body(Body::from(serde_json::to_string(&r).unwrap())) + .header("content-type", "application/json") + .build() + .unwrap() + } + + /// Creates a persisted document request with a document id and optional variables. + /// The document id is used to fetch the persisted document from the CDN. + /// { "documentId": "123", "variables": { ... } } + fn create_persisted_request( + document_id: &str, + variables: Option, + ) -> router::Request { + let body = json!({ + "documentId": document_id, + "variables": variables, + }); + + let body_str = serde_json::to_string(&body).unwrap(); + + router::Request::fake_builder() + .body(body_str) + .header("content-type", "application/json") + .build() + .unwrap() + } + + /// Creates an "invalid" persisted request with an empty JSON object body. + fn create_invalid_req() -> router::Request { + router::Request::fake_builder() + .method(Method::POST) + .body(serde_json::to_string(&json!({})).unwrap()) + .header("content-type", "application/json") + .build() + .unwrap() + } + + + struct PersistedDocumentsCDNMock { + server: MockServer, + } + + impl PersistedDocumentsCDNMock { + fn new() -> Self { + let server = MockServer::start(); + + Self { server } + } + + fn endpoint(&self) -> String { + self.server.url("") + } + + /// Registers a valid artifact URL with an actual GraphQL document + fn add_valid(&self, document_id: &str) -> Mock { + let valid_artifact_url = format!("/apps/{}", str::replace(document_id, "~", "/")); + let document = "query { __typename }"; + let mock = self.server.mock(|when, then| { + when.method(GET).path(valid_artifact_url); + then.status(200) + .header("content-type", "text/plain") + .body(document); + }); + + mock + } + } + + async fn get_body(router_req: router::Request) -> String { + let (_parts, body) = router_req.router_request.into_parts(); + let body = hyper::body::to_bytes(body).await.unwrap(); + String::from_utf8(body.to_vec()).unwrap() + } + + /// Creates a mocked router service that reflects the incoming body + /// back to the client. + /// We are using this mocked router in order to make sure that the Persisted Documents layer + /// is able to resolve, fetch and pass the document to the next layer. + fn create_reflecting_mocked_router() -> MockRouterService { + let mut mocked_execution: MockRouterService = MockRouterService::new(); + + mocked_execution + .expect_call() + .times(1) + .returning(move |req| { + let incoming_body = block_on(get_body(req)); + Ok(router::Response::fake_builder() + .data(json!({ + "incomingBody": incoming_body, + })) + .build() + .unwrap()) + }); + + mocked_execution + } + + /// Creates a mocked router service that returns a fake GraphQL response. + fn create_dummy_mocked_router() -> MockRouterService { + let mut mocked_execution = MockRouterService::new(); + + mocked_execution.expect_call().times(1).returning(move |_| { + Ok(router::Response::fake_builder() + .data(json!({ + "__typename": "Query" + })) + .build() + .unwrap()) + }); + + mocked_execution + } + + #[tokio::test] + async fn should_allow_arbitrary_when_regular_req_is_sent() { + let service = create_reflecting_mocked_router(); + let service_stack = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some("https://cdn.example.com".into()), + key: Some("123".into()), + allow_arbitrary_documents: Some(true), + ..Default::default() + }) + .router_service(service.boxed()); + + let request = create_regular_request(); + let mut response = service_stack.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "data": { + "incomingBody": "{\"query\":\"query { __typename }\"}" + } + }) + .to_string() + .as_bytes() + ); + } + + #[tokio::test] + async fn should_disallow_arbitrary_when_regular_req_sent() { + let service_stack = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some("https://cdn.example.com".into()), + key: Some("123".into()), + allow_arbitrary_documents: Some(false), + ..Default::default() + }) + .router_service(MockRouterService::new().boxed()); + + let request = create_regular_request(); + let mut response = service_stack.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "errors": [ + { + "message": "No persisted document provided, or document id cannot be resolved.", + "extensions": { + "code": "PERSISTED_DOCUMENT_REQUIRED" + } + } + ] + }) + .to_string() + .as_bytes() + ); + } + + #[tokio::test] + async fn returns_not_found_error_for_missing_persisted_query() { + let cdn_mock = PersistedDocumentsCDNMock::new(); + let service_stack = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some(cdn_mock.endpoint()), + key: Some("123".into()), + allow_arbitrary_documents: Some(true), + ..Default::default() + }) + .router_service(MockRouterService::new().boxed()); + + let request = create_persisted_request("123", None); + let mut response = service_stack.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "errors": [ + { + "message": "Persisted document not found.", + "extensions": { + "code": "PERSISTED_DOCUMENT_NOT_FOUND" + } + } + ] + }) + .to_string() + .as_bytes() + ); + } + + #[tokio::test] + async fn returns_key_not_found_error_for_missing_input() { + let service_stack = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some("https://cdn.example.com".into()), + key: Some("123".into()), + allow_arbitrary_documents: Some(true), + ..Default::default() + }) + .router_service(MockRouterService::new().boxed()); + + let request = create_invalid_req(); + let mut response = service_stack.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "errors": [ + { + "message": "Failed to locate the persisted document key in request.", + "extensions": { + "code": "PERSISTED_DOCUMENT_KEY_NOT_FOUND" + } + } + ] + }) + .to_string() + .as_bytes() + ); + } + + #[tokio::test] + async fn rejects_req_when_cdn_not_available() { + let service_stack = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some("https://127.0.0.1:9999".into()), // Invalid endpoint + key: Some("123".into()), + allow_arbitrary_documents: Some(false), + ..Default::default() + }) + .router_service(MockRouterService::new().boxed()); + + let request = create_persisted_request("123", None); + let mut response = service_stack.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "errors": [ + { + "message": "Failed to validate persisted document", + "extensions": { + "code": "FAILED_TO_FETCH_FROM_CDN" + } + } + ] + }) + .to_string() + .as_bytes() + ); + } + + #[tokio::test] + async fn should_return_valid_response() { + let cdn_mock = PersistedDocumentsCDNMock::new(); + cdn_mock.add_valid("my-app~cacb95c69ba4684aec972777a38cd106740c6453~04bfa72dfb83b297dd8a5b6fed9bafac2b395a0f"); + let upstream = create_dummy_mocked_router(); + let service_stack = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some(cdn_mock.endpoint()), + key: Some("123".into()), + allow_arbitrary_documents: Some(false), + ..Default::default() + }) + .router_service(upstream.boxed()); + + let request = create_persisted_request("my-app~cacb95c69ba4684aec972777a38cd106740c6453~04bfa72dfb83b297dd8a5b6fed9bafac2b395a0f", None); + let mut response = service_stack.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "data": { + "__typename": "Query" + } + }) + .to_string() + .as_bytes() + ); + } + + #[tokio::test] + async fn should_passthrough_additional_req_params() { + let cdn_mock = PersistedDocumentsCDNMock::new(); + cdn_mock.add_valid("my-app~cacb95c69ba4684aec972777a38cd106740c6453~04bfa72dfb83b297dd8a5b6fed9bafac2b395a0f"); + let upstream = create_reflecting_mocked_router(); + let service_stack = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some(cdn_mock.endpoint()), + key: Some("123".into()), + allow_arbitrary_documents: Some(false), + ..Default::default() + }) + .router_service(upstream.boxed()); + + let request = create_persisted_request( + "my-app~cacb95c69ba4684aec972777a38cd106740c6453~04bfa72dfb83b297dd8a5b6fed9bafac2b395a0f", + Some(json!({"var": "value"})) + ); + let mut response = service_stack.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + "{\"data\":{\"incomingBody\":\"{\\\"query\\\":\\\"query { __typename }\\\",\\\"variables\\\":{\\\"var\\\":\\\"value\\\"}}\"}}" + ); + } + + #[tokio::test] + async fn should_use_caching_for_documents() { + let cdn_mock = PersistedDocumentsCDNMock::new(); + let cdn_req_mock = cdn_mock.add_valid("my-app~cacb95c69ba4684aec972777a38cd106740c6453~04bfa72dfb83b297dd8a5b6fed9bafac2b395a0f"); + + let p = PersistedDocumentsPlugin::new(Config { + enabled: Some(true), + endpoint: Some(cdn_mock.endpoint()), + key: Some("123".into()), allow_arbitrary_documents: Some(false), + ..Default::default() + }); + let s1 = p.router_service(create_dummy_mocked_router().boxed()); + let s2 = p.router_service(create_dummy_mocked_router().boxed()); + + // first call + let request = create_persisted_request("my-app~cacb95c69ba4684aec972777a38cd106740c6453~04bfa72dfb83b297dd8a5b6fed9bafac2b395a0f", None); + + let mut response = s1.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "data": { + "__typename": "Query" + } + }) + .to_string() + .as_bytes() + ); + + // second call + let request = create_persisted_request("my-app~cacb95c69ba4684aec972777a38cd106740c6453~04bfa72dfb83b297dd8a5b6fed9bafac2b395a0f", None); + let mut response = s2.oneshot(request).await.unwrap(); + let response_inner = response.next_response().await.unwrap().unwrap(); + assert_eq!(response.response.status(), StatusCode::OK); + assert_eq!( + response_inner, + json!({ + "data": { + "__typename": "Query" + } + }) + .to_string() + .as_bytes() + ); + + // makes sure cdn called only once. If called more than once, it will fail with 404 -> leading to error (and the above assertion will fail...) + cdn_req_mock.assert(); + } +} diff --git a/packages/libraries/router/src/registry.rs b/packages/libraries/router/src/registry.rs index ae3d9a67a9..b30be02096 100644 --- a/packages/libraries/router/src/registry.rs +++ b/packages/libraries/router/src/registry.rs @@ -93,7 +93,6 @@ impl HiveRegistry { None => 10, }; let accept_invalid_certs = config.accept_invalid_certs.unwrap_or_else(|| false); - let logger = Logger::new(); // In case of an endpoint and an key being empty, we don't start the polling and skip the registry diff --git a/packages/libraries/router/src/usage.rs b/packages/libraries/router/src/usage.rs index d2cdbf180e..c0efc62cf7 100644 --- a/packages/libraries/router/src/usage.rs +++ b/packages/libraries/router/src/usage.rs @@ -1,8 +1,8 @@ use crate::agent::{AgentError, ExecutionReport, UsageAgent}; +use crate::persisted_documents::PERSISTED_DOCUMENT_HASH_KEY; use apollo_router::layers::ServiceBuilderExt; use apollo_router::plugin::Plugin; use apollo_router::plugin::PluginInit; -use apollo_router::register_plugin; use apollo_router::services::*; use apollo_router::Context; use core::ops::Drop; @@ -15,7 +15,7 @@ use std::collections::HashSet; use std::env; use std::sync::Arc; use std::sync::Mutex; -use std::time::Instant; +use std::time::{Duration, Instant}; use std::time::{SystemTime, UNIX_EPOCH}; use tower::BoxError; use tower::ServiceBuilder; @@ -23,7 +23,7 @@ use tower::ServiceExt; pub(crate) static OPERATION_CONTEXT: &str = "hive::operation_context"; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] struct OperationContext { pub(crate) client_name: Option, pub(crate) client_version: Option, @@ -41,15 +41,20 @@ struct OperationConfig { client_version_header: String, } -struct UsagePlugin { +pub struct UsagePlugin { config: OperationConfig, agent: Option>>, } #[derive(Clone, Debug, Deserialize, JsonSchema)] -struct Config { +pub struct Config { /// Default: true enabled: Option, + /// Hive token, can also be set using the HIVE_TOKEN environment variable + registry_token: Option, + /// Hive registry token. Set to your `/usage` endpoint if you are self-hosting. + /// Default: https://app.graphql-hive.com/usage + registry_usage_endpoint: Option, /// Sample rate to determine sampling. /// 0.0 = 0% chance of being sent /// 1.0 = 100% chance of being sent. @@ -73,12 +78,17 @@ struct Config { /// Accept invalid SSL certificates /// Default: false accept_invalid_certs: Option, + /// Frequency of flushing the buffer to the server + /// Default: 5 seconds + flush_interval: Option, } impl Default for Config { fn default() -> Self { Self { enabled: Some(true), + registry_token: None, + registry_usage_endpoint: Some(DEFAULT_HIVE_USAGE_ENDPOINT.into()), sample_rate: Some(1.0), exclude: None, client_name_header: Some(String::from("graphql-client-name")), @@ -87,6 +97,7 @@ impl Default for Config { buffer_size: Some(1000), connect_timeout: Some(5), request_timeout: Some(15), + flush_interval: Some(5), } } } @@ -158,18 +169,30 @@ impl UsagePlugin { } } +static DEFAULT_HIVE_USAGE_ENDPOINT: &str = "https://app.graphql-hive.com/usage"; + #[async_trait::async_trait] impl Plugin for UsagePlugin { type Config = Config; async fn new(init: PluginInit) -> Result { - let token = - env::var("HIVE_TOKEN").map_err(|_| "environment variable HIVE_TOKEN not found")?; - let endpoint = env::var("HIVE_ENDPOINT"); - let endpoint = match endpoint { - Ok(endpoint) => endpoint, - Err(_) => "https://app.graphql-hive.com/usage".to_string(), - }; + let token = init + .config + .registry_token + .clone() + .or_else(|| env::var("HIVE_TOKEN").ok()); + + if let None = token { + return Err("Hive token is required".into()); + } + + let endpoint = init + .config + .registry_usage_endpoint + .clone() + .unwrap_or_else(|| { + env::var("HIVE_ENDPOINT").unwrap_or(DEFAULT_HIVE_USAGE_ENDPOINT.to_string()) + }); let default_config = Config::default(); let user_config = init.config; @@ -193,6 +216,10 @@ impl Plugin for UsagePlugin { .request_timeout .or(default_config.request_timeout) .expect("request_timeout has no default value"); + let flush_interval = user_config + .flush_interval + .or(default_config.flush_interval) + .expect("request_timeout has no default value"); if enabled { tracing::info!("Starting GraphQL Hive Usage plugin"); @@ -217,12 +244,13 @@ impl Plugin for UsagePlugin { agent: match enabled { true => Some(Arc::new(Mutex::new(UsageAgent::new( init.supergraph_sdl.to_string(), - token, + token.unwrap(), endpoint, buffer_size, connect_timeout, request_timeout, accept_invalid_certs, + Duration::from_secs(flush_interval), )))), false => None, }, @@ -243,7 +271,7 @@ impl Plugin for UsagePlugin { move |ctx: Context, fut| { let agent_clone = agent.clone(); async move { - let start = Instant::now(); + let start: Instant = Instant::now(); // nested async block, bc async is unstable with closures that receive arguments let operation_context = ctx @@ -251,6 +279,13 @@ impl Plugin for UsagePlugin { .unwrap_or_default() .unwrap(); + // Injected by the persisted document plugin, if it was activated + // and discovered document id + let persisted_document_hash = ctx + .get::<_, String>(PERSISTED_DOCUMENT_HASH_KEY) + .ok() + .unwrap(); + let result: supergraph::ServiceResult = fut.await; if operation_context.dropped { @@ -289,6 +324,7 @@ impl Plugin for UsagePlugin { errors: 1, operation_body, operation_name, + persisted_document_hash, }, ); Err(e) @@ -318,6 +354,8 @@ impl Plugin for UsagePlugin { errors: response.errors.len(), operation_body: operation_body.clone(), operation_name: operation_name.clone(), + persisted_document_hash: + persisted_document_hash.clone(), }, ); @@ -342,9 +380,7 @@ impl Plugin for UsagePlugin { fn try_add_report(agent: Arc>, execution_report: ExecutionReport) { agent .lock() - .map_err(|e| { -AgentError::Lock(e.to_string()) - }) + .map_err(|e| AgentError::Lock(e.to_string())) .and_then(|a| a.add_report(execution_report)) .unwrap_or_else(|e| { tracing::error!("Error adding report: {}", e); @@ -358,7 +394,171 @@ impl Drop for UsagePlugin { } } -// Register the hive.usage plugin -pub fn register() { - register_plugin!("hive", "usage", UsagePlugin); +#[cfg(test)] +mod hive_usage_tests { + use apollo_router::{ + plugin::{test::MockSupergraphService, Plugin, PluginInit}, + services::supergraph, + }; + use httpmock::{Method::POST, Mock, MockServer}; + use jsonschema::Validator; + use serde_json::json; + use tower::ServiceExt; + + use super::{Config, UsagePlugin}; + + lazy_static::lazy_static! { + static ref SCHEMA_VALIDATOR: Validator = + jsonschema::validator_for(&serde_json::from_str(&std::fs::read_to_string("../../services/usage/usage-report-v2.schema.json").expect("can't load json schema file")).expect("failed to parse json schema")).expect("failed to parse schema"); + } + + struct UsageTestHelper { + mocked_upstream: MockServer, + plugin: UsagePlugin, + } + + impl UsageTestHelper { + async fn new() -> Self { + let server: MockServer = MockServer::start(); + let usage_endpoint = server.url("/usage"); + let mut config = Config::default(); + config.enabled = Some(true); + config.registry_usage_endpoint = Some(usage_endpoint.to_string()); + config.registry_token = Some("123".into()); + config.buffer_size = Some(1); + config.flush_interval = Some(1); + + let plugin_service = UsagePlugin::new( + PluginInit::fake_builder() + .config(config) + .supergraph_sdl("type Query { dummy: String! }".to_string().into()) + .build(), + ) + .await + .expect("failed to init plugin"); + + UsageTestHelper { + mocked_upstream: server, + plugin: plugin_service, + } + } + + fn wait_for_processing(&self) -> tokio::time::Sleep { + tokio::time::sleep(tokio::time::Duration::from_secs(1)) + } + + fn activate_usage_mock(&self) -> Mock { + self.mocked_upstream.mock(|when, then| { + when.method(POST).path("/usage").matches(|r| { + // This mock also validates that the content of the reported usage is valid + // when it comes to the JSON schema validation. + // if it does not match, the request matching will fail and this will lead + // to a failed assertion + let body = r.body.as_ref().unwrap(); + let body = String::from_utf8(body.to_vec()).unwrap(); + let body = serde_json::from_str(&body).unwrap(); + + SCHEMA_VALIDATOR.is_valid(&body) + }); + then.status(200); + }) + } + + async fn execute_operation(&self, req: supergraph::Request) -> supergraph::Response { + let mut supergraph_service_mock = MockSupergraphService::new(); + + supergraph_service_mock + .expect_call() + .times(1) + .returning(move |_| { + Ok(supergraph::Response::fake_builder() + .data(json!({ + "data": { "hello": "world" }, + })) + .build() + .unwrap()) + }); + + let tower_service = self + .plugin + .supergraph_service(supergraph_service_mock.boxed()); + + let response = tower_service + .oneshot(req) + .await + .expect("failed to execute operation"); + + response + } + } + + #[tokio::test] + async fn should_work_correctly_for_simple_query() { + let instance = UsageTestHelper::new().await; + let req = supergraph::Request::fake_builder() + .query("query test { hello }") + .operation_name("test") + .build() + .unwrap(); + let mock = instance.activate_usage_mock(); + + instance.execute_operation(req).await.next_response().await; + + instance.wait_for_processing().await; + + mock.assert(); + mock.assert_hits(1); + } + + #[tokio::test] + async fn without_operation_name() { + let instance = UsageTestHelper::new().await; + let req = supergraph::Request::fake_builder() + .query("query { hello }") + .build() + .unwrap(); + let mock = instance.activate_usage_mock(); + + instance.execute_operation(req).await.next_response().await; + + instance.wait_for_processing().await; + + mock.assert(); + mock.assert_hits(1); + } + + #[tokio::test] + async fn multiple_operations() { + let instance = UsageTestHelper::new().await; + let req = supergraph::Request::fake_builder() + .query("query test { hello } query test2 { hello }") + .operation_name("test") + .build() + .unwrap(); + let mock = instance.activate_usage_mock(); + + instance.execute_operation(req).await.next_response().await; + + instance.wait_for_processing().await; + + mock.assert(); + mock.assert_hits(1); + } + + #[tokio::test] + async fn invalid_query_reported() { + let instance = UsageTestHelper::new().await; + let req = supergraph::Request::fake_builder() + .query("query {") + .build() + .unwrap(); + let mock = instance.activate_usage_mock(); + + instance.execute_operation(req).await.next_response().await; + + instance.wait_for_processing().await; + + mock.assert(); + mock.assert_hits(1); + } } diff --git a/packages/services/usage/usage-report-v2.schema.json b/packages/services/usage/usage-report-v2.schema.json new file mode 100644 index 0000000000..37fcf5ea0e --- /dev/null +++ b/packages/services/usage/usage-report-v2.schema.json @@ -0,0 +1,136 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Report", + "additionalProperties": false, + "type": "object", + "properties": { + "size": { + "type": "integer" + }, + "map": { + "type": "object", + "patternProperties": { + "^(.*)$": { + "title": "OperationMapRecord", + "additionalProperties": false, + "type": "object", + "properties": { + "operation": { + "type": "string" + }, + "operationName": { + "type": "string" + }, + "fields": { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["operation", "fields"] + } + } + }, + "operations": { + "type": "array", + "items": { + "title": "RequestOperation", + "additionalProperties": false, + "type": "object", + "properties": { + "timestamp": { + "type": "integer" + }, + "operationMapKey": { + "type": "string" + }, + "execution": { + "title": "Execution", + "additionalProperties": false, + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "duration": { + "type": "integer" + }, + "errorsTotal": { + "type": "integer" + } + }, + "required": ["ok", "duration", "errorsTotal"] + }, + "metadata": { + "title": "Metadata", + "additionalProperties": false, + "type": "object", + "properties": { + "client": { + "title": "Client", + "additionalProperties": false, + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": ["name", "version"] + } + } + }, + "persistedDocumentHash": { + "type": "string", + "title": "PersistedDocumentHash", + "pattern": "^[a-zA-Z0-9_-]{1,64}~[a-zA-Z0-9._-]{1,64}~([A-Za-z]|[0-9]|_){1,128}$" + } + }, + "required": ["timestamp", "operationMapKey", "execution"] + } + }, + "subscriptionOperations": { + "type": "array", + "items": { + "title": "SubscriptionOperation", + "additionalProperties": false, + "type": "object", + "properties": { + "timestamp": { + "type": "integer" + }, + "operationMapKey": { + "type": "string" + }, + "metadata": { + "title": "Metadata", + "additionalProperties": false, + "type": "object", + "properties": { + "client": { + "title": "Client", + "additionalProperties": false, + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": ["name", "version"] + } + } + } + }, + "required": ["timestamp", "operationMapKey"] + } + } + }, + "required": ["size", "map"] +} diff --git a/packages/web/docs/src/components/navigation-menu/index.tsx b/packages/web/docs/src/components/navigation-menu/index.tsx index a7c60ac4be..5f4dde22e0 100644 --- a/packages/web/docs/src/components/navigation-menu/index.tsx +++ b/packages/web/docs/src/components/navigation-menu/index.tsx @@ -16,5 +16,12 @@ export function NavigationMenu(props: ComponentPropsWithoutRef) { ); } -const landingLikePages = ['/', '/pricing', '/federation', '/oss-friends', '/ecosystem']; +const landingLikePages = [ + '/', + '/pricing', + '/federation', + '/oss-friends', + '/ecosystem', + '/partners', +]; export const isLandingPage = (route: string) => landingLikePages.includes(route); diff --git a/packages/web/docs/src/components/usage-reports-json-schema.tsx b/packages/web/docs/src/components/usage-reports-json-schema.tsx new file mode 100644 index 0000000000..521412e25f --- /dev/null +++ b/packages/web/docs/src/components/usage-reports-json-schema.tsx @@ -0,0 +1,32 @@ +import { readFileSync } from 'node:fs'; +import type { GetStaticProps } from 'next'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { buildDynamicMDX } from 'nextra/remote'; +import { RemoteContent } from '@theguild/components'; + +export function UsageReportsJSONSchema() { + return ; +} + +export const getStaticProps: GetStaticProps = async () => { + const data = [ + '```json', + readFileSync('../../services/usage/usage-report-v2.schema.json', 'utf-8'), + '```', + ].join('\n'); + const dynamicMdx = await buildDynamicMDX(data, { + defaultShowCopyCode: true, + }); + + return { + props: { + ...dynamicMdx, + __nextra_dynamic_opts: { + title: 'Usage Report JSON Schema / Specification', + frontMatter: { + description: 'Hive Usage Report JSON Schema / Specification', + }, + }, + }, + }; +}; diff --git a/packages/web/docs/src/pages/docs/other-integrations/apollo-router.mdx b/packages/web/docs/src/pages/docs/other-integrations/apollo-router.mdx index da969c2f22..26b041a81a 100644 --- a/packages/web/docs/src/pages/docs/other-integrations/apollo-router.mdx +++ b/packages/web/docs/src/pages/docs/other-integrations/apollo-router.mdx @@ -178,8 +178,10 @@ You can send usage reporting to Hive registry by enabling `hive.usage` plugin in ### Configuration -- `HIVE_TOKEN` - Your 'Registry Access Token' as configured in hive For self hosted Hive: -- `HIVE_ENDPOINT` - The usage endpoint (defaults to https://app.graphql-hive.com/usage) +- `HIVE_TOKEN` (**required**) - Your + [Registry Access Token](/docs/management/targets#registry-access-tokens) with write permission. +- `HIVE_ENDPOINT` (**optional**) - For self-hosting, you can override `/usage` endpoint (defaults to + `https://app.graphql-hive.com/usage`) @@ -296,6 +298,71 @@ plugins: # accept_invalid_certs: true ``` +## Persisted Documents (App Deployments) + +To activate [App Deployments](/docs/schema-registry/app-deployments) in your Apollo Router, you need +to enable the `hive.persisted_documents` plugin. The plugin uses the `HIVE_CDN_ENDPOINT` and +`HIVE_CDN_KEY` environment variables to resolve the persisted documents IDs. + +```yaml filename="router.yaml" +# ... the rest of your configuration +plugins: + hive.usage: {} + hive.persisted_documents: + enabled: true +``` + +Once enabled, follow the +[App Deployments guide](/docs/schema-registry/app-deployments#sending-persisted-document-requests-from-your-app) +to send persisted document requests from your app, using the Hive CDN for resolving the document +IDs. + +### Configuration + +The following environment variables are required: + +- `HIVE_CDN_ENDPOINT` - the endpoint Hive generated for you in the previous step (for example: + `https://cdn.graphql-hive.com/artifacts/v1/TARGET_ID/supergraph`) +- `HIVE_CDN_KEY` - the access key to Hive CDN + +You may configure the plugin with the following options: + +```yaml filename="router.yaml" +# ... the rest of your configuration +plugins: + hive.usage: {} + hive.persisted_documents: + # Enables/disables the plugin + # Default: true + enabled: true + # The endpoint of the Hive CDN. You can either specify it here, or use the existing HIVE_CDN_ENDPOINT environment variable. + # Required + endpoint: 'https://cdn.graphql-hive.com/artifacts/v1/TARGET_ID/supergraph' + # The access key to the Hive CDN. You can either specify it here, or use the existing HIVE_CDN_KEY environment variable. + # Required + key: '...' + # Enables/disables the option to allow arbitrary documents to be executed. + # Optional + # Default: false + allow_arbitrary_documents: false + # HTTP connect timeout in seconds for the Hive CDN requests. + # Optional + # Default: 5 + connect_timeout: 5 + # HTTP request timeout in seconds for the Hive CDN requests. + # Optional + # Default: 15 + request_timeout: 15 + # Accepts invalid SSL certificates for the Hive CDN requests. + # Optional + # Default: false + accept_invalid_certs: false + # The maximum number of documents to hold in a LRU cache. + # Optional + # Default: 1000 + cache_size: 1000 +``` + ## Additional Resources - [Get started with Apollo Federation and Hive guide](/docs/get-started/apollo-federation) diff --git a/packages/web/docs/src/pages/docs/schema-registry/app-deployments.mdx b/packages/web/docs/src/pages/docs/schema-registry/app-deployments.mdx index 19d44a6bee..9f59f2ed79 100644 --- a/packages/web/docs/src/pages/docs/schema-registry/app-deployments.mdx +++ b/packages/web/docs/src/pages/docs/schema-registry/app-deployments.mdx @@ -336,9 +336,27 @@ For further configuration options, please refer to the -Using the Hive Schema Registry for persisted documents with Apollo Router is currently not -supported. Progress of the support is tracked in -[this GitHub issue](https://github.com/graphql-hive/platform/issues/5498). +To use App Deployments with Apollo-Router, you can use the Hive CDN for resolving persisted +documents. + +Use the [Apollo Router custom build for Hive](/docs/other-integrations/apollo-router) as an +alternative to the official Apollo Router. + +Enable the Persisted Document plugin by adding the following to your `router.yaml` configuration. + +```yaml filename="router.yaml" +# ... the rest of your configuration +plugins: + hive.usage: {} + hive.persisted_documents: + enabled: true +``` + +The plugin uses the `HIVE_CDN_ENDPOINT` and `HIVE_CDN_KEY` environment variables to resolve the +persisted documents IDs. + +For additional information and configuration options, please refer to the +[Apollo-Router integration page](/docs/other-integrations/apollo-router). diff --git a/packages/web/docs/src/pages/docs/specs/usage-reports.md b/packages/web/docs/src/pages/docs/specs/usage-reports.mdx similarity index 54% rename from packages/web/docs/src/pages/docs/specs/usage-reports.md rename to packages/web/docs/src/pages/docs/specs/usage-reports.mdx index 0659043922..348c7dc4bd 100644 --- a/packages/web/docs/src/pages/docs/specs/usage-reports.md +++ b/packages/web/docs/src/pages/docs/specs/usage-reports.mdx @@ -1,3 +1,7 @@ +import { UsageReportsJSONSchema } from '../../../components/usage-reports-json-schema' + +export { getStaticProps } from '../../../components/usage-reports-json-schema' + # Usage Reporting The official JavaScript Hive Client (`@graphql-hive/core`) collects executed operations and sends @@ -71,144 +75,7 @@ export interface Metadata {
JSON Schema -```json -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Report", - "additionalProperties": false, - "type": "object", - "properties": { - "size": { - "type": "integer" - }, - "map": { - "type": "object", - "patternProperties": { - "^(.*)$": { - "title": "OperationMapRecord", - "additionalProperties": false, - "type": "object", - "properties": { - "operation": { - "type": "string" - }, - "operationName": { - "type": "string" - }, - "fields": { - "minItems": 1, - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["operation", "fields"] - } - } - }, - "operations": { - "type": "array", - "items": { - "title": "RequestOperation", - "additionalProperties": false, - "type": "object", - "properties": { - "timestamp": { - "type": "integer" - }, - "operationMapKey": { - "type": "string" - }, - "execution": { - "title": "Execution", - "additionalProperties": false, - "type": "object", - "properties": { - "ok": { - "type": "boolean" - }, - "duration": { - "type": "integer" - }, - "errorsTotal": { - "type": "integer" - } - }, - "required": ["ok", "duration", "errorsTotal"] - }, - "metadata": { - "title": "Metadata", - "additionalProperties": false, - "type": "object", - "properties": { - "client": { - "title": "Client", - "additionalProperties": false, - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": ["name", "version"] - } - } - }, - "persistedDocumentHash": { - "type": "string", - "title": "PersistedDocumentHash", - "pattern": "^[a-zA-Z0-9_-]{1,64}~[a-zA-Z0-9._-]{1,64}~([A-Za-z]|[0-9]|_){1,128}$" - } - }, - "required": ["timestamp", "operationMapKey", "execution"] - } - }, - "subscriptionOperations": { - "type": "array", - "items": { - "title": "SubscriptionOperation", - "additionalProperties": false, - "type": "object", - "properties": { - "timestamp": { - "type": "integer" - }, - "operationMapKey": { - "type": "string" - }, - "metadata": { - "title": "Metadata", - "additionalProperties": false, - "type": "object", - "properties": { - "client": { - "title": "Client", - "additionalProperties": false, - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": ["name", "version"] - } - } - } - }, - "required": ["timestamp", "operationMapKey"] - } - } - }, - "required": ["size", "map"] -} -``` +
@@ -303,7 +170,7 @@ export interface Metadata { } ``` -## Curl example request +## `curl` example request ```bash curl -X POST \ diff --git a/packages/web/docs/src/pages/product-updates/2024-11-13-hive-plugin-apollo-router-crate.mdx b/packages/web/docs/src/pages/product-updates/2024-11-20-hive-plugin-apollo-router-crate.mdx similarity index 99% rename from packages/web/docs/src/pages/product-updates/2024-11-13-hive-plugin-apollo-router-crate.mdx rename to packages/web/docs/src/pages/product-updates/2024-11-20-hive-plugin-apollo-router-crate.mdx index 9d05540035..7f2155bacf 100644 --- a/packages/web/docs/src/pages/product-updates/2024-11-13-hive-plugin-apollo-router-crate.mdx +++ b/packages/web/docs/src/pages/product-updates/2024-11-20-hive-plugin-apollo-router-crate.mdx @@ -3,7 +3,7 @@ title: Hive Plugin for Apollo-Router now available on Crates.io description: We've published the Hive Plugin for Apollo-Router on Crates.io. Learn how to use it in your custom Apollo-Router projects. -date: 2024-11-13 +date: 2024-11-20 authors: [dotan] --- diff --git a/packages/web/docs/src/pages/product-updates/2024-12-16-apollo-router-app-deployments/index.mdx b/packages/web/docs/src/pages/product-updates/2024-12-16-apollo-router-app-deployments/index.mdx new file mode 100644 index 0000000000..028751d217 --- /dev/null +++ b/packages/web/docs/src/pages/product-updates/2024-12-16-apollo-router-app-deployments/index.mdx @@ -0,0 +1,66 @@ +--- +title: Persisted Documents Support for Apollo Router +description: + App Deployments feature is now available for Apollo Router. Learn how to use and how it can + improve your GraphQL API. +date: 2024-12-16 +authors: [dotan] +--- + +import NextImage from 'next/image' +import { Callout } from '@theguild/components' + +We're excited to announce that the [**App Deployments**](/docs/other-integrations/apollo-router) +feature is now available for Apollo Router! + +## App Deployments / Persisted Documents + +App Deployments (persisted documents) are a way to group and publish your GraphQL operations as a +single app version to the Hive Registry. This allows you to keep track of your different app +versions, their operations usage, and performance. + +import pendingAppImage from '../../../../public/docs/pages/features/app-deployments/pending-app.png' + + + +## How to use App Deployments with Apollo Router + +```mermaid +flowchart LR + C["Apollo Router"] + B["Hive CDN"] + D["App"] + + B-- "Load persisted + documents" -->C + C-- "Report persisted + document usage" -->B + D-- "Send persisted + document request" -->C +``` + +To use App Deployments with Apollo Router, follow the +[installation instructions](/docs/other-integrations/apollo-router), and use the latest image +version (`router1.58.0-plugin1.1.0`) of the +[custom build of Apollo Router](/docs/other-integrations/apollo-router). + +Push your GraphQL operations to the App Deployments store, and then configure Apollo-Router with the +`hive.persisted_documents` plugin: + +```yaml filename="router.yaml" +# ... the rest of your configuration +plugins: + hive.usage: {} + hive.persisted_documents: + enabled: true +``` + +To learn more about App Deployments and how to use them with Apollo Router, check out the following +resources: + +- [Hive custom build of Apollo-Router](/docs/other-integrations/apollo-router#app-deployments-persisted-documents) +- [App Deployments documentation](/docs/schema-registry/app-deployments)