| Crates.io | derive-wizard |
| lib.rs | derive-wizard |
| version | 0.5.1-use-the-elicitor-crate-instead |
| created_at | 2025-12-27 11:05:11.862357+00 |
| updated_at | 2026-01-17 11:51:40.772386+00 |
| description | Derives interactive wizard-like user input for Rust types. Backend-agnostic (supports at least requestty and egui). |
| homepage | |
| repository | https://github.com/barafael/derive-wizard |
| max_upload_size | |
| id | 2007046 |
| size | 476,564 |
DEPRECATED: This crate has been renamed to
elicitor. Please useelicitorfor all new projects. This crate will no longer receive updates. Elicitor is much better.
A Rust procedural macro that automatically generates interactive CLI wizards from struct definitions using requestty.
use derive_wizard::Wizard;
#[derive(Debug, Wizard)]
struct ShowCase {
// String types - defaults to 'input'
#[prompt("Enter your name:")]
name: String,
// Override with password question type
#[prompt("Enter your password:")]
#[mask]
password: String,
// Long text with multiline editor
#[prompt("Enter a bio:")]
#[multiline]
bio: String,
// Bool type - defaults to 'confirm'
#[prompt("Do you agree to the terms?")]
agree: bool,
// Integer types - defaults to 'int'
#[prompt("Enter your age (i32):")]
age: i32,
// Float types - defaults to 'float'
#[prompt("Enter your height in meters (f64):")]
height: f64,
#[prompt("Enter a decimal number (f32):")]
decimal: f32,
#[prompt("Enter your gender")]
gender: Gender,
}
#[derive(Debug, Wizard)]
enum Gender {
Male,
Female,
Other(
#[prompt("Please specify:")]
String
),
}
#[mask]For password inputs, use the convenient #[mask] attribute to hide user input:
use derive_wizard::Wizard;
#[derive(Debug, Wizard)]
struct LoginForm {
#[prompt("Enter your username:")]
username: String,
#[prompt("Enter your password:")]
#[mask]
password: String, // Input will be hidden
}
#[multiline]For longer text input, use the #[multiline] attribute to open the user's preferred text editor:
use derive_wizard::Wizard;
#[derive(Debug, Wizard)]
struct Article {
#[prompt("Enter the title:")]
title: String,
#[prompt("Write the article content:")]
#[multiline]
content: String, // Opens text editor (vim, nano, etc.)
}
#[prompt("message")] - Required. The message to display to the user#[mask] - Optional. For String fields: enables password input (hidden text)#[multiline] - Optional. For String fields: opens text editor for longer input#[validate("function_name")] - Optional. Validates input with a custom functionNote: #[mask] and #[multiline] are mutually exclusive and cannot be used on the same field.
The builder API provides a fluent interface for configuring and executing wizards:
use derive_wizard::Wizard;
#[derive(Debug, Clone, Wizard)]
struct Config {
#[prompt("Enter the server address:")]
server: String,
#[prompt("Enter the port:")]
port: u16,
#[prompt("Enable SSL?")]
use_ssl: bool,
}
// Simple usage with default backend (requestty)
let config = Config::wizard_builder().build().unwrap();
println!("Config: {config:#?}");
// Edit configuration with suggestions pre-filled
let updated_config = Config::wizard_builder()
.with_suggestions(config)
.build()
.unwrap();
println!("Updated config: {updated_config:#?}");
Additional examples:
use derive_wizard::Wizard;
# #[derive(Debug, Clone, Wizard)]
# struct Config {
# #[prompt("Enter the server address:")]
# server: String,
# #[prompt("Enter the port:")]
# port: u16,
# #[prompt("Enable SSL?")]
# use_ssl: bool,
# }
// With custom backend (e.g., requestty)
let backend = derive_wizard::RequesttyBackend::new();
let config = Config::wizard_builder()
.with_backend(backend)
.build()
.unwrap();
println!("Config: {config:#?}");
// Combine suggestions with custom backend
let backend = derive_wizard::RequesttyBackend::new();
let updated_config = Config::wizard_builder()
.with_suggestions(config)
.with_backend(backend)
.build()
.unwrap();
println!("Updated config: {updated_config:#?}");
When with_suggestions() is used:
#[mask]) and multiline (#[multiline]) fields: suggestions are shown as hints (backend-dependent)Instead of providing a complete struct with with_suggestions(), you can suggest values for specific fields using suggest_field():
use derive_wizard::Wizard;
# #[derive(Debug, Clone, Wizard)]
# struct Config {
# #[prompt("Enter the server address:")]
# server: String,
# #[prompt("Enter the port:")]
# port: u16,
# #[prompt("Enable SSL?")]
# use_ssl: bool,
# }
// Suggest specific fields, ask about all of them
let config = Config::wizard_builder()
.suggest_field("server", "localhost".to_string())
.suggest_field("port", 8080)
.suggest_field("use_ssl", false)
.build(); // All questions asked with pre-filled defaults
This is useful when you want to provide defaults for specific fields without needing to construct an entire struct.
Assumptions are different from suggestions - they completely skip the questions and use the provided values directly. Use assume_field() to set specific fields while still asking about others:
use derive_wizard::Wizard;
# #[derive(Debug, Clone, Wizard)]
# struct Config {
# #[prompt("Enter the server address:")]
# server: String,
# #[prompt("Enter the port:")]
# port: u16,
# #[prompt("Enable SSL?")]
# use_ssl: bool,
# }
// Assume specific fields, ask about the rest
let config = Config::wizard_builder()
.assume_field("use_ssl", true) // Always use SSL in production
.assume_field("port", 443) // Standard HTTPS port
.build(); // Will only ask about 'server'
Key differences:
with_suggestions() or suggest_field()): Questions are asked, but with pre-filled default valuesassume_field()): Questions are skipped entirely, values are used as-isYou can also combine both approaches:
use derive_wizard::Wizard;
# #[derive(Debug, Clone, Wizard)]
# struct Config {
# #[prompt("Enter the server address:")]
# server: String,
# #[prompt("Enter the port:")]
# port: u16,
# #[prompt("Enable SSL?")]
# use_ssl: bool,
# }
let config = Config::wizard_builder()
.suggest_field("server", "localhost".to_string()) // Suggest (will ask)
.assume_field("use_ssl", true) // Assume (will skip)
.assume_field("port", 443) // Assume (will skip)
.build(); // Only asks about 'server' with "localhost" as default
Assumptions are useful for:
When your struct contains other Wizard-derived types, the fields are automatically namespaced with dot notation to avoid conflicts:
use derive_wizard::Wizard;
#[derive(Debug, Clone, Wizard)]
struct Address {
#[prompt("Street:")]
street: String,
#[prompt("City:")]
city: String,
}
#[derive(Debug, Clone, Wizard)]
struct UserProfile {
#[prompt("Name:")]
name: String,
#[prompt("Home address:")]
address: Address, // Nested Wizard type
}
// The nested Address fields are automatically prefixed:
// - "address.street"
// - "address.city"
Namespace Prefixing: Each nested field is prefixed with its parent field name and a dot. This allows you to:
field! Macro for Nested FieldsTo reference nested fields in suggest_field() or assume_field(), use the field! macro with dot notation:
use derive_wizard::{Wizard, field};
# #[derive(Debug, Clone, Wizard)]
# struct Address {
# #[prompt("Street:")]
# street: String,
# #[prompt("City:")]
# city: String,
# }
#
# #[derive(Debug, Clone, Wizard)]
# struct UserProfile {
# #[prompt("Name:")]
# name: String,
# #[prompt("Address:")]
# address: Address,
# }
let profile = UserProfile::wizard_builder()
.suggest_field(field!(name), "John Doe".to_string())
.suggest_field(
field!(UserProfile::address::street),
"123 Main St".to_string()
)
.assume_field(
field!(UserProfile::address::city),
"Springfield".to_string()
)
.build();
The field! macro supports:
field!(name) → "name"field!(Type::field) → "field"field!(Type::nested::field) → "nested.field"Namespace prefixing automatically handles duplicate field names across different nested structures:
use derive_wizard::{Wizard, field};
#[derive(Debug, Clone, Wizard)]
struct Department {
#[prompt("Department name:")]
name: String,
#[prompt("Budget:")]
budget: i32,
}
#[derive(Debug, Clone, Wizard)]
struct Organization {
#[prompt("Organization name:")]
name: String, // Same field name as Department
#[prompt("Primary department:")]
primary: Department,
#[prompt("Secondary department:")]
secondary: Department,
}
// Each 'name' field gets a unique path:
// - "name" (Organization.name)
// - "primary.name" (primary Department.name)
// - "secondary.name" (secondary Department.name)
let org = Organization::wizard_builder()
.suggest_field(field!(name), "Acme Corp".to_string())
.assume_field(
field!(Organization::primary::name),
"Engineering".to_string()
)
.assume_field(
field!(Organization::secondary::name),
"Sales".to_string()
)
.build();
This namespace approach ensures that:
field! macroThe #[derive(Wizard)] macro supports all 11 requestty question types:
| Rust Type | Default Question Type | Override Options | Returns |
|---|---|---|---|
String |
input |
#[mask] for password, #[multiline] for text editor |
String |
bool |
confirm |
- | bool |
i8, i16, i32, i64, isize |
int |
- | i64 (cast to type) |
u8, u16, u32, u64, usize |
int |
- | i64 (cast to type) |
f32, f64 |
float |
- | f64 (cast to type) |
ListItem |
select |
- | ListItem |
ExpandItem |
expand |
- | ExpandItem |
Vec<ListItem> |
multi_select |
- | Vec<ListItem> |
#[mask] on String fields)#[multiline] on String fields)ListItem)ExpandItem)multi_select - Multiple selection from a list (default for Vec<ListItem>)Note: The following question types are available in requestty but not currently exposed through attributes:
raw_select - Single selection with index-based inputorder_select - Reorder items in a listMIT OR Apache-2.0