-
Notifications
You must be signed in to change notification settings - Fork 24
Description
There was a straightforward PR thrown up for discussion over at #65, effectively looking for a Serialize
and Deserialize
implementation for this library. The requests from @epage:
I think more of a case is needed for why a CLI flag needs serialization, particularly the fact that this was done in a raw way (separate
verbose
,quiet
asu8
s) - #65 (comment)
More design discussion is needed on this because exposing separate
verbose
andquiet
fields that areu8
does not make sense. - #65 (comment)
Use Cases
Instrumentation
If an entire args
object is being passed into tracing
it is quite nice to have a Serialize
so that derive
works. I don't want to have to impl Serialize for Args
for this reason. If I don't do some serialization then I end up with Debug
and that is super-verbose.
use clap::Parser;
use serde::Serialize;
use svix_ksuid::{Ksuid, KsuidLike};
use tracing::instrument;
mod telemetry;
#[derive(Serialize, Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
// #[command(flatten)]
// verbose: clap_verbosity_flag::Verbosity,
#[arg(short, long, default_value_t = 1)]
count: u8,
}
impl Args {
fn to_json(&self) -> String {
return serde_json::to_string(&self).expect("");
}
}
// Main is a very small setup shim, and then gets out of the way.
fn main() -> proc_exit::ExitResult {
let id = Ksuid::new(None, None);
telemetry::init(id);
let args = Args::parse();
let result = run(id, args);
proc_exit::exit(result);
}
#[instrument(name = "run()", fields(args = args.to_json(), id = id.to_string()) ret, err)]
fn run(id: Ksuid, args: Args) -> proc_exit::ExitResult {
proc_exit::Code::SUCCESS.ok()
}
Args From <Elsewhere>
This some-cli gui
command spins up a server and opens a browser to an HTML page. Eventually that HTML page makes a network request back to the server, which contains a serialized form of the arguments that would have been passed in. That's going to be JSON (cause it's coming from the web), deserializing into Args
(from the above example).
Starts the server:
$ some-cli gui
(Complicated configuration happens in a browser.)
Data from a "configuration finished!" network request passed back in to the CLI entry point:
let args: Args = serde_json::from_str(args)?;
let result = run(id, args);
Design Space
There are basically only two reasonable options here to represent configuration since it is a count
.
As Separate u8
s
exposing separate
verbose
andquiet
fields that areu8
does not make sense
Given that we have -q
and -v[vvv]
exposed as separate args this separation doesn't seem particularly egregious. Further, since the struct can be constructed with clap_verbosity_flag::Verbosity::new(2, 4)
, any serialization that doesn't give you both of those values is lossy.
Being able to represent the error conditions (error: the argument '--quiet...' cannot be used with '--verbose...'
) that can be triggered by command line usage is reasonable—even though typically it would never get through ::parse()
.
As A Single i16
Since we have the conflicts_with
lockout from the primary clap
use case, we could choose to serialize them into a single value, in spite of it being lossy. Take a page from the log
mapping, and do quiet
as -(count)
.
An i16
gives us way more bits than we need, but there isn't an i9
.
...
-4 => -qqqq
-3 => -qqq
-2 => -qq
-1 => -q
0 => None
1 => -v
2 => -vv
3 => -vvv
4 => -vvvv
...
This approach can't support { quiet: 4, verbosity: 4 }
like a manually constructed struct can.
I'm in favor of the separate u8
s for representation fidelity reasons and nominate reopening and accepting #65.