diff --git a/Cargo.lock b/Cargo.lock index 8c664c1e5..c714529f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5432,7 +5432,6 @@ dependencies = [ "async-trait", "cap-std", "chrono", - "clap", "crossbeam-channel", "futures", "hyper", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index e68173aa3..74391ed55 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -16,7 +16,6 @@ doctest = false anyhow = { workspace = true } async-trait = { workspace = true } chrono = { workspace = true } -clap = { workspace = true } prost-types = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/runtime/src/alpha/args.rs b/runtime/src/alpha/args.rs index dc053dc96..1642dac27 100644 --- a/runtime/src/alpha/args.rs +++ b/runtime/src/alpha/args.rs @@ -1,37 +1,33 @@ -use std::path::PathBuf; +use std::{fmt::Debug, path::PathBuf, str::FromStr}; -use clap::{Parser, ValueEnum}; use tonic::transport::{Endpoint, Uri}; -#[derive(Parser, Debug)] -#[command(version)] -pub struct Args { - /// Port to start runtime on - #[arg(long)] - pub port: u16, +use crate::args::args; - /// Address to reach provisioner at - #[arg(long, default_value = "http://localhost:3000")] - pub provisioner_address: Endpoint, - - /// Type of storage manager to start - #[arg(long, value_enum)] - pub storage_manager_type: StorageManagerType, - - /// Path to use for storage manager - #[arg(long)] - pub storage_manager_path: PathBuf, - - /// Address to reach the authentication service at - #[arg(long, default_value = "http://127.0.0.1:8008")] - pub auth_uri: Uri, +args! { + pub struct Args { + "--port" => pub port: u16, + "--provisioner-address" => #[arg(default_value = "http://localhost:3000")] pub provisioner_address: Endpoint, + "--storage-manager-type" => pub storage_manager_type: StorageManagerType, + "--storage-manager-path" => pub storage_manager_path: PathBuf, + "--auth-uri" => #[arg(default_value = "http://127.0.0.1:8008")] pub auth_uri: Uri, + } } -#[derive(Clone, Debug, ValueEnum)] +#[derive(Clone, Debug)] pub enum StorageManagerType { - /// Use a deloyer artifacts directory Artifacts, - - /// Use a local working directory WorkingDir, } + +impl FromStr for StorageManagerType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "artifacts" => Ok(StorageManagerType::Artifacts), + "working-dir" => Ok(StorageManagerType::WorkingDir), + _ => Err(()), + } + } +} diff --git a/runtime/src/alpha/mod.rs b/runtime/src/alpha/mod.rs index ce7cf714e..1b41924fc 100644 --- a/runtime/src/alpha/mod.rs +++ b/runtime/src/alpha/mod.rs @@ -10,7 +10,6 @@ use std::{ use anyhow::Context; use async_trait::async_trait; -use clap::Parser; use core::future::Future; use shuttle_common::{ backends::{ @@ -51,7 +50,7 @@ use self::args::Args; mod args; pub async fn start(loader: impl Loader + Send + 'static) { - let args = Args::parse(); + let args = Args::parse().expect("could not parse arguments"); let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), args.port); let provisioner_address = args.provisioner_address; diff --git a/runtime/src/args.rs b/runtime/src/args.rs new file mode 100644 index 000000000..1c2a48b9c --- /dev/null +++ b/runtime/src/args.rs @@ -0,0 +1,80 @@ +#[derive(Debug)] +pub enum Error { + DuplicatedArgument { arg: &'static str }, + MissingRequiredArgument { arg: &'static str }, + UnexpectedArgument { arg: String }, + + InvalidValue { arg: &'static str, value: String }, + MissingValue { arg: &'static str }, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::DuplicatedArgument { arg } => write!(f, "duplicated argument {arg}"), + Self::MissingRequiredArgument { arg } => write!(f, "missing required argument {arg}"), + Self::UnexpectedArgument { arg } => write!(f, "unexpected argument: {arg}"), + + Self::InvalidValue { arg, value } => { + write!(f, "invalid value for argument {arg}: {value}") + } + Self::MissingValue { arg } => write!(f, "missing value for argument {arg}"), + } + } +} + +impl std::error::Error for Error {} + +macro_rules! args { + // Internal rules used to handle optional default values + (@unwrap $arg:literal, $field:ident $(,)?) => { + $field.ok_or_else(|| $crate::args::Error::MissingRequiredArgument { arg: $arg })? + }; + (@unwrap $arg:literal, $field:ident, $default:literal) => { + $field.unwrap_or_else(|| $default.parse().unwrap()) + }; + + ( + pub struct $struct:ident { + $($arg:literal => $(#[arg(default_value = $default:literal)])? pub $field:ident: $ty:ty),+ $(,)? + } + ) => { + #[derive(::std::fmt::Debug)] + pub struct $struct { + $(pub $field: $ty,)+ + } + + impl $struct { + pub fn parse() -> ::std::result::Result { + $(let mut $field: ::std::option::Option<$ty> = None;)+ + + // The first argument is the path of the executable. + let mut args_iter = ::std::env::args().skip(1); + while let ::std::option::Option::Some(arg) = args_iter.next() { + match arg.as_str() { + $($arg => { + if $field.is_some() { + return ::std::result::Result::Err($crate::args::Error::DuplicatedArgument { arg: $arg }); + } + let raw_value = args_iter + .next() + .ok_or_else(|| $crate::args::Error::MissingValue { arg: $arg })?; + let value = raw_value.parse().map_err(|_| $crate::args::Error::InvalidValue { + arg: $arg, + value: raw_value, + })?; + $field = ::std::option::Option::Some(value); + })+ + _ => return ::std::result::Result::Err($crate::args::Error::UnexpectedArgument { arg }), + } + } + + ::std::result::Result::Ok($struct { + $($field: $crate::args::args!(@unwrap $arg, $field, $($default)?),)+ + }) + } + } + } +} + +pub(crate) use args; diff --git a/runtime/src/bin/shuttle-next.rs b/runtime/src/bin/shuttle-next.rs index d375ab17f..310a2e762 100644 --- a/runtime/src/bin/shuttle-next.rs +++ b/runtime/src/bin/shuttle-next.rs @@ -3,7 +3,6 @@ use std::{ time::Duration, }; -use clap::Parser; use shuttle_common::backends::tracing::{setup_tracing, ExtractPropagationLayer}; use shuttle_proto::runtime::runtime_server::RuntimeServer; use shuttle_runtime::{AxumWasm, NextArgs}; @@ -12,7 +11,7 @@ use tracing::trace; #[tokio::main(flavor = "multi_thread")] async fn main() { - let args = NextArgs::parse(); + let args = NextArgs::parse().unwrap(); setup_tracing(tracing_subscriber::registry(), "shuttle-next"); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d83081745..265e1f6e5 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -215,6 +215,7 @@ //! You can also [open an issue or a discussion on GitHub](https://github.com/shuttle-hq/shuttle). //! mod alpha; +mod args; mod logger; #[cfg(feature = "next")] mod next; diff --git a/runtime/src/next/args.rs b/runtime/src/next/args.rs index ccbb3ac6e..c51dfed82 100644 --- a/runtime/src/next/args.rs +++ b/runtime/src/next/args.rs @@ -1,9 +1,7 @@ -use clap::Parser; +use crate::args::args; -#[derive(Parser, Debug)] -#[command(version)] -pub struct NextArgs { - /// Port to start runtime on - #[arg(long)] - pub port: u16, +args! { + pub struct NextArgs { + "--port" => pub port: u16, + } }