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 } ;
56use crate :: rando;
67use futures:: future:: { join, ready, TryFutureExt } ;
78use futures:: stream:: { self , StreamExt } ;
89use reqwest:: Url ;
910use serde:: de:: { Deserialize , IntoDeserializer } ;
1011use snafu:: { futures:: try_future:: TryFutureExt as SnafuTryFutureExt , OptionExt , ResultExt } ;
12+ use std:: convert:: TryFrom ;
1113use std:: 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,36 @@ 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 ( error:: 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+
99+ error:: NoResolverSnafu {
100+ input_source : input. to_string ( ) ,
101+ }
102+ . fail ( )
110103}
111104
112105/// Takes a string of TOML or JSON settings data and reserializes
@@ -142,11 +135,11 @@ fn format_change(input: &str, input_source: &str) -> Result<String> {
142135 serde_json:: to_string ( & json_inner) . context ( error:: JsonSerializeSnafu { input_source } )
143136}
144137
145- mod error {
138+ pub ( crate ) mod error {
146139 use snafu:: Snafu ;
147140
148141 #[ derive( Debug , Snafu ) ]
149- #[ snafu( visibility( pub ( super ) ) ) ]
142+ #[ snafu( visibility( pub ( crate ) ) ) ]
150143 pub enum Error {
151144 #[ snafu( display( "Failed to commit combined settings to '{}': {}" , uri, source) ) ]
152145 CommitApply {
@@ -164,6 +157,9 @@ mod error {
164157 #[ snafu( display( "Given invalid file URI '{}'" , input_source) ) ]
165158 FileUri { input_source : String } ,
166159
160+ #[ snafu( display( "No URI resolver found for '{}'" , input_source) ) ]
161+ NoResolver { input_source : String } ,
162+
167163 #[ snafu( display(
168164 "Input '{}' is not valid TOML or JSON. (TOML error: {}) (JSON error: {})" ,
169165 input_source,
@@ -218,6 +214,12 @@ mod error {
218214 source : reqwest:: Error ,
219215 } ,
220216
217+ #[ snafu( display( "Given invalid file URI '{}'" , input_source) ) ]
218+ InvalidFileUri { input_source : String } ,
219+
220+ #[ snafu( display( "Given HTTP(S) URI '{}'" , input_source) ) ]
221+ InvalidHTTPUri { input_source : String } ,
222+
221223 #[ snafu( display( "Failed to read standard input: {}" , source) ) ]
222224 StdinRead { source : std:: io:: Error } ,
223225
0 commit comments