-
Notifications
You must be signed in to change notification settings - Fork 20
[rust-guard] Rust Guard: Two small deduplication opportunities in helpers.rs and constants.rs #3112
Description
🦀 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:398—ScopeKind::Public => vec!["private".to_string()]helpers.rs:410—vec!["private".to_string()]response_items.rs:69—vec!["private".to_string()]response_paths.rs:82—vec!["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_BASEwhich 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 inGRAPHQL_COLLECTION_FIELDSinstead 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