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 {