Skip to content

[rust-guard] Rust Guard: Merge duplicate search_issues / search_pull_requests match arms #3834

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Merge Duplicate search_issues / search_pull_requests Match Arms

Category: Duplication
File(s): guards/github-guard/rust-guard/src/labels/tool_rules.rs
Effort: Small (< 15 min)
Risk: Low

Problem

The search_issues arm (lines 223–236) and search_pull_requests arm (lines 315–328) in apply_tool_labels are byte-for-byte identical except for the desc format string prefix. Every future change to the shared logic must be applied twice, and the duplication is easy to miss.

// lines 223–236
"search_issues" => {
    let (s_owner, s_repo, s_repo_id) = resolve_search_scope(tool_args, &owner, &repo);
    if !s_repo_id.is_empty() {
        desc = format!("search_issues:{}", s_repo_id);     // ← only difference
        secrecy =
            apply_repo_visibility_secrecy(&s_owner, &s_repo, &s_repo_id, secrecy, ctx);
        let search_repo_private = repo_private
            .or_else(|| super::backend::is_repo_private(&s_owner, &s_repo));
        integrity = private_writer_integrity(&s_repo_id, search_repo_private, ctx);
    } else {
        integrity = vec![];
    }
}

// lines 315–328  (identical except for the string "search_pull_requests")
"search_pull_requests" => {
    let (s_owner, s_repo, s_repo_id) = resolve_search_scope(tool_args, &owner, &repo);
    if !s_repo_id.is_empty() {
        desc = format!("search_pull_requests:{}", s_repo_id); // ← only difference
        secrecy =
            apply_repo_visibility_secrecy(&s_owner, &s_repo, &s_repo_id, secrecy, ctx);
        let search_repo_private = repo_private
            .or_else(|| super::backend::is_repo_private(&s_owner, &s_repo));
        integrity = private_writer_integrity(&s_repo_id, search_repo_private, ctx);
    } else {
        integrity = vec![];
    }
}

Suggested Change

Combine the two arms into one, using the already-in-scope tool_name variable for the desc prefix:

Before

        // Search issues: extract repo scope from query or tool_args when available
        "search_issues" => {
            let (s_owner, s_repo, s_repo_id) = resolve_search_scope(tool_args, &owner, &repo);
            if !s_repo_id.is_empty() {
                desc = format!("search_issues:{}", s_repo_id);
                secrecy =
                    apply_repo_visibility_secrecy(&s_owner, &s_repo, &s_repo_id, secrecy, ctx);
                // Use the search query's repo for privacy check when tool_args lacks owner/repo
                let search_repo_private = repo_private
                    .or_else(|| super::backend::is_repo_private(&s_owner, &s_repo));
                integrity = private_writer_integrity(&s_repo_id, search_repo_private, ctx);
            } else {
                integrity = vec![];
            }
        }

        // ... (many arms in between) ...

        // Search pull requests: extract repo scope from query or tool_args when available
        "search_pull_requests" => {
            let (s_owner, s_repo, s_repo_id) = resolve_search_scope(tool_args, &owner, &repo);
            if !s_repo_id.is_empty() {
                desc = format!("search_pull_requests:{}", s_repo_id);
                secrecy =
                    apply_repo_visibility_secrecy(&s_owner, &s_repo, &s_repo_id, secrecy, ctx);
                // Use the search query's repo for privacy check when tool_args lacks owner/repo
                let search_repo_private = repo_private
                    .or_else(|| super::backend::is_repo_private(&s_owner, &s_repo));
                integrity = private_writer_integrity(&s_repo_id, search_repo_private, ctx);
            } else {
                integrity = vec![];
            }
        }

After

        // Search issues / pull requests: extract repo scope from query or tool_args when available
        "search_issues" | "search_pull_requests" => {
            let (s_owner, s_repo, s_repo_id) = resolve_search_scope(tool_args, &owner, &repo);
            if !s_repo_id.is_empty() {
                desc = format!("{}:{}", tool_name, s_repo_id);
                secrecy =
                    apply_repo_visibility_secrecy(&s_owner, &s_repo, &s_repo_id, secrecy, ctx);
                // Use the search query's repo for privacy check when tool_args lacks owner/repo
                let search_repo_private = repo_private
                    .or_else(|| super::backend::is_repo_private(&s_owner, &s_repo));
                integrity = private_writer_integrity(&s_repo_id, search_repo_private, ctx);
            } else {
                integrity = vec![];
            }
        }

The search_pull_requests arm can be removed entirely (it was between the PR block and the Commits block — keeping the merged arm near the top, beside search_issues, is the natural placement).

Why This Matters

  • Eliminates a maintenance hazard: any future tweak (e.g. adding a desc fallback, changing the empty-scope behaviour) currently must be applied to two identical blocks.
  • Reduces apply_tool_labels by ~14 lines.
  • The desc values produced are unchanged ("search_issues:owner/repo" / "search_pull_requests:owner/repo").
  • Zero behaviour change — verified by existing tests for both tool names.

Improvement 2: Remove Internal Utilities from labels/mod.rs pub use Re-export

Category: API Surface
File(s): guards/github-guard/rust-guard/src/labels/mod.rs
Effort: Small (< 15 min)
Risk: Low

Problem

labels/mod.rs re-exports a bundle of items from helpers (lines 41–52):

pub use helpers::{
    blocked_integrity, commit_integrity, ensure_integrity_baseline, extract_graphql_nodes,
    extract_graphql_single_object, extract_items_array,
    extract_number_as_string, extract_repo_from_item, extract_repo_info,
    extract_repo_info_from_search_query, get_bool_or, get_nested_str, get_str_or,
    has_author_association, is_blocked_user, is_graphql_wrapper, is_mcp_text_wrapper,
    is_search_result_wrapper, issue_integrity, limit_items_with_log, make_item_path,
    merged_integrity, none_integrity, pr_integrity, private_scope_label, private_user_label,
    project_github_label, reader_integrity, search_result_total_count,
    writer_integrity, MinIntegrity, PolicyContext, PolicyScopeEntry, ScopeKind,
};

Five of these — get_str_or, get_nested_str, get_bool_or, make_item_path, and has_author_association — are low-level JSON accessor utilities. They are never used outside the labels/ submodule tree (not in lib.rs, not anywhere else in the crate). Their presence in the re-export list:

  1. Widens the apparent public interface of the labels module with implementation details.
  2. Makes it ambiguous which helpers are semantically part of the labels API vs. incidental utilities.
  3. Increases the surface area that readers need to understand when studying the module.

Suggested Change

Remove the five utility names from the pub use list, and import them explicitly inside the mod tests block where they're needed.

Before

// labels/mod.rs, lines 41–52
pub use helpers::{
    blocked_integrity, commit_integrity, ensure_integrity_baseline, extract_graphql_nodes,
    extract_graphql_single_object, extract_items_array,
    extract_number_as_string, extract_repo_from_item, extract_repo_info,
    extract_repo_info_from_search_query, get_bool_or, get_nested_str, get_str_or,
    has_author_association, is_blocked_user, is_graphql_wrapper, is_mcp_text_wrapper,
    is_search_result_wrapper, issue_integrity, limit_items_with_log, make_item_path,
    merged_integrity, none_integrity, pr_integrity, private_scope_label, private_user_label,
    project_github_label, reader_integrity, search_result_total_count,
    writer_integrity, MinIntegrity, PolicyContext, PolicyScopeEntry, ScopeKind,
};
// labels/mod.rs, line 153
mod tests {
    use super::*;
    // ...

After

// labels/mod.rs, lines 41–52 — remove the five utilities
pub use helpers::{
    blocked_integrity, commit_integrity, ensure_integrity_baseline, extract_graphql_nodes,
    extract_graphql_single_object, extract_items_array,
    extract_number_as_string, extract_repo_from_item, extract_repo_info,
    extract_repo_info_from_search_query, is_blocked_user, is_graphql_wrapper, is_mcp_text_wrapper,
    is_search_result_wrapper, issue_integrity, limit_items_with_log,
    merged_integrity, none_integrity, pr_integrity, private_scope_label, private_user_label,
    project_github_label, reader_integrity, search_result_total_count,
    writer_integrity, MinIntegrity, PolicyContext, PolicyScopeEntry, ScopeKind,
};
// labels/mod.rs — inside mod tests { ... }
mod tests {
    use super::*;
    use super::helpers::{get_bool_or, get_nested_str, get_str_or, has_author_association, make_item_path};
    // ...

Why This Matters

  • Makes the labels module boundary cleaner: the re-export block becomes a list of semantically meaningful labeling concepts, not a mix of concepts and low-level JSON plumbing.
  • The five functions remain pub in helpers.rs and fully accessible via super::helpers::* — no behavior changes.
  • Readers of labels/mod.rs immediately understand which helpers are part of the module's contract.

Codebase Health Summary

  • Total Rust files: 9
  • Total lines: 12,018
  • Areas analyzed: lib.rs, tools.rs, labels/mod.rs, labels/helpers.rs, labels/tool_rules.rs, labels/backend.rs, labels/constants.rs, labels/response_items.rs, labels/response_paths.rs
  • Areas with no further improvements: tools.rs (well-structured, good test coverage), labels/constants.rs (clean), labels/backend.rs (complex but well-organized)

Generated by Rust Guard Improver • Run: §24447416059

Generated by Rust Guard Improver · ● 2.7M ·

  • expires on Apr 22, 2026, 9:50 AM UTC

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions