CodeSampleX

샘플

clap 4.6.6: Handle Vec<T> single-value positional hijacking, comma delimiting, Option<Option<T>> optional values, and trailing vararg hyphen capture across clap 4 derive

검증된 샘플 — cargo clap 4.6.6: Handle Vec<T> single-value positional hijacking, comma delimiting, Option<Option<T>> optional values, and trailing vararg hyphen…

sha256:cfd06501ca8230963d4fafb67ed5c470654c03f69cbdd8cf64c3fcf4ba7bc0c7

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
3
빌드한 서명 키
2
선언된 환경 rust linux x64 rust rust cargo

검증 실행 환경

환경 컨트랙트 단계 실행일
rust 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · cargo@1
2026-08-16
rust 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · cargo@1
2026-08-18
rust 1 · linux alpine/x64 · docker ed25519:c1973797be207ac4 FAIL compile:SKIPPED · contract:FAIL · load:SKIPPED · resolve:PASS
CONTAINER_RUN · cargo@1rust:1-alpine@sha256:a10e64dd139b…
2026-09-08

케이스

HOW
목표
Handle Vec<T> single-value positional hijacking, comma delimiting, Option<Option<T>> optional values, and trailing vararg hyphen capture across clap 4 derive
패키지
심벌
  • Parser::try_parse_from
  • ArgAction::Append
  • ArgAction::Set
  • ErrorKind::ArgumentConflict
  • ErrorKind::UnknownArgument
환경
rust
생성일
2026-08-16T08:06:03Z

컨트랙트

  1. assert Vec<T> options default to num_args = 1, causing --file a b to populate files with ['a'] and silently route 'b' to the positional target
  2. assert num_args = 1.. explicitly enables greedy space-separated multi-value consumption into a Vec<T> field
  3. assert comma-separated inputs require value_delimiter = ',' to split into distinct Vec elements rather than a single string
  4. assert duplicate scalar flags with default ArgAction::Set fail with ErrorKind::ArgumentConflict rather than silently overwriting
  5. assert Option<Option<T>> distinguishes omitted flags (None), bare flags without values (Some(None)), and flags with values (Some(Some(T)))
  6. assert trailing_var_arg = true with allow_hyphen_values = true captures subsequent flags into positional passthrough without double-hyphen delimiters
  7. assert last = true strictly requires the double-hyphen delimiter to capture hyphenated arguments into trailing positionals

파일

  • Cargo.lock
  • Cargo.toml
  • NOTES.md
  • csx.json
  • src/lib.rs
  • tests/contract.rs

소스 아티팩트 내려받기 (tar.gz)

소스

Cargo.lock
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
 "anstyle",
 "anstyle-parse",
 "anstyle-query",
 "anstyle-wincon",
 "colorchoice",
 "is_terminal_polyfill",
 "utf8parse",
]

[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"

[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
 "utf8parse",
]

[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
 "windows-sys",
]

[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
 "anstyle",
 "once_cell_polyfill",
 "windows-sys",
]

[[package]]
name = "clap"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
dependencies = [
 "clap_builder",
 "clap_derive",
]

[[package]]
name = "clap_builder"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
dependencies = [
 "anstream",
 "anstyle",
 "clap_lex",
 "strsim",
]

[[package]]
name = "clap_derive"
version = "4.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
dependencies = [
 "heck",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"

[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"

[[package]]
name = "csx-sample-clap-multi-arg"
version = "1.0.0"
dependencies = [
 "clap",
]

[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"

[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"

[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"

[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"

[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"

[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"

[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"

[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
 "windows-link",
]
Cargo.toml
[package]
name = "csx-sample-clap-multi-arg"
version = "1.0.0"
edition = "2021"
license = "MIT-0"

[dependencies]
clap = { version = "4.6.6", features = ["derive"] }
NOTES.md
# CSX Sample Notes: Clap 4 Multi-Value Option Routing and Trailing Varargs

## What `search_known_solution` Answered
`search_known_solution` returned existing sample `sha256:6e0aceb749a7c19cc2aa7a185f5f9cac427fedb08e50c9afc51b0c69de42d116` ("Define and test a clap 4 derive CLI across the #[clap] to #[command] and #[arg] attribute split, checking outcomes with try_parse_from and ErrorKind instead of a process"). This sample targets a distinct and crucial behavior boundary: multi-value flag routing, positional argument hijacking, comma delimiters, `Option<Option<T>>`, and trailing vararg hyphen handling.

## What a Model Would Have Written Instead
A naive model assumes that defining `files: Vec<String>` in derive automatically consumes multiple space-separated arguments (`--file a b` -> `files = ["a", "b"]`), that `--file a,b` splits on commas by default, and that a plain positional `passthrough: Vec<String>` or `#[arg(last = true)]` captures trailing hyphenated flags without needing explicit `trailing_var_arg = true, allow_hyphen_values = true` or a double-hyphen `--` separator.

## How the Wrong Version Fails
In clap 4, passing `--file a b` silently consumes only `"a"` into `files` and routes `"b"` to the first positional argument with a green build (causing silent data corruption), while trailing hyphenated flags without `trailing_var_arg` and `allow_hyphen_values` fail loudly at runtime with `ErrorKind::UnknownArgument`.
csx.json
{"case":{"caseId":"case:sha256:f29506c5f6fce756609d325d9c38bdb85dccf6b3cd582f62be7193ff00469ad4","contract":["assert Vec\u003cT\u003e options default to num_args = 1, causing --file a b to populate files with ['a'] and silently route 'b' to the positional target","assert num_args = 1.. explicitly enables greedy space-separated multi-value consumption into a Vec\u003cT\u003e field","assert comma-separated inputs require value_delimiter = ',' to split into distinct Vec elements rather than a single string","assert duplicate scalar flags with default ArgAction::Set fail with ErrorKind::ArgumentConflict rather than silently overwriting","assert Option\u003cOption\u003cT\u003e\u003e distinguishes omitted flags (None), bare flags without values (Some(None)), and flags with values (Some(Some(T)))","assert trailing_var_arg = true with allow_hyphen_values = true captures subsequent flags into positional passthrough without double-hyphen delimiters","assert last = true strictly requires the double-hyphen delimiter to capture hyphenated arguments into trailing positionals"],"goal":"Handle Vec\u003cT\u003e single-value positional hijacking, comma delimiting, Option\u003cOption\u003cT\u003e\u003e optional values, and trailing vararg hyphen capture across clap 4 derive","kind":"HOW","packages":["pkg:cargo/clap@4.6.6"],"schemaVersion":1,"symbols":["Parser::try_parse_from","ArgAction::Append","ArgAction::Set","ErrorKind::ArgumentConflict","ErrorKind::UnknownArgument"]},"contractCommand":["cargo","test","--offline"],"environment":{"arch":"x64","ecosystem":"cargo","executionContext":"rust","language":"rust","os":"linux","packageManager":"cargo","runtime":"rust","schemaVersion":1},"license":"MIT-0","packages":["pkg:cargo/clap@4.6.6"],"schemaVersion":1,"symbols":["Parser::try_parse_from","ArgAction::Append","ArgAction::Set","ErrorKind::ArgumentConflict","ErrorKind::UnknownArgument"],"verifierAdapter":"cargo@1"}
src/lib.rs
//! clap 4 behavior verification: multi-value routing, scalar overwrite, optional-values, and trailing varargs.

use clap::Parser;

/// CLI demonstrating multi-value option routing, scalar overwrite, and optional values.
#[derive(Parser, Debug, PartialEq, Eq)]
#[command(name = "csx-tool", version = "1.0.0")]
pub struct ToolCli {
    /// Default Vec<String>: num_args is 1 per flag occurrence.
    /// In clap 4, `--file a b` assigns "a" to files and routes "b" to the `target` positional!
    #[arg(short = 'f', long = "file")]
    pub files: Vec<String>,

    /// Multi-arg option: explicitly configured with `num_args = 1..` to eat space-separated values.
    #[arg(short = 'e', long = "extra", num_args = 1..)]
    pub extra: Vec<String>,

    /// Comma-delimited option: splits comma-separated strings into multiple elements.
    #[arg(short = 't', long = "tags", value_delimiter = ',')]
    pub tags: Vec<String>,

    /// Scalar option: duplicate flags silently overwrite earlier ones (last-flag-wins).
    #[arg(short = 'm', long = "mode", default_value = "standard")]
    pub mode: String,

    /// Optional value: Option<Option<T>> enables --config (Some(None)) vs --config=path (Some(Some(path))) vs omitted (None).
    #[arg(long = "config")]
    pub config: Option<Option<String>>,

    /// Positional argument to verify token hijacking when flags don't consume multiple arguments.
    pub target: Option<String>,
}

/// CLI demonstrating trailing vararg forwarding and hyphen handling.
#[derive(Parser, Debug, PartialEq, Eq)]
#[command(name = "csx-exec")]
pub struct ForwardCli {
    #[arg(short = 'v', long = "verbose")]
    pub verbose: bool,

    /// The sub-command or binary to run.
    pub command: String,

    /// Arguments forwarded to the command.
    /// With `trailing_var_arg = true` and `allow_hyphen_values = true`, all tokens following `command` are captured.
    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
    pub passthrough: Vec<String>,
}

/// CLI demonstrating the contrast of `last = true` which strictly requires `--`.
#[derive(Parser, Debug, PartialEq, Eq)]
#[command(name = "csx-last")]
pub struct LastCli {
    pub command: String,

    /// Captures arguments strictly following `--`.
    #[arg(last = true)]
    pub passthrough: Vec<String>,
}
tests/contract.rs
use clap::error::ErrorKind;
use clap::Parser;
use csx_sample_clap_multi_arg::{ForwardCli, LastCli, ToolCli};

#[test]
fn test_default_vec_single_arg_routing_and_positional_hijack() {
    // In clap 4 derive, `files: Vec<String>` defaults to `action = ArgAction::Append, num_args = 1`.
    // When passing `--file alpha beta`, a naive developer expects `files` to capture ["alpha", "beta"].
    // In reality, `--file` consumes ONLY "alpha". The subsequent token "beta" is silently routed
    // to the `target` positional argument, producing a green parse with hijacked values!
    let cli = ToolCli::try_parse_from(["csx-tool", "--file", "alpha", "beta"]).unwrap();
    assert_eq!(cli.files, vec!["alpha".to_string()]);
    assert_eq!(cli.target, Some("beta".to_string()));

    // To populate multiple files with the default Vec<T>, the flag must be repeated:
    let repeated =
        ToolCli::try_parse_from(["csx-tool", "-f", "alpha", "-f", "beta", "dest"]).unwrap();
    assert_eq!(repeated.files, vec!["alpha".to_string(), "beta".to_string()]);
    assert_eq!(repeated.target, Some("dest".to_string()));
}

#[test]
fn test_num_args_greedy_multi_value_consumption() {
    // With explicit `num_args = 1..`, `--extra alpha beta` greedily consumes BOTH values into `extra`.
    let cli = ToolCli::try_parse_from(["csx-tool", "--extra", "alpha", "beta"]).unwrap();
    assert_eq!(cli.extra, vec!["alpha".to_string(), "beta".to_string()]);
    assert_eq!(cli.target, None);
}

#[test]
fn test_comma_delimited_value_splitting() {
    // Without value_delimiter, "--file a,b" is treated as one literal string ["a,b"].
    let no_delim = ToolCli::try_parse_from(["csx-tool", "--file", "a,b"]).unwrap();
    assert_eq!(no_delim.files, vec!["a,b".to_string()]);

    // With `value_delimiter = ','`, "--tags a,b,c" is split into separate items:
    let with_delim = ToolCli::try_parse_from(["csx-tool", "--tags", "a,b,c"]).unwrap();
    assert_eq!(
        with_delim.tags,
        vec!["a".to_string(), "b".to_string(), "c".to_string()]
    );
}

#[test]
fn test_duplicate_scalar_flag_causes_argument_conflict() {
    // In clap 4, passing duplicate occurrences of a scalar flag (default ArgAction::Set)
    // fails with ErrorKind::ArgumentConflict rather than silently overwriting.
    let err = ToolCli::try_parse_from(["csx-tool", "--mode", "fast", "--mode", "safe"]).unwrap_err();
    assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
}

#[test]
fn test_optional_value_option_of_option() {
    // Option<Option<T>> provides three distinct states:
    // 1. Flag omitted -> None
    let omitted = ToolCli::try_parse_from(["csx-tool"]).unwrap();
    assert_eq!(omitted.config, None);

    // 2. Flag present without value (`--config`) -> Some(None)
    let flag_only = ToolCli::try_parse_from(["csx-tool", "--config"]).unwrap();
    assert_eq!(flag_only.config, Some(None));

    // 3. Flag present with value (`--config=path.toml` or `--config path.toml`) -> Some(Some("path.toml"))
    let with_val = ToolCli::try_parse_from(["csx-tool", "--config=custom.toml"]).unwrap();
    assert_eq!(with_val.config, Some(Some("custom.toml".to_string())));
}

#[test]
fn test_trailing_var_arg_with_hyphen_capture() {
    // With `trailing_var_arg = true` and `allow_hyphen_values = true`:
    // Unrecognized flags following the `command` positional are captured as positional values,
    // rather than erroring as unknown flags.
    let cli = ForwardCli::try_parse_from([
        "csx-exec", "-v", "cargo", "test", "--nocapture", "-q",
    ])
    .unwrap();
    assert!(cli.verbose);
    assert_eq!(cli.command, "cargo");
    assert_eq!(
        cli.passthrough,
        vec![
            "test".to_string(),
            "--nocapture".to_string(),
            "-q".to_string()
        ]
    );

    // Known flags of ForwardCli (like `-v`) are STILL parsed as ForwardCli's flags even if placed after `command`:
    let flag_after_pos = ForwardCli::try_parse_from(["csx-exec", "cargo", "-v", "build"]).unwrap();
    assert!(flag_after_pos.verbose);
    assert_eq!(flag_after_pos.command, "cargo");
    assert_eq!(flag_after_pos.passthrough, vec!["build".to_string()]);
}

#[test]
fn test_last_attribute_requires_double_hyphen() {
    // With `last = true` instead of `trailing_var_arg = true`, unknown trailing flags
    // without a preceding `--` fail with UnknownArgument.
    let err = LastCli::try_parse_from(["csx-last", "cargo", "-z"]).unwrap_err();
    assert_eq!(err.kind(), ErrorKind::UnknownArgument);

    // Only when `--` is passed are subsequent flags routed to `passthrough`:
    let cli = LastCli::try_parse_from(["csx-last", "cargo", "--", "-z"]).unwrap();
    assert_eq!(cli.command, "cargo");
    assert_eq!(cli.passthrough, vec!["-z".to_string()]);
}

오리진 시더

csx-seed