@@ -15,6 +15,7 @@ use trogon_source_incidentio::config::IncidentioConfig as IncidentioSourceConfig
1515use trogon_source_incidentio:: incidentio_signing_secret:: IncidentioSigningSecret ;
1616use trogon_source_linear:: config:: LinearWebhookSecret ;
1717use trogon_source_notion:: NotionVerificationToken ;
18+ use trogon_source_sentry:: SentryClientSecret ;
1819use trogon_source_slack:: config:: SlackSigningSecret ;
1920use trogon_source_telegram:: config:: TelegramWebhookSecret ;
2021use trogon_std:: { NonZeroDuration , ZeroDuration } ;
@@ -169,6 +170,8 @@ struct SourcesConfig {
169170 linear : LinearConfig ,
170171 #[ config( nested) ]
171172 notion : NotionConfig ,
173+ #[ config( nested) ]
174+ sentry : SentryConfig ,
172175}
173176
174177#[ derive( Config ) ]
@@ -316,6 +319,22 @@ struct NotionConfig {
316319 nats_ack_timeout_secs : u64 ,
317320}
318321
322+ #[ derive( Config ) ]
323+ struct SentryConfig {
324+ #[ config( env = "TROGON_SOURCE_SENTRY_STATUS" ) ]
325+ status : Option < String > ,
326+ #[ config( env = "TROGON_SOURCE_SENTRY_CLIENT_SECRET" ) ]
327+ client_secret : Option < String > ,
328+ #[ config( env = "TROGON_SOURCE_SENTRY_SUBJECT_PREFIX" , default = "sentry" ) ]
329+ subject_prefix : String ,
330+ #[ config( env = "TROGON_SOURCE_SENTRY_STREAM_NAME" , default = "SENTRY" ) ]
331+ stream_name : String ,
332+ #[ config( env = "TROGON_SOURCE_SENTRY_STREAM_MAX_AGE_SECS" , default = 604_800 ) ]
333+ stream_max_age_secs : u64 ,
334+ #[ config( env = "TROGON_SOURCE_SENTRY_NATS_ACK_TIMEOUT_SECS" , default = 10 ) ]
335+ nats_ack_timeout_secs : u64 ,
336+ }
337+
319338pub struct ResolvedHttpServerConfig {
320339 pub port : u16 ,
321340}
@@ -332,6 +351,7 @@ pub struct ResolvedConfig {
332351 pub incidentio : Option < trogon_source_incidentio:: IncidentioConfig > ,
333352 pub linear : Option < trogon_source_linear:: LinearConfig > ,
334353 pub notion : Option < trogon_source_notion:: NotionConfig > ,
354+ pub sentry : Option < trogon_source_sentry:: SentryConfig > ,
335355}
336356
337357impl ResolvedConfig {
@@ -344,6 +364,7 @@ impl ResolvedConfig {
344364 || self . incidentio . is_some ( )
345365 || self . linear . is_some ( )
346366 || self . notion . is_some ( )
367+ || self . sentry . is_some ( )
347368 }
348369}
349370
@@ -372,6 +393,7 @@ fn resolve(cfg: GatewayConfig, nats_overrides: &NatsArgs) -> Result<ResolvedConf
372393 let incidentio = resolve_incidentio ( cfg. sources . incidentio , & mut errors) ;
373394 let linear = resolve_linear ( cfg. sources . linear , & mut errors) ;
374395 let notion = resolve_notion ( cfg. sources . notion , & mut errors) ;
396+ let sentry = resolve_sentry ( cfg. sources . sentry , & mut errors) ;
375397
376398 let nats_max_payload_bytes = match NonZeroUsize :: new ( cfg. nats_max_payload_bytes ) {
377399 Some ( v) => v,
@@ -403,6 +425,7 @@ fn resolve(cfg: GatewayConfig, nats_overrides: &NatsArgs) -> Result<ResolvedConf
403425 incidentio,
404426 linear,
405427 notion,
428+ sentry,
406429 } )
407430}
408431
@@ -1072,6 +1095,88 @@ fn resolve_notion(
10721095 } )
10731096}
10741097
1098+ fn resolve_sentry (
1099+ section : SentryConfig ,
1100+ errors : & mut Vec < ConfigValidationError > ,
1101+ ) -> Option < trogon_source_sentry:: SentryConfig > {
1102+ if !resolve_source_status ( "sentry" , section. status . as_deref ( ) , errors) {
1103+ return None ;
1104+ }
1105+
1106+ let client_secret = match section. client_secret {
1107+ Some ( secret) => match SentryClientSecret :: new ( secret) {
1108+ Ok ( secret) => secret,
1109+ Err ( error) => {
1110+ errors. push ( ConfigValidationError :: invalid (
1111+ "sentry" ,
1112+ "client_secret" ,
1113+ error,
1114+ ) ) ;
1115+ return None ;
1116+ }
1117+ } ,
1118+ None => {
1119+ return None ;
1120+ }
1121+ } ;
1122+
1123+ let subject_prefix = match NatsToken :: new ( section. subject_prefix ) {
1124+ Ok ( token) => token,
1125+ Err ( error) => {
1126+ errors. push ( ConfigValidationError :: invalid_subject_token (
1127+ "sentry" ,
1128+ "subject_prefix" ,
1129+ error,
1130+ ) ) ;
1131+ return None ;
1132+ }
1133+ } ;
1134+
1135+ let stream_name = match NatsToken :: new ( section. stream_name ) {
1136+ Ok ( token) => token,
1137+ Err ( error) => {
1138+ errors. push ( ConfigValidationError :: invalid_subject_token (
1139+ "sentry" ,
1140+ "stream_name" ,
1141+ error,
1142+ ) ) ;
1143+ return None ;
1144+ }
1145+ } ;
1146+
1147+ let nats_ack_timeout = match NonZeroDuration :: from_secs ( section. nats_ack_timeout_secs ) {
1148+ Ok ( duration) => duration,
1149+ Err ( error) => {
1150+ errors. push ( ConfigValidationError :: invalid (
1151+ "sentry" ,
1152+ "nats_ack_timeout_secs" ,
1153+ error,
1154+ ) ) ;
1155+ return None ;
1156+ }
1157+ } ;
1158+
1159+ let stream_max_age = match StreamMaxAge :: from_secs ( section. stream_max_age_secs ) {
1160+ Ok ( age) => age,
1161+ Err ( error) => {
1162+ errors. push ( ConfigValidationError :: invalid (
1163+ "sentry" ,
1164+ "stream_max_age_secs" ,
1165+ error,
1166+ ) ) ;
1167+ return None ;
1168+ }
1169+ } ;
1170+
1171+ Some ( trogon_source_sentry:: SentryConfig {
1172+ client_secret,
1173+ subject_prefix,
1174+ stream_name,
1175+ stream_max_age,
1176+ nats_ack_timeout,
1177+ } )
1178+ }
1179+
10751180fn resolve_source_status (
10761181 source : & ' static str ,
10771182 status : Option < & str > ,
@@ -1185,6 +1290,15 @@ verification_token = "{token}"
11851290 )
11861291 }
11871292
1293+ fn sentry_toml ( secret : & str ) -> String {
1294+ format ! (
1295+ r#"
1296+ [sources.sentry]
1297+ client_secret = "{secret}"
1298+ "#
1299+ )
1300+ }
1301+
11881302 fn incidentio_valid_test_secret ( ) -> String {
11891303 [ "whsec_" , "dGVzdC1zZWNyZXQ=" ] . concat ( )
11901304 }
@@ -1450,6 +1564,25 @@ verification_token = "notion-verification-token-example"
14501564 assert ! ( cfg. notion. is_none( ) ) ;
14511565 }
14521566
1567+ #[ test]
1568+ fn sentry_resolves_with_valid_secret ( ) {
1569+ let f = write_toml ( & sentry_toml ( "sentry-client-secret" ) ) ;
1570+ let cfg = load ( Some ( f. path ( ) ) ) . expect ( "load failed" ) ;
1571+ assert ! ( cfg. sentry. is_some( ) ) ;
1572+ }
1573+
1574+ #[ test]
1575+ fn sentry_disabled_returns_none ( ) {
1576+ let toml = r#"
1577+ [sources.sentry]
1578+ status = "disabled"
1579+ client_secret = "sentry-client-secret"
1580+ "# ;
1581+ let f = write_toml ( toml) ;
1582+ let cfg = load ( Some ( f. path ( ) ) ) . expect ( "load failed" ) ;
1583+ assert ! ( cfg. sentry. is_none( ) ) ;
1584+ }
1585+
14531586 #[ test]
14541587 fn notion_missing_token_returns_none ( ) {
14551588 let toml = r#"
@@ -1992,6 +2125,15 @@ port = 9090
19922125 ) ;
19932126 }
19942127
2128+ #[ test]
2129+ fn sentry_empty_client_secret_is_invalid ( ) {
2130+ let f = write_toml ( & sentry_toml ( "" ) ) ;
2131+ let result = load ( Some ( f. path ( ) ) ) ;
2132+ assert ! (
2133+ matches!( result, Err ( ConfigError :: Validation ( ref errs) ) if errs. iter( ) . any( |e| e. contains( "sentry: invalid client_secret" ) ) )
2134+ ) ;
2135+ }
2136+
19952137 #[ test]
19962138 fn incidentio_invalid_secret_is_invalid ( ) {
19972139 let f = write_toml ( & incidentio_toml ( "whsec_not-base64!" ) ) ;
@@ -2274,6 +2416,34 @@ stream_max_age_secs = 0
22742416 ) ;
22752417 }
22762418
2419+ #[ test]
2420+ fn sentry_zero_nats_ack_timeout_is_error ( ) {
2421+ let toml = r#"
2422+ [sources.sentry]
2423+ client_secret = "sentry-client-secret"
2424+ nats_ack_timeout_secs = 0
2425+ "# ;
2426+ let f = write_toml ( toml) ;
2427+ let result = load ( Some ( f. path ( ) ) ) ;
2428+ assert ! (
2429+ matches!( result, Err ( ConfigError :: Validation ( ref errs) ) if errs. iter( ) . any( |e| e. contains( "sentry: nats_ack_timeout_secs must not be zero" ) ) )
2430+ ) ;
2431+ }
2432+
2433+ #[ test]
2434+ fn sentry_zero_stream_max_age_is_error ( ) {
2435+ let toml = r#"
2436+ [sources.sentry]
2437+ client_secret = "sentry-client-secret"
2438+ stream_max_age_secs = 0
2439+ "# ;
2440+ let f = write_toml ( toml) ;
2441+ let result = load ( Some ( f. path ( ) ) ) ;
2442+ assert ! (
2443+ matches!( result, Err ( ConfigError :: Validation ( ref errs) ) if errs. iter( ) . any( |e| e. contains( "sentry: stream_max_age_secs must not be zero" ) ) )
2444+ ) ;
2445+ }
2446+
22772447 #[ test]
22782448 fn incidentio_zero_timestamp_tolerance_is_error ( ) {
22792449 let toml = format ! (
0 commit comments