2
2
//! TOML settings files, in the same format as user data, or the JSON equivalent. The inputs are
3
3
//! pulled and applied to the API server in a single transaction.
4
4
5
+ use crate :: uri_resolver:: { StdinUri , FileUri , HttpUri , UriResolver } ;
6
+ use crate :: apply:: error:: { UriSnafu , NoResolverSnafu } ;
5
7
use crate :: rando;
6
8
use futures:: future:: { join, ready, TryFutureExt } ;
7
9
use futures:: stream:: { self , StreamExt } ;
8
10
use reqwest:: Url ;
9
11
use serde:: de:: { Deserialize , IntoDeserializer } ;
10
12
use snafu:: { futures:: try_future:: TryFutureExt as SnafuTryFutureExt , OptionExt , ResultExt } ;
11
- use std:: path:: Path ;
13
+ use std:: { convert :: TryFrom , path:: Path } ;
12
14
use tokio:: io:: AsyncReadExt ;
13
15
16
+
14
17
/// Reads settings in TOML or JSON format from files at the requested URIs (or from stdin, if given
15
18
/// "-"), then commits them in a single transaction and applies them to the system.
16
19
pub async fn apply < P > ( socket_path : P , input_sources : Vec < String > ) -> Result < ( ) >
@@ -67,46 +70,35 @@ where
67
70
}
68
71
69
72
/// 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) ) ;
84
83
}
85
84
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) ) ;
109
91
}
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 ( )
110
102
}
111
103
112
104
/// 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> {
142
134
serde_json:: to_string ( & json_inner) . context ( error:: JsonSerializeSnafu { input_source } )
143
135
}
144
136
145
- mod error {
137
+ pub ( crate ) mod error {
146
138
use snafu:: Snafu ;
147
139
148
140
#[ derive( Debug , Snafu ) ]
149
- #[ snafu( visibility( pub ( super ) ) ) ]
141
+ #[ snafu( visibility( pub ( crate ) ) ) ]
150
142
pub enum Error {
151
143
#[ snafu( display( "Failed to commit combined settings to '{}': {}" , uri, source) ) ]
152
144
CommitApply {
@@ -164,6 +156,9 @@ mod error {
164
156
#[ snafu( display( "Given invalid file URI '{}'" , input_source) ) ]
165
157
FileUri { input_source : String } ,
166
158
159
+ #[ snafu( display( "No URI resolver for '{}'" , input_source) ) ]
160
+ NoResolver { input_source : String } ,
161
+
167
162
#[ snafu( display(
168
163
"Input '{}' is not valid TOML or JSON. (TOML error: {}) (JSON error: {})" ,
169
164
input_source,
@@ -218,6 +213,12 @@ mod error {
218
213
source : reqwest:: Error ,
219
214
} ,
220
215
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
+
221
222
#[ snafu( display( "Failed to read standard input: {}" , source) ) ]
222
223
StdinRead { source : std:: io:: Error } ,
223
224
0 commit comments