From a36997982abcab8edf2311c60555eecded0beee5 Mon Sep 17 00:00:00 2001 From: teernisse Date: Thu, 12 Feb 2026 16:14:01 -0500 Subject: [PATCH] CLI: list --sort by method/tag, show merges path-level parameters list: new --sort flag accepts path (default), method, or tag. All sort modes maintain alias-first grouping for cross-alias queries. method sort orders GET/POST/PUT/PATCH/DELETE/etc. tag sort uses first tag as primary key with path and method as tiebreakers. show: merge path-item-level parameters into operation parameters per OpenAPI 3.x spec. Path-level params apply to all operations unless overridden by an operation-level param with the same (name, in) pair. Uses the operation_ptr to derive the parent path-item pointer and resolve_json_pointer to access the raw spec. --- src/cli/list.rs | 38 +++++++++++++++++++++++++++++------- src/cli/show.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/cli/list.rs b/src/cli/list.rs index c30e428..5178581 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -388,13 +388,37 @@ async fn execute_all_aliases(args: &Args, robot_mode: bool) -> Result<(), Swagge let filtered_count = all_entries.len(); - // Sort: alias ASC, path ASC, method_rank ASC - all_entries.sort_by(|a, b| { - a.alias - .cmp(&b.alias) - .then_with(|| a.path.cmp(&b.path)) - .then_with(|| method_rank(&a.method).cmp(&method_rank(&b.method))) - }); + // Sort: alias first for grouping, then apply user's --sort preference + match args.sort.as_str() { + "method" => { + all_entries.sort_by(|a, b| { + a.alias + .cmp(&b.alias) + .then_with(|| method_rank(&a.method).cmp(&method_rank(&b.method))) + .then_with(|| a.path.cmp(&b.path)) + }); + } + "tag" => { + all_entries.sort_by(|a, b| { + let tag_a = a.tags.first().map(String::as_str).unwrap_or(""); + let tag_b = b.tags.first().map(String::as_str).unwrap_or(""); + a.alias + .cmp(&b.alias) + .then_with(|| tag_a.cmp(tag_b)) + .then_with(|| a.path.cmp(&b.path)) + .then_with(|| method_rank(&a.method).cmp(&method_rank(&b.method))) + }); + } + // "path" or default + _ => { + all_entries.sort_by(|a, b| { + a.alias + .cmp(&b.alias) + .then_with(|| a.path.cmp(&b.path)) + .then_with(|| method_rank(&a.method).cmp(&method_rank(&b.method))) + }); + } + } // ---- Limit ---- if !args.all { diff --git a/src/cli/show.rs b/src/cli/show.rs index fad97b6..9d0ecd9 100644 --- a/src/cli/show.rs +++ b/src/cli/show.rs @@ -112,10 +112,53 @@ pub async fn execute(args: &Args, robot: bool) -> Result<(), SwaggerCliError> { expand_refs(&mut operation, &raw, args.max_depth); } - let parameters = operation - .get("parameters") - .cloned() - .unwrap_or(Value::Array(vec![])); + // Merge path-level parameters into operation parameters. + // Per OpenAPI 3.x, parameters defined at the path-item level apply to all + // operations unless overridden (same name + location) at the operation level. + let parameters = { + let op_params = operation + .get("parameters") + .and_then(Value::as_array) + .cloned() + .unwrap_or_default(); + + // Derive path-item pointer by stripping the last segment (method) from operation_ptr + let path_item_ptr = endpoint + .operation_ptr + .rfind('/') + .map(|i| &endpoint.operation_ptr[..i]); + + let mut merged = op_params.clone(); + + if let Some(ptr) = path_item_ptr + && let Some(path_item) = resolve_json_pointer(&raw, ptr) + && let Some(path_params) = path_item.get("parameters").and_then(Value::as_array) + { + // Collect (name, in) pairs from operation-level params for override detection + let op_keys: std::collections::HashSet<(String, String)> = op_params + .iter() + .filter_map(|p| { + let name = p.get("name")?.as_str()?.to_string(); + let loc = p.get("in")?.as_str()?.to_string(); + Some((name, loc)) + }) + .collect(); + + for pp in path_params { + let key = pp + .get("name") + .and_then(Value::as_str) + .zip(pp.get("in").and_then(Value::as_str)); + if let Some((name, loc)) = key + && !op_keys.contains(&(name.to_string(), loc.to_string())) + { + merged.push(pp.clone()); + } + } + } + + Value::Array(merged) + }; let request_body = operation.get("requestBody").cloned();