From 5697962f2d32463182242845a01cbede8e6e20a5 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 22 Mar 2021 13:38:45 -0500 Subject: [PATCH] feat(spike): rover core build (#340) * feat(spike): rover graph build this PR introduces a TOML config format for specifying multiple subgraphs to compose. usage: rover graph build --services ./path/to/services.toml This will attempt to compose your subgraph schemas. If it successfully composes, it will print the composed CSDL. If there are errors, they will be printed. examples of both composing and non-composing inputs can be tested by running: `cargo run -- graph build --services spike/composes.toml` and `cargo run -- graph build --services spike/errors.toml` --- Cargo.lock | 148 ++++++++++++++++ Cargo.toml | 6 +- installers/npm/package-lock.json | 295 +------------------------------ src/cli.rs | 4 + src/command/core/build.rs | 45 +++++ src/command/core/config.rs | 192 ++++++++++++++++++++ src/command/core/mod.rs | 28 +++ src/command/graph/mod.rs | 2 +- src/command/mod.rs | 2 + src/command/output.rs | 5 + src/command/subgraph/check.rs | 3 +- 11 files changed, 431 insertions(+), 299 deletions(-) create mode 100644 src/command/core/build.rs create mode 100644 src/command/core/config.rs create mode 100644 src/command/core/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4dfe1733c..6ae2ccbcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,6 +219,15 @@ name = "camino" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd065703998b183ed0b348a22555691373a9345a1431141e5778b48bb17e4703" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_gn" +version = "0.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba7d7f7b201dfcbc314b14f2176c92f8ba521dab538b40e426ffed25ed7cd80" [[package]] name = "cc" @@ -391,6 +400,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "deno_core" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ebf4fa4aeff8925615f339d885a73b684c274e12d329b60bb88009c2a3ab2" +dependencies = [ + "anyhow", + "futures", + "indexmap", + "lazy_static", + "libc", + "log", + "pin-project", + "rusty_v8", + "serde", + "serde_json", + "smallvec", + "url", +] + [[package]] name = "difference" version = "2.0.0" @@ -444,6 +473,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + [[package]] name = "either" version = "1.6.1" @@ -543,6 +578,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" +[[package]] +name = "futures" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.13" @@ -550,6 +600,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -558,12 +609,35 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" +[[package]] +name = "futures-executor" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" +[[package]] +name = "futures-macro" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.13" @@ -582,12 +656,17 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", + "proc-macro-hack", + "proc-macro-nested", "slab", ] @@ -762,6 +841,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "harmonizer" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ae098cb63d7cb70d41de858363be2d31d9d817081f5f7d40526620bb8091ad" +dependencies = [ + "anyhow", + "deno_core", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -1006,6 +1098,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "lock_api" version = "0.4.2" @@ -1396,6 +1494,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -1592,6 +1702,7 @@ dependencies = [ "console 0.14.0", "git-url-parse", "git2", + "harmonizer", "heck", "houston", "humantime", @@ -1607,6 +1718,7 @@ dependencies = [ "semver", "serde", "serde_json", + "serde_yaml", "serial_test", "sputnik", "strsim 0.10.0", @@ -1662,6 +1774,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" +[[package]] +name = "rusty_v8" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdaa4aeeae3253c3b34486af66527d9982105d79ba57cf9b50b217e7b47a8b6" +dependencies = [ + "bitflags", + "cargo_gn", + "lazy_static", + "libc", + "which", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1761,6 +1886,7 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ + "indexmap", "itoa", "ryu", "serde", @@ -1778,6 +1904,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + [[package]] name = "serial_test" version = "0.5.1" @@ -2292,6 +2430,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] [[package]] @@ -2506,3 +2645,12 @@ checksum = "d107f8c6e916235c4c01cabb3e8acf7bea8ef6a63ca2e7fa0527c049badfc48c" dependencies = [ "winapi", ] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index b382350e5..55879feaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,12 +35,13 @@ timber = { path = "./crates/timber" } anyhow = "1.0.38" atty = "0.2.14" ansi_term = "0.12.1" -camino = "1.0.2" +camino = { version = "1.0.2", features = ["serde1"] } billboard = { git = "https://github.com/EverlastingBugstopper/billboard.git", branch = "main" } chrono = "0.4" console = "0.14.0" git2 = "0.13.17" git-url-parse = "0.3.1" +harmonizer = "0.1.2" heck = "0.3.2" humantime = "2.1.0" opener = "0.4.1" @@ -51,10 +52,11 @@ strsim = "0.10" serde_json = "1.0" structopt = "0.3.21" tracing = "0.1.22" +toml = "0.5" regex = "1" url = "2.2.0" semver = "0.11" -toml = "0.5" +serde_yaml = "0.8" [dev-dependencies] assert_cmd = "1.0.1" diff --git a/installers/npm/package-lock.json b/installers/npm/package-lock.json index 3eded8c7b..811fcad41 100644 --- a/installers/npm/package-lock.json +++ b/installers/npm/package-lock.json @@ -1,301 +1,8 @@ { "name": "@apollo/rover", "version": "0.0.3", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "@apollo/rover", - "version": "0.0.3", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "binary-install": "^0.1.1", - "console.table": "^0.10.0" - }, - "bin": { - "rover": "run.js" - }, - "devDependencies": { - "prettier": "^2.2.1" - } - }, - "node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/binary-install": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.1.1.tgz", - "integrity": "sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ==", - "dependencies": { - "axios": "^0.21.1", - "rimraf": "^3.0.2", - "tar": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "optional": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/console.table": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz", - "integrity": "sha1-CRcCVYiHW+/XDPLv9L7yxuLXXQQ=", - "dependencies": { - "easy-table": "1.1.0" - }, - "engines": { - "node": "> 0.10" - } - }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "optional": true, - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/easy-table": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", - "integrity": "sha1-hvmrTBAvA3G3KXuSplHVgkvIy3M=", - "optionalDependencies": { - "wcwidth": ">=1.0.1" - } - }, - "node_modules/follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tar": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", - "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "optional": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - }, "dependencies": { "axios": { "version": "0.21.1", diff --git a/src/cli.rs b/src/cli.rs index eadc0291c..77024dd48 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -90,6 +90,9 @@ pub enum Command { /// Configuration profile commands Config(command::Config), + /// Core schema commands + Core(command::Core), + /// Non-federated schema/graph commands Graph(command::Graph), @@ -126,6 +129,7 @@ impl Rover { Command::Config(command) => { command.run(self.get_rover_config()?, self.get_client_config()?) } + Command::Core(command) => command.run(), Command::Docs(command) => command.run(), Command::Graph(command) => { command.run(self.get_client_config()?, self.get_git_context()?) diff --git a/src/command/core/build.rs b/src/command/core/build.rs new file mode 100644 index 000000000..52bf1d338 --- /dev/null +++ b/src/command/core/build.rs @@ -0,0 +1,45 @@ +use crate::{anyhow, command::RoverStdout, Result}; + +use ansi_term::Colour::Red; +use camino::Utf8PathBuf; +use serde::Serialize; +use structopt::StructOpt; + +use super::config; + +#[derive(Debug, Serialize, StructOpt)] +pub struct Build { + /// The relative path to the core configuration file. + #[structopt(long = "config")] + #[serde(skip_serializing)] + config_path: Utf8PathBuf, +} + +impl Build { + pub fn run(&self) -> Result { + let core_config = config::parse_core_config(&self.config_path)?; + let subgraph_definitions = core_config.get_subgraph_definitions(&self.config_path)?; + + match harmonizer::harmonize(subgraph_definitions) { + Ok(csdl) => Ok(RoverStdout::CSDL(csdl)), + Err(composition_errors) => { + let num_failures = composition_errors.len(); + for composition_error in composition_errors { + eprintln!("{} {}", Red.bold().paint("error:"), &composition_error) + } + match num_failures { + 0 => unreachable!("Composition somehow failed with no composition errors."), + 1 => Err( + anyhow!("Encountered 1 composition error while composing the graph.") + .into(), + ), + _ => Err(anyhow!( + "Encountered {} composition errors while composing the graph.", + num_failures + ) + .into()), + } + } + } + } +} diff --git a/src/command/core/config.rs b/src/command/core/config.rs new file mode 100644 index 000000000..e6224a932 --- /dev/null +++ b/src/command/core/config.rs @@ -0,0 +1,192 @@ +use crate::{anyhow, Result}; + +use camino::Utf8PathBuf; +use harmonizer::ServiceDefinition as SubgraphDefinition; +use serde::{Deserialize, Serialize}; + +use std::collections::HashMap; +use std::fs; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct CoreConfig { + pub(crate) subgraphs: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Subgraph { + pub(crate) routing_url: String, + pub(crate) schema: Schema, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Schema { + pub(crate) file: Utf8PathBuf, +} + +pub(crate) fn parse_core_config(config_path: &Utf8PathBuf) -> Result { + let raw_core_config = fs::read_to_string(config_path) + .map_err(|e| anyhow!("Could not read \"{}\": {}", config_path, e))?; + + let parsed_config = serde_yaml::from_str(&raw_core_config) + .map_err(|e| anyhow!("Could not parse YAML from \"{}\": {}", config_path, e))?; + + tracing::debug!(?parsed_config); + + Ok(parsed_config) +} + +impl CoreConfig { + pub(crate) fn get_subgraph_definitions( + &self, + config_path: &Utf8PathBuf, + ) -> Result> { + let mut subgraphs = Vec::new(); + + for (subgraph_name, subgraph_data) in &self.subgraphs { + // compute the path to the schema relative to the config file itself, not the working directory. + let relative_schema_path = if let Some(parent) = config_path.parent() { + let mut schema_path = parent.to_path_buf(); + schema_path.push(&subgraph_data.schema.file); + schema_path + } else { + subgraph_data.schema.file.clone() + }; + + let schema = fs::read_to_string(&relative_schema_path) + .map_err(|e| anyhow!("Could not read \"{}\": {}", &relative_schema_path, e))?; + + let subgraph_definition = + SubgraphDefinition::new(subgraph_name, &subgraph_data.routing_url, &schema); + + subgraphs.push(subgraph_definition); + } + + Ok(subgraphs) + } +} + +#[cfg(test)] +mod tests { + use assert_fs::TempDir; + use camino::Utf8PathBuf; + use std::fs; + + #[test] + fn it_can_parse_valid_config() { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./good-films.graphql + people: + routing_url: https://people.example.com + schema: + file: ./good-people.graphql +"#; + let tmp_home = TempDir::new().unwrap(); + let mut config_path = Utf8PathBuf::from_path_buf(tmp_home.path().to_path_buf()).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + + let core_config = super::parse_core_config(&config_path); + if let Err(e) = core_config { + panic!(e.to_string()) + } + } + + #[test] + fn it_errors_on_invalid_config() { + let raw_bad_yaml = r#"subgraphs: + films: + routing_______url: https://films.example.com + schemaaaa: + file:: ./good-films.graphql + people: + routing____url: https://people.example.com + schema_____file: ./good-people.graphql"#; + let tmp_home = TempDir::new().unwrap(); + let mut config_path = Utf8PathBuf::from_path_buf(tmp_home.path().to_path_buf()).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_bad_yaml).unwrap(); + assert!(super::parse_core_config(&config_path).is_err()) + } + + #[test] + fn it_errs_on_invalid_subgraph_path() { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./films-do-not-exist.graphql + people: + routing_url: https://people.example.com + schema: + file: ./people-do-not-exist.graphql"#; + let tmp_home = TempDir::new().unwrap(); + let mut config_path = Utf8PathBuf::from_path_buf(tmp_home.path().to_path_buf()).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + let core_config = super::parse_core_config(&config_path).unwrap(); + assert!(core_config.get_subgraph_definitions(&config_path).is_err()) + } + + #[test] + fn it_can_get_subgraph_definitions_from_fs() { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ./films.graphql + people: + routing_url: https://people.example.com + schema: + file: ./people.graphql"#; + let tmp_home = TempDir::new().unwrap(); + let mut config_path = Utf8PathBuf::from_path_buf(tmp_home.path().to_path_buf()).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + let tmp_dir = config_path.parent().unwrap().to_path_buf(); + let films_path = tmp_dir.join("films.graphql"); + let people_path = tmp_dir.join("people.graphql"); + fs::write(films_path, "there is something here").unwrap(); + fs::write(people_path, "there is also something here").unwrap(); + let core_config = super::parse_core_config(&config_path).unwrap(); + assert!(core_config.get_subgraph_definitions(&config_path).is_ok()) + } + + #[test] + fn it_can_compute_relative_schema_paths() { + let raw_good_yaml = r#"subgraphs: + films: + routing_url: https://films.example.com + schema: + file: ../../films.graphql + people: + routing_url: https://people.example.com + schema: + file: ../../people.graphql"#; + let tmp_home = TempDir::new().unwrap(); + let tmp_dir = Utf8PathBuf::from_path_buf(tmp_home.path().to_path_buf()).unwrap(); + let mut config_path = tmp_dir.clone(); + config_path.push("layer"); + config_path.push("layer"); + fs::create_dir_all(&config_path).unwrap(); + config_path.push("config.yaml"); + fs::write(&config_path, raw_good_yaml).unwrap(); + let films_path = tmp_dir.join("films.graphql"); + let people_path = tmp_dir.join("people.graphql"); + fs::write(films_path, "there is something here").unwrap(); + fs::write(people_path, "there is also something here").unwrap(); + let core_config = super::parse_core_config(&config_path).unwrap(); + let subgraph_definitions = core_config.get_subgraph_definitions(&config_path).unwrap(); + let people_subgraph = subgraph_definitions.get(0).unwrap(); + let film_subgraph = subgraph_definitions.get(1).unwrap(); + + assert_eq!(film_subgraph.name, "films"); + assert_eq!(film_subgraph.url, "https://films.example.com"); + assert_eq!(film_subgraph.type_defs, "there is something here"); + assert_eq!(people_subgraph.name, "people"); + assert_eq!(people_subgraph.url, "https://people.example.com"); + assert_eq!(people_subgraph.type_defs, "there is also something here"); + } +} diff --git a/src/command/core/mod.rs b/src/command/core/mod.rs new file mode 100644 index 000000000..eec91ce7f --- /dev/null +++ b/src/command/core/mod.rs @@ -0,0 +1,28 @@ +mod build; +pub(crate) mod config; + +use serde::Serialize; +use structopt::StructOpt; + +use crate::command::RoverStdout; +use crate::Result; + +#[derive(Debug, Serialize, StructOpt)] +pub struct Core { + #[structopt(subcommand)] + command: Command, +} + +#[derive(Debug, Serialize, StructOpt)] +pub enum Command { + /// Build a core schema from a set of subgraphs. + Build(build::Build), +} + +impl Core { + pub fn run(&self) -> Result { + match &self.command { + Command::Build(command) => command.run(), + } + } +} diff --git a/src/command/graph/mod.rs b/src/command/graph/mod.rs index 9c80664fa..a655ea68a 100644 --- a/src/command/graph/mod.rs +++ b/src/command/graph/mod.rs @@ -35,9 +35,9 @@ impl Graph { git_context: GitContext, ) -> Result { match &self.command { + Command::Check(command) => command.run(client_config, git_context), Command::Fetch(command) => command.run(client_config), Command::Publish(command) => command.run(client_config, git_context), - Command::Check(command) => command.run(client_config, git_context), } } } diff --git a/src/command/mod.rs b/src/command/mod.rs index 7bbe9440f..38bef50b5 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,4 +1,5 @@ mod config; +mod core; mod docs; mod graph; mod info; @@ -7,6 +8,7 @@ mod output; mod subgraph; mod update; +pub use self::core::Core; pub use config::Config; pub use docs::Docs; pub use graph::Graph; diff --git a/src/command/output.rs b/src/command/output.rs index ae289b66e..4eb673853 100644 --- a/src/command/output.rs +++ b/src/command/output.rs @@ -18,6 +18,7 @@ use crate::utils::table::{self, cell, row}; pub enum RoverStdout { DocsList(HashMap<&'static str, &'static str>), SDL(String), + CSDL(String), SchemaHash(String), SubgraphList(ListDetails), VariantList(Vec), @@ -46,6 +47,10 @@ impl RoverStdout { eprintln!("SDL:"); println!("{}", &sdl); } + RoverStdout::CSDL(csdl) => { + eprintln!("CSDL:"); + println!("{}", &csdl); + } RoverStdout::SchemaHash(hash) => { eprint!("Schema Hash: "); println!("{}", &hash); diff --git a/src/command/subgraph/check.rs b/src/command/subgraph/check.rs index 22755c587..eda0ed251 100644 --- a/src/command/subgraph/check.rs +++ b/src/command/subgraph/check.rs @@ -157,9 +157,8 @@ fn handle_checks(check_result: check::CheckResult) -> Result { fn handle_composition_errors( composition_errors: &[check::check_partial_schema_query::CheckPartialSchemaQueryServiceCheckPartialSchemaCompositionValidationResultErrors], ) -> Result { - let mut num_failures = 0; + let num_failures = composition_errors.len(); for error in composition_errors { - num_failures += 1; eprintln!("{} {}", Red.bold().paint("error:"), &error.message); } match num_failures {