1- use anyhow:: Result ;
1+ use anyhow:: { Context as _ , Result , ensure } ;
22use docs_rs_config:: AppConfig ;
33use docs_rs_env_vars:: maybe_env;
44use docs_rs_types:: { ByteSize , Duration } ;
5+ use url:: Url ;
56
67#[ derive( Debug , bon:: Builder ) ]
78#[ builder( on( _, overwritable) ) ]
@@ -39,10 +40,9 @@ pub struct Config {
3940 #[ builder( default ) ]
4041 pub ( crate ) csp_report_only : bool ,
4142
42- /// Datadog graph embed URLs generated for a 400 × 200 viewport.
43- /// Unset URLs hide the corresponding graphs on the build queue page.
44- pub ( crate ) build_queue_length_graph_url : Option < String > ,
45- pub ( crate ) build_queue_wait_time_graph_url : Option < String > ,
43+ /// Graph embed URLs, displayed in order. An empty list hides the metrics column.
44+ #[ builder( default ) ]
45+ pub ( crate ) build_queue_graph_urls : Vec < Url > ,
4646
4747 // Cache-Control header, for versioned URLs.
4848 // If both are absent, don't generate the header. If only one is present,
@@ -60,18 +60,19 @@ use config_builder::State;
6060
6161impl < S : State > ConfigBuilder < S > {
6262 pub ( crate ) fn load_environment ( self ) -> Result < ConfigBuilder < S > > {
63+ let graph_urls = maybe_env :: < String > ( "DOCSRS_BUILD_QUEUE_GRAPH_URLS" ) ?
64+ . map ( |value| parse_graph_urls ( & value) )
65+ . transpose ( ) ?
66+ . unwrap_or_default ( ) ;
6367 Ok ( self
68+ . build_queue_graph_urls ( graph_urls)
6469 . maybe_cratesio_token ( maybe_env ( "DOCSRS_CRATESIO_TOKEN" ) ?)
6570 . maybe_max_parse_memory ( maybe_env ( "DOCSRS_MAX_PARSE_MEMORY" ) ?)
6671 . maybe_render_threads ( maybe_env ( "DOCSRS_RENDER_THREADS" ) ?)
6772 . maybe_request_timeout ( maybe_env ( "DOCSRS_REQUEST_TIMEOUT" ) ?)
6873 . maybe_report_request_timeouts ( maybe_env ( "DOCSRS_REPORT_REQUEST_TIMEOUTS" ) ?)
6974 . maybe_random_crate_search_view_size ( maybe_env ( "DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE" ) ?)
7075 . maybe_csp_report_only ( maybe_env ( "DOCSRS_CSP_REPORT_ONLY" ) ?)
71- . maybe_build_queue_length_graph_url ( maybe_env ( "DOCSRS_BUILD_QUEUE_LENGTH_GRAPH_URL" ) ?)
72- . maybe_build_queue_wait_time_graph_url ( maybe_env (
73- "DOCSRS_BUILD_QUEUE_WAIT_TIME_GRAPH_URL" ,
74- ) ?)
7576 . maybe_cache_control_stale_while_revalidate ( maybe_env (
7677 "CACHE_CONTROL_STALE_WHILE_REVALIDATE" ,
7778 ) ?)
@@ -101,3 +102,36 @@ impl AppConfig for Config {
101102 Ok ( Self :: builder ( ) . test_config ( ) ?. build ( ) )
102103 }
103104}
105+
106+ fn parse_graph_urls ( value : & str ) -> Result < Vec < Url > > {
107+ value
108+ . split_whitespace ( )
109+ . map ( |value| {
110+ let url = Url :: parse ( value) . context ( "invalid URL in DOCSRS_BUILD_QUEUE_GRAPH_URLS" ) ?;
111+ ensure ! (
112+ matches!( url. scheme( ) , "http" | "https" ) && url. host_str( ) . is_some( ) ,
113+ "DOCSRS_BUILD_QUEUE_GRAPH_URLS requires HTTP(S) URLs"
114+ ) ;
115+ Ok ( url)
116+ } )
117+ . collect ( )
118+ }
119+
120+ #[ cfg( test) ]
121+ mod tests {
122+ use super :: * ;
123+
124+ #[ test]
125+ fn graph_urls_are_validated_and_preserve_order ( ) {
126+ let urls = parse_graph_urls (
127+ "https://app.datadoghq.com/graph/embed?token=a&legend=true\n https://example.com/graph?token=b" ,
128+ )
129+ . unwrap ( ) ;
130+ assert_eq ! ( urls. len( ) , 2 ) ;
131+ assert_eq ! ( urls[ 0 ] . query( ) , Some ( "token=a&legend=true" ) ) ;
132+ assert_eq ! ( urls[ 1 ] . host_str( ) , Some ( "example.com" ) ) ;
133+ assert ! ( parse_graph_urls( " " ) . unwrap( ) . is_empty( ) ) ;
134+ assert ! ( parse_graph_urls( "not-a-url" ) . is_err( ) ) ;
135+ assert ! ( parse_graph_urls( "javascript:alert(1)" ) . is_err( ) ) ;
136+ }
137+ }
0 commit comments