@@ -18,6 +18,7 @@ use trogon_source_notion::NotionVerificationToken;
1818use trogon_source_sentry:: SentryClientSecret ;
1919use trogon_source_slack:: config:: SlackSigningSecret ;
2020use trogon_source_telegram:: config:: TelegramWebhookSecret ;
21+ use trogon_source_twitter:: config:: TwitterConsumerSecret ;
2122use trogon_std:: { NonZeroDuration , ZeroDuration } ;
2223
2324use crate :: source_status:: SourceStatus ;
@@ -198,6 +199,8 @@ struct SourcesConfig {
198199 #[ config( nested) ]
199200 telegram : TelegramConfig ,
200201 #[ config( nested) ]
202+ twitter : TwitterConfig ,
203+ #[ config( nested) ]
201204 gitlab : GitlabConfig ,
202205 #[ config( nested) ]
203206 incidentio : IncidentioConfig ,
@@ -277,6 +280,22 @@ struct TelegramConfig {
277280 nats_ack_timeout_secs : u64 ,
278281}
279282
283+ #[ derive( Config ) ]
284+ struct TwitterConfig {
285+ #[ config( env = "TROGON_SOURCE_TWITTER_STATUS" ) ]
286+ status : Option < String > ,
287+ #[ config( env = "TROGON_SOURCE_TWITTER_CONSUMER_SECRET" ) ]
288+ consumer_secret : Option < String > ,
289+ #[ config( env = "TROGON_SOURCE_TWITTER_SUBJECT_PREFIX" , default = "twitter" ) ]
290+ subject_prefix : String ,
291+ #[ config( env = "TROGON_SOURCE_TWITTER_STREAM_NAME" , default = "TWITTER" ) ]
292+ stream_name : String ,
293+ #[ config( env = "TROGON_SOURCE_TWITTER_STREAM_MAX_AGE_SECS" , default = 604_800 ) ]
294+ stream_max_age_secs : u64 ,
295+ #[ config( env = "TROGON_SOURCE_TWITTER_NATS_ACK_TIMEOUT_SECS" , default = 10 ) ]
296+ nats_ack_timeout_secs : u64 ,
297+ }
298+
280299#[ derive( Config ) ]
281300struct GitlabConfig {
282301 #[ config( env = "TROGON_SOURCE_GITLAB_STATUS" ) ]
@@ -382,6 +401,7 @@ pub struct ResolvedConfig {
382401 pub discord : Option < trogon_source_discord:: DiscordConfig > ,
383402 pub slack : Option < trogon_source_slack:: SlackConfig > ,
384403 pub telegram : Option < trogon_source_telegram:: TelegramSourceConfig > ,
404+ pub twitter : Option < trogon_source_twitter:: TwitterConfig > ,
385405 pub gitlab : Option < trogon_source_gitlab:: GitlabConfig > ,
386406 pub incidentio : Option < trogon_source_incidentio:: IncidentioConfig > ,
387407 pub linear : Option < trogon_source_linear:: LinearConfig > ,
@@ -395,6 +415,7 @@ impl ResolvedConfig {
395415 || self . discord . is_some ( )
396416 || self . slack . is_some ( )
397417 || self . telegram . is_some ( )
418+ || self . twitter . is_some ( )
398419 || self . gitlab . is_some ( )
399420 || self . incidentio . is_some ( )
400421 || self . linear . is_some ( )
@@ -424,6 +445,7 @@ fn resolve(cfg: GatewayConfig, nats_overrides: &NatsArgs) -> Result<ResolvedConf
424445 let discord = resolve_discord ( cfg. sources . discord , & mut errors) ;
425446 let slack = resolve_slack ( cfg. sources . slack , & mut errors) ;
426447 let telegram = resolve_telegram ( cfg. sources . telegram , & mut errors) ;
448+ let twitter = resolve_twitter ( cfg. sources . twitter , & mut errors) ;
427449 let gitlab = resolve_gitlab ( cfg. sources . gitlab , & mut errors) ;
428450 let incidentio = resolve_incidentio ( cfg. sources . incidentio , & mut errors) ;
429451 let linear = resolve_linear ( cfg. sources . linear , & mut errors) ;
@@ -456,6 +478,7 @@ fn resolve(cfg: GatewayConfig, nats_overrides: &NatsArgs) -> Result<ResolvedConf
456478 discord,
457479 slack,
458480 telegram,
481+ twitter,
459482 gitlab,
460483 incidentio,
461484 linear,
@@ -798,6 +821,84 @@ fn resolve_telegram(
798821 } )
799822}
800823
824+ fn resolve_twitter (
825+ section : TwitterConfig ,
826+ errors : & mut Vec < ConfigValidationError > ,
827+ ) -> Option < trogon_source_twitter:: TwitterConfig > {
828+ if !resolve_source_status ( "twitter" , section. status . as_deref ( ) , errors) {
829+ return None ;
830+ }
831+
832+ let secret_str = section. consumer_secret ?;
833+ let consumer_secret = match TwitterConsumerSecret :: new ( secret_str) {
834+ Ok ( secret) => secret,
835+ Err ( error) => {
836+ errors. push ( ConfigValidationError :: invalid (
837+ "twitter" ,
838+ "consumer_secret" ,
839+ error,
840+ ) ) ;
841+ return None ;
842+ }
843+ } ;
844+
845+ let subject_prefix = match NatsToken :: new ( section. subject_prefix ) {
846+ Ok ( token) => token,
847+ Err ( error) => {
848+ errors. push ( ConfigValidationError :: invalid_subject_token (
849+ "twitter" ,
850+ "subject_prefix" ,
851+ error,
852+ ) ) ;
853+ return None ;
854+ }
855+ } ;
856+
857+ let stream_name = match NatsToken :: new ( section. stream_name ) {
858+ Ok ( token) => token,
859+ Err ( error) => {
860+ errors. push ( ConfigValidationError :: invalid_subject_token (
861+ "twitter" ,
862+ "stream_name" ,
863+ error,
864+ ) ) ;
865+ return None ;
866+ }
867+ } ;
868+
869+ let nats_ack_timeout = match NonZeroDuration :: from_secs ( section. nats_ack_timeout_secs ) {
870+ Ok ( duration) => duration,
871+ Err ( error) => {
872+ errors. push ( ConfigValidationError :: invalid (
873+ "twitter" ,
874+ "nats_ack_timeout_secs" ,
875+ error,
876+ ) ) ;
877+ return None ;
878+ }
879+ } ;
880+
881+ let stream_max_age = match StreamMaxAge :: from_secs ( section. stream_max_age_secs ) {
882+ Ok ( age) => age,
883+ Err ( error) => {
884+ errors. push ( ConfigValidationError :: invalid (
885+ "twitter" ,
886+ "stream_max_age_secs" ,
887+ error,
888+ ) ) ;
889+ return None ;
890+ }
891+ } ;
892+
893+ Some ( trogon_source_twitter:: TwitterConfig {
894+ consumer_secret,
895+ subject_prefix,
896+ stream_name,
897+ stream_max_age,
898+ nats_ack_timeout,
899+ } )
900+ }
901+
801902fn resolve_gitlab (
802903 section : GitlabConfig ,
803904 errors : & mut Vec < ConfigValidationError > ,
@@ -1305,6 +1406,15 @@ webhook_secret = "{secret}"
13051406 )
13061407 }
13071408
1409+ fn twitter_toml ( secret : & str ) -> String {
1410+ format ! (
1411+ r#"
1412+ [sources.twitter]
1413+ consumer_secret = "{secret}"
1414+ "#
1415+ )
1416+ }
1417+
13081418 fn gitlab_toml ( secret : & str ) -> String {
13091419 format ! (
13101420 r#"
@@ -1536,6 +1646,25 @@ webhook_secret = "telegram-webhook-secret"
15361646 assert ! ( cfg. telegram. is_none( ) ) ;
15371647 }
15381648
1649+ #[ test]
1650+ fn twitter_resolves_with_valid_consumer_secret ( ) {
1651+ let f = write_toml ( & twitter_toml ( "twitter-consumer-secret" ) ) ;
1652+ let cfg = load ( Some ( f. path ( ) ) ) . expect ( "load failed" ) ;
1653+ assert ! ( cfg. twitter. is_some( ) ) ;
1654+ }
1655+
1656+ #[ test]
1657+ fn twitter_disabled_returns_none ( ) {
1658+ let toml = r#"
1659+ [sources.twitter]
1660+ status = "disabled"
1661+ consumer_secret = "twitter-consumer-secret"
1662+ "# ;
1663+ let f = write_toml ( toml) ;
1664+ let cfg = load ( Some ( f. path ( ) ) ) . expect ( "load failed" ) ;
1665+ assert ! ( cfg. twitter. is_none( ) ) ;
1666+ }
1667+
15391668 #[ test]
15401669 fn gitlab_resolves_with_valid_secret ( ) {
15411670 let f = write_toml ( & gitlab_toml ( "gitlab-webhook-secret" ) ) ;
0 commit comments