Skip to content

[rust-guard] Rust Guard: Fix misleading items_path.to_string() clone + test extract_items_array #4463

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Replace items_path.to_string() with a move (avoids misleading hidden clone)

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

Problem

In three PathLabelResult constructions (lines 216, 314, 631), the final items_path field is assigned with:

items_path: if items_path.is_empty() {
    None
} else {
    Some(items_path.to_string())
},

items_path is already a String (returned from extract_items_array). Calling .to_string() on an owned String invokes the ToString blanket impl, which ultimately calls .to_owned() — a full heap clone. This is misleading: a reader might assume it's a no-op type coercion when it's actually cloning the string a second time.

At this point in each match arm, items_path has already been used by reference in the for loop body (make_item_path(&items_path, i)), so it is still owned and can be moved directly.

Suggested Change

Replace all three occurrences of Some(items_path.to_string()) with Some(items_path).

Before

// list_pull_requests arm (line ~216), list_issues arm (~314), list_commits arm (~631)
items_path: if items_path.is_empty() {
    None
} else {
    Some(items_path.to_string())   // ← hidden clone; items_path is already String
},

After

items_path: if items_path.is_empty() {
    None
} else {
    Some(items_path)   // move — no allocation needed
},

Why This Matters

  • Eliminates 3 unnecessary heap allocations per labeled-list response (one per arm reached).
  • Makes ownership intent explicit: the String is moved into the Option, not copied.
  • Prevents future confusion for contributors who might think .to_string() is a type conversion rather than a clone.

Improvement 2: Add unit tests for extract_items_array

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

Problem

extract_items_array is the critical dispatch function that determines which JSON path the response labelers will iterate. It handles five distinct response shapes:

  1. Bare JSON array (REST list endpoints)
  2. {"items": [...]} wrapper (search results)
  3. {"issues": [...]} wrapper (some GitHub queries)
  4. {"pull_requests": [...]} wrapper
  5. GraphQL data.repository.<field>.nodes / data.search.nodes (via find_graphql_nodes_with_path)
  6. None — unknown shape, caller falls back to resource-level labels

Despite being called in every response-labeling path, it has zero unit tests. A regression here would silently mis-label all items in a list response.

Suggested Change

Add a #[cfg(test)] block inside helpers.rs (or extend the existing one) covering each response shape:

Before

// helpers.rs — no test for extract_items_array
pub fn extract_items_array(response: &Value) -> (Option<&Vec<Value>>, String) {
    // ... 5 match paths, 0 tests
}

After

#[cfg(test)]
mod tests {
    // ... (existing tests) ...

    #[test]
    fn extract_items_array_bare_array() {
        let response = json!([{"id": 1}, {"id": 2}]);
        let (items, path) = extract_items_array(&response);
        assert!(items.is_some());
        assert_eq!(items.unwrap().len(), 2);
        assert_eq!(path, "");
    }

    #[test]
    fn extract_items_array_items_wrapper() {
        let response = json!({"items": [{"id": 1}], "total_count": 1});
        let (items, path) = extract_items_array(&response);
        assert!(items.is_some());
        assert_eq!(path, "/items");
    }

    #[test]
    fn extract_items_array_issues_wrapper() {
        let response = json!({"issues": [{"number": 42}]});
        let (items, path) = extract_items_array(&response);
        assert!(items.is_some());
        assert_eq!(path, "/issues");
    }

    #[test]
    fn extract_items_array_pull_requests_wrapper() {
        let response = json!({"pull_requests": [{"number": 7}]});
        let (items, path) = extract_items_array(&response);
        assert!(items.is_some());
        assert_eq!(path, "/pull_requests");
    }

    #[test]
    fn extract_items_array_unknown_shape_returns_none() {
        let response = json!({"something_else": [{"id": 1}]});
        let (items, path) = extract_items_array(&response);
        assert!(items.is_none());
        assert_eq!(path, "");
    }
}

Why This Matters

  • Guards against silent regressions in the core response-dispatch logic.
  • Documents the supported response shapes as executable specifications.
  • Complements the existing label_response_paths integration tests (which test the full path but not the internal dispatch).

Codebase Health Summary

  • Total Rust files: 10
  • Total lines: 12,869
  • Areas analyzed: lib.rs, labels/response_paths.rs, labels/response_items.rs, labels/helpers.rs, labels/tool_rules.rs, labels/backend.rs, labels/mod.rs, tools.rs
  • Areas with no further improvements: none yet exhausted

Generated by Rust Guard Improver • Run: §24882934914

Generated by Rust Guard Improver · ● 1.5M ·

  • expires on May 1, 2026, 9:46 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