Skip to content

[rust-guard] Rust Guard: Extract is_any_trusted_actor helper and collapse URL fallback loop #4252

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Extract is_any_trusted_actor Convenience Helper

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

Problem

The three-way trust check:

is_trusted_first_party_bot(x) || is_configured_trusted_bot(x, ctx) || is_trusted_user(x, ctx)

is copy-pasted verbatim in three separate call sites:

File Lines Context
labels/helpers.rs 1282–1284 author_association_floor — bot/trusted-user early-return
labels/helpers.rs 1479–1481 PR-enrichment block — elevate enriched floor for trusted actors
labels/tool_rules.rs 98–100 compute_user_authored_resource_integrity — floor elevation

Each site pairs the same three predicates with the same intent: "does this username belong to any trusted tier?". If a fourth trust tier is ever added (e.g. is_saml_bot) every site needs a matching edit.

Suggested Change

Add a single helper near the three constituent functions in helpers.rs:

/// Returns `true` if `username` belongs to any trusted actor tier:
/// first-party bots, gateway-configured bots, or trusted users.
pub(crate) fn is_any_trusted_actor(username: &str, ctx: &PolicyContext) -> bool {
    is_trusted_first_party_bot(username)
        || is_configured_trusted_bot(username, ctx)
        || is_trusted_user(username, ctx)
}

Then replace each of the three call sites.

Before

// helpers.rs ~1282
if !author_login.is_empty()
    && (is_trusted_first_party_bot(author_login)
        || is_configured_trusted_bot(author_login, ctx)
        || is_trusted_user(author_login, ctx))
{
    return writer_integrity(scope, ctx);
}

After

// helpers.rs ~1282
if !author_login.is_empty() && is_any_trusted_actor(author_login, ctx) {
    return writer_integrity(scope, ctx);
}

The same one-liner substitution applies to helpers.rs:1479–1481 and tool_rules.rs:98–100.

Why This Matters

  • Maintainability: adding a new trust tier is a one-line change in one place instead of three.
  • Readability: is_any_trusted_actor expresses intent; the raw OR chain requires the reader to recognise all three predicates before understanding the semantics.
  • Safety: impossible to forget a site — the triple-OR is only written once.

Improvement 2: Replace Repeated URL-Field Blocks with a Loop in extract_repo_from_item

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

Problem

extract_repo_from_item (lines ~977–993) contains three structurally identical if let blocks that differ only in the JSON field name queried:

// repository_url parsing for search endpoints
if let Some(url) = item.get("repository_url").and_then(|v| v.as_str()) {
    if let Some(repo_id) = extract_repo_from_github_url(url) {
        return repo_id;
    }
}
// html_url parsing as last resort ...
if let Some(url) = item.get("html_url").and_then(|v| v.as_str()) {
    if let Some(repo_id) = extract_repo_from_github_url(url) {
        return repo_id;
    }
}
// Generic URL field fallback
if let Some(url) = item.get("url").and_then(|v| v.as_str()) {
    if let Some(repo_id) = extract_repo_from_github_url(url) {
        return repo_id;
    }
}

Adding a fourth URL field (e.g. clone_url) requires duplicating the block a fourth time; there is nothing to distinguish the pattern from a deliberate copy-paste.

It is also worth noting the same early-return pattern for ["html_url", "url"] already exists in extract_number_from_url (lines ~45–49), so the idiom of a small slice loop is already established in this file.

Suggested Change

// URL field fallback (repository_url for search results, html_url / url as generic fallbacks)
for field in &["repository_url", "html_url", "url"] {
    if let Some(url) = item.get(*field).and_then(|v| v.as_str()) {
        if let Some(repo_id) = extract_repo_from_github_url(url) {
            return repo_id;
        }
    }
}

The consolidated comment replaces the three inline comments. Priority order is preserved (first match wins, identical to the original).

Before

// repository_url parsing for search endpoints
if let Some(url) = item.get("repository_url").and_then(|v| v.as_str()) {
    if let Some(repo_id) = extract_repo_from_github_url(url) {
        return repo_id;
    }
}
// html_url parsing as last resort - extract owner/repo from URLs like:
// https://github.com/owner/repo/pull/123 or https://github.com/owner/repo/issues/456
if let Some(url) = item.get("html_url").and_then(|v| v.as_str()) {
    if let Some(repo_id) = extract_repo_from_github_url(url) {
        return repo_id;
    }
}
// Generic URL field fallback
if let Some(url) = item.get("url").and_then(|v| v.as_str()) {
    if let Some(repo_id) = extract_repo_from_github_url(url) {
        return repo_id;
    }
}
String::new()

After

// URL field fallback (repository_url for search results, html_url / url as generic fallbacks)
for field in &["repository_url", "html_url", "url"] {
    if let Some(url) = item.get(*field).and_then(|v| v.as_str()) {
        if let Some(repo_id) = extract_repo_from_github_url(url) {
            return repo_id;
        }
    }
}
String::new()

Why This Matters

  • Removes ~12 lines of near-identical boilerplate; the function shrinks from ~55 lines to ~43.
  • Extending to a new URL field is a one-character diff (add to the array literal).
  • Mirrors the existing pattern in extract_number_from_url (lines ~45–49) which already uses a for field in &["html_url", "url"] loop for the same URL-extraction idiom.

Codebase Health Summary

  • Total Rust files: 9
  • Total lines: 12,721
  • Areas analyzed: lib.rs, labels/mod.rs, labels/helpers.rs, labels/backend.rs, labels/tool_rules.rs, labels/response_items.rs, labels/response_paths.rs, labels/constants.rs, tools.rs
  • Areas with no further obvious improvements: labels/constants.rs, tools.rs

Generated by Rust Guard Improver • Run: §24715259603

Generated by Rust Guard Improver · ● 1.9M ·

  • expires on Apr 28, 2026, 9:45 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