Problem Statement
The OpenShell CLI has grown to support --output flags on many commands (e.g., sandbox list, gateway list, provider list-profiles), allowing users to specify output format as JSON, YAML, or table. However, each command duplicates the same 15-20 line match statement to handle output formatting, leading to significant code duplication and maintenance burden.
Current duplication:
gateway_list() - line 1292 in run.rs
sandbox_list() - line 3185 in run.rs
provider_list_profiles() - line 4700 in run.rs
provider_profile_export() - line 4751 in run.rs
- Policy commands - lines 6622, 6729, 6809 in run.rs
Each command repeats ~15-20 lines of boilerplate for the same logic, totaling ~91 lines of duplicated code initially (and growing with each new command that supports --output).
Proposed Design
Create a new output.rs module with generic helper functions that handle the repetitive output formatting logic using an early-return pattern. This preserves the existing code structure while eliminating duplication.
Key design decisions:
- Helper functions over traits/macros - Simpler, easier to understand, works with existing code
- Early-return pattern - Returns
bool to signal if output was handled, caller returns immediately or continues to table rendering
- Three helper variants - Handles different use cases (collections, single items, pre-formatted output)
- Incremental migration - Can be adopted command-by-command without breaking changes
Core API:
// For collections of items
pub fn print_output_collection<T, F>(
format: &str,
items: &[T],
to_json: F,
) -> Result<bool>
where
F: Fn(&T) -> serde_json::Value
// For single items
pub fn print_output_single<T>(
format: &str,
item: &T,
to_json: impl Fn(&T) -> serde_json::Value,
) -> Result<bool>
// For pre-formatted output
pub fn print_output_direct(
format: &str,
json_fn: impl FnOnce() -> Result<String>,
yaml_fn: impl FnOnce() -> Result<String>,
) -> Result<bool>
Usage example:
Before (18 lines):
match output {
"json" => {
let items: Vec<serde_json::Value> = sandboxes.iter().map(sandbox_to_json).collect();
println!("{}", serde_json::to_string_pretty(&items).into_diagnostic()?);
return Ok(());
}
"yaml" => {
let items: Vec<serde_json::Value> = sandboxes.iter().map(sandbox_to_json).collect();
print!("{}", serde_yml::to_string(&items).into_diagnostic()?);
return Ok(());
}
"table" => {}
_ => return Err(miette!("unsupported output format: {output}")),
}
After (3 lines):
if print_output_collection(output, &sandboxes, sandbox_to_json)? {
return Ok(());
}
Files to modify:
- Create:
crates/openshell-cli/src/output.rs (~80 lines)
- Update:
crates/openshell-cli/src/lib.rs - add module declaration
- Update:
crates/openshell-cli/src/run.rs - migrate commands to use helpers
Alternatives Considered
- Traits - More idiomatic Rust but requires implementing traits on types we don't own, adding complexity
- Macros - Would reduce boilerplate but makes code harder to debug and understand
- Do nothing - Continue duplicating code, but this makes adding new output formats harder and increases maintenance burden
The helper function approach was chosen for simplicity, type safety, and ease of adoption.
Agent Investigation
Analyzed crates/openshell-cli/src/main.rs (2745 lines) and crates/openshell-cli/src/run.rs (8768 lines):
- Identified 7+ commands with duplicated output formatting logic
- Confirmed
OutputFormat enum exists at line 652 of main.rs
- Verified dependencies (
serde, serde_json, serde_yml) are already available
- Each command has its own converter function (e.g.,
sandbox_to_json, gateway_to_json)
- Pattern is consistent across all commands, making a generic solution feasible
Benefits:
- Reduces duplication by ~91 lines initially
- Single source of truth for output formatting
- Type-safe, compiler-verified
- Easy to extend (e.g., adding CSV format requires updating one module)
- Backward compatible
- Incremental adoption (migrate commands one at a time)
Problem Statement
The OpenShell CLI has grown to support
--outputflags on many commands (e.g.,sandbox list,gateway list,provider list-profiles), allowing users to specify output format as JSON, YAML, or table. However, each command duplicates the same 15-20 line match statement to handle output formatting, leading to significant code duplication and maintenance burden.Current duplication:
gateway_list()- line 1292 in run.rssandbox_list()- line 3185 in run.rsprovider_list_profiles()- line 4700 in run.rsprovider_profile_export()- line 4751 in run.rsEach command repeats ~15-20 lines of boilerplate for the same logic, totaling ~91 lines of duplicated code initially (and growing with each new command that supports
--output).Proposed Design
Create a new
output.rsmodule with generic helper functions that handle the repetitive output formatting logic using an early-return pattern. This preserves the existing code structure while eliminating duplication.Key design decisions:
boolto signal if output was handled, caller returns immediately or continues to table renderingCore API:
Usage example:
Before (18 lines):
After (3 lines):
Files to modify:
crates/openshell-cli/src/output.rs(~80 lines)crates/openshell-cli/src/lib.rs- add module declarationcrates/openshell-cli/src/run.rs- migrate commands to use helpersAlternatives Considered
The helper function approach was chosen for simplicity, type safety, and ease of adoption.
Agent Investigation
Analyzed
crates/openshell-cli/src/main.rs(2745 lines) andcrates/openshell-cli/src/run.rs(8768 lines):OutputFormatenum exists at line 652 ofmain.rsserde,serde_json,serde_yml) are already availablesandbox_to_json,gateway_to_json)Benefits: