feat(cli): add validate command (#2241)
This commit is contained in:
parent
602ffda466
commit
676327f781
5 changed files with 262 additions and 9 deletions
125
Cargo.lock
generated
125
Cargo.lock
generated
|
@ -240,6 +240,15 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
|
@ -378,6 +387,21 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "assert_fs"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f070617a68e5c2ed5d06ee8dd620ee18fb72b99f6c094bed34cf8ab07c875b48"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"doc-comment",
|
||||
"globwalk",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.8.0"
|
||||
|
@ -829,6 +853,16 @@ dependencies = [
|
|||
"alloc-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
|
@ -1362,6 +1396,12 @@ version = "1.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1bba4f227a4a53d12b653f50ca7bf10c9119ae2aba56aff9e0338b5c98f36a"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
@ -1403,6 +1443,12 @@ dependencies = [
|
|||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "earcutr"
|
||||
version = "0.4.2"
|
||||
|
@ -1900,6 +1946,30 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.20",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globwalk"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio"
|
||||
version = "0.8.3"
|
||||
|
@ -2227,6 +2297,23 @@ dependencies = [
|
|||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex",
|
||||
"same-file",
|
||||
"thread_local",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imbl"
|
||||
version = "2.0.0"
|
||||
|
@ -3211,6 +3298,34 @@ version = "0.2.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
"itertools 0.10.5",
|
||||
"predicates-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.25"
|
||||
|
@ -3650,7 +3765,7 @@ version = "1.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"aho-corasick 1.0.2",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.2",
|
||||
]
|
||||
|
@ -4477,10 +4592,12 @@ name = "surreal"
|
|||
version = "1.0.0-beta.9"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"assert_fs",
|
||||
"base64 0.21.2",
|
||||
"bytes",
|
||||
"clap 4.3.10",
|
||||
"futures 0.3.28",
|
||||
"glob",
|
||||
"http",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
|
@ -4797,6 +4914,12 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
|
||||
[[package]]
|
||||
name = "test-log"
|
||||
version = "0.2.12"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -38,6 +38,7 @@ base64 = "0.21.2"
|
|||
bytes = "1.4.0"
|
||||
clap = { version = "4.3.10", features = ["env", "derive", "wrap_help", "unicode"] }
|
||||
futures = "0.3.28"
|
||||
glob = "0.3.1"
|
||||
http = "0.2.9"
|
||||
hyper = "0.14.27"
|
||||
ipnet = "2.8.0"
|
||||
|
@ -49,30 +50,31 @@ reqwest = { version = "0.11.18", features = ["blocking"] }
|
|||
rustyline = { version = "11.0.0", features = ["derive"] }
|
||||
serde = { version = "1.0.164", features = ["derive"] }
|
||||
serde_cbor = "0.11.2"
|
||||
serde_pack = { version = "1.1.1", package = "rmp-serde" }
|
||||
serde_json = "1.0.99"
|
||||
serde_pack = { version = "1.1.1", package = "rmp-serde" }
|
||||
surrealdb = { path = "lib", features = ["protocol-http", "protocol-ws", "rustls"] }
|
||||
tempfile = "3.6.0"
|
||||
thiserror = "1.0.40"
|
||||
tokio = { version = "1.29.1", features = ["macros", "signal"] }
|
||||
tokio-util = { version = "0.7.8", features = ["io"] }
|
||||
uuid = { version = "1.4.0", features = ["serde", "js", "v4", "v7"] }
|
||||
tracing = "0.1"
|
||||
tracing-opentelemetry = "0.18.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
urlencoding = "2.1.2"
|
||||
uuid = { version = "1.4.0", features = ["serde", "js", "v4", "v7"] }
|
||||
warp = { version = "0.3.5", features = ["compression", "tls", "websocket"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = "0.26.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rcgen = "0.10.0"
|
||||
tonic = "0.8.3"
|
||||
assert_fs = "1.0.13"
|
||||
opentelemetry-proto = { version = "0.1.0", features = ["gen-tonic", "traces", "build-server"] }
|
||||
rcgen = "0.10.0"
|
||||
serial_test = "2.0.0"
|
||||
tokio-stream = { version = "0.1", features = ["net"] }
|
||||
temp-env = "0.3.4"
|
||||
tokio-stream = { version = "0.1", features = ["net"] }
|
||||
tonic = "0.8.3"
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer-scripts = "pkg/deb/"
|
||||
|
|
|
@ -8,10 +8,10 @@ mod sql;
|
|||
#[cfg(feature = "has-storage")]
|
||||
mod start;
|
||||
mod upgrade;
|
||||
mod validate;
|
||||
pub(crate) mod validator;
|
||||
mod version;
|
||||
|
||||
use self::upgrade::UpgradeCommandArguments;
|
||||
use crate::cnf::LOGO;
|
||||
use backup::BackupCommandArguments;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
@ -24,6 +24,8 @@ use sql::SqlCommandArguments;
|
|||
#[cfg(feature = "has-storage")]
|
||||
use start::StartCommandArguments;
|
||||
use std::process::ExitCode;
|
||||
use upgrade::UpgradeCommandArguments;
|
||||
use validate::ValidateCommandArguments;
|
||||
use version::VersionCommandArguments;
|
||||
|
||||
const INFO: &str = "
|
||||
|
@ -71,6 +73,8 @@ enum Commands {
|
|||
visible_alias = "isready"
|
||||
)]
|
||||
IsReady(IsReadyCommandArguments),
|
||||
#[command(about = "Validate SurrealQL query files")]
|
||||
Validate(ValidateCommandArguments),
|
||||
}
|
||||
|
||||
pub async fn init() -> ExitCode {
|
||||
|
@ -85,6 +89,7 @@ pub async fn init() -> ExitCode {
|
|||
Commands::Upgrade(args) => upgrade::init(args).await,
|
||||
Commands::Sql(args) => sql::init(args).await,
|
||||
Commands::IsReady(args) => isready::init(args).await,
|
||||
Commands::Validate(args) => validate::init(args).await,
|
||||
};
|
||||
if let Err(e) = output {
|
||||
error!("{}", e);
|
||||
|
|
62
src/cli/validate.rs
Normal file
62
src/cli/validate.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use crate::err::Error;
|
||||
use clap::Args;
|
||||
use glob::glob;
|
||||
use std::io::{Error as IoError, ErrorKind};
|
||||
use surrealdb::sql::parse;
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ValidateCommandArguments {
|
||||
#[arg(help = "Glob pattern for the files to validate")]
|
||||
#[arg(default_value = "**/*.surql")]
|
||||
pattern: String,
|
||||
}
|
||||
|
||||
pub async fn init(args: ValidateCommandArguments) -> Result<(), Error> {
|
||||
let ValidateCommandArguments {
|
||||
pattern,
|
||||
} = args;
|
||||
|
||||
let entries = match glob(&pattern) {
|
||||
Ok(entries) => entries,
|
||||
Err(error) => {
|
||||
eprintln!("Error parsing glob pattern {pattern}: {error}");
|
||||
|
||||
return Err(Error::Io(IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("Error parsing glob pattern {pattern}: {error}"),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut has_entries = false;
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let file_content = tokio::fs::read_to_string(entry.clone()).await?;
|
||||
let parse_result = parse(&file_content);
|
||||
|
||||
match parse_result {
|
||||
Ok(_) => {
|
||||
println!("{}: OK", entry.display());
|
||||
}
|
||||
Err(error) => {
|
||||
println!("{}: KO", entry.display());
|
||||
eprintln!("{error}");
|
||||
|
||||
return Err(crate::err::Error::from(error));
|
||||
}
|
||||
}
|
||||
|
||||
has_entries = true;
|
||||
}
|
||||
|
||||
if !has_entries {
|
||||
eprintln!("No files found for pattern {pattern}");
|
||||
|
||||
return Err(Error::Io(IoError::new(
|
||||
ErrorKind::NotFound,
|
||||
format!("No files found for pattern {pattern}"),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
65
tests/cli.rs
65
tests/cli.rs
|
@ -1,6 +1,7 @@
|
|||
mod cli_integration {
|
||||
// cargo test --package surreal --bin surreal --no-default-features --features storage-mem --test cli -- cli_integration --nocapture
|
||||
|
||||
use assert_fs::prelude::{FileTouch, FileWriteStr, PathChild};
|
||||
use rand::{thread_rng, Rng};
|
||||
use serial_test::serial;
|
||||
use std::fs;
|
||||
|
@ -50,8 +51,7 @@ mod cli_integration {
|
|||
}
|
||||
}
|
||||
|
||||
/// Run the CLI with the given args
|
||||
fn run(args: &str) -> Child {
|
||||
fn run_internal<P: AsRef<Path>>(args: &str, current_dir: Option<P>) -> Child {
|
||||
let mut path = std::env::current_exe().unwrap();
|
||||
assert!(path.pop());
|
||||
if path.ends_with("deps") {
|
||||
|
@ -62,6 +62,9 @@ mod cli_integration {
|
|||
path.push(format!("{}{}", env!("CARGO_PKG_NAME"), std::env::consts::EXE_SUFFIX));
|
||||
|
||||
let mut cmd = Command::new(path);
|
||||
if let Some(dir) = current_dir {
|
||||
cmd.current_dir(&dir);
|
||||
}
|
||||
cmd.stdin(Stdio::piped());
|
||||
cmd.stdout(Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
|
@ -71,6 +74,16 @@ mod cli_integration {
|
|||
}
|
||||
}
|
||||
|
||||
/// Run the CLI with the given args
|
||||
fn run(args: &str) -> Child {
|
||||
run_internal::<String>(args, None)
|
||||
}
|
||||
|
||||
/// Run the CLI with the given args inside a temporary directory
|
||||
fn run_in_dir<P: AsRef<Path>>(args: &str, current_dir: P) -> Child {
|
||||
run_internal(args, Some(current_dir))
|
||||
}
|
||||
|
||||
fn tmp_file(name: &str) -> String {
|
||||
let path = Path::new(env!("OUT_DIR")).join(name);
|
||||
path.to_string_lossy().into_owned()
|
||||
|
@ -290,4 +303,52 @@ mod cli_integration {
|
|||
let output = server.kill().output().unwrap_err();
|
||||
assert!(output.contains("Started web server"), "couldn't start web server: {output}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn validate_found_no_files() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
temp_dir.child("file.txt").touch().unwrap();
|
||||
|
||||
assert!(run_in_dir("validate", &temp_dir).output().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn validate_succeed_for_valid_surql_files() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let statement_file = temp_dir.child("statement.surql");
|
||||
|
||||
statement_file.touch().unwrap();
|
||||
statement_file.write_str("CREATE thing:success;").unwrap();
|
||||
|
||||
assert!(run_in_dir("validate", &temp_dir).output().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn validate_failed_due_to_invalid_glob_pattern() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
const WRONG_GLOB_PATTERN: &str = "**/*{.txt";
|
||||
|
||||
let args = format!("validate \"{}\"", WRONG_GLOB_PATTERN);
|
||||
|
||||
assert!(run_in_dir(&args, &temp_dir).output().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn validate_failed_due_to_invalid_surql_files_syntax() {
|
||||
let temp_dir = assert_fs::TempDir::new().unwrap();
|
||||
|
||||
let statement_file = temp_dir.child("statement.surql");
|
||||
|
||||
statement_file.touch().unwrap();
|
||||
statement_file.write_str("CREATE $thing WHERE value = '';").unwrap();
|
||||
|
||||
assert!(run_in_dir("validate", &temp_dir).output().is_err());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue