#cli #argument #config #parser


Merge results from clap and serde into struct with derive

2 unstable releases

0.2.0 Oct 14, 2022
0.1.0 Oct 13, 2022

#188 in Configuration

Download history 229/week @ 2023-02-11 260/week @ 2023-02-18 312/week @ 2023-02-25 490/week @ 2023-03-04 397/week @ 2023-03-11 238/week @ 2023-03-18 638/week @ 2023-03-25 380/week @ 2023-04-01 404/week @ 2023-04-08 506/week @ 2023-04-15 517/week @ 2023-04-22 359/week @ 2023-04-29 327/week @ 2023-05-06 179/week @ 2023-05-13 123/week @ 2023-05-20 90/week @ 2023-05-27

729 downloads per month
Used in invoice2storage



Clap and Serde derive

Crates.io Crates.io Docs.rs License GitLab pipeline

With the ClapSerde procedural macro both clap and serde can be derived from a struct.
Then the struct can be parsed from clap and serde sources as in a layered config: the last source has the precedence.


In the snippet the precedence is:

  1. Command line from clap;
  2. Config file from serde;
  3. Default values.


In this example we define a struct which derives both clap and serde. The struct has various parameter type and also various attributes on its fields.

Finally we parse the structure from a YAML file with serde and then from command line with clap. The arguments from clap will override those from serde; the default value will be used if no source contained the field.

use clap_serde_derive::{
    clap::{self, ArgAction},

#[derive(ClapSerde, Serialize)]
#[clap(author, version, about)]
pub struct Args {
    /// Input files
    pub input: Vec<std::path::PathBuf>,

    /// String argument
    #[clap(short, long)]
    name: String,

    /// Skip serde deserialize
    #[clap(long = "num")]
    pub clap_num: u32,

    /// Skip clap
    #[serde(rename = "number")]
    pub serde_num: u32,

    /// Recursive fields
    pub suboptions: SubConfig,

#[derive(ClapSerde, Serialize)]
pub struct SubConfig {
    #[clap(long = "no-flag", action = ArgAction::SetFalse)]
    pub flag: bool,

let args = Args::from(serde_yaml::from_str::<<Args as ClapSerde>::Opt>("number: 12").unwrap())
    serde_yaml::to_string(&Args {
        serde_num: 12,
        clap_num: 13,

Config path from command line

You can easily take the config file path from command line in this way.

use std::{fs::File, io::BufReader};

use clap_serde_derive::{
    clap::{self, Parser},

#[clap(author, version, about)]
struct Args {
    /// Input files
    input: Vec<std::path::PathBuf>,

    /// Config file
    #[clap(short, long = "config", default_value = "config.yml")]
    config_path: std::path::PathBuf,

    /// Rest of arguments
    pub config: <Config as ClapSerde>::Opt,

struct Config {
    /// String argument
    #[clap(short, long)]
    name: String,

// Parse whole args with clap
let mut args = Args::parse();

// Get config file
let config = if let Ok(f) = File::open(&args.config_path) {
    // Parse config with serde
    match serde_yaml::from_reader::<_, <Config as ClapSerde>::Opt>(BufReader::new(f)) {
        // merge config already parsed from clap
        Ok(config) => Config::from(config).merge(&mut args.config),
        Err(err) => panic!("Error in configuration file:\n{}", err),
} else {
    // If there is not config file return only config parsed from clap
    Config::from(&mut args.config)


~46K SLoC