From 455b42d01dcf53a293f22ac977dc9f1da6a99a3f Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Fri, 22 Nov 2024 05:03:43 +0000 Subject: [PATCH 1/9] ROVER-245 Fix up Cargo.tomls and deny.toml --- Cargo.lock | 666 ++++++++++++++++++++++++++---------- Cargo.toml | 3 + crates/rover-std/Cargo.toml | 2 - deny.toml | 18 + 4 files changed, 509 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79698acd8..e8fbf91ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,17 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -35,6 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -140,6 +152,37 @@ version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +[[package]] +name = "apollo-compiler" +version = "1.0.0-beta.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875f39060728ac3e775fc3fe5421225d6df92c4d5155a9524cdb198f05006d36" +dependencies = [ + "ahash 0.8.11", + "apollo-parser 0.8.2", + "ariadne 0.4.1", + "indexmap 2.5.0", + "rowan", + "serde", + "serde_json_bytes", + "thiserror", + "triomphe", + "typed-arena", + "uuid", +] + +[[package]] +name = "apollo-composition" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6864cf58167a074ac909b387cebdbc0a6957d7eb95b6e4cdaf7df8c9cd46108a" +dependencies = [ + "apollo-compiler", + "apollo-federation", + "apollo-federation-types", + "either", +] + [[package]] name = "apollo-encoder" version = "0.8.0" @@ -150,6 +193,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "apollo-federation" +version = "2.0.0-alpha.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b327840a1c216f7a1e6ac1a4fee347a9dee4fe7e3db94f5f9756337f5754969c" +dependencies = [ + "apollo-compiler", + "derive_more", + "either", + "http 0.2.12", + "indexmap 2.5.0", + "itertools 0.13.0", + "lazy_static", + "multimap", + "nom", + "nom_locate", + "petgraph", + "serde", + "serde_json", + "serde_json_bytes", + "strum", + "strum_macros", + "thiserror", + "time", + "tracing", + "url", +] + [[package]] name = "apollo-federation-types" version = "0.14.1" @@ -167,6 +238,34 @@ dependencies = [ "url", ] +[[package]] +name = "apollo-language-server" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e82651825f3f99089803394c0f68b573fe6195993892099bf2fa6387ba4438a" +dependencies = [ + "apollo-compiler", + "apollo-composition", + "apollo-federation", + "apollo-federation-types", + "apollo-parser 0.8.2", + "debounced", + "futures", + "indexmap 2.5.0", + "itertools 0.13.0", + "lsp-types", + "memoize", + "once_cell", + "ropey", + "semver", + "serde", + "serde_json", + "tokio", + "tower-lsp", + "tracing", + "url", +] + [[package]] name = "apollo-parser" version = "0.7.7" @@ -204,6 +303,17 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "ariadne" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44055e597c674aef7cb903b2b9f6e4cba1277ed0d2d61dae7cd52d7ffa81f8e2" +dependencies = [ + "concolor", + "unicode-width", + "yansi 1.0.1", +] + [[package]] name = "ariadne" version = "0.5.0" @@ -563,6 +673,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -596,53 +717,6 @@ dependencies = [ "paste", ] -[[package]] -name = "axum" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 1.0.1", - "tower 0.5.1", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.1", - "tower-layer", - "tower-service", -] - [[package]] name = "backoff" version = "0.4.0" @@ -877,6 +951,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "byteorder" version = "1.5.0" @@ -916,7 +996,7 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d73155ae6b28cf5de4cfc29aeb02b8a1c6dab883cb015d15cd514e42766846" dependencies = [ - "ahash", + "ahash 0.8.11", "cached_proc_macro", "cached_proc_macro_types", "hashbrown 0.14.5", @@ -1176,6 +1256,26 @@ dependencies = [ "unreachable", ] +[[package]] +name = "concolor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3" +dependencies = [ + "bitflags 1.3.2", + "concolor-query", + "is-terminal", +] + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1199,49 +1299,16 @@ dependencies = [ ] [[package]] -name = "console-api" -version = "0.8.0" +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ed14aa9c9f927213c6e4f3ef75faaad3406134efe84ba2cb7983431d5f0931" -dependencies = [ - "futures-core", - "prost", - "prost-types", - "tonic", - "tracing-core", -] +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] -name = "console-subscriber" +name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e3a111a37f3333946ebf9da370ba5c5577b18eb342ec683eb488dd21980302" -dependencies = [ - "console-api", - "crossbeam-channel", - "crossbeam-utils", - "futures-task", - "hdrhistogram", - "humantime", - "hyper-util", - "prost", - "prost-types", - "serde", - "serde_json", - "thread_local", - "tokio", - "tokio-stream", - "tonic", - "tracing", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" @@ -1600,6 +1667,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "debounced" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107e5cd9b5163c19751e53eef634cae25cf5ed5f6d0c81125feaa92e43703cc7" +dependencies = [ + "futures-timer", + "futures-util", +] + [[package]] name = "deflate64" version = "0.1.9" @@ -1674,6 +1751,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.79", +] + [[package]] name = "dialoguer" version = "0.11.0" @@ -2417,6 +2507,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -2424,23 +2517,10 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", ] -[[package]] -name = "hdrhistogram" -version = "7.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" -dependencies = [ - "base64 0.21.7", - "byteorder", - "flate2", - "nom", - "num-traits", -] - [[package]] name = "headers" version = "0.4.0" @@ -2681,7 +2761,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -3202,6 +3282,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonpath-rust" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06cc127b7c3d270be504572364f9569761a180b981919dd0d87693a7f5fb7829" +dependencies = [ + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + [[package]] name = "jsonwebtoken" version = "9.3.0" @@ -3364,7 +3457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -3475,6 +3568,15 @@ dependencies = [ "logos-codegen", ] +[[package]] +name = "lru" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +dependencies = [ + "hashbrown 0.12.3", +] + [[package]] name = "lru-cache" version = "0.1.2" @@ -3484,6 +3586,19 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "lsp-types" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + [[package]] name = "lychee-lib" version = "0.16.0" @@ -3589,12 +3704,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "md5" version = "0.7.0" @@ -3607,6 +3716,29 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df4051db13d0816cf23196d3baa216385ae099339f5d0645a8d9ff2305e82b8" +dependencies = [ + "lazy_static", + "lru", + "memoize-inner", +] + +[[package]] +name = "memoize-inner" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bdece7e91f0d1e33df7b46ec187a93ea0d4e642113a1039ac8bfdd4a3273ac" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "mime" version = "0.3.17" @@ -3706,6 +3838,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +dependencies = [ + "serde", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -3760,6 +3901,17 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -3902,6 +4054,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.36.4" @@ -4189,6 +4350,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -4197,6 +4403,8 @@ checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap 2.5.0", + "serde", + "serde_derive", ] [[package]] @@ -4438,38 +4646,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" -dependencies = [ - "anyhow", - "itertools 0.13.0", - "proc-macro2", - "quote", - "syn 2.0.79", -] - -[[package]] -name = "prost-types" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" -dependencies = [ - "prost", -] - [[package]] name = "psl-types" version = "2.0.11" @@ -4809,12 +4985,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "ropey" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" +dependencies = [ + "smallvec", + "str_indices", +] + [[package]] name = "rover" version = "0.26.3" dependencies = [ "anyhow", "apollo-federation-types", + "apollo-language-server", "apollo-parser 0.8.2", "assert-json-diff", "assert_cmd", @@ -4889,6 +5076,7 @@ dependencies = [ "tokio-util", "toml", "tower 0.5.1", + "tower-lsp", "tower-test", "tracing", "tracing-test", @@ -4905,7 +5093,7 @@ dependencies = [ "apollo-encoder", "apollo-federation-types", "apollo-parser 0.8.2", - "ariadne", + "ariadne 0.5.0", "backoff", "buildstructor", "camino", @@ -5000,7 +5188,6 @@ dependencies = [ "anyhow", "camino", "console", - "console-subscriber", "notify", "rstest", "speculoos", @@ -5008,7 +5195,6 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "tokio-stream", "tokio-util", "tracing", "url", @@ -5333,12 +5519,28 @@ version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ + "indexmap 2.5.0", "itoa", "memchr", "ryu", "serde", ] +[[package]] +name = "serde_json_bytes" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ecd92a088fb2500b2f146c9ddc5da9950bb7264d3f00932cd2a6fb369c26c46" +dependencies = [ + "ahash 0.8.11", + "bytes", + "indexmap 2.5.0", + "jsonpath-rust", + "regex", + "serde", + "serde_json", +] + [[package]] name = "serde_json_traversal" version = "0.2.0" @@ -5365,6 +5567,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "serde_spanned" version = "0.6.7" @@ -5645,7 +5858,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.79", @@ -5721,6 +5934,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + [[package]] name = "str_inflector" version = "0.12.0" @@ -6037,7 +6256,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -6109,7 +6330,6 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.7", "tokio-macros", - "tracing", "windows-sys 0.52.0", ] @@ -6239,36 +6459,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tonic" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.22.1", - "bytes", - "h2 0.4.6", - "http 1.1.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.4.1", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "prost", - "socket2 0.5.7", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.4.13" @@ -6277,11 +6467,8 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", - "slab", "tokio", "tokio-util", "tower-layer", @@ -6349,6 +6536,40 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +[[package]] +name = "tower-lsp" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" +dependencies = [ + "async-trait", + "auto_impl", + "bytes", + "dashmap", + "futures", + "httparse", + "lsp-types", + "memchr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tower 0.4.13", + "tower-lsp-macros", + "tracing", +] + +[[package]] +name = "tower-lsp-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -6453,6 +6674,16 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "serde", + "stable_deref_trait", +] + [[package]] name = "trust-dns-proto" version = "0.21.2" @@ -6523,6 +6754,12 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typed-builder" version = "0.20.0" @@ -6549,6 +6786,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicase" version = "2.7.0" @@ -6656,6 +6899,7 @@ checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "serde", + "wasm-bindgen", ] [[package]] @@ -6929,6 +7173,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -6956,6 +7209,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6987,6 +7255,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6999,6 +7273,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -7011,6 +7291,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -7029,6 +7315,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -7041,6 +7333,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -7053,6 +7351,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -7065,6 +7369,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 941ff80d4..4a6b17494 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,8 @@ apollo-encoder = "0.8" # https://github.com/apollographql/federation-rs apollo-federation-types = "0.14.1" +apollo-language-server = { version = "0.3.0", default-features = false, features = ["tokio"] } + # crates.io dependencies anyhow = "1" ariadne = "0.5" @@ -168,6 +170,7 @@ zip = "2.0" anyhow = { workspace = true } assert_fs = { workspace = true } async-trait = { workspace = true } +apollo-language-server = { workspace = true} apollo-federation-types = { workspace = true } apollo-parser = { workspace = true } billboard = { workspace = true } diff --git a/crates/rover-std/Cargo.toml b/crates/rover-std/Cargo.toml index 4d1f5d2c4..0810e8660 100644 --- a/crates/rover-std/Cargo.toml +++ b/crates/rover-std/Cargo.toml @@ -14,11 +14,9 @@ notify = { workspace = true } tap = { workspace = true } tokio = { workspace = true, features = [ "macros", "rt", "rt-multi-thread", "time" ] } tokio-util = { workspace = true} -tokio-stream = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true } -console-subscriber = "0.4.0" [dev-dependencies] notify = { workspace = true } diff --git a/deny.toml b/deny.toml index c503e809f..bfcdb758c 100644 --- a/deny.toml +++ b/deny.toml @@ -64,6 +64,24 @@ version = "*" expression = "(Apache-2.0 OR MIT) AND BSD-3-Clause" license-files = [{ path = "COPYRIGHT", hash = 0x39f8ad31 }] +[[licenses.clarify]] +name = "apollo-language-server" +version = "*" +expression = "Elastic-2.0" +license-files = [{ path = "LICENSE.md", hash = 0x5fc4a573 }] + +[[licenses.exceptions]] +name = "apollo-language-server" +allow = ["Elastic-2.0"] + +[[licenses.exceptions]] +name = "apollo-federation" +allow = ["Elastic-2.0"] + +[[licenses.exceptions]] +name = "apollo-composition" +allow = ["Elastic-2.0"] + # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html From fd6b482f795ed30218d64500136ef214f6d9aca4 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Sun, 24 Nov 2024 08:46:14 +0000 Subject: [PATCH 2/9] ROVER-245 Removing deprecated PanicInfo and other fixes Clumps together other minor fixes without them dirtying other bits of the commit history. --- crates/robot-panic/src/lib.rs | 6 +++--- src/utils/supergraph_config.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/robot-panic/src/lib.rs b/crates/robot-panic/src/lib.rs index cf44b3fee..ea044fa58 100644 --- a/crates/robot-panic/src/lib.rs +++ b/crates/robot-panic/src/lib.rs @@ -93,7 +93,7 @@ macro_rules! setup_panic { #[cfg(not(debug_assertions))] match ::std::env::var("RUST_BACKTRACE") { Err(_) => { - panic::set_hook(Box::new(move |info: &PanicInfo| { + panic::set_hook(Box::new(move |info: &PanicHookInfo| { let crash_report = get_report(&$meta, info); print_msg(&crash_report, &$meta) .expect("robot-panic: printing error message to console failed"); @@ -105,7 +105,7 @@ macro_rules! setup_panic { () => { #[allow(unused_imports)] - use std::panic::{self, PanicInfo}; + use std::panic::{self, PanicHookInfo}; #[allow(unused_imports)] use $crate::{get_report, print_msg, Metadata}; @@ -120,7 +120,7 @@ macro_rules! setup_panic { repository: env!("CARGO_PKG_REPOSITORY").into(), }; - panic::set_hook(Box::new(move |info: &PanicInfo| { + panic::set_hook(Box::new(move |info: &PanicHookInfo| { let crash_report = get_report(&meta, info); print_msg(&crash_report, &meta) .expect("robot-panic: printing error message to console failed"); diff --git a/src/utils/supergraph_config.rs b/src/utils/supergraph_config.rs index a8f64e4e2..09820cbcc 100644 --- a/src/utils/supergraph_config.rs +++ b/src/utils/supergraph_config.rs @@ -487,7 +487,7 @@ mod test_get_supergraph_config { let supergraph_config_path = third_level_folder.path().join("supergraph.yaml"); fs::write( supergraph_config_path.clone(), - &supergraph_config.into_bytes(), + supergraph_config.into_bytes(), ) .expect("Could not write supergraph.yaml"); @@ -1185,7 +1185,7 @@ subgraphs: routing_url: https://people.example.com schema: file: ./people.graphql"#, - latest_fed2_version.to_string() + latest_fed2_version ); let tmp_home = TempDir::new().unwrap(); let mut config_path = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); @@ -1225,7 +1225,7 @@ subgraphs: routing_url: https://people.example.com schema: file: ../../people.graphql"#, - latest_fed2_version.to_string() + latest_fed2_version ); let tmp_home = TempDir::new().unwrap(); let tmp_dir = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); @@ -1279,7 +1279,7 @@ subgraphs: routing_url: https://people.example.com schema: file: ../../people.graphql"#, - latest_fed2_version.to_string() + latest_fed2_version ); let tmp_home = TempDir::new().unwrap(); let tmp_dir = Utf8PathBuf::try_from(tmp_home.path().to_path_buf()).unwrap(); From b647a033f2816cdc6fd9385b84295ee0f27a6cd3 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Sun, 24 Nov 2024 06:35:33 +0000 Subject: [PATCH 3/9] ROVER-245 Integrate existing LSP code Takes the approach of dumping the existing LSP code straight into what we have and integrating it to the point of compilation. --- Cargo.toml | 2 + src/cli.rs | 7 + src/command/lsp/errors.rs | 11 + src/command/lsp/mod.rs | 301 ++++++++++++++++++ src/command/mod.rs | 4 + src/composition/events/mod.rs | 8 +- src/composition/mod.rs | 38 ++- src/composition/pipeline.rs | 28 +- src/composition/runner/mod.rs | 26 +- .../supergraph/config/error/subgraph.rs | 2 + .../supergraph/config/full/subgraph.rs | 223 +++++++++---- .../supergraph/config/full/supergraph.rs | 9 +- .../supergraph/config/lazy/subgraph.rs | 3 + .../supergraph/config/lazy/supergraph.rs | 54 +++- .../supergraph/config/unresolved/subgraph.rs | 1 + .../config/unresolved/supergraph.rs | 8 +- src/composition/watchers/composition.rs | 131 ++++++-- src/composition/watchers/subgraphs.rs | 17 +- 18 files changed, 716 insertions(+), 157 deletions(-) create mode 100644 src/command/lsp/errors.rs create mode 100644 src/command/lsp/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 4a6b17494..141a8d95b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ apollo-federation-types = "0.14.1" apollo-language-server = { version = "0.3.0", default-features = false, features = ["tokio"] } + # crates.io dependencies anyhow = "1" ariadne = "0.5" @@ -225,6 +226,7 @@ tokio-stream = { workspace = true } tokio-util = { workspace = true } toml = { workspace = true } tower = { workspace = true } +tower-lsp = { version = "0.20.0" } tracing = { workspace = true } which = { workspace = true } uuid = { workspace = true } diff --git a/src/cli.rs b/src/cli.rs index 6b7d9fdbd..48bc9536f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -234,6 +234,8 @@ impl Rover { Command::Explain(command) => command.run(), Command::PersistedQueries(command) => command.run(self.get_client_config()?).await, Command::License(command) => command.run(self.get_client_config()?).await, + #[cfg(feature = "composition-js")] + Command::Lsp(command) => command.run(self.get_client_config()?).await, } } @@ -432,6 +434,11 @@ pub enum Command { /// Commands for fetching offline licenses License(command::License), + + /// Start the language server + #[cfg(feature = "composition-js")] + #[clap(hide = true)] + Lsp(command::Lsp), } #[derive(Default, ValueEnum, Debug, Serialize, Clone, Copy, Eq, PartialEq)] diff --git a/src/command/lsp/errors.rs b/src/command/lsp/errors.rs new file mode 100644 index 000000000..53f0c572d --- /dev/null +++ b/src/command/lsp/errors.rs @@ -0,0 +1,11 @@ +use camino::Utf8PathBuf; + +use crate::composition::CompositionError; + +#[derive(thiserror::Error, Debug)] +pub enum StartCompositionError { + #[error("Could not convert Supergraph path to URL")] + SupergraphYamlUrlConversionFailed(Utf8PathBuf), + #[error("Could not run initial composition")] + InitialCompositionFailed(#[from] CompositionError), +} diff --git a/src/command/lsp/mod.rs b/src/command/lsp/mod.rs new file mode 100644 index 000000000..2c8cd0911 --- /dev/null +++ b/src/command/lsp/mod.rs @@ -0,0 +1,301 @@ +mod errors; + +use std::collections::{BTreeMap, HashMap}; +use std::env::temp_dir; +use std::io::stdin; + +use anyhow::Error; +use apollo_federation_types::config::FederationVersion::LatestFedTwo; +use apollo_federation_types::config::SubgraphConfig; +use apollo_language_server::{ApolloLanguageServer, Config}; +use async_trait::async_trait; +use camino::Utf8PathBuf; +use clap::Parser; +use futures::StreamExt; +use rover_client::shared::GraphRef; +use rover_client::RoverClientError; +use serde::Serialize; +use tower_lsp::lsp_types::{Diagnostic, Range}; +use tower_lsp::Server; +use tracing::debug; +use url::Url; + +use crate::command::lsp::errors::StartCompositionError; +use crate::command::lsp::errors::StartCompositionError::SupergraphYamlUrlConversionFailed; +use crate::composition::events::CompositionEvent; +use crate::composition::runner::Runner; +use crate::composition::supergraph::config::lazy::LazilyResolvedSupergraphConfig; +use crate::composition::supergraph::config::resolver::{ + ResolveSupergraphConfigError, SubgraphPrompt, SupergraphConfigResolver, +}; +use crate::composition::supergraph::install::InstallSupergraph; +use crate::composition::SupergraphConfigResolutionError::PathDoesNotPointToAFile; +use crate::composition::{ + CompositionError, CompositionSubgraphAdded, CompositionSubgraphRemoved, CompositionSuccess, + SupergraphConfigResolutionError, +}; +use crate::utils::effect::exec::TokioCommand; +use crate::utils::effect::fetch_remote_subgraphs::FetchRemoteSubgraphs; +use crate::utils::effect::install::InstallBinary; +use crate::utils::effect::read_file::FsReadFile; +use crate::utils::effect::write_file::FsWriteFile; +use crate::{ + options::PluginOpts, + utils::{client::StudioClientConfig, parsers::FileDescriptorType}, + RoverOutput, RoverResult, +}; + +#[derive(Debug, Serialize, Parser)] +pub struct Lsp { + #[clap(flatten)] + pub(crate) opts: LspOpts, +} + +#[derive(Clone, Debug, Serialize, Parser)] +pub struct LspOpts { + #[clap(flatten)] + pub plugin_opts: PluginOpts, + + /// The absolute path to the supergraph configuration file. + #[serde(skip_serializing)] + #[arg(long = "supergraph-config")] + supergraph_yaml: Option, +} + +impl Lsp { + pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult { + self.opts + .plugin_opts + .prompt_for_license_accept(&client_config)?; + + run_lsp(client_config, self.opts.clone()).await?; + Ok(RoverOutput::EmptySuccess) + } +} + +async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverResult<()> { + let supergraph_yaml_path = lsp_opts.supergraph_yaml.as_ref().and_then(|path| { + if path.is_relative() { + Some( + Utf8PathBuf::try_from(std::env::current_dir().ok()?) + .ok()? + .join(path), + ) + } else { + Some(path.clone()) + } + }); + + // Early return if there is no `supergraph.yaml` given as there is no further need to construct + // anything + let (service, socket) = match supergraph_yaml_path { + None => { + let (service, socket, _receiver) = ApolloLanguageServer::build_service( + Config { + // TODO Do we need to worry about these now? + root_uri: String::default(), + enable_auto_composition: false, + force_federation: false, + disable_telemetry: false, + }, + HashMap::new(), + ); + (service, socket) + } + Some(supergraph_yaml_path) => { + // Resolve Supergraph Config -> Lazy + let lazily_resolved_supergraph_config = + generate_lazily_resolved_supergraph_config(supergraph_yaml_path.clone()).await?; + let supergraph_yaml_url = Url::from_file_path(supergraph_yaml_path.clone()) + .map_err(|_| SupergraphYamlUrlConversionFailed(supergraph_yaml_path.clone()))?; + debug!("Supergraph Config Root: {:?}", supergraph_yaml_url); + // Generate the config needed to spin up the Language Server + let (service, socket, _receiver) = ApolloLanguageServer::build_service( + Config { + root_uri: String::from(supergraph_yaml_url.clone()), + enable_auto_composition: false, + force_federation: false, + disable_telemetry: false, + }, + HashMap::from_iter( + lazily_resolved_supergraph_config + .subgraphs() + .iter() + .map(|(a, b)| (a.to_string(), b.schema().clone())), + ), + ); + // Start running composition + start_composition( + lazily_resolved_supergraph_config, + supergraph_yaml_url, + client_config, + lsp_opts, + service.inner().to_owned(), + ) + .await?; + (service, socket) + } + }; + + let stdin = tokio::io::stdin(); + let stdout = tokio::io::stdout(); + let server = Server::new(stdin, stdout, socket); + server.serve(service).await; + Ok(()) +} + +async fn generate_lazily_resolved_supergraph_config( + supergraph_yaml_path: Utf8PathBuf, +) -> Result { + // Get the SupergraphConfig in a form we can use + let supergraph_config = SupergraphConfigResolver::default() + .load_remote_subgraphs(&NullFetchRemoteSubgraphs {}, None) + .await? + .load_from_file_descriptor( + &mut stdin(), + Some(&FileDescriptorType::File(supergraph_yaml_path.clone())), + )?; + if let Some(parent) = supergraph_yaml_path.parent() { + Ok(supergraph_config + .lazily_resolve_subgraphs(Some(&parent.to_owned()), &SubgraphPrompt::default()) + .await?) + } else { + Err(PathDoesNotPointToAFile( + supergraph_yaml_path.into_std_path_buf(), + )) + } +} + +async fn start_composition( + lazily_resolved_supergraph_config: LazilyResolvedSupergraphConfig, + supergraph_yaml_url: Url, + client_config: StudioClientConfig, + lsp_opts: LspOpts, + language_server: ApolloLanguageServer, +) -> Result<(), StartCompositionError> { + let federation_version = lazily_resolved_supergraph_config + .federation_version() + .clone() + .unwrap_or(LatestFedTwo); + + // Spawn a separate thread to handle composition and passing that data to the language server + tokio::spawn(async move { + // TODO: Let the supergraph binary exist inside its own task that can respond to being re-installed etc. + let supergraph_binary = + InstallSupergraph::new(federation_version.clone(), client_config.clone()) + .install( + None, + lsp_opts.plugin_opts.elv2_license_accepter, + lsp_opts.plugin_opts.skip_update, + ) + .await?; + + // Spin up Runner + let mut stream = Runner::default() + .setup_subgraph_watchers( + lazily_resolved_supergraph_config.subgraphs().clone(), + &lsp_opts.plugin_opts.profile, + &client_config, + 500, + ) + .setup_supergraph_config_watcher(lazily_resolved_supergraph_config.clone()) + .setup_composition_watcher( + lazily_resolved_supergraph_config + .fully_resolve( + &client_config, + &client_config.get_authenticated_client(&lsp_opts.plugin_opts.profile)?, + ) + .await + .map_err(ResolveSupergraphConfigError::ResolveSubgraphs)?, + supergraph_binary, + TokioCommand::default(), + FsReadFile::default(), + FsWriteFile::default(), + Utf8PathBuf::try_from(temp_dir())?, + true, + ) + .run(); + + while let Some(event) = stream.next().await { + match event { + CompositionEvent::Started => { + // Even though it's hidden by library calls, this function emits a WorkDoneProgressBegin event, + // which is paired with a WorkDoneProgressEnd event, sent by the `composition_did_update` function. + // Any refactoring needs to ensure that we don't break this ordering, otherwise the LSP may well + // cease to function in a useful way. + language_server.composition_did_start().await; + } + CompositionEvent::Success(CompositionSuccess { + supergraph_sdl, + federation_version, + hints, + }) => { + debug!("Successfully composed with version {}", federation_version); + // Clear any previous errors on `supergraph.yaml` + language_server + .publish_diagnostics(supergraph_yaml_url.clone(), vec![]) + .await; + language_server + .composition_did_update( + Some(supergraph_sdl), + hints.into_iter().map(Into::into).collect(), + None, + ) + .await; + } + CompositionEvent::Error(CompositionError::Build { source: errors }) => { + debug!( + ?errors, + "Composition {federation_version} completed with errors" + ); + // Clear any previous errors on `supergraph.yaml` + language_server + .publish_diagnostics(supergraph_yaml_url.clone(), vec![]) + .await; + language_server + .composition_did_update( + None, + errors.into_iter().map(Into::into).collect(), + None, + ) + .await + } + CompositionEvent::Error(err) => { + debug!("Composition {federation_version} failed: {err}"); + let message = format!("Failed run composition {federation_version}: {err}",); + let diagnostic = Diagnostic::new_simple(Range::default(), message); + language_server + .publish_diagnostics(supergraph_yaml_url.clone(), vec![diagnostic]) + .await; + } + CompositionEvent::SubgraphAdded(CompositionSubgraphAdded { + name, + schema_source, + }) => { + debug!("Subgraph {} added", name); + language_server.add_subgraph(name, schema_source).await; + } + CompositionEvent::SubgraphRemoved(CompositionSubgraphRemoved { name }) => { + debug!("Subgraph {} removed", name); + language_server.remove_subgraph(&name).await; + } + } + } + Ok::<(), Error>(()) + }); + Ok(()) +} + +struct NullFetchRemoteSubgraphs {} + +#[async_trait] +impl FetchRemoteSubgraphs for NullFetchRemoteSubgraphs { + type Error = RoverClientError; + + async fn fetch_remote_subgraphs( + &self, + _: &GraphRef, + ) -> Result, Self::Error> { + Ok(BTreeMap::new()) + } +} diff --git a/src/command/mod.rs b/src/command/mod.rs index ae55ebd70..289d2bb41 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -8,6 +8,8 @@ mod graph; mod info; pub(crate) mod install; mod license; +#[cfg(feature = "composition-js")] +mod lsp; pub(crate) mod output; mod persisted_queries; mod readme; @@ -26,6 +28,8 @@ pub use graph::Graph; pub use info::Info; pub use install::Install; pub use license::License; +#[cfg(feature = "composition-js")] +pub use lsp::Lsp; pub use output::RoverOutput; pub use persisted_queries::PersistedQueries; pub use readme::Readme; diff --git a/src/composition/events/mod.rs b/src/composition/events/mod.rs index 60dc8d87b..bdfda81ba 100644 --- a/src/composition/events/mod.rs +++ b/src/composition/events/mod.rs @@ -1,4 +1,6 @@ -use super::{CompositionError, CompositionSuccess}; +use super::{ + CompositionError, CompositionSubgraphAdded, CompositionSubgraphRemoved, CompositionSuccess, +}; /// Events emitted from composition #[derive(Debug)] @@ -10,4 +12,8 @@ pub enum CompositionEvent { Success(CompositionSuccess), /// Composition errored Error(CompositionError), + /// Subgraph Added + SubgraphAdded(CompositionSubgraphAdded), + /// SubgraphRemoved + SubgraphRemoved(CompositionSubgraphRemoved), } diff --git a/src/composition/mod.rs b/src/composition/mod.rs index dca367f81..befd09560 100644 --- a/src/composition/mod.rs +++ b/src/composition/mod.rs @@ -1,5 +1,8 @@ use std::fmt::Debug; +use std::path::PathBuf; +use anyhow::Error; +use apollo_federation_types::config::SchemaSource; use apollo_federation_types::{ config::FederationVersion, rover::{BuildErrors, BuildHint}, @@ -7,6 +10,10 @@ use apollo_federation_types::{ use camino::Utf8PathBuf; use derive_getters::Getters; +use crate::composition::supergraph::config::resolver::{ + LoadRemoteSubgraphsError, LoadSupergraphConfigError, ResolveSupergraphConfigError, +}; + pub mod events; pub mod pipeline; pub mod runner; @@ -20,9 +27,9 @@ mod watchers; #[derive(Getters, Debug, Clone, Eq, PartialEq)] pub struct CompositionSuccess { - supergraph_sdl: String, - hints: Vec, - federation_version: FederationVersion, + pub(crate) supergraph_sdl: String, + pub(crate) hints: Vec, + pub(crate) federation_version: FederationVersion, } #[derive(thiserror::Error, Debug)] @@ -54,3 +61,28 @@ pub enum CompositionError { #[error("Serialization error.\n{}", .0)] SerdeYaml(#[from] serde_yaml::Error), } + +#[derive(Debug, Eq, PartialEq)] +pub struct CompositionSubgraphAdded { + pub(crate) name: String, + pub(crate) schema_source: SchemaSource, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct CompositionSubgraphRemoved { + pub(crate) name: String, +} + +#[derive(thiserror::Error, Debug)] +pub enum SupergraphConfigResolutionError { + #[error("Could not instantiate Studio Client")] + StudioClientInitialisationFailed(#[from] Error), + #[error("Could not load remote subgraphs")] + LoadRemoteSubgraphsFailed(#[from] LoadRemoteSubgraphsError), + #[error("Could not load supergraph config from local file")] + LoadLocalSupergraphConfigFailed(#[from] LoadSupergraphConfigError), + #[error("Could not resolve local and remote elements into complete SupergraphConfig")] + ResolveSupergraphConfigFailed(#[from] ResolveSupergraphConfigError), + #[error("Path `{0}` does not point to a file")] + PathDoesNotPointToAFile(PathBuf), +} diff --git a/src/composition/pipeline.rs b/src/composition/pipeline.rs index f2b3a712f..2e51d8719 100644 --- a/src/composition/pipeline.rs +++ b/src/composition/pipeline.rs @@ -5,26 +5,11 @@ use camino::Utf8PathBuf; use rover_client::shared::GraphRef; use tempfile::tempdir; use tower::MakeService; - -use crate::{ - options::{LicenseAccepter, ProfileOpt}, - utils::{ - client::StudioClientConfig, - effect::{ - exec::ExecCommand, install::InstallBinary, introspect::IntrospectSubgraph, - read_file::ReadFile, read_stdin::ReadStdin, write_file::WriteFile, - }, - parsers::FileDescriptorType, - }, -}; - use super::{ runner::{CompositionRunner, Runner}, supergraph::{ binary::OutputTarget, config::resolver::{ - fetch_remote_subgraph::{FetchRemoteSubgraphRequest, RemoteSubgraph}, - fetch_remote_subgraphs::FetchRemoteSubgraphsRequest, LoadRemoteSubgraphsError, LoadSupergraphConfigError, ResolveSupergraphConfigError, SubgraphPrompt, SupergraphConfigResolver, }, @@ -32,6 +17,17 @@ use super::{ }, CompositionError, CompositionSuccess, }; +use crate::{ + options::{LicenseAccepter, ProfileOpt}, + utils::{ + client::StudioClientConfig, + effect::{ + exec::ExecCommand, install::InstallBinary, introspect::IntrospectSubgraph, + read_file::ReadFile, read_stdin::ReadStdin, write_file::WriteFile, + }, + parsers::FileDescriptorType, + }, +}; #[derive(thiserror::Error, Debug)] pub enum CompositionPipelineError { @@ -224,6 +220,7 @@ impl CompositionPipeline { client_config: &StudioClientConfig, introspection_polling_interval: u64, output_dir: Utf8PathBuf, + compose_on_initialisation: bool, ) -> Result, CompositionPipelineError> where ReadF: ReadFile + Debug + Eq + PartialEq + Send + Sync + 'static, @@ -254,6 +251,7 @@ impl CompositionPipeline { read_file, write_file, output_dir, + compose_on_initialisation, ); Ok(runner) } diff --git a/src/composition/runner/mod.rs b/src/composition/runner/mod.rs index f68942b98..cdf3d8709 100644 --- a/src/composition/runner/mod.rs +++ b/src/composition/runner/mod.rs @@ -8,20 +8,7 @@ use std::{collections::BTreeMap, fmt::Debug}; use camino::Utf8PathBuf; use futures::stream::{BoxStream, StreamExt}; -use crate::{ - composition::watchers::watcher::{ - file::FileWatcher, supergraph_config::SupergraphConfigWatcher, - }, - options::ProfileOpt, - subtask::{Subtask, SubtaskRunStream, SubtaskRunUnit}, - utils::{ - client::StudioClientConfig, - effect::{exec::ExecCommand, read_file::ReadFile, write_file::WriteFile}, - }, -}; - use self::state::SetupSubgraphWatchers; - use super::{ events::CompositionEvent, supergraph::{ @@ -33,6 +20,17 @@ use super::{ }, watchers::{composition::CompositionWatcher, subgraphs::SubgraphWatchers}, }; +use crate::{ + composition::watchers::watcher::{ + file::FileWatcher, supergraph_config::SupergraphConfigWatcher, + }, + options::ProfileOpt, + subtask::{Subtask, SubtaskRunStream, SubtaskRunUnit}, + utils::{ + client::StudioClientConfig, + effect::{exec::ExecCommand, read_file::ReadFile, write_file::WriteFile}, + }, +}; mod state; @@ -125,6 +123,7 @@ impl Runner { read_file: ReadF, write_file: WriteF, temp_dir: Utf8PathBuf, + compose_on_initialisation: bool, ) -> Runner> where ExecC: ExecCommand + Debug + Eq + PartialEq + Send + Sync + 'static, @@ -139,6 +138,7 @@ impl Runner { .read_file(read_file) .write_file(write_file) .temp_dir(temp_dir) + .compose_on_initialisation(compose_on_initialisation) .build(); Runner { state: state::Run { diff --git a/src/composition/supergraph/config/error/subgraph.rs b/src/composition/supergraph/config/error/subgraph.rs index 59f10a56a..d8c899f6a 100644 --- a/src/composition/supergraph/config/error/subgraph.rs +++ b/src/composition/supergraph/config/error/subgraph.rs @@ -15,6 +15,8 @@ pub enum ResolveSubgraphError { supergraph_config_path: Utf8PathBuf, /// Supplied path to the subgraph schema file path: PathBuf, + /// The result of joining the paths together, that caused the failure + joined_path: PathBuf, /// The source error source: std::io::Error, }, diff --git a/src/composition/supergraph/config/full/subgraph.rs b/src/composition/supergraph/config/full/subgraph.rs index ee949ad4c..0df193499 100644 --- a/src/composition/supergraph/config/full/subgraph.rs +++ b/src/composition/supergraph/config/full/subgraph.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::str::FromStr; use apollo_federation_types::config::{SchemaSource, SubgraphConfig}; @@ -8,7 +9,9 @@ use derive_getters::Getters; use rover_client::shared::GraphRef; use rover_std::Fs; use tower::{MakeService, Service, ServiceExt}; +use url::Url; +use crate::composition::supergraph::config::lazy::LazilyResolvedSubgraph; use crate::{ composition::supergraph::config::{ error::ResolveSubgraphError, @@ -39,6 +42,7 @@ impl FullyResolvedSubgraph { is_fed_two, } } + /// Resolves a [`UnresolvedSubgraph`] to a [`FullyResolvedSubgraph`] pub async fn resolve( introspect_subgraph_impl: &impl IntrospectSubgraph, @@ -56,88 +60,171 @@ impl FullyResolvedSubgraph { let supergraph_config_root = supergraph_config_root.ok_or(ResolveSubgraphError::SupergraphConfigMissing)?; let file = unresolved_subgraph.resolve_file_path(supergraph_config_root, file)?; - let schema = - Fs::read_file(&file).map_err(|err| ResolveSubgraphError::Fs(Box::new(err)))?; - Ok(FullyResolvedSubgraph::builder() - .and_routing_url(unresolved_subgraph.routing_url().clone()) - .schema(schema) - .build()) + Self::resolve_file(unresolved_subgraph.routing_url().clone(), &file) } SchemaSource::SubgraphIntrospection { subgraph_url, introspection_headers, } => { - let schema = introspect_subgraph_impl - .introspect_subgraph( - subgraph_url.clone(), - introspection_headers.clone().unwrap_or_default(), - ) - .await - .map_err(|err| ResolveSubgraphError::IntrospectionError { - subgraph_name: unresolved_subgraph.name().to_string(), - source: Box::new(err), - })?; - let routing_url = unresolved_subgraph - .routing_url() - .clone() - .unwrap_or_else(|| subgraph_url.to_string()); - Ok(FullyResolvedSubgraph::builder() - .routing_url(routing_url) - .schema(schema) - .build()) + Self::resolve_subgraph_introspection( + introspect_subgraph_impl, + unresolved_subgraph.name().clone(), + unresolved_subgraph.routing_url().clone(), + subgraph_url, + introspection_headers, + ) + .await } SchemaSource::Subgraph { graphref: graph_ref, subgraph, } => { - let graph_ref = GraphRef::from_str(graph_ref).map_err(|err| { - ResolveSubgraphError::InvalidGraphRef { - graph_ref: graph_ref.clone(), - source: Box::new(err), - } - })?; - let remote_subgraph = fetch_remote_subgraph_impl - .make_service(()) - .await - .map_err(|err| ResolveSubgraphError::FetchRemoteSdlError { - subgraph_name: subgraph.to_string(), - source: Box::new(err), - })? - .ready() - .await - .map_err(|err| ResolveSubgraphError::FetchRemoteSdlError { - subgraph_name: subgraph.to_string(), - source: Box::new(err), - })? - .call( - FetchRemoteSubgraphRequest::builder() - .graph_ref(graph_ref) - .subgraph_name(subgraph.to_string()) - .build(), - ) - .await - .map_err(|err| ResolveSubgraphError::FetchRemoteSdlError { - subgraph_name: subgraph.to_string(), - source: Box::new(err), - })?; - let schema = remote_subgraph.schema().clone(); - Ok(FullyResolvedSubgraph::builder() - .routing_url( - unresolved_subgraph - .routing_url() - .clone() - .unwrap_or_else(|| remote_subgraph.routing_url().to_string()), - ) - .schema(schema) - .build()) + Self::resolve_subgraph( + fetch_remote_subgraph_impl, + unresolved_subgraph.routing_url().clone(), + graph_ref, + subgraph, + ) + .await + } + SchemaSource::Sdl { sdl } => { + Self::resolve_sdl(unresolved_subgraph.routing_url().clone(), sdl) } - SchemaSource::Sdl { sdl } => Ok(FullyResolvedSubgraph::builder() - .and_routing_url(unresolved_subgraph.routing_url().clone()) - .schema(sdl.to_string()) - .build()), } } + /// Resolves a [`LazilyResolvedSubgraph`] to a [`FullyResolvedSubgraph`] + pub async fn fully_resolve( + introspect_subgraph_impl: &impl IntrospectSubgraph, + mut fetch_remote_subgraph_impl: MakeFetchSubgraph, + lazily_resolved_subgraph: LazilyResolvedSubgraph, + ) -> Result + where + MakeFetchSubgraph: MakeService<(), FetchRemoteSubgraphRequest, Response = RemoteSubgraph>, + MakeFetchSubgraph::MakeError: std::error::Error + Send + Sync + 'static, + MakeFetchSubgraph::Error: std::error::Error + Send + Sync + 'static, + { + match lazily_resolved_subgraph.schema() { + SchemaSource::File { file } => { + Self::resolve_file(lazily_resolved_subgraph.routing_url().clone(), &file) + } + SchemaSource::SubgraphIntrospection { + subgraph_url, + introspection_headers, + } => { + Self::resolve_subgraph_introspection( + introspect_subgraph_impl, + lazily_resolved_subgraph.name().clone(), + lazily_resolved_subgraph.routing_url().clone(), + subgraph_url, + introspection_headers, + ) + .await + } + SchemaSource::Subgraph { + graphref: graph_ref, + subgraph, + } => { + Self::resolve_subgraph( + fetch_remote_subgraph_impl, + lazily_resolved_subgraph.routing_url().clone(), + graph_ref, + subgraph, + ) + .await + } + SchemaSource::Sdl { sdl } => { + Self::resolve_sdl(lazily_resolved_subgraph.routing_url().clone(), sdl) + } + } + } + + fn resolve_file( + routing_url: Option, + file: &Utf8PathBuf, + ) -> Result { + let schema = Fs::read_file(&file).map_err(|err| ResolveSubgraphError::Fs(Box::new(err)))?; + Ok(FullyResolvedSubgraph::builder() + .and_routing_url(routing_url) + .schema(schema) + .build()) + } + + async fn resolve_subgraph_introspection( + introspect_subgraph_impl: &impl IntrospectSubgraph, + subgraph_name: String, + routing_url: Option, + subgraph_url: &Url, + introspection_headers: &Option>, + ) -> Result { + let schema = introspect_subgraph_impl + .introspect_subgraph( + subgraph_url.clone(), + introspection_headers.clone().unwrap_or_default(), + ) + .await + .map_err(|err| ResolveSubgraphError::IntrospectionError { + subgraph_name, + source: Box::new(err), + })?; + Ok(FullyResolvedSubgraph::builder() + .and_routing_url(routing_url) + .schema(schema) + .build()) + } + + async fn resolve_subgraph( + fetch_remote_subgraph_impl: &impl FetchRemoteSubgraph, + routing_url: Option, + graph_ref: &String, + subgraph: &String, + ) -> Result { + let graph_ref = + GraphRef::from_str(graph_ref).map_err(|err| ResolveSubgraphError::InvalidGraphRef { + graph_ref: graph_ref.clone(), + source: Box::new(err), + })?; + let remote_subgraph = fetch_remote_subgraph_impl + .make_service(()) + .await + .map_err(|err| ResolveSubgraphError::FetchRemoteSdlError { + subgraph_name: subgraph.to_string(), + source: Box::new(err), + })? + .ready() + .await + .map_err(|err| ResolveSubgraphError::FetchRemoteSdlError { + subgraph_name: subgraph.to_string(), + source: Box::new(err), + })? + .call( + FetchRemoteSubgraphRequest::builder() + .graph_ref(graph_ref) + .subgraph_name(subgraph.to_string()) + .build(), + ) + .await + .map_err(|err| ResolveSubgraphError::FetchRemoteSdlError { + subgraph_name: subgraph.to_string(), + source: Box::new(err), + })?; + let schema = remote_subgraph.schema().clone(); + Ok(FullyResolvedSubgraph::builder() + .and_routing_url(routing_url) + .schema(schema) + .build()) + } + + fn resolve_sdl( + routing_url: Option, + sdl: &String, + ) -> Result { + Ok(FullyResolvedSubgraph::builder() + .and_routing_url(routing_url) + .schema(sdl.to_string()) + .build()) + } + /// Mutably updates this subgraph's schema pub fn update_schema(&mut self, schema: String) { self.schema = schema; diff --git a/src/composition/supergraph/config/full/supergraph.rs b/src/composition/supergraph/config/full/supergraph.rs index 6dbc790d0..db2d35a4c 100644 --- a/src/composition/supergraph/config/full/supergraph.rs +++ b/src/composition/supergraph/config/full/supergraph.rs @@ -7,6 +7,7 @@ use futures::{stream, StreamExt, TryFutureExt}; use itertools::Itertools; use tower::MakeService; +use super::FullyResolvedSubgraph; use crate::{ composition::supergraph::config::{ error::ResolveSubgraphError, @@ -19,16 +20,14 @@ use crate::{ utils::effect::introspect::IntrospectSubgraph, }; -use super::FullyResolvedSubgraph; - /// Represents a [`SupergraphConfig`] that has a known [`FederationVersion`] and /// its subgraph [`SchemaSource`]s reduced to [`SchemaSource::Sdl`] #[derive(Clone, Debug, Eq, PartialEq, Getters)] #[cfg_attr(test, derive(buildstructor::Builder))] pub struct FullyResolvedSupergraphConfig { - origin_path: Option, - subgraphs: BTreeMap, - federation_version: FederationVersion, + pub(crate) origin_path: Option, + pub(crate) subgraphs: BTreeMap, + pub(crate) federation_version: FederationVersion, } impl From for SupergraphConfig { diff --git a/src/composition/supergraph/config/lazy/subgraph.rs b/src/composition/supergraph/config/lazy/subgraph.rs index fede4b4a1..1275e1a2f 100644 --- a/src/composition/supergraph/config/lazy/subgraph.rs +++ b/src/composition/supergraph/config/lazy/subgraph.rs @@ -11,6 +11,7 @@ use crate::composition::supergraph::config::{ /// confirmed to be relative to a supergraph config file #[derive(Clone, Debug, Eq, PartialEq, Getters, Builder)] pub struct LazilyResolvedSubgraph { + name: String, routing_url: Option, schema: SchemaSource, } @@ -26,11 +27,13 @@ impl LazilyResolvedSubgraph { SchemaSource::File { file } => { let file = unresolved_subgraph.resolve_file_path(supergraph_config_root, file)?; Ok(LazilyResolvedSubgraph { + name: unresolved_subgraph.name().to_owned(), routing_url: unresolved_subgraph.routing_url().clone(), schema: SchemaSource::File { file }, }) } _ => Ok(LazilyResolvedSubgraph { + name: unresolved_subgraph.name().to_owned(), routing_url: unresolved_subgraph.routing_url().clone(), schema: unresolved_subgraph.schema().clone(), }), diff --git a/src/composition/supergraph/config/lazy/supergraph.rs b/src/composition/supergraph/config/lazy/supergraph.rs index d966119af..b55166b31 100644 --- a/src/composition/supergraph/config/lazy/supergraph.rs +++ b/src/composition/supergraph/config/lazy/supergraph.rs @@ -1,16 +1,22 @@ use std::collections::BTreeMap; +use apollo_federation_types::config::FederationVersion::LatestFedTwo; use apollo_federation_types::config::{FederationVersion, SupergraphConfig}; use camino::Utf8PathBuf; use derive_getters::Getters; -use futures::{stream, StreamExt}; +use futures::future::join_all; +use futures::TryFutureExt; use itertools::Itertools; +use super::LazilyResolvedSubgraph; +use crate::composition::supergraph::config::full::{ + FullyResolvedSubgraph, FullyResolvedSupergraphConfig, +}; use crate::composition::supergraph::config::{ error::ResolveSubgraphError, unresolved::UnresolvedSupergraphConfig, }; - -use super::LazilyResolvedSubgraph; +use crate::utils::effect::fetch_remote_subgraph::FetchRemoteSubgraph; +use crate::utils::effect::introspect::IntrospectSubgraph; /// Represents a [`SupergraphConfig`] where all its [`SchemaSource::File`] subgraphs have /// known and valid file paths relative to a supergraph config file (or working directory of the @@ -28,7 +34,7 @@ impl LazilyResolvedSupergraphConfig { pub async fn resolve( supergraph_config_root: &Utf8PathBuf, unresolved_supergraph_config: UnresolvedSupergraphConfig, - ) -> Result> { + ) -> Result> { let subgraphs = stream::iter( unresolved_supergraph_config .subgraphs() @@ -39,12 +45,12 @@ impl LazilyResolvedSupergraphConfig { supergraph_config_root, unresolved_subgraph.clone(), ) - .map_err(|err| (name.to_string(), err))?; + .map_err(|err| (name.to_string(), err))?; Ok((name.to_string(), result)) }), ) - .buffer_unordered(50) - .collect::>>() + .buffer_unordered(50) + .collect::>>() .await; #[allow(clippy::type_complexity)] let (subgraphs, errors): ( @@ -61,6 +67,40 @@ impl LazilyResolvedSupergraphConfig { Err(BTreeMap::from_iter(errors.into_iter())) } } + + /// Transforms a [`LazilyResolvedSupergraphConfig`] into a [`FullyResolvedSupergraphConfig`] + /// consuming self in the process + pub async fn fully_resolve( + self, + introspect_subgraph_impl: &impl IntrospectSubgraph, + fetch_remote_subgraph_impl: &impl FetchRemoteSubgraph, + ) -> Result> { + let subgraphs = join_all(self.subgraphs().iter().map( + |(name, lazily_resolved_subgraph)| { + FullyResolvedSubgraph::fully_resolve( + introspect_subgraph_impl, + fetch_remote_subgraph_impl, + lazily_resolved_subgraph.clone(), + ) + .map_ok(|result| (name.to_string(), result)) + }, + )) + .await; + let (subgraphs, errors): ( + Vec<(String, FullyResolvedSubgraph)>, + Vec, + ) = subgraphs.into_iter().partition_result(); + if errors.is_empty() { + let subgraphs = BTreeMap::from_iter(subgraphs); + Ok(FullyResolvedSupergraphConfig { + origin_path: self.origin_path, + subgraphs, + federation_version: self.federation_version.unwrap_or(LatestFedTwo), + }) + } else { + Err(errors) + } + } } impl From for SupergraphConfig { diff --git a/src/composition/supergraph/config/unresolved/subgraph.rs b/src/composition/supergraph/config/unresolved/subgraph.rs index ce699a9b4..fd807d901 100644 --- a/src/composition/supergraph/config/unresolved/subgraph.rs +++ b/src/composition/supergraph/config/unresolved/subgraph.rs @@ -36,6 +36,7 @@ impl UnresolvedSubgraph { subgraph_name: self.name.to_string(), supergraph_config_path: root.clone(), path: path.as_std_path().to_path_buf(), + joined_path: joined_path.as_std_path().to_path_buf(), source: err, }), } diff --git a/src/composition/supergraph/config/unresolved/supergraph.rs b/src/composition/supergraph/config/unresolved/supergraph.rs index 1c4f77650..c1175e7ce 100644 --- a/src/composition/supergraph/config/unresolved/supergraph.rs +++ b/src/composition/supergraph/config/unresolved/supergraph.rs @@ -6,9 +6,8 @@ use buildstructor::buildstructor; use camino::Utf8PathBuf; use derive_getters::Getters; -use crate::composition::supergraph::config::federation::FederationVersionResolverFromSubgraphs; - use super::UnresolvedSubgraph; +use crate::composition::supergraph::config::federation::FederationVersionResolverFromSubgraphs; /// Object that represents a [`SupergraphConfig`] that requires resolution #[derive(Getters)] @@ -49,7 +48,6 @@ impl UnresolvedSupergraphConfig { #[cfg(test)] mod tests { - use std::{ collections::{BTreeMap, HashSet}, str::FromStr, @@ -667,6 +665,7 @@ mod tests { ( sdl_subgraph_name.clone(), LazilyResolvedSubgraph::builder() + .name(sdl_subgraph_name.clone()) .schema(SchemaSource::Sdl { sdl: sdl_subgraph_scenario.sdl.clone(), }) @@ -680,6 +679,7 @@ mod tests { .join(file_subgraph_scenario.schema_file_path) .canonicalize_utf8()?, }) + .name(file_subgraph_name.clone()) .routing_url(file_subgraph_scenario.routing_url) .build(), ), @@ -690,6 +690,7 @@ mod tests { graphref: remote_subgraph_scenario.graph_ref.to_string(), subgraph: remote_subgraph_scenario.subgraph_name.clone(), }) + .name(remote_subgraph_name.clone()) .routing_url(remote_subgraph_scenario.routing_url.clone()) .build(), ), @@ -704,6 +705,7 @@ mod tests { introspect_subgraph_scenario.introspection_headers.clone(), ), }) + .name(introspect_subgraph_name.clone()) .routing_url(introspect_subgraph_scenario.routing_url.clone()) .build(), ), diff --git a/src/composition/watchers/composition.rs b/src/composition/watchers/composition.rs index 73853c850..0e0db7a2d 100644 --- a/src/composition/watchers/composition.rs +++ b/src/composition/watchers/composition.rs @@ -6,7 +6,9 @@ use rover_std::errln; use tap::TapFallible; use tokio::{sync::mpsc::UnboundedSender, task::AbortHandle}; use tokio_stream::StreamExt; +use tracing::error; +use crate::composition::{CompositionError, CompositionSuccess}; use crate::{ composition::{ events::CompositionEvent, @@ -28,6 +30,7 @@ pub struct CompositionWatcher { read_file: ReadF, write_file: WriteF, temp_dir: Utf8PathBuf, + compose_on_initialisation: bool, } impl SubtaskHandleStream for CompositionWatcher @@ -48,6 +51,31 @@ where let mut supergraph_config = self.supergraph_config.clone(); let target_file = self.temp_dir.join("supergraph.yaml"); async move { + if self.compose_on_initialisation { + if let Err(err) = self + .setup_temporary_supergraph_yaml(&supergraph_config, &target_file) + .await + { + error!("Could not setup initial supergraph schema: {}", err); + }; + let _ = sender + .send(CompositionEvent::Started) + .tap_err(|err| error!("{:?}", err)); + let output = self.run_composition(&target_file).await; + match output { + Ok(success) => { + let _ = sender + .send(CompositionEvent::Success(success)) + .tap_err(|err| error!("{:?}", err)); + } + Err(err) => { + let _ = sender + .send(CompositionEvent::Error(err)) + .tap_err(|err| error!("{:?}", err)); + } + } + } + while let Some(event) = input.next().await { match event { SubgraphEvent::SubgraphChanged(subgraph_schema_changed) => { @@ -65,53 +93,30 @@ where } } - let supergraph_config = SupergraphConfig::from(supergraph_config.clone()); - let supergraph_config_yaml = serde_yaml::to_string(&supergraph_config); - - let supergraph_config_yaml = match supergraph_config_yaml { - Ok(supergraph_config_yaml) => supergraph_config_yaml, - Err(err) => { - errln!("Failed to serialize supergraph config into yaml"); - tracing::error!("{:?}", err); - continue; - } - }; - - let write_file_result = self - .write_file - .write_file(&target_file, supergraph_config_yaml.as_bytes()) - .await; - - if let Err(err) = write_file_result { - errln!("Failed to write the supergraph config to disk"); - tracing::error!("{:?}", err); + if let Err(err) = self + .setup_temporary_supergraph_yaml(&supergraph_config, &target_file) + .await + { + error!("Could not setup supergraph schema: {}", err); continue; - } + }; let _ = sender .send(CompositionEvent::Started) - .tap_err(|err| tracing::error!("{:?}", err)); + .tap_err(|err| error!("{:?}", err)); - let output = self - .supergraph_binary - .compose( - &self.exec_command, - &self.read_file, - &OutputTarget::Stdout, - target_file.clone(), - ) - .await; + let output = self.run_composition(&target_file).await; match output { Ok(success) => { let _ = sender .send(CompositionEvent::Success(success)) - .tap_err(|err| tracing::error!("{:?}", err)); + .tap_err(|err| error!("{:?}", err)); } Err(err) => { let _ = sender .send(CompositionEvent::Error(err)) - .tap_err(|err| tracing::error!("{:?}", err)); + .tap_err(|err| error!("{:?}", err)); } } } @@ -121,6 +126,62 @@ where } } +impl CompositionWatcher +where + ExecC: 'static + ExecCommand + Send + Sync, + ReadF: 'static + ReadFile + Send + Sync, + WriteF: 'static + Send + Sync + WriteFile, +{ + async fn setup_temporary_supergraph_yaml( + &self, + supergraph_config: &FullyResolvedSupergraphConfig, + target_file: &Utf8PathBuf, + ) -> Result<(), CompositionError> { + let supergraph_config = SupergraphConfig::from(supergraph_config.clone()); + let supergraph_config_yaml = serde_yaml::to_string(&supergraph_config); + + let supergraph_config_yaml = match supergraph_config_yaml { + Ok(supergraph_config_yaml) => supergraph_config_yaml, + Err(err) => { + errln!("Failed to serialize supergraph config into yaml"); + error!("{:?}", err); + return Err(CompositionError::SerdeYaml(err)); + } + }; + + let write_file_result = self + .write_file + .write_file(&target_file, supergraph_config_yaml.as_bytes()) + .await; + + if let Err(err) = write_file_result { + errln!("Failed to write the supergraph config to disk"); + error!("{:?}", err); + Err(CompositionError::WriteFile { + path: target_file.clone(), + error: Box::new(err), + }) + } else { + Ok(()) + } + } + async fn run_composition( + &self, + target_file: &Utf8PathBuf, + ) -> Result { + let output = self + .supergraph_binary + .compose( + &self.exec_command, + &self.read_file, + &OutputTarget::Stdout, + target_file.clone(), + ) + .await; + output + } +} + #[cfg(test)] mod tests { use std::{ @@ -142,6 +203,7 @@ mod tests { use speculoos::prelude::*; use tracing_test::traced_test; + use super::CompositionWatcher; use crate::{ composition::{ events::CompositionEvent, @@ -158,8 +220,6 @@ mod tests { }, }; - use super::CompositionWatcher; - #[rstest] #[case::success(false, serde_json::to_string(&default_composition_json()).unwrap())] #[case::error(true, "invalid".to_string())] @@ -234,6 +294,7 @@ mod tests { .read_file(mock_read_file) .write_file(mock_write_file) .temp_dir(temp_dir_path) + .compose_on_initialisation(false) .build(); let subgraph_change_events: BoxStream = once(async { diff --git a/src/composition/watchers/subgraphs.rs b/src/composition/watchers/subgraphs.rs index 43afe12ec..544313b8f 100644 --- a/src/composition/watchers/subgraphs.rs +++ b/src/composition/watchers/subgraphs.rs @@ -7,6 +7,11 @@ use tap::TapFallible; use tokio::{sync::mpsc::UnboundedSender, task::AbortHandle}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; +use super::watcher::{ + subgraph::{SubgraphWatcher, SubgraphWatcherKind, WatchedSdlChange}, + supergraph_config::SupergraphConfigDiff, +}; +use crate::composition::watchers::watcher::subgraph::NonRepeatingFetch; use crate::{ composition::supergraph::config::{ error::ResolveSubgraphError, full::FullyResolvedSubgraph, lazy::LazilyResolvedSubgraph, @@ -16,11 +21,6 @@ use crate::{ utils::client::StudioClientConfig, }; -use super::watcher::{ - subgraph::{NonRepeatingFetch, SubgraphWatcher, SubgraphWatcherKind, WatchedSdlChange}, - supergraph_config::SupergraphConfigDiff, -}; - #[derive(Debug)] #[cfg_attr(test, derive(derive_getters::Getters))] pub struct SubgraphWatchers { @@ -375,14 +375,13 @@ mod tests { use apollo_federation_types::config::SchemaSource; use camino::Utf8PathBuf; + use super::SubgraphWatchers; use crate::{ composition::supergraph::config::lazy::LazilyResolvedSubgraph, options::ProfileOpt, utils::client::{ClientBuilder, StudioClientConfig}, }; - use super::SubgraphWatchers; - #[test] fn test_subgraphwatchers_new() { let subgraphs = [ @@ -392,6 +391,7 @@ mod tests { .schema(SchemaSource::File { file: "/path/to/file".into(), }) + .name("file".to_string()) .build(), ), ( @@ -401,6 +401,7 @@ mod tests { subgraph_url: "http://subgraph_url".try_into().unwrap(), introspection_headers: None, }) + .name("introspection".to_string()) .build(), ), ( @@ -410,6 +411,7 @@ mod tests { graphref: "graphref".to_string(), subgraph: "subgraph".to_string(), }) + .name("subgraph".to_string()) .build(), ), ( @@ -418,6 +420,7 @@ mod tests { .schema(SchemaSource::Sdl { sdl: "sdl".to_string(), }) + .name("sdl".to_string()) .build(), ), ] From 8dc727a04f36802184f8655cec6de94ee9ca6501 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 19 Dec 2024 12:53:29 +0000 Subject: [PATCH 4/9] ROVER-245 Integrate with Tower services --- src/command/lsp/mod.rs | 50 ++++++++--------- src/composition/pipeline.rs | 5 ++ .../supergraph/config/full/subgraph.rs | 21 ++++--- .../supergraph/config/lazy/supergraph.rs | 55 +++++++++++++------ 4 files changed, 79 insertions(+), 52 deletions(-) diff --git a/src/command/lsp/mod.rs b/src/command/lsp/mod.rs index 2c8cd0911..a7ebb89ee 100644 --- a/src/command/lsp/mod.rs +++ b/src/command/lsp/mod.rs @@ -1,19 +1,15 @@ mod errors; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::env::temp_dir; use std::io::stdin; use anyhow::Error; use apollo_federation_types::config::FederationVersion::LatestFedTwo; -use apollo_federation_types::config::SubgraphConfig; use apollo_language_server::{ApolloLanguageServer, Config}; -use async_trait::async_trait; use camino::Utf8PathBuf; use clap::Parser; use futures::StreamExt; -use rover_client::shared::GraphRef; -use rover_client::RoverClientError; use serde::Serialize; use tower_lsp::lsp_types::{Diagnostic, Range}; use tower_lsp::Server; @@ -25,6 +21,8 @@ use crate::command::lsp::errors::StartCompositionError::SupergraphYamlUrlConvers use crate::composition::events::CompositionEvent; use crate::composition::runner::Runner; use crate::composition::supergraph::config::lazy::LazilyResolvedSupergraphConfig; +use crate::composition::supergraph::config::resolver::fetch_remote_subgraph::MakeFetchRemoteSubgraph; +use crate::composition::supergraph::config::resolver::fetch_remote_subgraphs::MakeFetchRemoteSubgraphs; use crate::composition::supergraph::config::resolver::{ ResolveSupergraphConfigError, SubgraphPrompt, SupergraphConfigResolver, }; @@ -34,8 +32,8 @@ use crate::composition::{ CompositionError, CompositionSubgraphAdded, CompositionSubgraphRemoved, CompositionSuccess, SupergraphConfigResolutionError, }; +use crate::options::ProfileOpt; use crate::utils::effect::exec::TokioCommand; -use crate::utils::effect::fetch_remote_subgraphs::FetchRemoteSubgraphs; use crate::utils::effect::install::InstallBinary; use crate::utils::effect::read_file::FsReadFile; use crate::utils::effect::write_file::FsWriteFile; @@ -104,8 +102,12 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR } Some(supergraph_yaml_path) => { // Resolve Supergraph Config -> Lazy - let lazily_resolved_supergraph_config = - generate_lazily_resolved_supergraph_config(supergraph_yaml_path.clone()).await?; + let lazily_resolved_supergraph_config = generate_lazily_resolved_supergraph_config( + supergraph_yaml_path.clone(), + client_config.clone(), + lsp_opts.plugin_opts.profile.clone(), + ) + .await?; let supergraph_yaml_url = Url::from_file_path(supergraph_yaml_path.clone()) .map_err(|_| SupergraphYamlUrlConversionFailed(supergraph_yaml_path.clone()))?; debug!("Supergraph Config Root: {:?}", supergraph_yaml_url); @@ -146,10 +148,16 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR async fn generate_lazily_resolved_supergraph_config( supergraph_yaml_path: Utf8PathBuf, + client_config: StudioClientConfig, + profile: ProfileOpt, ) -> Result { + let make_fetch_remote_subgraphs = MakeFetchRemoteSubgraphs::builder() + .studio_client_config(client_config) + .profile(profile) + .build(); // Get the SupergraphConfig in a form we can use let supergraph_config = SupergraphConfigResolver::default() - .load_remote_subgraphs(&NullFetchRemoteSubgraphs {}, None) + .load_remote_subgraphs(make_fetch_remote_subgraphs, None) .await? .load_from_file_descriptor( &mut stdin(), @@ -190,6 +198,11 @@ async fn start_composition( ) .await?; + let make_fetch_remote_subgraph = MakeFetchRemoteSubgraph::builder() + .studio_client_config(client_config.clone()) + .profile(lsp_opts.plugin_opts.profile.clone()) + .build(); + // Spin up Runner let mut stream = Runner::default() .setup_subgraph_watchers( @@ -201,10 +214,7 @@ async fn start_composition( .setup_supergraph_config_watcher(lazily_resolved_supergraph_config.clone()) .setup_composition_watcher( lazily_resolved_supergraph_config - .fully_resolve( - &client_config, - &client_config.get_authenticated_client(&lsp_opts.plugin_opts.profile)?, - ) + .fully_resolve(&client_config, make_fetch_remote_subgraph) .await .map_err(ResolveSupergraphConfigError::ResolveSubgraphs)?, supergraph_binary, @@ -285,17 +295,3 @@ async fn start_composition( }); Ok(()) } - -struct NullFetchRemoteSubgraphs {} - -#[async_trait] -impl FetchRemoteSubgraphs for NullFetchRemoteSubgraphs { - type Error = RoverClientError; - - async fn fetch_remote_subgraphs( - &self, - _: &GraphRef, - ) -> Result, Self::Error> { - Ok(BTreeMap::new()) - } -} diff --git a/src/composition/pipeline.rs b/src/composition/pipeline.rs index 2e51d8719..ba40d4824 100644 --- a/src/composition/pipeline.rs +++ b/src/composition/pipeline.rs @@ -5,6 +5,7 @@ use camino::Utf8PathBuf; use rover_client::shared::GraphRef; use tempfile::tempdir; use tower::MakeService; + use super::{ runner::{CompositionRunner, Runner}, supergraph::{ @@ -17,6 +18,10 @@ use super::{ }, CompositionError, CompositionSuccess, }; +use crate::composition::supergraph::config::resolver::fetch_remote_subgraph::{ + FetchRemoteSubgraphRequest, RemoteSubgraph, +}; +use crate::composition::supergraph::config::resolver::fetch_remote_subgraphs::FetchRemoteSubgraphsRequest; use crate::{ options::{LicenseAccepter, ProfileOpt}, utils::{ diff --git a/src/composition/supergraph/config/full/subgraph.rs b/src/composition/supergraph/config/full/subgraph.rs index 0df193499..145c1fc47 100644 --- a/src/composition/supergraph/config/full/subgraph.rs +++ b/src/composition/supergraph/config/full/subgraph.rs @@ -46,7 +46,7 @@ impl FullyResolvedSubgraph { /// Resolves a [`UnresolvedSubgraph`] to a [`FullyResolvedSubgraph`] pub async fn resolve( introspect_subgraph_impl: &impl IntrospectSubgraph, - mut fetch_remote_subgraph_impl: MakeFetchSubgraph, + fetch_remote_subgraph_impl: MakeFetchSubgraph, supergraph_config_root: Option<&Utf8PathBuf>, unresolved_subgraph: UnresolvedSubgraph, ) -> Result @@ -94,9 +94,9 @@ impl FullyResolvedSubgraph { } /// Resolves a [`LazilyResolvedSubgraph`] to a [`FullyResolvedSubgraph`] - pub async fn fully_resolve( + pub async fn fully_resolve( introspect_subgraph_impl: &impl IntrospectSubgraph, - mut fetch_remote_subgraph_impl: MakeFetchSubgraph, + fetch_remote_subgraph_impl: MakeFetchSubgraph, lazily_resolved_subgraph: LazilyResolvedSubgraph, ) -> Result where @@ -168,17 +168,22 @@ impl FullyResolvedSubgraph { source: Box::new(err), })?; Ok(FullyResolvedSubgraph::builder() - .and_routing_url(routing_url) + .and_routing_url(routing_url.or(Some(subgraph_url.to_string()))) .schema(schema) .build()) } - async fn resolve_subgraph( - fetch_remote_subgraph_impl: &impl FetchRemoteSubgraph, + async fn resolve_subgraph( + mut fetch_remote_subgraph_impl: MakeFetchSubgraph, routing_url: Option, graph_ref: &String, subgraph: &String, - ) -> Result { + ) -> Result + where + MakeFetchSubgraph: MakeService<(), FetchRemoteSubgraphRequest, Response = RemoteSubgraph>, + MakeFetchSubgraph::MakeError: std::error::Error + Send + Sync + 'static, + MakeFetchSubgraph::Error: std::error::Error + Send + Sync + 'static, + { let graph_ref = GraphRef::from_str(graph_ref).map_err(|err| ResolveSubgraphError::InvalidGraphRef { graph_ref: graph_ref.clone(), @@ -210,7 +215,7 @@ impl FullyResolvedSubgraph { })?; let schema = remote_subgraph.schema().clone(); Ok(FullyResolvedSubgraph::builder() - .and_routing_url(routing_url) + .routing_url(routing_url.unwrap_or(remote_subgraph.routing_url().to_string())) .schema(schema) .build()) } diff --git a/src/composition/supergraph/config/lazy/supergraph.rs b/src/composition/supergraph/config/lazy/supergraph.rs index b55166b31..667b4fff8 100644 --- a/src/composition/supergraph/config/lazy/supergraph.rs +++ b/src/composition/supergraph/config/lazy/supergraph.rs @@ -5,17 +5,20 @@ use apollo_federation_types::config::{FederationVersion, SupergraphConfig}; use camino::Utf8PathBuf; use derive_getters::Getters; use futures::future::join_all; -use futures::TryFutureExt; +use futures::{stream, StreamExt}; use itertools::Itertools; +use tower::MakeService; use super::LazilyResolvedSubgraph; use crate::composition::supergraph::config::full::{ FullyResolvedSubgraph, FullyResolvedSupergraphConfig, }; +use crate::composition::supergraph::config::resolver::fetch_remote_subgraph::{ + FetchRemoteSubgraphRequest, RemoteSubgraph, +}; use crate::composition::supergraph::config::{ error::ResolveSubgraphError, unresolved::UnresolvedSupergraphConfig, }; -use crate::utils::effect::fetch_remote_subgraph::FetchRemoteSubgraph; use crate::utils::effect::introspect::IntrospectSubgraph; /// Represents a [`SupergraphConfig`] where all its [`SchemaSource::File`] subgraphs have @@ -34,7 +37,7 @@ impl LazilyResolvedSupergraphConfig { pub async fn resolve( supergraph_config_root: &Utf8PathBuf, unresolved_supergraph_config: UnresolvedSupergraphConfig, - ) -> Result> { + ) -> Result> { let subgraphs = stream::iter( unresolved_supergraph_config .subgraphs() @@ -45,12 +48,12 @@ impl LazilyResolvedSupergraphConfig { supergraph_config_root, unresolved_subgraph.clone(), ) - .map_err(|err| (name.to_string(), err))?; + .map_err(|err| (name.to_string(), err))?; Ok((name.to_string(), result)) }), ) - .buffer_unordered(50) - .collect::>>() + .buffer_unordered(50) + .collect::>>() .await; #[allow(clippy::type_complexity)] let (subgraphs, errors): ( @@ -70,25 +73,43 @@ impl LazilyResolvedSupergraphConfig { /// Transforms a [`LazilyResolvedSupergraphConfig`] into a [`FullyResolvedSupergraphConfig`] /// consuming self in the process - pub async fn fully_resolve( + pub async fn fully_resolve( self, introspect_subgraph_impl: &impl IntrospectSubgraph, - fetch_remote_subgraph_impl: &impl FetchRemoteSubgraph, - ) -> Result> { + fetch_remote_subgraph_impl: MakeFetchSubgraph, + ) -> Result> + where + MakeFetchSubgraph: + MakeService<(), FetchRemoteSubgraphRequest, Response = RemoteSubgraph> + Clone, + MakeFetchSubgraph::MakeError: std::error::Error + Send + Sync + 'static, + MakeFetchSubgraph::Error: std::error::Error + Send + Sync + 'static, + { let subgraphs = join_all(self.subgraphs().iter().map( |(name, lazily_resolved_subgraph)| { - FullyResolvedSubgraph::fully_resolve( - introspect_subgraph_impl, - fetch_remote_subgraph_impl, - lazily_resolved_subgraph.clone(), - ) - .map_ok(|result| (name.to_string(), result)) + let fetch_remote_subgraph_impl = fetch_remote_subgraph_impl.clone(); + async move { + match FullyResolvedSubgraph::fully_resolve( + introspect_subgraph_impl, + fetch_remote_subgraph_impl, + lazily_resolved_subgraph.clone(), + ) + .await + { + Ok(fully_resolved_subgraph) => { + Ok((name.to_owned(), fully_resolved_subgraph)) + } + Err(resolve_subgraph_error) => { + Err((name.to_owned(), resolve_subgraph_error)) + } + } + } }, )) .await; + #[allow(clippy::type_complexity)] let (subgraphs, errors): ( Vec<(String, FullyResolvedSubgraph)>, - Vec, + Vec<(String, ResolveSubgraphError)>, ) = subgraphs.into_iter().partition_result(); if errors.is_empty() { let subgraphs = BTreeMap::from_iter(subgraphs); @@ -98,7 +119,7 @@ impl LazilyResolvedSupergraphConfig { federation_version: self.federation_version.unwrap_or(LatestFedTwo), }) } else { - Err(errors) + Err(BTreeMap::from_iter(errors.into_iter())) } } } From c66058b1cdf0b96da46b0538be74398ec18e23aa Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 19 Dec 2024 13:19:50 +0000 Subject: [PATCH 5/9] ROVER-245 Fix clippies --- src/composition/supergraph/config/federation.rs | 12 +++++------- src/composition/supergraph/config/full/subgraph.rs | 8 ++++---- src/composition/watchers/composition.rs | 8 +++----- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/composition/supergraph/config/federation.rs b/src/composition/supergraph/config/federation.rs index 9a1b4324a..e4e615fdf 100644 --- a/src/composition/supergraph/config/federation.rs +++ b/src/composition/supergraph/config/federation.rs @@ -6,9 +6,8 @@ use std::marker::PhantomData; use apollo_federation_types::config::{FederationVersion, SupergraphConfig}; use derive_getters::Getters; -use crate::command::supergraph::compose::do_compose::SupergraphComposeOpts; - use super::full::FullyResolvedSubgraph; +use crate::command::supergraph::compose::do_compose::SupergraphComposeOpts; mod state { #[derive(Clone, Debug)] @@ -166,9 +165,8 @@ mod tests { use apollo_federation_types::config::{FederationVersion, SubgraphConfig, SupergraphConfig}; use speculoos::prelude::*; - use crate::composition::supergraph::config::{full::FullyResolvedSubgraph, scenario::*}; - use super::FederationVersionResolverFromSupergraphConfig; + use crate::composition::supergraph::config::{full::FullyResolvedSubgraph, scenario::*}; /// Test showing that federation version is selected from the user-specified fed version /// over local supergraph config or resolved subgraphs @@ -189,7 +187,7 @@ mod tests { let supergraph_config = SupergraphConfig::new(unresolved_subgraphs, Some(FederationVersion::LatestFedOne)); - let resolved_subgraphs = vec![( + let resolved_subgraphs = [( subgraph_name.to_string(), FullyResolvedSubgraph::builder() .schema(subgraph_scenario.sdl) @@ -222,7 +220,7 @@ mod tests { let supergraph_config = SupergraphConfig::new(unresolved_subgraphs, Some(FederationVersion::LatestFedTwo)); - let resolved_subgraphs = vec![( + let resolved_subgraphs = [( subgraph_name.to_string(), FullyResolvedSubgraph::builder() .schema(subgraph_scenario.sdl) @@ -254,7 +252,7 @@ mod tests { let federation_version_resolver = FederationVersionResolverFromSupergraphConfig::default(); let supergraph_config = SupergraphConfig::new(unresolved_subgraphs, None); - let resolved_subgraphs = vec![( + let resolved_subgraphs = [( subgraph_name.to_string(), FullyResolvedSubgraph::builder() .schema(subgraph_scenario.sdl) diff --git a/src/composition/supergraph/config/full/subgraph.rs b/src/composition/supergraph/config/full/subgraph.rs index 145c1fc47..c4e02fece 100644 --- a/src/composition/supergraph/config/full/subgraph.rs +++ b/src/composition/supergraph/config/full/subgraph.rs @@ -106,7 +106,7 @@ impl FullyResolvedSubgraph { { match lazily_resolved_subgraph.schema() { SchemaSource::File { file } => { - Self::resolve_file(lazily_resolved_subgraph.routing_url().clone(), &file) + Self::resolve_file(lazily_resolved_subgraph.routing_url().clone(), file) } SchemaSource::SubgraphIntrospection { subgraph_url, @@ -143,7 +143,7 @@ impl FullyResolvedSubgraph { routing_url: Option, file: &Utf8PathBuf, ) -> Result { - let schema = Fs::read_file(&file).map_err(|err| ResolveSubgraphError::Fs(Box::new(err)))?; + let schema = Fs::read_file(file).map_err(|err| ResolveSubgraphError::Fs(Box::new(err)))?; Ok(FullyResolvedSubgraph::builder() .and_routing_url(routing_url) .schema(schema) @@ -176,7 +176,7 @@ impl FullyResolvedSubgraph { async fn resolve_subgraph( mut fetch_remote_subgraph_impl: MakeFetchSubgraph, routing_url: Option, - graph_ref: &String, + graph_ref: &str, subgraph: &String, ) -> Result where @@ -186,7 +186,7 @@ impl FullyResolvedSubgraph { { let graph_ref = GraphRef::from_str(graph_ref).map_err(|err| ResolveSubgraphError::InvalidGraphRef { - graph_ref: graph_ref.clone(), + graph_ref: graph_ref.to_owned(), source: Box::new(err), })?; let remote_subgraph = fetch_remote_subgraph_impl diff --git a/src/composition/watchers/composition.rs b/src/composition/watchers/composition.rs index 0e0db7a2d..87a4ad34e 100644 --- a/src/composition/watchers/composition.rs +++ b/src/composition/watchers/composition.rs @@ -151,7 +151,7 @@ where let write_file_result = self .write_file - .write_file(&target_file, supergraph_config_yaml.as_bytes()) + .write_file(target_file, supergraph_config_yaml.as_bytes()) .await; if let Err(err) = write_file_result { @@ -169,16 +169,14 @@ where &self, target_file: &Utf8PathBuf, ) -> Result { - let output = self - .supergraph_binary + self.supergraph_binary .compose( &self.exec_command, &self.read_file, &OutputTarget::Stdout, target_file.clone(), ) - .await; - output + .await } } From 8490fdc7d30f7e057a67fed96d5759106c61fb11 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 19 Dec 2024 13:38:18 +0000 Subject: [PATCH 6/9] ROVER-245 Add code to handle adding/removing subgraphs We need to emit extra events here such that the LSP can react to them --- .../supergraph/config/full/supergraph.rs | 8 +++- .../config/unresolved/supergraph.rs | 17 ++++++-- src/composition/watchers/composition.rs | 43 ++++++++++++++++--- src/composition/watchers/subgraphs.rs | 2 +- .../watchers/watcher/supergraph_config.rs | 26 ++++++----- 5 files changed, 72 insertions(+), 24 deletions(-) diff --git a/src/composition/supergraph/config/full/supergraph.rs b/src/composition/supergraph/config/full/supergraph.rs index db2d35a4c..27576d27c 100644 --- a/src/composition/supergraph/config/full/supergraph.rs +++ b/src/composition/supergraph/config/full/supergraph.rs @@ -97,8 +97,12 @@ impl FullyResolvedSupergraphConfig { } /// Updates the subgraph with the provided name using the provided schema - pub fn update_subgraph_schema(&mut self, name: String, subgraph: FullyResolvedSubgraph) { - self.subgraphs.insert(name, subgraph); + pub fn update_subgraph_schema( + &mut self, + name: String, + subgraph: FullyResolvedSubgraph, + ) -> Option { + self.subgraphs.insert(name, subgraph) } /// Removes the subgraph with the name provided diff --git a/src/composition/supergraph/config/unresolved/supergraph.rs b/src/composition/supergraph/config/unresolved/supergraph.rs index c1175e7ce..4f08cdd48 100644 --- a/src/composition/supergraph/config/unresolved/supergraph.rs +++ b/src/composition/supergraph/config/unresolved/supergraph.rs @@ -625,22 +625,31 @@ mod tests { file_subgraph_scenario.write_schema_file(supergraph_config_root_dir.path())?; let mut unresolved_subgraphs = BTreeMap::new(); - let sdl_subgraph_name = "sdl_subgraph".to_string(); + let sdl_subgraph_name = sdl_subgraph_scenario.unresolved_subgraph.name().to_string(); unresolved_subgraphs.insert( sdl_subgraph_name.clone(), sdl_subgraph_scenario.unresolved_subgraph, ); - let remote_subgraph_name = "remote_subgraph".to_string(); + let remote_subgraph_name = remote_subgraph_scenario + .unresolved_subgraph + .name() + .to_string(); unresolved_subgraphs.insert( remote_subgraph_name.clone(), remote_subgraph_scenario.unresolved_subgraph, ); - let introspect_subgraph_name = "introspect_subgraph".to_string(); + let introspect_subgraph_name = introspect_subgraph_scenario + .unresolved_subgraph + .name() + .to_string(); unresolved_subgraphs.insert( introspect_subgraph_name.clone(), introspect_subgraph_scenario.unresolved_subgraph, ); - let file_subgraph_name = "file_subgraph".to_string(); + let file_subgraph_name = file_subgraph_scenario + .unresolved_subgraph + .name() + .to_string(); unresolved_subgraphs.insert( file_subgraph_name.clone(), file_subgraph_scenario.unresolved_subgraph, diff --git a/src/composition/watchers/composition.rs b/src/composition/watchers/composition.rs index 87a4ad34e..43dce4096 100644 --- a/src/composition/watchers/composition.rs +++ b/src/composition/watchers/composition.rs @@ -1,3 +1,4 @@ +use apollo_federation_types::config::SchemaSource::Sdl; use apollo_federation_types::config::SupergraphConfig; use buildstructor::Builder; use camino::Utf8PathBuf; @@ -8,7 +9,9 @@ use tokio::{sync::mpsc::UnboundedSender, task::AbortHandle}; use tokio_stream::StreamExt; use tracing::error; -use crate::composition::{CompositionError, CompositionSuccess}; +use crate::composition::{ + CompositionError, CompositionSubgraphAdded, CompositionSubgraphRemoved, CompositionSuccess, +}; use crate::{ composition::{ events::CompositionEvent, @@ -79,17 +82,35 @@ where while let Some(event) = input.next().await { match event { SubgraphEvent::SubgraphChanged(subgraph_schema_changed) => { - let name = subgraph_schema_changed.name(); + let name = subgraph_schema_changed.name().clone(); + let sdl = subgraph_schema_changed.sdl().clone(); tracing::info!("Schema change detected for subgraph: {}", name); - supergraph_config.update_subgraph_schema( - name.to_string(), - subgraph_schema_changed.into(), - ); + if supergraph_config + .update_subgraph_schema( + name.clone(), + subgraph_schema_changed.into(), + ) + .is_none() + { + let _ = sender + .send(CompositionEvent::SubgraphAdded( + CompositionSubgraphAdded { + name, + schema_source: Sdl { sdl }, + }, + )) + .tap_err(|err| error!("{:?}", err)); + }; } SubgraphEvent::SubgraphRemoved(subgraph_removed) => { let name = subgraph_removed.name(); tracing::info!("Subgraph removed: {}", name); supergraph_config.remove_subgraph(name); + let _ = sender + .send(CompositionEvent::SubgraphRemoved( + CompositionSubgraphRemoved { name: name.clone() }, + )) + .tap_err(|err| error!("{:?}", err)); } } @@ -202,6 +223,7 @@ mod tests { use tracing_test::traced_test; use super::CompositionWatcher; + use crate::composition::CompositionSubgraphAdded; use crate::{ composition::{ events::CompositionEvent, @@ -306,6 +328,15 @@ mod tests { let (mut composition_messages, composition_subtask) = Subtask::new(composition_handler); let abort_handle = composition_subtask.run(subgraph_change_events); + // Assert we always get a subgraph added event. + let next_message = composition_messages.next().await; + assert_that!(next_message).is_some().matches(|event| { + matches!( + event, + CompositionEvent::SubgraphAdded(CompositionSubgraphAdded { .. }) + ) + }); + // Assert we always get a composition started event. let next_message = composition_messages.next().await; assert_that!(next_message) diff --git a/src/composition/watchers/subgraphs.rs b/src/composition/watchers/subgraphs.rs index 544313b8f..9650f6de6 100644 --- a/src/composition/watchers/subgraphs.rs +++ b/src/composition/watchers/subgraphs.rs @@ -81,7 +81,7 @@ pub enum SubgraphEvent { } /// An event denoting that the subgraph has changed, emitting its name and the SDL reflecting that /// change -#[derive(derive_getters::Getters, Eq, PartialEq, Debug)] +#[derive(derive_getters::Getters, Eq, PartialEq, Debug, Clone)] pub struct SubgraphSchemaChanged { /// Subgraph name name: String, diff --git a/src/composition/watchers/watcher/supergraph_config.rs b/src/composition/watchers/watcher/supergraph_config.rs index 29b3438bb..ff1a59f9c 100644 --- a/src/composition/watchers/watcher/supergraph_config.rs +++ b/src/composition/watchers/watcher/supergraph_config.rs @@ -7,6 +7,7 @@ use rover_std::errln; use tap::TapFallible; use tokio::{sync::mpsc::UnboundedSender, task::AbortHandle}; +use super::file::FileWatcher; use crate::{ composition::supergraph::config::{ error::ResolveSubgraphError, lazy::LazilyResolvedSupergraphConfig, @@ -15,8 +16,6 @@ use crate::{ subtask::SubtaskHandleUnit, }; -use super::file::FileWatcher; - #[derive(Debug)] pub struct SupergraphConfigWatcher { file_watcher: FileWatcher, @@ -126,27 +125,32 @@ impl SupergraphConfigDiff { old: &SupergraphConfig, new: SupergraphConfig, ) -> Result { - let old_subgraph_names: HashSet = old + let old_subgraph_names_and_urls: HashSet<(String, Option)> = old .clone() .into_iter() - .map(|(name, _config)| name) + .map(|(name, config)| (name, config.routing_url)) .collect(); - let new_subgraph_names: HashSet = new + let new_subgraph_names_and_urls: HashSet<(String, Option)> = new .clone() .into_iter() - .map(|(name, _config)| name) + .map(|(name, config)| (name, config.routing_url)) .collect(); // Collect the subgraph definitions from the new supergraph config. let new_subgraphs: BTreeMap = new.clone().into_iter().collect(); // Compare the old and new subgraph names to find additions. - let added_names: HashSet = - HashSet::from_iter(new_subgraph_names.difference(&old_subgraph_names).cloned()); + let added_names: HashSet = new_subgraph_names_and_urls + .difference(&old_subgraph_names_and_urls) + .map(|(a, _)| a.clone()) + .collect(); // Compare the old and new subgraph names to find removals. - let removed_names = old_subgraph_names.difference(&new_subgraph_names); + let removed_names: HashSet = old_subgraph_names_and_urls + .difference(&new_subgraph_names_and_urls) + .map(|(a, _)| a.clone()) + .collect(); // Filter the added and removed subgraphs from the new supergraph config. let added = new_subgraphs @@ -154,7 +158,7 @@ impl SupergraphConfigDiff { .into_iter() .filter(|(name, _)| added_names.contains(name)) .collect::>(); - let removed = removed_names.into_iter().cloned().collect::>(); + let removed = removed_names.into_iter().collect::>(); // Find any in-place changes (eg, SDL, SchemaSource::Subgraph) let changed = old @@ -183,10 +187,10 @@ impl SupergraphConfigDiff { #[cfg(test)] mod tests { - use rstest::rstest; use std::collections::BTreeMap; use apollo_federation_types::config::{SchemaSource, SubgraphConfig, SupergraphConfig}; + use rstest::rstest; use super::SupergraphConfigDiff; From d772c17b2ee1f6c0aadced20c5bb242fbc0d2620 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Thu, 19 Dec 2024 14:03:10 +0000 Subject: [PATCH 7/9] ROVER-245 Upgrade to language-server 0.3.3 --- Cargo.lock | 27 ++++++++++++++++++--------- Cargo.toml | 2 +- src/command/lsp/mod.rs | 10 +++++++++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8fbf91ea..c13fff208 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,9 +154,9 @@ checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" [[package]] name = "apollo-compiler" -version = "1.0.0-beta.23" +version = "1.0.0-beta.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875f39060728ac3e775fc3fe5421225d6df92c4d5155a9524cdb198f05006d36" +checksum = "71153ad85c85f7aa63f0e0a5868912c220bb48e4c764556f5841d37fc17b0103" dependencies = [ "ahash 0.8.11", "apollo-parser 0.8.2", @@ -173,9 +173,9 @@ dependencies = [ [[package]] name = "apollo-composition" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6864cf58167a074ac909b387cebdbc0a6957d7eb95b6e4cdaf7df8c9cd46108a" +checksum = "01d82be69fce8ac07544017fe8cd52befc61d4548d3233bd1757984d56c53804" dependencies = [ "apollo-compiler", "apollo-federation", @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "apollo-federation" -version = "2.0.0-alpha.7" +version = "2.0.0-preview.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b327840a1c216f7a1e6ac1a4fee347a9dee4fe7e3db94f5f9756337f5754969c" +checksum = "3c87a5a622646520e2e7df15ed33de1558e9d6eaaa7834eeafd02a8378cd73d2" dependencies = [ "apollo-compiler", "derive_more", @@ -206,10 +206,13 @@ dependencies = [ "indexmap 2.5.0", "itertools 0.13.0", "lazy_static", + "line-col", "multimap", "nom", "nom_locate", + "once_cell", "petgraph", + "regex", "serde", "serde_json", "serde_json_bytes", @@ -240,9 +243,9 @@ dependencies = [ [[package]] name = "apollo-language-server" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e82651825f3f99089803394c0f68b573fe6195993892099bf2fa6387ba4438a" +checksum = "e30875b30bc668b8d10a57436ad26888183245a367403ad708d5f3fd52d7b351" dependencies = [ "apollo-compiler", "apollo-composition", @@ -2761,7 +2764,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -3483,6 +3486,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "line-col" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e69cdf6b85b5c8dce514f694089a2cf8b1a702f6cd28607bcb3cf296c9778db" + [[package]] name = "linked-hash-map" version = "0.5.6" diff --git a/Cargo.toml b/Cargo.toml index 141a8d95b..3e05095bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ apollo-encoder = "0.8" # https://github.com/apollographql/federation-rs apollo-federation-types = "0.14.1" -apollo-language-server = { version = "0.3.0", default-features = false, features = ["tokio"] } +apollo-language-server = { version = "0.3.3", default-features = false, features = ["tokio"] } # crates.io dependencies diff --git a/src/command/lsp/mod.rs b/src/command/lsp/mod.rs index a7ebb89ee..d411f5064 100644 --- a/src/command/lsp/mod.rs +++ b/src/command/lsp/mod.rs @@ -6,7 +6,7 @@ use std::io::stdin; use anyhow::Error; use apollo_federation_types::config::FederationVersion::LatestFedTwo; -use apollo_language_server::{ApolloLanguageServer, Config}; +use apollo_language_server::{ApolloLanguageServer, Config, MaxSpecVersions}; use camino::Utf8PathBuf; use clap::Parser; use futures::StreamExt; @@ -95,6 +95,10 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR enable_auto_composition: false, force_federation: false, disable_telemetry: false, + max_spec_versions: MaxSpecVersions { + connect: None, + federation: None, + }, }, HashMap::new(), ); @@ -118,6 +122,10 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR enable_auto_composition: false, force_federation: false, disable_telemetry: false, + max_spec_versions: MaxSpecVersions { + connect: None, + federation: None, + }, }, HashMap::from_iter( lazily_resolved_supergraph_config From 12f9c70608c6eb3822bd760e6fac9d2b55a8fddc Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Fri, 20 Dec 2024 06:13:56 +0000 Subject: [PATCH 8/9] ROVER-245 Add new InMemory OutputTarget At present, we only support 2 composition output targets, this adds a third, which means that the output of composition only exists in memory. This means we don't have to write/manage temporary files, and we don't compete with the LSP for usage of stdout. --- src/command/lsp/mod.rs | 2 ++ src/composition/pipeline.rs | 1 + src/composition/runner/mod.rs | 3 +++ src/composition/supergraph/binary.rs | 33 ++++++++++++++++--------- src/composition/watchers/composition.rs | 14 ++++++++--- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/command/lsp/mod.rs b/src/command/lsp/mod.rs index d411f5064..f5884d54c 100644 --- a/src/command/lsp/mod.rs +++ b/src/command/lsp/mod.rs @@ -20,6 +20,7 @@ use crate::command::lsp::errors::StartCompositionError; use crate::command::lsp::errors::StartCompositionError::SupergraphYamlUrlConversionFailed; use crate::composition::events::CompositionEvent; use crate::composition::runner::Runner; +use crate::composition::supergraph::binary::OutputTarget; use crate::composition::supergraph::config::lazy::LazilyResolvedSupergraphConfig; use crate::composition::supergraph::config::resolver::fetch_remote_subgraph::MakeFetchRemoteSubgraph; use crate::composition::supergraph::config::resolver::fetch_remote_subgraphs::MakeFetchRemoteSubgraphs; @@ -231,6 +232,7 @@ async fn start_composition( FsWriteFile::default(), Utf8PathBuf::try_from(temp_dir())?, true, + OutputTarget::InMemory, ) .run(); diff --git a/src/composition/pipeline.rs b/src/composition/pipeline.rs index ba40d4824..b74256926 100644 --- a/src/composition/pipeline.rs +++ b/src/composition/pipeline.rs @@ -257,6 +257,7 @@ impl CompositionPipeline { write_file, output_dir, compose_on_initialisation, + OutputTarget::Stdout, ); Ok(runner) } diff --git a/src/composition/runner/mod.rs b/src/composition/runner/mod.rs index cdf3d8709..47ba264a0 100644 --- a/src/composition/runner/mod.rs +++ b/src/composition/runner/mod.rs @@ -20,6 +20,7 @@ use super::{ }, watchers::{composition::CompositionWatcher, subgraphs::SubgraphWatchers}, }; +use crate::composition::supergraph::binary::OutputTarget; use crate::{ composition::watchers::watcher::{ file::FileWatcher, supergraph_config::SupergraphConfigWatcher, @@ -124,6 +125,7 @@ impl Runner { write_file: WriteF, temp_dir: Utf8PathBuf, compose_on_initialisation: bool, + output_target: OutputTarget, ) -> Runner> where ExecC: ExecCommand + Debug + Eq + PartialEq + Send + Sync + 'static, @@ -139,6 +141,7 @@ impl Runner { .write_file(write_file) .temp_dir(temp_dir) .compose_on_initialisation(compose_on_initialisation) + .output_target(output_target) .build(); Runner { state: state::Run { diff --git a/src/composition/supergraph/binary.rs b/src/composition/supergraph/binary.rs index 4607c21ae..d88ce90c7 100644 --- a/src/composition/supergraph/binary.rs +++ b/src/composition/supergraph/binary.rs @@ -1,4 +1,5 @@ use std::fmt::Debug; +use std::process::Stdio; use apollo_federation_types::{ config::FederationVersion, @@ -9,6 +10,8 @@ use camino::Utf8PathBuf; use rover_std::warnln; use tap::TapFallible; +use super::version::SupergraphVersion; +use crate::utils::effect::exec::ExecCommandOutput; use crate::{ composition::{CompositionError, CompositionSuccess}, utils::effect::{ @@ -17,12 +20,14 @@ use crate::{ }, }; -use super::version::SupergraphVersion; - #[derive(Clone, Debug, Eq, PartialEq)] pub enum OutputTarget { + /// Output to a file, given by the parameter File(Utf8PathBuf), + /// Output to stdout (inappropriate for LSP usage because stdout is reserved for the LSP itself) Stdout, + /// Produce an output that only exists in memory, for passing to events + InMemory, } impl OutputTarget { @@ -37,6 +42,7 @@ impl OutputTarget { } } OutputTarget::Stdout => OutputTarget::Stdout, + OutputTarget::InMemory => OutputTarget::InMemory, } } } @@ -89,14 +95,20 @@ impl SupergraphBinary { supergraph_config_path: Utf8PathBuf, ) -> Result { let args = self.prepare_compose_args(output_target, &supergraph_config_path); + let config = match output_target { + OutputTarget::File(_) | OutputTarget::Stdout => ExecCommandConfig::builder() + .exe(self.exe.clone()) + .args(args) + .build(), + OutputTarget::InMemory => ExecCommandConfig::builder() + .exe(self.exe.clone()) + .args(args) + .output(ExecCommandOutput::builder().stdout(Stdio::piped()).build()) + .build(), + }; let output = exec_impl - .exec_command( - ExecCommandConfig::builder() - .exe(self.exe.clone()) - .args(args) - .build(), - ) + .exec_command(config) .await .tap_err(|err| tracing::error!("{:?}", err)) .map_err(|err| CompositionError::Binary { @@ -122,7 +134,7 @@ impl SupergraphBinary { error: Box::new(err), })? } - OutputTarget::Stdout => std::str::from_utf8(&output.stdout) + OutputTarget::Stdout | OutputTarget::InMemory => std::str::from_utf8(&output.stdout) .map_err(|err| CompositionError::InvalidOutput { binary: self.exe.clone(), error: format!("{:?}", err), @@ -199,6 +211,7 @@ mod tests { use semver::Version; use speculoos::prelude::*; + use super::{CompositionSuccess, OutputTarget, SupergraphBinary}; use crate::{ command::supergraph::compose::do_compose::SupergraphComposeOpts, composition::{supergraph::version::SupergraphVersion, test::default_composition_json}, @@ -208,8 +221,6 @@ mod tests { }, }; - use super::{CompositionSuccess, OutputTarget, SupergraphBinary}; - fn fed_one() -> Version { Version::from_str("1.0.0").unwrap() } diff --git a/src/composition/watchers/composition.rs b/src/composition/watchers/composition.rs index 43dce4096..0cc855259 100644 --- a/src/composition/watchers/composition.rs +++ b/src/composition/watchers/composition.rs @@ -34,6 +34,7 @@ pub struct CompositionWatcher { write_file: WriteF, temp_dir: Utf8PathBuf, compose_on_initialisation: bool, + output_target: OutputTarget, } impl SubtaskHandleStream for CompositionWatcher @@ -64,7 +65,9 @@ where let _ = sender .send(CompositionEvent::Started) .tap_err(|err| error!("{:?}", err)); - let output = self.run_composition(&target_file).await; + let output = self + .run_composition(&target_file, &self.output_target) + .await; match output { Ok(success) => { let _ = sender @@ -126,7 +129,9 @@ where .send(CompositionEvent::Started) .tap_err(|err| error!("{:?}", err)); - let output = self.run_composition(&target_file).await; + let output = self + .run_composition(&target_file, &self.output_target) + .await; match output { Ok(success) => { @@ -189,12 +194,13 @@ where async fn run_composition( &self, target_file: &Utf8PathBuf, + output_target: &OutputTarget, ) -> Result { self.supergraph_binary .compose( &self.exec_command, &self.read_file, - &OutputTarget::Stdout, + output_target, target_file.clone(), ) .await @@ -223,6 +229,7 @@ mod tests { use tracing_test::traced_test; use super::CompositionWatcher; + use crate::composition::supergraph::binary::OutputTarget; use crate::composition::CompositionSubgraphAdded; use crate::{ composition::{ @@ -315,6 +322,7 @@ mod tests { .write_file(mock_write_file) .temp_dir(temp_dir_path) .compose_on_initialisation(false) + .output_target(OutputTarget::Stdout) .build(); let subgraph_change_events: BoxStream = once(async { From e443b0088f384c8ac87feafa48ee7ea4178b1d37 Mon Sep 17 00:00:00 2001 From: jonathanrainer Date: Fri, 20 Dec 2024 06:47:28 +0000 Subject: [PATCH 9/9] ROVER-245 Extra error handling when initialising stream --- src/command/lsp/errors.rs | 10 ++- src/command/lsp/mod.rs | 117 +++++++++++++++++---------- src/composition/mod.rs | 5 +- src/composition/supergraph/binary.rs | 3 +- 4 files changed, 89 insertions(+), 46 deletions(-) diff --git a/src/command/lsp/errors.rs b/src/command/lsp/errors.rs index 53f0c572d..3224f0c15 100644 --- a/src/command/lsp/errors.rs +++ b/src/command/lsp/errors.rs @@ -1,5 +1,7 @@ -use camino::Utf8PathBuf; +use camino::{FromPathBufError, Utf8PathBuf}; +use crate::composition::supergraph::config::resolver::ResolveSupergraphConfigError; +use crate::composition::supergraph::install::InstallSupergraphError; use crate::composition::CompositionError; #[derive(thiserror::Error, Debug)] @@ -8,4 +10,10 @@ pub enum StartCompositionError { SupergraphYamlUrlConversionFailed(Utf8PathBuf), #[error("Could not run initial composition")] InitialCompositionFailed(#[from] CompositionError), + #[error("Could not install supergraph plugin")] + InstallSupergraphPluginFailed(#[from] InstallSupergraphError), + #[error("Could not resolve Supergraph Config")] + ResolvingSupergraphConfigFailed(#[from] ResolveSupergraphConfigError), + #[error("Could not establish temporary directory")] + TemporaryDirectoryCouldNotBeEstablished(#[from] FromPathBufError), } diff --git a/src/command/lsp/mod.rs b/src/command/lsp/mod.rs index f5884d54c..34fb7468e 100644 --- a/src/command/lsp/mod.rs +++ b/src/command/lsp/mod.rs @@ -4,11 +4,12 @@ use std::collections::HashMap; use std::env::temp_dir; use std::io::stdin; -use anyhow::Error; +use apollo_federation_types::config::FederationVersion; use apollo_federation_types::config::FederationVersion::LatestFedTwo; use apollo_language_server::{ApolloLanguageServer, Config, MaxSpecVersions}; use camino::Utf8PathBuf; use clap::Parser; +use futures::stream::BoxStream; use futures::StreamExt; use serde::Serialize; use tower_lsp::lsp_types::{Diagnostic, Range}; @@ -197,44 +198,24 @@ async fn start_composition( // Spawn a separate thread to handle composition and passing that data to the language server tokio::spawn(async move { - // TODO: Let the supergraph binary exist inside its own task that can respond to being re-installed etc. - let supergraph_binary = - InstallSupergraph::new(federation_version.clone(), client_config.clone()) - .install( - None, - lsp_opts.plugin_opts.elv2_license_accepter, - lsp_opts.plugin_opts.skip_update, - ) - .await?; - - let make_fetch_remote_subgraph = MakeFetchRemoteSubgraph::builder() - .studio_client_config(client_config.clone()) - .profile(lsp_opts.plugin_opts.profile.clone()) - .build(); - - // Spin up Runner - let mut stream = Runner::default() - .setup_subgraph_watchers( - lazily_resolved_supergraph_config.subgraphs().clone(), - &lsp_opts.plugin_opts.profile, - &client_config, - 500, - ) - .setup_supergraph_config_watcher(lazily_resolved_supergraph_config.clone()) - .setup_composition_watcher( - lazily_resolved_supergraph_config - .fully_resolve(&client_config, make_fetch_remote_subgraph) - .await - .map_err(ResolveSupergraphConfigError::ResolveSubgraphs)?, - supergraph_binary, - TokioCommand::default(), - FsReadFile::default(), - FsWriteFile::default(), - Utf8PathBuf::try_from(temp_dir())?, - true, - OutputTarget::InMemory, - ) - .run(); + let mut stream = match create_composition_stream( + lazily_resolved_supergraph_config, + client_config, + lsp_opts, + federation_version, + ) + .await + { + Ok(stream) => stream, + Err(e) => { + let message = format!("Could not initialise composition process: {e}"); + let diagnostic = Diagnostic::new_simple(Range::default(), message); + language_server + .publish_diagnostics(supergraph_yaml_url.clone(), vec![diagnostic]) + .await; + return Err(e); + } + }; while let Some(event) = stream.next().await { match event { @@ -263,7 +244,10 @@ async fn start_composition( ) .await; } - CompositionEvent::Error(CompositionError::Build { source: errors }) => { + CompositionEvent::Error(CompositionError::Build { + source: errors, + federation_version, + }) => { debug!( ?errors, "Composition {federation_version} completed with errors" @@ -281,8 +265,8 @@ async fn start_composition( .await } CompositionEvent::Error(err) => { - debug!("Composition {federation_version} failed: {err}"); - let message = format!("Failed run composition {federation_version}: {err}",); + debug!("Composition failed: {err}"); + let message = format!("Failed run composition: {err}",); let diagnostic = Diagnostic::new_simple(Range::default(), message); language_server .publish_diagnostics(supergraph_yaml_url.clone(), vec![diagnostic]) @@ -301,7 +285,54 @@ async fn start_composition( } } } - Ok::<(), Error>(()) + Ok::<(), StartCompositionError>(()) }); Ok(()) } + +async fn create_composition_stream( + lazily_resolved_supergraph_config: LazilyResolvedSupergraphConfig, + client_config: StudioClientConfig, + lsp_opts: LspOpts, + federation_version: FederationVersion, +) -> Result, StartCompositionError> { + // TODO: Let the supergraph binary exist inside its own task that can respond to being re-installed etc. + let supergraph_binary = + InstallSupergraph::new(federation_version.clone(), client_config.clone()) + .install( + None, + lsp_opts.plugin_opts.elv2_license_accepter, + lsp_opts.plugin_opts.skip_update, + ) + .await?; + + let make_fetch_remote_subgraph = MakeFetchRemoteSubgraph::builder() + .studio_client_config(client_config.clone()) + .profile(lsp_opts.plugin_opts.profile.clone()) + .build(); + + // Spin up Runner + let stream = Runner::default() + .setup_subgraph_watchers( + lazily_resolved_supergraph_config.subgraphs().clone(), + &lsp_opts.plugin_opts.profile, + &client_config, + 500, + ) + .setup_supergraph_config_watcher(lazily_resolved_supergraph_config.clone()) + .setup_composition_watcher( + lazily_resolved_supergraph_config + .fully_resolve(&client_config, make_fetch_remote_subgraph) + .await + .map_err(ResolveSupergraphConfigError::ResolveSubgraphs)?, + supergraph_binary, + TokioCommand::default(), + FsReadFile::default(), + FsWriteFile::default(), + Utf8PathBuf::try_from(temp_dir())?, + true, + OutputTarget::InMemory, + ) + .run(); + Ok(stream) +} diff --git a/src/composition/mod.rs b/src/composition/mod.rs index befd09560..8ea0ac111 100644 --- a/src/composition/mod.rs +++ b/src/composition/mod.rs @@ -57,7 +57,10 @@ pub enum CompositionError { error: Box, }, #[error("Encountered {} while trying to build a supergraph.", .source.length_string())] - Build { source: BuildErrors }, + Build { + source: BuildErrors, + federation_version: FederationVersion, + }, #[error("Serialization error.\n{}", .0)] SerdeYaml(#[from] serde_yaml::Error), } diff --git a/src/composition/supergraph/binary.rs b/src/composition/supergraph/binary.rs index d88ce90c7..9266044b9 100644 --- a/src/composition/supergraph/binary.rs +++ b/src/composition/supergraph/binary.rs @@ -173,10 +173,11 @@ impl SupergraphBinary { .map(|build_output| CompositionSuccess { hints: build_output.hints, supergraph_sdl: build_output.supergraph_sdl, - federation_version, + federation_version: federation_version.clone(), }) .map_err(|build_errors| CompositionError::Build { source: build_errors, + federation_version, }) }