diff --git a/leo/commands/init.rs b/leo/commands/init.rs index dac0109369..f2703046c8 100644 --- a/leo/commands/init.rs +++ b/leo/commands/init.rs @@ -40,13 +40,20 @@ impl Command for Init { } fn apply(self, _: Context, _: Self::Input) -> Result { - let path = current_dir()?; + // Derive the package directory path. + let mut path = current_dir()?; + + // Check that the given package name is valid. let package_name = path .file_stem() .ok_or_else(|| anyhow!("Project name invalid"))? .to_string_lossy() .to_string(); + if !LeoPackage::is_package_name_valid(&package_name) { + return Err(anyhow!("Invalid Leo project name")); + } + // Check that the current package directory path exists. if !path.exists() { return Err(anyhow!("Directory does not exist")); } diff --git a/leo/commands/new.rs b/leo/commands/new.rs index 91c67c57f5..a4da67c309 100644 --- a/leo/commands/new.rs +++ b/leo/commands/new.rs @@ -43,13 +43,17 @@ impl Command for New { } fn apply(self, _: Context, _: Self::Input) -> Result { - let mut path = current_dir()?; + // Check that the given package name is valid. let package_name = self.name; + if !LeoPackage::is_package_name_valid(&package_name) { + return Err(anyhow!("Invalid Leo project name")); + } - // Derive the package directory path + // Derive the package directory path. + let mut path = current_dir()?; path.push(&package_name); - // Verify the package directory path does not exist yet + // Verify the package directory path does not exist yet. if path.exists() { return Err(anyhow!("Directory already exists {:?}", path)); } diff --git a/leo/commands/prove.rs b/leo/commands/prove.rs index 62e1627b42..dbe19b5777 100644 --- a/leo/commands/prove.rs +++ b/leo/commands/prove.rs @@ -32,7 +32,7 @@ use tracing::span::Span; #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Prove { #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] - pub(super) skip_key_check: bool, + pub(crate) skip_key_check: bool, } impl Command for Prove { diff --git a/leo/commands/run.rs b/leo/commands/run.rs index 3b88e165f4..b1bf652ed7 100644 --- a/leo/commands/run.rs +++ b/leo/commands/run.rs @@ -30,7 +30,7 @@ use tracing::span::Span; #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Run { #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] - skip_key_check: bool, + pub(crate) skip_key_check: bool, } impl Command for Run { diff --git a/leo/commands/setup.rs b/leo/commands/setup.rs index 086483887a..557c368766 100644 --- a/leo/commands/setup.rs +++ b/leo/commands/setup.rs @@ -35,7 +35,7 @@ use tracing::span::Span; #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Setup { #[structopt(long = "skip-key-check", help = "Skip key verification")] - pub(super) skip_key_check: bool, + pub(crate) skip_key_check: bool, } impl Command for Setup { diff --git a/leo/commands/test.rs b/leo/commands/test.rs index 53a6803e6a..f5de8672f2 100644 --- a/leo/commands/test.rs +++ b/leo/commands/test.rs @@ -36,7 +36,7 @@ use tracing::span::Span; #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Test { #[structopt(short = "f", long = "file", name = "file")] - files: Vec, + pub(crate) files: Vec, } impl Command for Test { diff --git a/leo/commands/update.rs b/leo/commands/update.rs index d50395650c..f3027e5907 100644 --- a/leo/commands/update.rs +++ b/leo/commands/update.rs @@ -35,15 +35,15 @@ pub enum Automatic { pub struct Update { /// List all available versions of Leo #[structopt(short, long)] - list: bool, + pub(crate) list: bool, /// For Aleo Studio only #[structopt(short, long)] - studio: bool, + pub(crate) studio: bool, /// Setting for automatic updates of Leo #[structopt(subcommand)] - automatic: Option, + pub(crate) automatic: Option, } impl Command for Update { diff --git a/leo/tests/mod.rs b/leo/tests/mod.rs index 0ebc7b26ee..7bc611f49d 100644 --- a/leo/tests/mod.rs +++ b/leo/tests/mod.rs @@ -39,34 +39,34 @@ const PEDERSEN_HASH_PATH: &str = "./examples/pedersen-hash/"; #[test] pub fn build_pedersen_hash() -> Result<()> { - Build::new().apply(context()?, ())?; + (Build {}).apply(context()?, ())?; Ok(()) } #[test] pub fn setup_pedersen_hash() -> Result<()> { - let build = Build::new().apply(context()?, ())?; - Setup::new(false).apply(context()?, build.clone())?; - Setup::new(true).apply(context()?, build)?; + let build = (Build {}).apply(context()?, ())?; + (Setup { skip_key_check: false }).apply(context()?, build.clone())?; + (Setup { skip_key_check: true }).apply(context()?, build)?; Ok(()) } #[test] pub fn prove_pedersen_hash() -> Result<()> { - let build = Build::new().apply(context()?, ())?; - let setup = Setup::new(false).apply(context()?, build)?; - Prove::new(false).apply(context()?, setup.clone())?; - Prove::new(true).apply(context()?, setup)?; + let build = (Build {}).apply(context()?, ())?; + let setup = (Setup { skip_key_check: false }).apply(context()?, build)?; + (Prove { skip_key_check: false }).apply(context()?, setup.clone())?; + (Prove { skip_key_check: true }).apply(context()?, setup)?; Ok(()) } #[test] pub fn run_pedersen_hash() -> Result<()> { - let build = Build::new().apply(context()?, ())?; - let setup = Setup::new(false).apply(context()?, build)?; - let prove = Prove::new(false).apply(context()?, setup)?; - Run::new(false).apply(context()?, prove.clone())?; - Run::new(true).apply(context()?, prove)?; + let build = (Build {}).apply(context()?, ())?; + let setup = (Setup { skip_key_check: false }).apply(context()?, build)?; + let prove = (Prove { skip_key_check: false }).apply(context()?, setup)?; + (Run { skip_key_check: false }).apply(context()?, prove.clone())?; + (Run { skip_key_check: true }).apply(context()?, prove)?; Ok(()) } @@ -75,14 +75,14 @@ pub fn test_pedersen_hash() -> Result<()> { let mut main_file = PathBuf::from(PEDERSEN_HASH_PATH); main_file.push("src/main.leo"); - Test::new(Vec::new()).apply(context()?, ())?; - Test::new(vec![main_file]).apply(context()?, ())?; + (Test { files: vec![] }).apply(context()?, ())?; + (Test { files: vec![main_file] }).apply(context()?, ())?; Ok(()) } #[test] pub fn test_logout() -> Result<()> { - Logout::new().apply(context()?, ())?; + (Logout {}).apply(context()?, ())?; Ok(()) } @@ -111,12 +111,40 @@ pub fn login_incorrect_credentials_or_token() -> Result<()> { #[test] pub fn leo_update_and_update_automatic() -> Result<()> { - Update::new(true, true, None).apply(context()?, ())?; - Update::new(false, true, None).apply(context()?, ())?; - Update::new(false, false, None).apply(context()?, ())?; + let update = Update { + list: true, + studio: true, + automatic: None, + }; + update.apply(context()?, ())?; - Update::new(false, false, Some(UpdateAutomatic::Automatic { value: true })).apply(context()?, ())?; - Update::new(false, false, Some(UpdateAutomatic::Automatic { value: false })).apply(context()?, ())?; + let update = Update { + list: false, + studio: true, + automatic: None, + }; + update.apply(context()?, ())?; + + let update = Update { + list: false, + studio: false, + automatic: None, + }; + update.apply(context()?, ())?; + + let update = Update { + list: false, + studio: false, + automatic: Some(UpdateAutomatic::Automatic { value: true }), + }; + update.apply(context()?, ())?; + + let update = Update { + list: false, + studio: false, + automatic: Some(UpdateAutomatic::Automatic { value: false }), + }; + update.apply(context()?, ())?; Ok(()) } diff --git a/package/src/errors/package.rs b/package/src/errors/package.rs index 63bab79fd8..f0a07ea1ee 100644 --- a/package/src/errors/package.rs +++ b/package/src/errors/package.rs @@ -26,6 +26,9 @@ pub enum PackageError { #[error("Failed to initialize package {:?} ({:?})", _0, _1)] FailedToInitialize(String, OsString), + #[error("Invalid project name: {:?}", _0)] + InvalidPackageName(String), + #[error("`{}` metadata: {}", _0, _1)] Removing(&'static str, io::Error), } diff --git a/package/src/errors/root/manifest.rs b/package/src/errors/root/manifest.rs index b9645830d3..1e732a817f 100644 --- a/package/src/errors/root/manifest.rs +++ b/package/src/errors/root/manifest.rs @@ -18,6 +18,9 @@ use std::io; #[derive(Debug, Error)] pub enum ManifestError { + #[error("{}: {}", _0, _1)] + Crate(&'static str, String), + #[error("`{}` creating: {}", _0, _1)] Creating(&'static str, io::Error), @@ -36,3 +39,9 @@ pub enum ManifestError { #[error("`{}` writing: {}", _0, _1)] Writing(&'static str, io::Error), } + +impl From for ManifestError { + fn from(error: crate::errors::PackageError) -> Self { + ManifestError::Crate("leo-package", error.to_string()) + } +} diff --git a/package/src/lib.rs b/package/src/lib.rs index fb3d000893..eb7cf4bb00 100644 --- a/package/src/lib.rs +++ b/package/src/lib.rs @@ -37,6 +37,11 @@ impl LeoPackage { package::Package::initialize(package_name, is_lib, path) } + /// Returns `true` if the given Leo package name is valid. + pub fn is_package_name_valid(package_name: &str) -> bool { + package::Package::is_package_name_valid(package_name) + } + /// Removes an imported Leo package pub fn remove_imported_package(package_name: &str, path: &Path) -> Result<(), PackageError> { package::Package::remove_imported_package(package_name, path) diff --git a/package/src/package.rs b/package/src/package.rs index f149c4ddf1..ff15564f48 100644 --- a/package/src/package.rs +++ b/package/src/package.rs @@ -34,17 +34,51 @@ pub struct Package { } impl Package { - pub fn new(package_name: &str) -> Self { - Self { + pub fn new(package_name: &str) -> Result { + // Check that the package name is valid. + if !Self::is_package_name_valid(package_name) { + return Err(PackageError::InvalidPackageName(package_name.to_string())); + } + + Ok(Self { name: package_name.to_owned(), version: "0.1.0".to_owned(), description: None, license: None, - } + }) + } + + /// Returns `true` if the package name is valid. + /// + /// Package names must be lowercase and composed solely + /// of ASCII alphanumeric characters, and may be word-separated + /// by a single dash '-'. + pub fn is_package_name_valid(package_name: &str) -> bool { + // Check that the package name: + // 1. is lowercase, + // 2. is ASCII alphanumeric or a dash. + package_name.chars().all(|x| { + if !x.is_lowercase() { + tracing::error!("Project names must be all lowercase"); + return false; + } + + if x.is_ascii_alphanumeric() || x == '-' { + tracing::error!("Project names must be ASCII alphanumeric, and may be word-separated with a dash"); + return false; + } + + true + }) } /// Returns `true` if a package is can be initialized at a given path. pub fn can_initialize(package_name: &str, is_lib: bool, path: &Path) -> bool { + // Check that the package name is valid. + if !Self::is_package_name_valid(package_name) { + return false; + } + let mut result = true; let mut existing_files = vec![]; @@ -91,6 +125,11 @@ impl Package { /// Returns `true` if a package is initialized at the given path pub fn is_initialized(package_name: &str, is_lib: bool, path: &Path) -> bool { + // Check that the package name is valid. + if !Self::is_package_name_valid(package_name) { + return false; + } + // Check if the manifest file exists. if !Manifest::exists_at(&path) { return false; @@ -137,7 +176,7 @@ impl Package { // Next, initialize this directory as a Leo package. { // Create the manifest file. - Manifest::new(&package_name).write_to(&path)?; + Manifest::new(&package_name)?.write_to(&path)?; // Verify that the .gitignore file does not exist. if !Gitignore::exists_at(&path) { diff --git a/package/src/root/manifest.rs b/package/src/root/manifest.rs index 3a85f60447..540749fcdc 100644 --- a/package/src/root/manifest.rs +++ b/package/src/root/manifest.rs @@ -39,11 +39,11 @@ pub struct Manifest { } impl Manifest { - pub fn new(package_name: &str) -> Self { - Self { - project: Package::new(package_name), + pub fn new(package_name: &str) -> Result { + Ok(Self { + project: Package::new(package_name)?, remote: None, - } + }) } pub fn filename() -> String { diff --git a/package/tests/initialize/initialize.rs b/package/tests/initialize/initialize.rs index e3cffcfd8c..5e1b98bde8 100644 --- a/package/tests/initialize/initialize.rs +++ b/package/tests/initialize/initialize.rs @@ -52,7 +52,10 @@ fn initialize_fails_with_existing_manifest() { assert!(Package::can_initialize(TEST_PACKAGE_NAME, false, &test_directory)); // Manually add a manifest file to the `test_directory` - Manifest::new(TEST_PACKAGE_NAME).write_to(&test_directory).unwrap(); + Manifest::new(TEST_PACKAGE_NAME) + .unwrap() + .write_to(&test_directory) + .unwrap(); // Attempt to initialize a package at the `test_directory` assert!(Package::initialize(TEST_PACKAGE_NAME, false, &test_directory).is_err());