Skip to content

Commit 9cf76c7

Browse files
Merge pull request #421 from datadog-labs/refactor/extract-parse-compute-raw
refactor(util): extract shared parse_compute_raw helper
2 parents c0c6a7f + f3b5e04 commit 9cf76c7

3 files changed

Lines changed: 158 additions & 208 deletions

File tree

src/commands/logs.rs

Lines changed: 1 addition & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -83,56 +83,6 @@ pub fn split_compute_args(input: &str) -> Vec<String> {
8383
result
8484
}
8585

86-
fn parse_compute_raw(input: &str) -> Result<(String, Option<String>)> {
87-
let input = input.trim();
88-
if input.is_empty() {
89-
bail!("--compute is required");
90-
}
91-
92-
if input == "count" {
93-
return Ok(("count".into(), None));
94-
}
95-
96-
if let Some(paren) = input.find('(') {
97-
let func = &input[..paren];
98-
let rest = input[paren + 1..].trim_end_matches(')').trim();
99-
100-
if func == "percentile" {
101-
let parts: Vec<&str> = rest.splitn(2, ',').collect();
102-
if parts.len() != 2 {
103-
bail!("percentile requires field and value: percentile(@duration, 99)");
104-
}
105-
let metric = parts[0].trim().to_string();
106-
let pct: u32 = parts[1]
107-
.trim()
108-
.parse()
109-
.map_err(|_| anyhow::anyhow!("invalid percentile value: {}", parts[1].trim()))?;
110-
let agg_name = match pct {
111-
75 => "pc75",
112-
90 => "pc90",
113-
95 => "pc95",
114-
98 => "pc98",
115-
99 => "pc99",
116-
_ => bail!("unsupported percentile: {pct} (supported: 75, 90, 95, 98, 99)"),
117-
};
118-
return Ok((agg_name.into(), Some(metric)));
119-
}
120-
121-
let metric = rest.to_string();
122-
let agg_name = match func {
123-
"avg" | "sum" | "min" | "max" | "median" | "cardinality" => func.to_string(),
124-
"count" => bail!("count does not accept a field argument; use just 'count'"),
125-
_ => bail!("unknown aggregation function: {func}"),
126-
};
127-
return Ok((agg_name, Some(metric)));
128-
}
129-
130-
bail!(
131-
"invalid --compute format: {input:?}\n\
132-
Expected: count, avg(@duration), sum(@duration), percentile(@duration, 99), etc."
133-
)
134-
}
135-
13686
const VALID_SORT_AGGREGATIONS: &[&str] = &[
13787
"count",
13888
"cardinality",
@@ -187,7 +137,7 @@ fn build_aggregate_body(
187137
let compute_arr: Vec<serde_json::Value> = computes
188138
.iter()
189139
.map(|c| {
190-
let (aggregation, metric) = parse_compute_raw(c)?;
140+
let (aggregation, metric) = util::parse_compute_raw(c)?;
191141
let mut obj = serde_json::json!({ "aggregation": aggregation });
192142
if let Some(m) = metric {
193143
obj["metric"] = serde_json::Value::String(m);
@@ -451,107 +401,6 @@ mod tests {
451401

452402
use super::*;
453403

454-
#[test]
455-
fn test_parse_compute_count() {
456-
let (aggregation, metric) = parse_compute_raw("count").unwrap();
457-
assert_eq!(aggregation, "count");
458-
assert!(metric.is_none());
459-
}
460-
461-
#[test]
462-
fn test_parse_compute_avg() {
463-
let (aggregation, metric) = parse_compute_raw("avg(@duration)").unwrap();
464-
assert_eq!(aggregation, "avg");
465-
assert_eq!(metric.unwrap(), "@duration");
466-
}
467-
468-
#[test]
469-
fn test_parse_compute_sum() {
470-
let (aggregation, metric) = parse_compute_raw("sum(@duration)").unwrap();
471-
assert_eq!(aggregation, "sum");
472-
assert_eq!(metric.unwrap(), "@duration");
473-
}
474-
475-
#[test]
476-
fn test_parse_compute_min() {
477-
let (aggregation, metric) = parse_compute_raw("min(@duration)").unwrap();
478-
assert_eq!(aggregation, "min");
479-
assert_eq!(metric.unwrap(), "@duration");
480-
}
481-
482-
#[test]
483-
fn test_parse_compute_max() {
484-
let (aggregation, metric) = parse_compute_raw("max(@duration)").unwrap();
485-
assert_eq!(aggregation, "max");
486-
assert_eq!(metric.unwrap(), "@duration");
487-
}
488-
489-
#[test]
490-
fn test_parse_compute_median() {
491-
let (aggregation, metric) = parse_compute_raw("median(@duration)").unwrap();
492-
assert_eq!(aggregation, "median");
493-
assert_eq!(metric.unwrap(), "@duration");
494-
}
495-
496-
#[test]
497-
fn test_parse_compute_cardinality() {
498-
let (aggregation, metric) = parse_compute_raw("cardinality(@usr.id)").unwrap();
499-
assert_eq!(aggregation, "cardinality");
500-
assert_eq!(metric.unwrap(), "@usr.id");
501-
}
502-
503-
#[test]
504-
fn test_parse_compute_percentile_99() {
505-
let (aggregation, metric) = parse_compute_raw("percentile(@duration, 99)").unwrap();
506-
assert_eq!(aggregation, "pc99");
507-
assert_eq!(metric.unwrap(), "@duration");
508-
}
509-
510-
#[test]
511-
fn test_parse_compute_percentile_95() {
512-
let (aggregation, metric) = parse_compute_raw("percentile(@duration, 95)").unwrap();
513-
assert_eq!(aggregation, "pc95");
514-
assert_eq!(metric.unwrap(), "@duration");
515-
}
516-
517-
#[test]
518-
fn test_parse_compute_percentile_90() {
519-
let (aggregation, metric) = parse_compute_raw("percentile(@duration, 90)").unwrap();
520-
assert_eq!(aggregation, "pc90");
521-
assert_eq!(metric.unwrap(), "@duration");
522-
}
523-
524-
#[test]
525-
fn test_parse_compute_empty() {
526-
assert!(parse_compute_raw("").is_err());
527-
}
528-
529-
#[test]
530-
fn test_parse_compute_invalid() {
531-
assert!(parse_compute_raw("invalid").is_err());
532-
}
533-
534-
#[test]
535-
fn test_parse_compute_unknown_function() {
536-
assert!(parse_compute_raw("foo(@bar)").is_err());
537-
}
538-
539-
#[test]
540-
fn test_parse_compute_unsupported_percentile() {
541-
assert!(parse_compute_raw("percentile(@duration, 42)").is_err());
542-
}
543-
544-
#[test]
545-
fn test_parse_compute_percentile_missing_value() {
546-
assert!(parse_compute_raw("percentile(@duration)").is_err());
547-
}
548-
549-
#[test]
550-
fn test_parse_compute_rejects_invalid_count_metric() {
551-
let err = parse_compute_raw("count(@duration)").unwrap_err();
552-
assert!(err.to_string().contains("does not accept a field"));
553-
}
554-
555404
#[test]
556405
fn test_normalize_storage_tier_alias() {
557406
let tier = normalize_storage_tier(Some("online_archives".into())).unwrap();

src/commands/traces.rs

Lines changed: 1 addition & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -80,64 +80,9 @@ fn validate_sort(sort: &str) -> Result<()> {
8080
}
8181
}
8282

83-
/// Parse a compute string like "count", "avg(@duration)", "percentile(@duration, 99)"
84-
/// into a (function_name, Option<metric>) pair as raw strings.
85-
fn parse_compute_raw(input: &str) -> Result<(String, Option<String>)> {
86-
let input = input.trim();
87-
if input.is_empty() {
88-
bail!("--compute is required");
89-
}
90-
91-
// Simple aggregations without a metric
92-
if input == "count" {
93-
return Ok(("count".into(), None));
94-
}
95-
96-
// func(@field) pattern
97-
if let Some(paren) = input.find('(') {
98-
let func = &input[..paren];
99-
let rest = input[paren + 1..].trim_end_matches(')').trim();
100-
101-
// Handle percentile(@field, N)
102-
if func == "percentile" {
103-
let parts: Vec<&str> = rest.splitn(2, ',').collect();
104-
if parts.len() != 2 {
105-
bail!("percentile requires field and value: percentile(@duration, 99)");
106-
}
107-
let metric = parts[0].trim().to_string();
108-
let pct: u32 = parts[1]
109-
.trim()
110-
.parse()
111-
.map_err(|_| anyhow::anyhow!("invalid percentile value: {}", parts[1].trim()))?;
112-
let agg_name = match pct {
113-
75 => "pc75",
114-
90 => "pc90",
115-
95 => "pc95",
116-
98 => "pc98",
117-
99 => "pc99",
118-
_ => bail!("unsupported percentile: {pct} (supported: 75, 90, 95, 98, 99)"),
119-
};
120-
return Ok((agg_name.into(), Some(metric)));
121-
}
122-
123-
let metric = rest.to_string();
124-
let agg_name = match func {
125-
"avg" | "sum" | "min" | "max" | "median" | "cardinality" => func.to_string(),
126-
"count" => bail!("count does not accept a field argument; use just 'count'"),
127-
_ => bail!("unknown aggregation function: {func}"),
128-
};
129-
return Ok((agg_name, Some(metric)));
130-
}
131-
132-
bail!(
133-
"invalid --compute format: {input:?}\n\
134-
Expected: count, avg(@duration), sum(@duration), percentile(@duration, 99), etc."
135-
)
136-
}
137-
13883
/// Parse a compute string into (SpansAggregationFunction, Option<metric>).
13984
fn parse_compute(input: &str) -> Result<(SpansAggregationFunction, Option<String>)> {
140-
let (func, metric) = parse_compute_raw(input)?;
85+
let (func, metric) = util::parse_compute_raw(input)?;
14186
let agg = match func.as_str() {
14287
"count" => SpansAggregationFunction::COUNT,
14388
"avg" => SpansAggregationFunction::AVG,

0 commit comments

Comments
 (0)