Requirements Extraction
A common shape: your global config has many optional fields, but a particular
subcommand requires a specific subset. "migrate needs a database URL even
though most commands don't." Requirements extraction lets you declare that
subset as its own struct and get a precise, hint-rich error if anything is
missing — after a successful parse, scoped to the operation.
The pattern
- Parse normally with
.into_result()so you keep theDriverOutput. - Define a small struct whose fields carry
#[facet(args::origin = "path")]pointing at locations in the merged config. - Call
output.extract::<Requirements>().
use facet:: Facet ;
use figue::{ self as args, builder, Driver };
# [ derive ( Facet , Debug )]
struct Args {
# [ facet ( args :: config , args :: env_prefix = "MYAPP" )]
config : AppConfig ,
# [ facet ( args :: subcommand )]
command : Command ,
}
# [ derive ( Facet , Debug )]
struct AppConfig {
/// Optional in general — not every command touches the DB
# [ facet ( default )]
database_url : Option < String >,
# [ facet ( default = 8080 )]
port : u16 ,
}
# [ derive ( Facet , Debug )]
# [ repr ( u8 )]
enum Command {
Serve ,
Migrate ,
}
/// What `migrate` needs, regardless of how it was supplied.
# [ derive ( Facet , Debug )]
struct MigrateRequirements {
# [ facet ( args :: origin = "config.database_url" )]
database_url : String , // required for THIS operation
# [ facet ( args :: origin = "config.port" )]
port : u16 ,
}
fn main () {
let config = builder ::< Args >()
. unwrap ()
. cli ( |cli| cli. args ( std:: env:: args (). skip ( 1 )))
. env ( |env| env)
. build ();
let output = Driver :: new ( config). run (). into_result (). unwrap_or_else ( |e| {
eprint! ( "{e}" );
std:: process:: exit ( e. exit_code ());
});
match output. value . command {
Command :: Serve => { /* … */ }
Command :: Migrate => {
match output. extract ::< MigrateRequirements >() {
Ok ( req) => run_migrations ( & req. database_url , req. port ),
Err ( e) => {
eprint! ( "{e}" );
std:: process:: exit ( 1 );
}
}
}
}
}
# fn run_migrations ( _: & str , _: u16 ) {} Origin paths
args::origin = "config.database_url" is an absolute, dot-separated path into
the merged configuration (the same tree the provenance dump prints). It can
descend as deep as you like: "config.server.tls.cert_path".
- Field present and non-null → used.
- Field missing/null and the requirements field is
Option<T>→None, no error. - Field missing/null and the requirements field is required → collected into the error.
Every field of the requirements struct must have an args::origin.
The error
When required origins are missing, ExtractError's Display produces a message
that names each field, its type, where it should have come from, and both
concrete ways to provide it:
Missing required fields for this operation:
database_url <String> at config.database_url
Set via: --config.database-url or $MYAPP__CONFIG__DATABASE_URLThe CLI hint is the kebab-cased override flag; the env hint uses the config
root's env_prefix (or no prefix if there isn't a single one). It is the same
"tell the user exactly how to fix it" philosophy as the rest of figue, but
scoped to this subcommand instead of the whole program.
Why not just make the field required?
Because it isn't required in general. serve shouldn't fail because there's
no database URL. Requirements extraction keeps the global config permissive
while still giving each operation a hard, well-explained contract — without
duplicating the config struct per subcommand.
That's the end of the guide. The Reference has the exact rules; the Recipes have complete programs.