From ccb09aaa83fb6f285786c731b043f6728dfdab6a Mon Sep 17 00:00:00 2001 From: PSeitz Date: Thu, 8 Jun 2023 15:07:08 +0800 Subject: [PATCH] allow histogram bounds to be passed as Rfc3339 (#2076) --- .../bucket/histogram/date_histogram.rs | 36 +++++++++++++++++++ src/aggregation/bucket/histogram/histogram.rs | 27 ++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/aggregation/bucket/histogram/date_histogram.rs b/src/aggregation/bucket/histogram/date_histogram.rs index aa024177c..6528616d4 100644 --- a/src/aggregation/bucket/histogram/date_histogram.rs +++ b/src/aggregation/bucket/histogram/date_histogram.rs @@ -604,6 +604,42 @@ mod tests { }); assert_eq!(res, expected_res); } + + { + // 1day + hard_bounds as Rfc3339 + let elasticsearch_compatible_json = json!( + { + "sales_over_time": { + "date_histogram": { + "field": "date", + "fixed_interval": "1d", + "hard_bounds": { + "min": "2015-01-02T00:00:00Z", + "max": "2015-01-02T12:00:00Z" + } + } + } + } + ); + + let agg_req: Aggregations = serde_json::from_str( + &serde_json::to_string(&elasticsearch_compatible_json).unwrap(), + ) + .unwrap(); + let res = exec_request(agg_req, &index).unwrap(); + let expected_res = json!({ + "sales_over_time" : { + "buckets": [ + { + "doc_count": 1, + "key": 1420156800000.0, + "key_as_string": "2015-01-02T00:00:00Z" + } + ] + } + }); + assert_eq!(res, expected_res); + } } #[test] fn histogram_test_invalid_req() { diff --git a/src/aggregation/bucket/histogram/histogram.rs b/src/aggregation/bucket/histogram/histogram.rs index 678a8e1e6..3ea8d52db 100644 --- a/src/aggregation/bucket/histogram/histogram.rs +++ b/src/aggregation/bucket/histogram/histogram.rs @@ -177,11 +177,38 @@ impl HistogramAggregation { #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub struct HistogramBounds { /// The lower bounds. + #[serde(deserialize_with = "deserialize_date_or_num")] pub min: f64, /// The upper bounds. + #[serde(deserialize_with = "deserialize_date_or_num")] pub max: f64, } +fn deserialize_date_or_num<'de, D>(deserializer: D) -> Result +where D: serde::Deserializer<'de> { + let value: serde_json::Value = Deserialize::deserialize(deserializer)?; + + // Check if the value is a string representing an Rfc3339 formatted date + if let serde_json::Value::String(date_str) = value { + // Parse the Rfc3339 formatted date string into a DateTime + let date = + time::OffsetDateTime::parse(&date_str, &time::format_description::well_known::Rfc3339) + .map_err(|_| serde::de::Error::custom("Invalid Rfc3339 formatted date"))?; + + let milliseconds: i64 = (date.unix_timestamp_nanos() / 1_000_000) + .try_into() + .map_err(|_| serde::de::Error::custom("{date_str} out of allowed range"))?; + + // Return the milliseconds as f64 + Ok(milliseconds as f64) + } else { + // The value is not a string, so assume it's a regular f64 number + value + .as_f64() + .ok_or_else(|| serde::de::Error::custom("Invalid number format")) + } +} + impl Display for HistogramBounds { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("[{},{}]", self.min, self.max))