How to create a StructOpt command where all subcomands are optional - rust

I want to arrange subcommands like:
mycmd status : Prints a short status - NOT WORKING
mycmd status full : Prints verbose status - OK
mycmd status dump : Dumps full debug status to a file - OK
I'm unable to achieve the simple mycmd status because StructOpt believes I am missing a required subcommand (sub-subcommand?) and prints the usage. The docs indicate that I need to use the Option<> trait somehow, but I cannot figure out how in this case.
I have something very like the following:
main.rs
use structopt::StructOpt;
// ... other use cmds ...
#[derive(Debug, StructOpt)]
#[structopt(
name = "mycmd",
about = "A utility to do stuff."
)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
#[structopt(setting = structopt::clap::AppSettings::SubcommandRequired)]
struct Opts {
#[structopt(short = "v", parse(from_occurrences))]
/// Increase message verbosity
verbosity: usize,
#[structopt(subcommand)]
cmd: Tool,
}
#[derive(Debug, StructOpt)]
enum Tool {
#[structopt(name = "dofoo")]
DoFoo(dofoo::Command),
#[structopt(name = "status")]
Status(status::Command),
}
status.rs
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(name = "status", about = "Get the status of stuff.")]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
#[structopt(max_term_width = 80)]
pub enum Command {
#[structopt(name = "full")]
/// Print full (i.e. verbose) status
Full {},
#[structopt(name = "dump")]
/// Creates a zipped dump of the full system status to a file
Dump {
#[structopt(short = "o", long = "out", value_name = "FILE", parse(from_os_str))]
/// Filename of the output file.
out_fname: PathBuf,
},
}
impl Command {
pub fn execute(self) -> Result<()> {
match self {
Command::Full {} => cmd_do_verbose_print(),
Command::Dump { out_fname } => cmd_do_file_dump(out_fname),
// TODO: Bad. This is dead code.
_ => cmd_do_print(),
}
}
}

The docs indicate that I need to use the Option<> trait somehow, but I cannot figure out how in this case.
Option is not a trait, and there's an example right there in the documentation's index at "optional subcommand".
It's just doing the same thing as your Opts but making the [structopt(subcommand)] member an Option<T> instead of a T:
#[derive(Debug, StructOpt)]
pub struct Command {
#[structopt(subcommand)]
cmd: Option<Cmd>,
}
#[derive(Debug, StructOpt)]
pub enum Cmd {
/// Print full (i.e. verbose) status
Full {},
...
I have something very like the following:
An actual runnable reproduction case is useful when you have an issue...

Related

Why am I getting a "missing field" error using config-rs?

I'm confused why I am getting an Err(missing field "web3_node_provider") error when I cargo run using config-rs. Appears to fail at s.try_deserialize():
use config::{Config, ConfigError, Environment, File};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[allow(unused)]
struct Web3NodeProvider {
ethereum_mainnet_node_url_http: String,
alchemy_api_key: String,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Settings {
web3_node_provider: Web3NodeProvider,
}
impl Settings {
pub fn new() -> Result<Self, ConfigError> {
let s = Config::builder()
.add_source(File::with_name("config/default"))
.add_source(File::with_name("config/local").required(false))
.add_source(Environment::with_prefix("app"))
.build()?;
s.try_deserialize()
}
}
fn main() {
let settings = Settings::new();
println!("{:?}", settings);
}
I've pretty much followed the hierarchy example in config-rs, so I'm sure I'm just misunderstanding something basic or missing something. I am able to use "Web3NodeProvider.url" but not "web3_node_provider.ethereum_mainnet_node_url_http".
default.toml
[Web3NodeProvider]
ethereum_mainnet_node_url_http = "https://eth-mainnet.g.alchemy.com/v2/"
alchemy_api_key = "alchemy-api-key"
local.toml
[Web3NodeProvider]
alchemy_api_key = "randomapikey"
As per example, you have to name your field in config as name of attribute not name of structure (type). Like so:
[web3_node_provider]
ethereum_mainnet_node_url_http = "https://eth-mainnet.g.alchemy.com/v2/"
alchemy_api_key = "alchemy-api-key"

How to deserialize a nested big array

I have a type Foo which contains a big array. With the help of serde_big_array, serde_derive, and serde_with, I can derive serialization and deserializalization.
use serde_big_array;
use serde_big_array::BigArray;
use serde_derive::{Deserialize, Serialize};
pub const SIZE: usize = 30000;
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Foo {
#[serde(with = "BigArray")]
pub vals: [bool; SIZE],
}
This works fine.
However, when use this type in another structure I run into trouble.
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Bar {
field0: Foo,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialization_attempt_test() {
let foo = Foo {
vals: [false; SIZE],
};
let instance: Bar = Bar { field0: foo };
let json = serde_json::to_string(&instance).unwrap();
println!("PMD0");
let s_back = serde_json::from_str::<Bar>(&json).unwrap();
println!("PMD1");
}
}
Running this tests with cargo t deserialization_attempt_test -- --nocapture prints PMD0 but not PMD1. The test fails with the message
thread 'tests::deserialization_attempt' has overflowed its stack
fatal runtime error: stack overflow
How do I implement Serialize and Deserialize for Bar?
The problem seems to be that the stack (when running through cargo test) is too small to handle your test. Note that if you move deserialization_attempt_test into a main function and run it with cargo run it will work. Similarly if you reduce the array size to e.g. 2000 it will also work.
A workaround, although not that satsifying, is to manually set RUST_MIN_STACK when running cargo test. For instance, this will work:
RUST_MIN_STACK=8388608 cargo test
More information about the error can be found here and here. Here is the documentation on RUST_MIN_STACK.
If you wrap your JSON deserializer in a serde_stacker instance, then it works without setting the environment variable that #munksgaard suggests.
#[test]
fn serialization_bug_fix_attempt_test() {
let foo = Foo {
vals: [false; SIZE],
};
let instance: Bar = Bar { field0: foo };
let json = serde_json::to_string(&instance).unwrap();
println!("PMD0");
let mut deserializer: serde_json::Deserializer<serde_json::de::StrRead> =
serde_json::Deserializer::from_str(&json);
let deserializer = serde_stacker::Deserializer::new(&mut deserializer);
let value: Value = Value::deserialize(deserializer).unwrap();
let target: Bar = from_value(value).unwrap();
println!("PMD1");
}

NetworkBehaviour unsatisfied trait bounds

I'm a beginner at Rust and I've been following this tutorial on creating a simple blockchain using Rust.
chain.rs
use byteorder::{BigEndian, ReadBytesExt};
use chrono::offset::Utc;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::io::Cursor;
// Represents the entire chain in the network
pub struct Chain {
// Actual chain
pub blocks: Vec<Block>,
}
// Represents a single block in the blockchain
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Block {
pub id: u64,
// Hash representing block
pub hash: String,
// Hash of the previous block
pub previous_hash: String,
// Time of creation in UTC
pub timestamp: i64,
// Data contained in the block
pub data: String,
// Value for hashing the block(PoW)
pub nonce: u64,
}
p2p.rs
use std::collections::HashSet;
use super::chain::{Block, Chain};
use libp2p::{
floodsub::{Floodsub, FloodsubEvent, Topic},
identity::Keypair,
mdns::{Mdns, MdnsEvent},
swarm::{NetworkBehaviourEventProcess, Swarm},
NetworkBehaviour, PeerId,
};
use log::{error, info};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde_json;
use tokio::sync::mpsc;
// Topics for pub/sub protocol
// Impairements: Broadcasts on each request thus extremely inefficient
pub static BLOCK_TOPIC: Lazy<Topic> = Lazy::new(|| Topic::new("blocks"));
pub static CHAIN_TOPIC: Lazy<Topic> = Lazy::new(|| Topic::new("chains"));
// Key Pair for peer identification on network
pub static KEYS: Lazy<Keypair> = Lazy::new(Keypair::generate_ed25519);
// Peer id for peer identification on network
pub static PEER_ID: Lazy<PeerId> = Lazy::new(|| PeerId::from(KEYS.public()));
...
// Defines overall NetworkBehaviour for the chain
#[derive(NetworkBehaviour)]
pub struct ChainBehaviour {
// Chain
#[behaviour(ignore)]
pub chain: Chain,
// Handles FloodSub protocol
pub floodsub: Floodsub,
// Sends response to the UnboundedReceiver
#[behaviour(ignore)]
pub init_sender: mpsc::UnboundedSender<bool>,
// Handles automatic discovery of peers on the local network
// and adds them to the topology
pub mdns: Mdns,
// Sends response to the UnboundedReceiver
#[behaviour(ignore)]
pub response_sender: mpsc::UnboundedSender<ChainResponse>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ChainResponse {
pub blocks: Vec<Block>,
pub receiver: String,
}
// Triggers chain communication for requested ID
#[derive(Debug, Deserialize, Serialize)]
pub struct LocalChainRequest {
pub from_peer_id: String,
}
// Keep states for handling incoming messages, lazy init
// and keyboard input by the client's user
pub enum EventType {
LocalChainRequest(ChainResponse),
Input(String),
Init,
}
// Implemnt FloodsubEvent for ChainBehaviour
impl NetworkBehaviourEventProcess<FloodsubEvent> for ChainBehaviour {
fn inject_event(&mut self, event: FloodsubEvent) {
if let FloodsubEvent::Message(msg) = event {
// If message is of type ChainResponse and that the message is ours,
// we execute our consensus.
if let Ok(response) = serde_json::from_slice::<ChainResponse>(&msg.data) {
if response.receiver == PEER_ID.to_string() {
info!("Response from {}:", msg.source);
response.blocks.iter().for_each(|r| info!("{:?}", r));
self.chain.blocks = self
.chain
.choose(self.chain.blocks.clone(), response.blocks);
}
} else if let Ok(response) = serde_json::from_slice::<LocalChainRequest>(&msg.data) {
// If of type LocalChainRequest, we send ChainResponse to
// initiator
info!("sending local chain to {}", msg.source.to_string());
let peer_id = response.from_peer_id;
if PEER_ID.to_string() == peer_id {
if let Err(e) = self.response_sender.send(ChainResponse {
blocks: self.chain.blocks.clone(),
receiver: msg.source.to_string(),
}) {
error!("error sending response via channel, {}", e);
};
}
} else if let Ok(block) = serde_json::from_slice::<Block>(&msg.data) {
// If of type Block, we try adding the block if valid
info!("received new block from {}", msg.source.to_string());
self.chain.try_add_block(block);
}
}
}
}
// Implement MdnsEvents for ChainBehaviour
impl NetworkBehaviourEventProcess<MdnsEvent> for ChainBehaviour {
fn inject_event(&mut self, event: MdnsEvent) {
match event {
// Add node to list of nodes when discovered
MdnsEvent::Discovered(nodes) => {
for (peer, _) in nodes {
self.floodsub.add_node_to_partial_view(peer)
}
}
// Remove node from list of nodes when TTL expires and
// address hasn't been refreshed
MdnsEvent::Expired(nodes) => {
for (peer, _) in nodes {
if !self.mdns.has_node(&peer) {
self.floodsub.remove_node_from_partial_view(&peer);
}
}
}
}
}
}
Here's my cargo.toml:
byteorder = "1"
chrono = "0.4.19"
getrandom = "0.2.3"
hex = "0.4.3"
libp2p = {version = "0.41.0", features = ['tcp-tokio', "mdns"]}
log = "0.4.14"
once_cell = "1.9.0"
oorandom = "11.1.3"
pretty_env_logger = "0.4.0"
serde = {version = "1.0.133", features = ["derive"]}
serde_json = "1.0.74"
sha2 = "0.10.0"
tokio = { version = "1.15.0", features = ["io-util", "io-std", "macros", "rt", "rt-multi-thread", "sync", "time"] }
I keep getting the following error on this struct:
error[E0277]: the trait bound `(): From<MdnsEvent>` is not satisfied
--> src/p2p.rs:30:10
|
30 | #[derive(NetworkBehaviour)]
| ^^^^^^^^^^^^^^^^ the trait `From<MdnsEvent>` is not implemented for `()`
|
= help: see issue #48214
= note: this error originates in the derive macro `NetworkBehaviour` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `(): From<FloodsubEvent>` is not satisfied
--> src/p2p.rs:30:10
|
30 | #[derive(NetworkBehaviour)]
| ^^^^^^^^^^^^^^^^ the trait `From<FloodsubEvent>` is not implemented for `()`
|
= help: see issue #48214
= note: this error originates in the derive macro `NetworkBehaviour` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0277`.
I tried following the suggestions from this forum and got the following error:
error[E0599]: no method named `into_inner` found for struct `OneShotHandler` in the current scope
--> src/p2p.rs:30:10
|
30 | #[derive(NetworkBehaviour)]
| ^^^^^^^^^^^^^^^^ method not found in `OneShotHandler<FloodsubProtocol, FloodsubRpc, floodsub::layer::InnerMessage>`
|
= note: this error originates in the derive macro `NetworkBehaviour` (in Nightly builds, run with -Z macro-backtrace for more info)
I also keep getting an add reference here error on #[derive(NetworkBehaviour)]. What could be causing the error and how can I fix it? I'm using rust-analyzer.
Link to the tutorial's github repo.
Link to my github repo. The entire code is rather large but should only the afforementioned errors.
This question is nearly a year old, but maybe will run into this again in the future.
I stumbled over the same issue, funnily enough while working through the same tutorial. It seems that newer versions of libp2p require you to do two things that the version used for the tutorial did not seem to require:
Specify #[behaviour(out_event="Event")] For the AppBehaviour struct
This is mentioned to be optional in the docs, but if you don't specify this then the macro will use StructName<Event>.
Implement the trait From<> for the enum Event for all events emitted by struct members of AppBehaviour, as the events emitted by the struct members are wrapped in the event enum.
I've added two new enum values for this as shown in the libp2p docs:
use crate::net::{ChainResponse};
use libp2p::{floodsub::FloodsubEvent, mdns::MdnsEvent};
pub enum Event {
ChainResponse(ChainResponse),
Floodsub(FloodsubEvent),
Mdns(MdnsEvent),
Input(String),
Init,
}
impl From<FloodsubEvent> for Event {
fn from(event: FloodsubEvent) -> Self {
Self::Floodsub(event)
}
}
impl From<MdnsEvent> for Event {
fn from(event: MdnsEvent) -> Self {
Self::Mdns(event)
}
}

Enum to &str / &str to Enum

Wondering if there's a "proper" way of converting an Enum to a &str and back.
The problem I'm trying to solve:
In the clap crate, args/subcommands are defined and identified by &strs. (Which I'm assuming don't fully take advantage of the type checker.) I'd like to pass a Command Enum to my application instead of a &str which would be verified by the type-checker and also save me from typing (typo-ing?) strings all over the place.
This is what I came up with from searching StackOverflow and std:
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Command {
EatCake,
MakeCake,
}
impl FromStr for Command {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"eat-cake" => Ok(Self::EatCake),
"make-cake" => Ok(Self::MakeCake),
_ => Err(()),
}
}
}
impl<'a> From<Command> for &'a str {
fn from(c: Command) -> Self {
match c {
Command::EatCake => "eat-cake",
Command::MakeCake => "make-cake",
}
}
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}
And here's a working playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b5e9ac450fd6a79b855306e96d4707fa
Here's an abridged version of what I'm running in clap.
let matches = App::new("cake")
.setting(AppSettings::SubcommandRequiredElseHelp)
// ...
.subcommand(
SubCommand::with_name(Command::MakeCake.into())
// ...
)
.subcommand(
SubCommand::with_name(Command::EatCake.into())
// ...
)
.get_matches();
It seems to work, but I'm not sure if I'm missing something / a bigger picture.
Related:
How to use an internal library Enum for Clap Args
How do I return an error within match statement while implementing from_str in rust?
Thanks!
The strum crate may save you some work. Using strum I was able to get the simple main() you have to work without any additional From implementations.
use strum_macros::{Display, EnumString, IntoStaticStr};
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Display, EnumString, IntoStaticStr)] // strum macros.
pub enum Command {
#[strum(serialize = "eat-cake")]
EatCake,
#[strum(serialize = "make-cake")]
MakeCake,
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}

StructOpt - how to provide a default value for a Vec?

I'm looking for a way to initialize a structopt Vec field with multiple items by default. I can do it for a single item with:
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
pub struct Cli {
#[structopt(default_value = "foo")]
foo: Vec<String>,
}
fn main() {
let cli = Cli::from_iter(Vec::<String>::new());
assert_eq!(cli.foo, vec!["foo"]);
}
But how to make cli.foo to be equal let's say vec!["foo", "bar"] by default?
I've followed the L. Riemer advice, and it seems it's enough to implement FromStr only:
use structopt::StructOpt;
#[derive(Debug, PartialEq)]
struct Foo(Vec<String>);
impl std::str::FromStr for Foo {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Foo(s.split(",").map(|x| x.trim().to_owned()).collect()))
}
}
#[derive(StructOpt)]
pub struct Cli {
#[structopt(long, default_value = "foo, bar")]
foo: Foo,
}
fn main() {
let cli = Cli::from_iter(Vec::<String>::new());
assert_eq!(cli.foo, Foo(vec!["foo".into(), "bar".into()]));
let cli = Cli::from_iter(vec!["", "--foo", "foo"]);
assert_eq!(cli.foo, Foo(vec!["foo".into()]));
let cli = Cli::from_iter(vec!["", "--foo", "foo,bar,baz"]);
assert_eq!(cli.foo, Foo(vec!["foo".into(), "bar".into(), "baz".into()]));
}
I don't think you can do that: while StructOpt has some tricks around default values, I expect this still ends with the default value being injected in the CLI parsing as if it had been provided explicitly, which means there is likely no way to provide multiple default values (though I could certainly be wrong).
You probably want to handle this at the application-level e.g. right after having parsed the CLI, check for the arity of foo and update it if it's empty.

Resources