Skip to content

Commit 1b8f527

Browse files
committed
fix: store validated PathBuf, remove dead code, delete duplicate SubscribeConfig
Addresses review comments: - Store validated PathBuf from validate_safe_output_dir instead of discarding it (output_dir is now Option<PathBuf>) - Remove duplicate SubscribeConfig from events/mod.rs - Delete unused validate_msg_format (clap value_parser handles this) - Remove all #[allow(dead_code)] annotations
1 parent 1c56c01 commit 1b8f527

File tree

6 files changed

+18
-95
lines changed

6 files changed

+18
-95
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ When adding new helpers or CLI flags that accept file paths, **always validate**
8585
|---|---|---|
8686
| File path for writing (`--output-dir`) | `validate::validate_safe_output_dir()` | Absolute paths, `../` traversal, symlinks outside CWD, control chars |
8787
| File path for reading (`--dir`) | `validate::validate_safe_dir_path()` | Absolute paths, `../` traversal, control chars |
88-
| Enum/allowlist values (`--msg-format`) | `validate::validate_msg_format()` or clap `value_parser` | Any value not in the allowlist |
88+
| Enum/allowlist values (`--msg-format`) | clap `value_parser` (see `gmail/mod.rs`) | Any value not in the allowlist |
8989

9090
```rust
9191
// In your argument parser:

docs/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ This CLI is designed to be invoked by AI/LLM agents, so all user-supplied inputs
8383
| Embedding a value in a URL path segment | `helpers::encode_path_segment()` |
8484
| Passing query parameters | reqwest `.query()` builder (never string interpolation) |
8585
| Using a resource name in a URL (`--project`, `--space`) | `helpers::validate_resource_name()` |
86-
| Accepting an enum flag (`--msg-format`) | clap `value_parser` or `validate::validate_msg_format()` |
86+
| Accepting an enum flag (`--msg-format`) | clap `value_parser` (see `gmail/mod.rs`) |
8787

8888
### Testing Expectations
8989

src/helpers/events/mod.rs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,6 @@ impl std::fmt::Display for SubscriptionName {
4949
}
5050
}
5151

52-
#[allow(dead_code)]
53-
#[derive(Debug, Clone, Builder)]
54-
#[builder(setter(into))]
55-
pub struct SubscribeConfig {
56-
#[builder(default)]
57-
pub target: Option<String>,
58-
#[builder(default)]
59-
pub event_types: Vec<String>,
60-
#[builder(default)]
61-
pub project: Option<ProjectId>,
62-
#[builder(default)]
63-
pub subscription: Option<SubscriptionName>,
64-
#[builder(default = "10")]
65-
pub max_messages: u32,
66-
#[builder(default = "5")]
67-
pub poll_interval: u64,
68-
#[builder(default = "false")]
69-
pub once: bool,
70-
#[builder(default = "false")]
71-
pub cleanup: bool,
72-
#[builder(default = "false")]
73-
pub no_ack: bool,
74-
#[builder(default)]
75-
pub output_dir: Option<String>,
76-
}
77-
7852
impl Helper for EventsHelper {
7953
fn inject_commands(
8054
&self,

src/helpers/events/subscribe.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
use std::path::PathBuf;
23

34
#[derive(Debug, Clone, Default, Builder)]
45
#[builder(setter(into))]
@@ -22,7 +23,7 @@ pub struct SubscribeConfig {
2223
#[builder(default)]
2324
no_ack: bool,
2425
#[builder(default)]
25-
output_dir: Option<String>,
26+
output_dir: Option<PathBuf>,
2627
}
2728

2829
fn parse_subscribe_args(matches: &ArgMatches) -> Result<SubscribeConfig, GwsError> {
@@ -66,8 +67,7 @@ fn parse_subscribe_args(matches: &ArgMatches) -> Result<SubscribeConfig, GwsErro
6667
builder.cleanup(matches.get_flag("cleanup"));
6768
builder.no_ack(matches.get_flag("no-ack"));
6869
if let Some(output_dir) = matches.get_one::<String>("output-dir") {
69-
crate::validate::validate_safe_output_dir(output_dir)?;
70-
builder.output_dir(Some(output_dir.clone()));
70+
builder.output_dir(Some(crate::validate::validate_safe_output_dir(output_dir)?));
7171
}
7272

7373
let config = builder
@@ -355,11 +355,11 @@ async fn pull_loop(
355355
.duration_since(std::time::UNIX_EPOCH)
356356
.map(|d| d.as_millis())
357357
.unwrap_or(0);
358-
let path = format!("{dir}/{ts}_{file_counter}.json");
358+
let path = dir.join(format!("{ts}_{file_counter}.json"));
359359
if let Err(e) = std::fs::write(&path, &json_str) {
360-
eprintln!("Warning: failed to write {path}: {e}");
360+
eprintln!("Warning: failed to write {}: {e}", path.display());
361361
} else {
362-
eprintln!("Wrote {path}");
362+
eprintln!("Wrote {}", path.display());
363363
}
364364
} else {
365365
println!(

src/helpers/gmail/watch.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ async fn watch_pull_loop(
295295
gmail_token,
296296
*last_history_id,
297297
&config.format,
298-
config.output_dir.as_deref(),
298+
config.output_dir.as_ref(),
299299
sanitize_config,
300300
)
301301
.await?;
@@ -377,7 +377,7 @@ async fn fetch_and_output_messages(
377377
gmail_token: &str,
378378
start_history_id: u64,
379379
msg_format: &str,
380-
output_dir: Option<&str>,
380+
output_dir: Option<&std::path::PathBuf>,
381381
sanitize_config: &crate::helpers::modelarmor::SanitizeConfig,
382382
) -> Result<(), GwsError> {
383383
let url = format!(
@@ -432,11 +432,11 @@ async fn fetch_and_output_messages(
432432
let json_str =
433433
serde_json::to_string_pretty(&full_msg).unwrap_or_else(|_| "{}".to_string());
434434
if let Some(dir) = output_dir {
435-
let path = format!("{dir}/{msg_id}.json");
435+
let path = dir.join(format!("{msg_id}.json"));
436436
if let Err(e) = std::fs::write(&path, &json_str) {
437-
eprintln!("Warning: failed to write {path}: {e}");
437+
eprintln!("Warning: failed to write {}: {e}", path.display());
438438
} else {
439-
eprintln!("Wrote {path}");
439+
eprintln!("Wrote {}", path.display());
440440
}
441441
} else {
442442
println!(
@@ -512,7 +512,7 @@ struct WatchConfig {
512512
format: String,
513513
once: bool,
514514
cleanup: bool,
515-
output_dir: Option<String>,
515+
output_dir: Option<std::path::PathBuf>,
516516
}
517517

518518
fn parse_watch_args(matches: &ArgMatches) -> Result<WatchConfig, GwsError> {
@@ -522,10 +522,10 @@ fn parse_watch_args(matches: &ArgMatches) -> Result<WatchConfig, GwsError> {
522522
.unwrap_or("full");
523523
// Note: msg-format is already constrained by clap's value_parser
524524

525-
let output_dir = matches.get_one::<String>("output-dir").cloned();
526-
if let Some(ref dir) = output_dir {
527-
crate::validate::validate_safe_output_dir(dir)?;
528-
}
525+
let output_dir = matches
526+
.get_one::<String>("output-dir")
527+
.map(|dir| crate::validate::validate_safe_output_dir(dir))
528+
.transpose()?;
529529

530530
Ok(WatchConfig {
531531
project: matches.get_one::<String>("project").cloned(),

src/validate.rs

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,6 @@
2121
use crate::error::GwsError;
2222
use std::path::{Path, PathBuf};
2323

24-
/// Allowed values for Gmail message format (`--msg-format`).
25-
#[allow(dead_code)]
26-
pub const VALID_MSG_FORMATS: &[&str] = &["full", "metadata", "minimal", "raw"];
27-
28-
/// Validates that `value` is one of the allowed Gmail message formats.
29-
///
30-
/// Returns the validated value on success, or a descriptive
31-
/// [`GwsError::Validation`] listing the allowed options.
32-
#[allow(dead_code)]
33-
pub fn validate_msg_format(value: &str) -> Result<&str, GwsError> {
34-
if VALID_MSG_FORMATS.contains(&value) {
35-
Ok(value)
36-
} else {
37-
Err(GwsError::Validation(format!(
38-
"Invalid message format '{}'. Allowed values: {}",
39-
value,
40-
VALID_MSG_FORMATS.join(", ")
41-
)))
42-
}
43-
}
44-
4524
/// Validates that `dir` is a safe output directory.
4625
///
4726
/// The path is resolved relative to CWD. The function rejects paths that
@@ -194,36 +173,6 @@ mod tests {
194173
use std::fs;
195174
use tempfile::tempdir;
196175

197-
// --- validate_msg_format ---
198-
199-
#[test]
200-
fn test_valid_msg_formats() {
201-
for fmt in VALID_MSG_FORMATS {
202-
assert!(
203-
validate_msg_format(fmt).is_ok(),
204-
"expected '{fmt}' to be valid"
205-
);
206-
}
207-
}
208-
209-
#[test]
210-
fn test_invalid_msg_format() {
211-
let err = validate_msg_format("FULL").unwrap_err();
212-
let msg = err.to_string();
213-
assert!(msg.contains("Invalid message format"), "got: {msg}");
214-
assert!(msg.contains("full, metadata, minimal, raw"));
215-
}
216-
217-
#[test]
218-
fn test_empty_msg_format() {
219-
assert!(validate_msg_format("").is_err());
220-
}
221-
222-
#[test]
223-
fn test_msg_format_injection_attempt() {
224-
assert!(validate_msg_format("full&extra=1").is_err());
225-
}
226-
227176
// --- validate_safe_output_dir ---
228177

229178
#[test]

0 commit comments

Comments
 (0)