@@ -39,6 +39,35 @@ use crate::{
3939 storage:: object_storage:: { alert_json_path, alert_state_json_path} ,
4040} ;
4141
42+ const RESERVED_FIELDS : & [ & str ] = & [
43+ "version" ,
44+ "id" ,
45+ "severity" ,
46+ "title" ,
47+ "query" ,
48+ "datasets" ,
49+ "alertType" ,
50+ "alert_type" ,
51+ "anomalyConfig" ,
52+ "anomaly_config" ,
53+ "forecastConfig" ,
54+ "forecast_config" ,
55+ "thresholdConfig" ,
56+ "threshold_config" ,
57+ "evalConfig" ,
58+ "eval_config" ,
59+ "targets" ,
60+ "state" ,
61+ "notificationState" ,
62+ "notification_state" ,
63+ "notificationConfig" ,
64+ "notification_config" ,
65+ "created" ,
66+ "tags" ,
67+ "lastTriggeredAt" ,
68+ "last_triggered_at" ,
69+ ] ;
70+
4271/// Helper struct for basic alert fields during migration
4372pub struct BasicAlertFields {
4473 pub id : Ulid ,
@@ -261,15 +290,29 @@ pub struct AlertRequest {
261290impl AlertRequest {
262291 pub async fn into ( self ) -> Result < AlertConfig , AlertError > {
263292 // Validate that other_fields doesn't contain reserved field names
264- if let Some ( ref other_fields) = self . other_fields {
293+ let other_fields = if let Some ( mut other_fields) = self . other_fields {
265294 // Limit other_fields to maximum 10 fields
266295 if other_fields. len ( ) > 10 {
267296 return Err ( AlertError :: ValidationFailure ( format ! (
268297 "other_fields can contain at most 10 fields, found {}" ,
269298 other_fields. len( )
270299 ) ) ) ;
271300 }
272- }
301+
302+ for reserved in RESERVED_FIELDS {
303+ if other_fields. remove ( * reserved) . is_some ( ) {
304+ tracing:: warn!( "Removed reserved field '{}' from other_fields" , reserved) ;
305+ }
306+ }
307+
308+ if other_fields. is_empty ( ) {
309+ None
310+ } else {
311+ Some ( other_fields)
312+ }
313+ } else {
314+ None
315+ } ;
273316
274317 // Validate that all target IDs exist
275318 for id in & self . targets {
@@ -283,6 +326,8 @@ impl AlertRequest {
283326 ) ) ) ;
284327 }
285328
329+ let created_timestamp = Utc :: now ( ) ;
330+
286331 let config = AlertConfig {
287332 version : AlertVersion :: from ( CURRENT_ALERTS_VERSION ) ,
288333 id : Ulid :: new ( ) ,
@@ -320,11 +365,12 @@ impl AlertRequest {
320365 state : AlertState :: default ( ) ,
321366 notification_state : NotificationState :: Notify ,
322367 notification_config : self . notification_config ,
323- created : Utc :: now ( ) ,
368+ created : created_timestamp ,
324369 tags : self . tags ,
325370 last_triggered_at : None ,
326- other_fields : self . other_fields ,
371+ other_fields,
327372 } ;
373+
328374 Ok ( config)
329375 }
330376}
@@ -384,6 +430,25 @@ pub struct AlertConfigResponse {
384430}
385431
386432impl AlertConfig {
433+ /// Filters out reserved field names from other_fields
434+ /// This prevents conflicts when flattening other_fields during serialization
435+ pub fn sanitize_other_fields ( & mut self ) {
436+ if let Some ( ref mut other_fields) = self . other_fields {
437+ for reserved in RESERVED_FIELDS {
438+ if other_fields. remove ( * reserved) . is_some ( ) {
439+ tracing:: warn!(
440+ "Removed reserved field '{}' from other_fields during sanitization" ,
441+ reserved
442+ ) ;
443+ }
444+ }
445+
446+ if other_fields. is_empty ( ) {
447+ self . other_fields = None ;
448+ }
449+ }
450+ }
451+
387452 pub fn to_response ( self ) -> AlertConfigResponse {
388453 AlertConfigResponse {
389454 version : self . version ,
0 commit comments