diff --git a/src/commands/kv/add_namespace.rs b/src/commands/kv/add_namespace.rs new file mode 100644 index 000000000..a7e0e0ef3 --- /dev/null +++ b/src/commands/kv/add_namespace.rs @@ -0,0 +1,25 @@ +use cloudflare::apiclient::APIClient; + +use cloudflare::workerskv::create_namespace::CreateNamespace; +use cloudflare::workerskv::create_namespace::CreateNamespaceParams; + +use crate::terminal::message; + +pub fn add_namespace(title: &str) -> Result<(), failure::Error> { + let client = super::api_client()?; + let account_id = super::account_id()?; + + let msg = format!("Creating namespace with title \"{}\"", title); + message::working(&msg); + + let response = client.request(&CreateNamespace { + account_identifier: &account_id, + params: CreateNamespaceParams { + title: title.to_string(), + }, + }); + + super::print_response(response); + + Ok(()) +} diff --git a/src/commands/kv/mod.rs b/src/commands/kv/mod.rs new file mode 100644 index 000000000..9c8e5b3c2 --- /dev/null +++ b/src/commands/kv/mod.rs @@ -0,0 +1,68 @@ +use cloudflare::auth::Credentials; +use cloudflare::response::APIFailure; +use cloudflare::response::APIResponse; +use cloudflare::response::APIResult; +use cloudflare::HTTPAPIClient; + +use crate::settings; +use crate::terminal::message; + +mod add_namespace; + +pub use add_namespace::add_namespace; + +fn api_client() -> Result { + let user = settings::global_user::GlobalUser::new()?; + + Ok(HTTPAPIClient::new(Credentials::from(user))) +} + +fn account_id() -> Result { + let project = settings::project::Project::new()?; + // we need to be certain that account id is present to make kv calls + if project.account_id.is_empty() { + panic!("Your wrangler.toml is missing the account_id field which is required to create KV namespaces!"); + } + Ok(project.account_id) +} + +fn print_response(response: APIResponse) { + match response { + Ok(success) => message::success(&format!("Success: {:#?}", success.result)), + Err(e) => match e { + APIFailure::Error(_status, errors) => { + for error in errors { + message::warn(&format!("Error {}: {}", error.code, error.message,)); + + let suggestion = help(error.code); + if !suggestion.is_empty() { + message::help(suggestion); + } + } + } + APIFailure::Invalid(reqwest_err) => message::warn(&format!("Error: {}", reqwest_err)), + }, + } +} + +fn help(error_code: u16) -> &'static str { + // https://api.cloudflare.com/#workers-kv-namespace-errors + match error_code { + // namespace errors + 10010 | 10011 | 10012 | 10013 | 10014 | 10018 => { + "Run `wrangler kv list` to see your existing namespaces with IDs" + } + 10009 | 10020 => "Run `wrangler kv list ` to see your existing keys", // key errors + // TODO: link to more info + // limit errors + 10022 | 10023 | 10024 | 10030 => "See documentation", + // TODO: link to tool for this + // legacy namespace errors + 10021 | 10035 | 10038 => "Consider moving this namespace", + // cloudflare account errors + 10017 | 10026 | 10027 | 10031 | 10032 | 10037 => { + "Check your account settings in the Cloudflare dashboard" + } + _ => "", + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index fae41f154..888f23de7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -6,6 +6,7 @@ pub mod build; pub mod config; pub mod generate; pub mod init; +pub mod kv; pub mod publish; pub mod subdomain; pub mod whoami; diff --git a/src/main.rs b/src/main.rs index 8e0e39ce9..6252fa077 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,19 @@ fn run() -> Result<(), failure::Error> { .author("ashley g williams ") .setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::DeriveDisplayOrder) + .subcommand( + SubCommand::with_name("kv") + .about(&*format!( + "{} Interact with your Workers KV Store", + emoji::KV + )) + .subcommand( + SubCommand::with_name("add") + .arg( + Arg::with_name("title") + ) + ) + ) .subcommand( SubCommand::with_name("generate") .about(&*format!( @@ -233,6 +246,15 @@ fn run() -> Result<(), failure::Error> { .expect("The subdomain name you are requesting must be provided."); commands::subdomain(name, &user, &project)?; + } else if let Some(kv_matches) = matches.subcommand_matches("kv") { + match kv_matches.subcommand() { + ("add", Some(add_matches)) => { + let title = add_matches.value_of("title").unwrap(); + commands::kv::add_namespace(title)?; + } + ("", None) => println!("kv expects a subcommand"), + _ => unreachable!(), + } } Ok(()) } diff --git a/src/terminal/emoji.rs b/src/terminal/emoji.rs index da62fa835..6ef16c6d8 100644 --- a/src/terminal/emoji.rs +++ b/src/terminal/emoji.rs @@ -18,10 +18,11 @@ pub static DANCERS: Emoji = Emoji("👯 ", ""); pub static EYES: Emoji = Emoji("👀 ", ""); pub static INBOX: Emoji = Emoji("📥 ", ""); pub static INFO: Emoji = Emoji("💁‍ ", ""); +pub static KV: Emoji = Emoji("🗂️ ", ""); pub static MICROSCOPE: Emoji = Emoji("🔬 ", ""); pub static SHEEP: Emoji = Emoji("🐑 ", ""); -pub static SLEUTH: Emoji = Emoji("🕵️‍♂️", ""); -pub static SPARKLES: Emoji = Emoji("✨ ", ""); +pub static SLEUTH: Emoji = Emoji("🕵️‍♂️ ", ""); +pub static SPARKLES: Emoji = Emoji("✨ ", ""); pub static SWIRL: Emoji = Emoji("🌀 ", ""); pub static UP: Emoji = Emoji("🆙 ", ""); pub static WARN: Emoji = Emoji("⚠️ ", "");