Skip to content

[rust-guard] Rust Guard: Two small deduplication opportunities in helpers.rs and constants.rs #3112

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Add PRIVATE_BASE constant to label_constants

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

Problem

The bare "private" string is used as a secrecy label value in 4 places across three files, but label_constants has no constant for it. The module already defines PRIVATE_PREFIX ("private:"), PRIVATE_USER ("private:user"), and BLOCKED_BASE / READER_BASE / etc. — but not the base "private" token that acts as the "fully private / unknown scope" fallback.

Affected lines:

  • helpers.rs:398ScopeKind::Public => vec!["private".to_string()]
  • helpers.rs:410vec!["private".to_string()]
  • response_items.rs:69vec!["private".to_string()]
  • response_paths.rs:82vec!["private".to_string()]

Suggested Change

Step 1 – add the constant to label_constants in constants.rs:

Before

pub mod label_constants {
    pub const NONE: &str = "none";
    pub const PRIVATE_USER: &str = "private:user";
    pub const PRIVATE_PREFIX: &str = "private:";
    // ... (no bare PRIVATE constant)

After

pub mod label_constants {
    pub const NONE: &str = "none";
    pub const PRIVATE_USER: &str = "private:user";
    pub const PRIVATE_BASE: &str = "private";   // ← new
    pub const PRIVATE_PREFIX: &str = "private:";
    // ...

Step 2 – replace the 4 raw literals (all three files):

// Before (e.g. helpers.rs:398, response_items.rs:69, response_paths.rs:82)
vec!["private".to_string()]

// After
vec![label_constants::PRIVATE_BASE.to_string()]

Why This Matters

  • Keeps all label token spelling in one place; a future rename only requires touching constants.rs.
  • Makes the intent ("this is the fallback 'fully private' label") explicit instead of relying on the reader to infer it from a raw string.
  • Consistent with BLOCKED_BASE, READER_BASE, WRITER_BASE, MERGED_BASE which already follow this pattern.

Improvement 2: Eliminate GraphQL traversal duplication between extract_items_array and extract_graphql_nodes

Category: Code Duplication
File(s): guards/github-guard/rust-guard/src/labels/helpers.rs
Effort: Medium (15–30 min)
Risk: Low

Problem

extract_items_array (line 663) and extract_graphql_nodes (line 726) both contain an identical GraphQL-node-traversal loop over GRAPHQL_COLLECTION_FIELDS plus an identical data.search.nodes check — roughly 12 lines duplicated verbatim. The only difference is that extract_items_array also returns a JSON-Pointer path string, while extract_graphql_nodes discards it.

Before

// Inside extract_items_array (~lines 678–694):
if let Some(data) = response.get("data") {
    if let Some(repo) = data.get("repository") {
        for (field, pointer) in GRAPHQL_COLLECTION_FIELDS {
            if let Some(arr) = repo.get(*field)
                .and_then(|v| v.get("nodes"))
                .and_then(|v| v.as_array())
            {
                return (Some(arr), pointer.to_string());
            }
        }
    }
    if let Some(arr) = data.get("search")
        .and_then(|v| v.get("nodes"))
        .and_then(|v| v.as_array())
    {
        return (Some(arr), "/data/search/nodes".to_string());
    }
}

// extract_graphql_nodes (~lines 726–742): ← identical logic, pointer ignored
pub fn extract_graphql_nodes(response: &Value) -> Option<&Vec<Value>> {
    let data = response.get("data")?;
    if let Some(repo) = data.get("repository") {
        for (field, _) in GRAPHQL_COLLECTION_FIELDS {
            if let Some(arr) = repo.get(*field)
                .and_then(|v| v.get("nodes"))
                .and_then(|v| v.as_array())
            {
                return Some(arr);
            }
        }
    }
    if let Some(arr) = data.get("search")
        .and_then(|v| v.get("nodes"))
        .and_then(|v| v.as_array())
    {
        return Some(arr);
    }
    None
}

After

/// Private helper: find GraphQL nodes and return both the array and its JSON Pointer path.
fn find_graphql_nodes_with_path(response: &Value) -> Option<(&Vec<Value>, &'static str)> {
    let data = response.get("data")?;
    if let Some(repo) = data.get("repository") {
        for (field, pointer) in GRAPHQL_COLLECTION_FIELDS {
            if let Some(arr) = repo.get(*field)
                .and_then(|v| v.get("nodes"))
                .and_then(|v| v.as_array())
            {
                return Some((arr, pointer));
            }
        }
    }
    if let Some(arr) = data.get("search")
        .and_then(|v| v.get("nodes"))
        .and_then(|v| v.as_array())
    {
        return Some((arr, "/data/search/nodes"));
    }
    None
}

// extract_items_array — GraphQL branch becomes a one-liner:
if let Some((arr, pointer)) = find_graphql_nodes_with_path(response) {
    return (Some(arr), pointer.to_string());
}

// extract_graphql_nodes delegates entirely:
pub fn extract_graphql_nodes(response: &Value) -> Option<&Vec<Value>> {
    find_graphql_nodes_with_path(response).map(|(arr, _)| arr)
}

Why This Matters

  • Single source of truth for GraphQL traversal — adding a new collection field (e.g. "projects") is a one-line change in GRAPHQL_COLLECTION_FIELDS instead of two matching edits.
  • Existing tests for both functions continue to pass with no changes.
  • WASM binary size is unchanged (the compiler already inlines these small functions).

Codebase Health Summary

  • Total Rust files: 10
  • Total lines: ~10,925
  • Areas analyzed: constants.rs, helpers.rs, response_items.rs, response_paths.rs, tool_rules.rs, backend.rs, mod.rs, lib.rs
  • Areas with no further improvements found (this run): lib.rs, permissions.rs, tools.rs

Generated by Rust Guard Improver • Run: §23941429459

Generated by Rust Guard Improver ·

  • expires on Apr 10, 2026, 9:31 AM UTC

Metadata

Metadata

Assignees

No one assigned

    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