22//! TOML settings files, in the same format as user data, or the JSON equivalent. The inputs are
33//! pulled and applied to the API server in a single transaction.
44
5+ use crate :: uri_resolver:: { StdinUri , FileUri , HttpUri , UriResolver } ;
6+ use crate :: apply:: error:: { UriSnafu , NoResolverSnafu } ;
57use crate :: rando;
68use futures:: future:: { join, ready, TryFutureExt } ;
79use futures:: stream:: { self , StreamExt } ;
810use reqwest:: Url ;
911use serde:: de:: { Deserialize , IntoDeserializer } ;
1012use snafu:: { futures:: try_future:: TryFutureExt as SnafuTryFutureExt , OptionExt , ResultExt } ;
11- use std:: path:: Path ;
13+ use std:: { convert :: TryFrom , path:: Path } ;
1214use tokio:: io:: AsyncReadExt ;
1315
16+
1417/// Reads settings in TOML or JSON format from files at the requested URIs (or from stdin, if given
1518/// "-"), then commits them in a single transaction and applies them to the system.
1619pub async fn apply < P > ( socket_path : P , input_sources : Vec < String > ) -> Result < ( ) >
@@ -67,46 +70,35 @@ where
6770}
6871
6972/// Retrieves the given source location and returns the result in a String.
70- async fn get < S > ( input_source : S ) -> Result < String >
71- where
72- S : Into < String > ,
73- {
74- let input_source = input_source. into ( ) ;
75-
76- // Read from stdin if "-" was given.
77- if input_source == "-" {
78- let mut output = String :: new ( ) ;
79- tokio:: io:: stdin ( )
80- . read_to_string ( & mut output)
81- . context ( error:: StdinReadSnafu )
82- . await ?;
83- return Ok ( output) ;
73+ pub async fn get ( input : & str ) -> Result < String > {
74+ let resolver = select_resolver ( input) ?;
75+ resolver. resolve ( ) . await
76+ }
77+
78+ /// Choose which UriResolver applies to `input` (stdin, file://, http(s)://).
79+ fn select_resolver ( input : & str ) -> Result < Box < dyn UriResolver > > {
80+ // 1) "-" → stdin
81+ if let Ok ( r) = StdinUri :: try_from ( input) {
82+ return Ok ( Box :: new ( r) ) ;
8483 }
8584
86- // Otherwise, the input should be a URI; parse it to know what kind.
87- // Until reqwest handles file:// URIs: https://github.com/seanmonstar/reqwest/issues/178
88- let uri = Url :: parse ( & input_source) . context ( error:: UriSnafu {
89- input_source : & input_source,
90- } ) ?;
91- if uri. scheme ( ) == "file" {
92- // Turn the URI to a file path, and return a future that reads it.
93- let path = uri. to_file_path ( ) . ok ( ) . context ( error:: FileUriSnafu {
94- input_source : & input_source,
95- } ) ?;
96- tokio:: fs:: read_to_string ( path)
97- . context ( error:: FileReadSnafu { input_source } )
98- . await
99- } else {
100- // Return a future that contains the text of the (non-file) URI.
101- reqwest:: get ( uri)
102- . and_then ( |response| ready ( response. error_for_status ( ) ) )
103- . and_then ( |response| response. text ( ) )
104- . context ( error:: ReqwestSnafu {
105- uri : input_source,
106- method : "GET" ,
107- } )
108- . await
85+ // 2) parse as a URL
86+ let url = Url :: parse ( input) . context ( UriSnafu { input_source : input. to_string ( ) } ) ?;
87+
88+ // 3) file://
89+ if let Ok ( r) = FileUri :: try_from ( url. clone ( ) ) {
90+ return Ok ( Box :: new ( r) ) ;
10991 }
92+
93+ // 4) http(s)://
94+ if let Ok ( r) = HttpUri :: try_from ( url. clone ( ) ) {
95+ return Ok ( Box :: new ( r) ) ;
96+ }
97+
98+ NoResolverSnafu {
99+ input_source : input. to_string ( ) ,
100+ }
101+ . fail ( )
110102}
111103
112104/// Takes a string of TOML or JSON settings data and reserializes
@@ -142,11 +134,11 @@ fn format_change(input: &str, input_source: &str) -> Result<String> {
142134 serde_json:: to_string ( & json_inner) . context ( error:: JsonSerializeSnafu { input_source } )
143135}
144136
145- mod error {
137+ pub ( crate ) mod error {
146138 use snafu:: Snafu ;
147139
148140 #[ derive( Debug , Snafu ) ]
149- #[ snafu( visibility( pub ( super ) ) ) ]
141+ #[ snafu( visibility( pub ( crate ) ) ) ]
150142 pub enum Error {
151143 #[ snafu( display( "Failed to commit combined settings to '{}': {}" , uri, source) ) ]
152144 CommitApply {
@@ -164,6 +156,9 @@ mod error {
164156 #[ snafu( display( "Given invalid file URI '{}'" , input_source) ) ]
165157 FileUri { input_source : String } ,
166158
159+ #[ snafu( display( "No URI resolver for '{}'" , input_source) ) ]
160+ NoResolver { input_source : String } ,
161+
167162 #[ snafu( display(
168163 "Input '{}' is not valid TOML or JSON. (TOML error: {}) (JSON error: {})" ,
169164 input_source,
@@ -218,6 +213,12 @@ mod error {
218213 source : reqwest:: Error ,
219214 } ,
220215
216+ #[ snafu( display( "Given invalid file URI '{}'" , input_source) ) ]
217+ InvalidFileUri { input_source : String } ,
218+
219+ #[ snafu( display( "Given HTTP(S) URI '{}'" , input_source) ) ]
220+ InvalidHTTPUri { input_source : String } ,
221+
221222 #[ snafu( display( "Failed to read standard input: {}" , source) ) ]
222223 StdinRead { source : std:: io:: Error } ,
223224
0 commit comments