-
Notifications
You must be signed in to change notification settings - Fork 15
feat: CLI configuration #447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
2e0b704
to
41c56b3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
58 files reviewed, 14 comments
Edit PR Review Bot Settings | Greptile
magicblock-config-macro/README.md
Outdated
@@ -0,0 +1,74 @@ | |||
# MagicBlock Config Macro | |||
|
|||
A set a macro helpers to keep the config DRY (Don't Repeat Yourself). It contains two attributes meant to be used on struct that need to derive `serde::Deserialize` and `clap::Args`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax: Grammar error in 'A set a macro helpers' - should be 'A set of macro helpers'
A set a macro helpers to keep the config DRY (Don't Repeat Yourself). It contains two attributes meant to be used on struct that need to derive `serde::Deserialize` and `clap::Args`. | |
A set of macro helpers to keep the config DRY (Don't Repeat Yourself). It contains two attributes meant to be used on struct that need to derive `serde::Deserialize` and `clap::Args`. |
magicblock-config-macro/README.md
Outdated
#[serde(default = "helpers::serde_defaults::bool_true")] | ||
enabled: bool, | ||
#[clap_from_serde_skip] | ||
#[serde(default = "helpers::serde_default::url_none")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax: Typo in attribute path: 'serde_default' should be 'serde_defaults' to match the example above
#[serde(default = "helpers::serde_default::url_none")] | |
#[serde(default = "helpers::serde_defaults::url_none")] |
magicblock-config/src/ledger.rs
Outdated
#[derive_env_var] | ||
#[clap_from_serde_skip] | ||
#[arg(help = "Whether to reset the ledger before starting the validator.")] | ||
#[serde(default = "bool_true")] | ||
pub reset: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: reset field has both derive_env_var and clap_from_serde_skip. Inconsistent handling could lead to confusion about whether this is configurable via env vars.
pub snapshot_frequency: u64, | ||
} | ||
|
||
pub const TEST_SNAPSHOT_FREQUENCY: u64 = 50; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: TEST_SNAPSHOT_FREQUENCY used as production default. Should separate test and production constants
env::set_var("REMOTE_WS_URL", base_cluster_ws); | ||
let config = MagicBlockConfig::try_parse_config_from_arg(&vec![ | ||
"--config-file".to_string(), | ||
config_file_dir.to_str().unwrap().to_string(), | ||
]) | ||
.unwrap() | ||
.config; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Duplicating config loading logic - consider extracting to a helper function
#[derive_env_var] | ||
#[arg(help = "The address the RPC will listen on.")] | ||
#[serde( | ||
default = "default_addr", | ||
deserialize_with = "deserialize_addr", | ||
serialize_with = "serialize_addr" | ||
)] | ||
pub addr: IpAddr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: max_ws_connections is missing #[derive_env_var] attribute, breaking pattern with other fields and preventing env var configuration
#[derive_env_var] | |
#[arg(help = "The address the RPC will listen on.")] | |
#[serde( | |
default = "default_addr", | |
deserialize_with = "deserialize_addr", | |
serialize_with = "serialize_addr" | |
)] | |
pub addr: IpAddr, | |
#[derive_env_var] | |
#[arg(help = "The address the RPC will listen on.")] | |
#[serde( | |
default = "default_addr", | |
deserialize_with = "deserialize_addr", | |
serialize_with = "serialize_addr" | |
)] | |
pub addr: IpAddr, | |
#[derive_env_var] | |
#[arg(help = "The port the RPC will listen on.")] | |
#[serde(default = "default_port")] | |
pub port: u16, | |
#[derive_env_var] | |
#[arg(help = "The max number of WebSocket connections to accept.")] | |
#[serde(default = "default_max_ws_connections")] | |
pub max_ws_connections: usize, |
fn clap_deserialize_addr(s: &str) -> Result<IpAddr, String> { | ||
s.parse().map_err(|err| format!("Invalid address: {err}")) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: clap_deserialize_addr appears unused - should be referenced by a #[arg(value_parser)] attribute on addr field if intended for CLI parsing
s.split(':').map(|part| part.to_string()).collect(); | ||
let [id, path] = parts.as_slice() else { | ||
return Err(format!("Invalid program config: {s}")); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Program config parser assumes exactly 2 parts - doesn't handle paths with colons properly
s.split(':').map(|part| part.to_string()).collect(); | |
let [id, path] = parts.as_slice() else { | |
return Err(format!("Invalid program config: {s}")); | |
}; | |
let parts: Vec<String> = | |
s.splitn(2, ':').map(|part| part.to_string()).collect(); | |
let [id, path] = parts.as_slice() else { | |
return Err(format!("Invalid program config: {s}")); | |
}; |
// Remove the attribute | ||
attrs.remove(i); | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Potential index out of bounds when removing attribute - use drain_filter()
or iterate in reverse to safely remove items
if nv.path.is_ident("default") { | ||
self.meta_attrs | ||
.push(quote! { default_value_t = #value_token () }); | ||
} else if nv.path.is_ident("deserialize_with") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax: Default value has unexpected extra parentheses ()
which could cause compilation errors
if nv.path.is_ident("default") { | |
self.meta_attrs | |
.push(quote! { default_value_t = #value_token () }); | |
} else if nv.path.is_ident("deserialize_with") { | |
if nv.path.is_ident("default") { | |
self.meta_attrs | |
.push(quote! { default_value_t = #value_token }); | |
} else if nv.path.is_ident("deserialize_with") { |
7813027
to
1058a25
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks legit, cleaner than it was, breaking changes fine for now. Please address the failing integration test, and good to merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
This PR is another attempt to allow layered configuration (default -> file -> args -> env) while providing users with information on what parameters they can adjust.
It overrides #346 with the following advantages:
The weakness of this approach is that it utilizes small procedural macros to avoid repetition, which can compromise readability and make debugging more challenging.
It introduces a breaking change in the format of the remote because Clap does not accept an enum variant with complex values.
Greptile Summary
Major refactoring of configuration management using Clap for CLI support, introducing layered configuration (default -> file -> args -> env) with automatic CLI and environment variable derivation.
magicblock-config-macro
crate with procedural macrosclap_prefix
andclap_from_serde
for automatic CLI argument generationremote = "devnet"
toremote.cluster = "devnet"
across all TOML files (breaking change)magicblock-config
cratefdqn
tofqdn
)