1
- use std:: { collections:: HashMap , process} ;
1
+ use std:: { collections:: hash_map :: Entry , process} ;
2
2
3
3
use anyhow:: anyhow;
4
4
use axum:: {
5
- body:: { to_bytes , Body } ,
5
+ body:: Body ,
6
6
extract:: { Extension , Json , Path , State } ,
7
7
http:: { Request , StatusCode } ,
8
8
middleware:: Next ,
@@ -17,8 +17,11 @@ use serde_json::json;
17
17
use socketioxide:: extract:: { Data , SocketRef } ;
18
18
19
19
use crate :: {
20
- shared:: { call_rpc, filter_json, verify_rune} ,
21
- PluginState , SWAGGER_FALLBACK ,
20
+ shared:: {
21
+ call_rpc, filter_json, generate_response, get_clnrest_manifests, get_content_type,
22
+ get_plugin_methods, handle_custom_paths, merge_params, parse_request_body, verify_rune,
23
+ } ,
24
+ ClnrestMap , PluginState , SWAGGER_FALLBACK ,
22
25
} ;
23
26
24
27
#[ derive( Debug ) ]
@@ -55,7 +58,13 @@ impl IntoResponse for AppError {
55
58
pub async fn list_methods (
56
59
Extension ( plugin) : Extension < Plugin < PluginState > > ,
57
60
) -> Result < Html < String > , AppError > {
58
- match call_rpc ( plugin, "help" , json ! ( HelpRequest { command: None } ) ) . await {
61
+ match call_rpc (
62
+ & plugin. configuration ( ) . rpc_file ,
63
+ "help" ,
64
+ Some ( json ! ( HelpRequest { command: None } ) ) ,
65
+ )
66
+ . await
67
+ {
59
68
Ok ( help_response) => {
60
69
let html_content = process_help_response ( help_response) ;
61
70
Ok ( Html ( html_content) )
@@ -76,7 +85,14 @@ fn process_help_response(help_response: serde_json::Value) -> String {
76
85
let mut processed_html_res = String :: new ( ) ;
77
86
78
87
for row in processed_res. help {
79
- processed_html_res. push_str ( & format ! ( "Command: {}\n " , row. command) ) ;
88
+ processed_html_res. push_str ( & format ! ( "Command: {}<br>" , row. command) ) ;
89
+ if let Some ( clnrest) = row. clnrest {
90
+ processed_html_res. push_str ( & format ! ( "Clnrest path:: {}\n " , clnrest. path) ) ;
91
+ processed_html_res. push_str ( & format ! ( "Clnrest method: {}\n " , clnrest. method) ) ;
92
+ processed_html_res
93
+ . push_str ( & format ! ( "Clnrest content-type: {}\n " , clnrest. content_type) ) ;
94
+ processed_html_res. push_str ( & format ! ( "Clnrest rune: {}\n " , clnrest. rune) ) ;
95
+ }
80
96
processed_html_res. push_str ( line) ;
81
97
}
82
98
@@ -86,9 +102,9 @@ fn process_help_response(help_response: serde_json::Value) -> String {
86
102
/* Handler for calling RPC methods */
87
103
#[ utoipa:: path(
88
104
post,
89
- path = "/v1/{rpc_method }" ,
105
+ path = "/v1/{rpc_method_or_path }" ,
90
106
responses(
91
- ( status = 201 , description = "Call rpc method" , body = serde_json:: Value ) ,
107
+ ( status = 201 , description = "Call rpc method by name or custom path " , body = serde_json:: Value ) ,
92
108
( status = 401 , description = "Unauthorized" , body = serde_json:: Value ) ,
93
109
( status = 403 , description = "Forbidden" , body = serde_json:: Value ) ,
94
110
( status = 404 , description = "Not Found" , body = serde_json:: Value ) ,
@@ -98,67 +114,93 @@ fn process_help_response(help_response: serde_json::Value) -> String {
98
114
example = json!( { } ) ) ,
99
115
security( ( "api_key" = [ ] ) )
100
116
) ]
101
- pub async fn call_rpc_method (
102
- Path ( rpc_method ) : Path < String > ,
117
+ pub async fn post_rpc_method (
118
+ Path ( path ) : Path < String > ,
103
119
headers : axum:: http:: HeaderMap ,
104
120
Extension ( plugin) : Extension < Plugin < PluginState > > ,
105
121
body : Request < Body > ,
106
122
) -> Result < Response , AppError > {
107
- let rune = headers
108
- . get ( "rune" )
109
- . and_then ( |v| v. to_str ( ) . ok ( ) )
110
- . map ( String :: from) ;
123
+ let ( rpc_method, path_params, rest_map) = handle_custom_paths ( & plugin, & path, "POST" ) . await ?;
111
124
112
- let bytes = match to_bytes ( body. into_body ( ) , usize:: MAX ) . await {
113
- Ok ( o) => o,
114
- Err ( e) => {
115
- return Err ( AppError :: InternalServerError ( RpcError {
116
- code : None ,
117
- data : None ,
118
- message : format ! ( "Could not read request body: {}" , e) ,
119
- } ) )
120
- }
121
- } ;
122
-
123
- let mut rpc_params = match serde_json:: from_slice ( & bytes) {
124
- Ok ( o) => o,
125
- Err ( e1) => {
126
- /* it's not json but a form instead */
127
- let form_str = String :: from_utf8 ( bytes. to_vec ( ) ) . unwrap ( ) ;
128
- let mut form_data = HashMap :: new ( ) ;
129
- for pair in form_str. split ( '&' ) {
130
- let mut kv = pair. split ( '=' ) ;
131
- if let ( Some ( key) , Some ( value) ) = ( kv. next ( ) , kv. next ( ) ) {
132
- form_data. insert ( key. to_string ( ) , value. to_string ( ) ) ;
133
- }
134
- }
135
- match serde_json:: to_value ( form_data) {
136
- Ok ( o) => o,
137
- Err ( e2) => {
138
- return Err ( AppError :: InternalServerError ( RpcError {
139
- code : None ,
140
- data : None ,
141
- message : format ! (
142
- "Could not parse json from form data: {}\
143
- Original serde_json error: {}",
144
- e2, e1
145
- ) ,
146
- } ) )
125
+ let mut rpc_params = parse_request_body ( body) . await ?;
126
+
127
+ filter_json ( & mut rpc_params) ;
128
+
129
+ merge_params ( & mut rpc_params, path_params) ?;
130
+
131
+ if rest_map. as_ref ( ) . map_or_else ( || true , |map| map. rune ) {
132
+ let rune = headers
133
+ . get ( "rune" )
134
+ . and_then ( |v| v. to_str ( ) . ok ( ) )
135
+ . map ( String :: from) ;
136
+ verify_rune (
137
+ & plugin. configuration ( ) . rpc_file ,
138
+ rune,
139
+ & rpc_method,
140
+ Some ( rpc_params. clone ( ) ) ,
141
+ )
142
+ . await ?;
143
+ }
144
+
145
+ let content_type = get_content_type ( rest_map) ?;
146
+
147
+ match call_rpc (
148
+ & plugin. configuration ( ) . rpc_file ,
149
+ & rpc_method,
150
+ Some ( rpc_params) ,
151
+ )
152
+ . await
153
+ {
154
+ Ok ( result) => Ok ( generate_response ( result, content_type) ) ,
155
+ Err ( err) => {
156
+ if let Some ( code) = err. code {
157
+ if code == -32601 {
158
+ return Err ( AppError :: NotFound ( err) ) ;
147
159
}
148
160
}
161
+ Err ( AppError :: InternalServerError ( err) )
149
162
}
150
- } ;
163
+ }
164
+ }
151
165
152
- filter_json ( & mut rpc_params) ;
166
+ // Handler for calling RPC methods
167
+ #[ utoipa:: path(
168
+ get,
169
+ path = "/v1/{rpc_method_or_path}" ,
170
+ responses(
171
+ ( status = 201 , description = "Call rpc method by name or custom path" , body = serde_json:: Value ) ,
172
+ ( status = 401 , description = "Unauthorized" , body = serde_json:: Value ) ,
173
+ ( status = 403 , description = "Forbidden" , body = serde_json:: Value ) ,
174
+ ( status = 404 , description = "Not Found" , body = serde_json:: Value ) ,
175
+ ( status = 500 , description = "Server Error" , body = serde_json:: Value )
176
+ ) ,
177
+ security( ( "api_key" = [ ] ) )
178
+ ) ]
179
+ pub async fn get_rpc_method (
180
+ Path ( path) : Path < String > ,
181
+ headers : axum:: http:: HeaderMap ,
182
+ Extension ( plugin) : Extension < Plugin < PluginState > > ,
183
+ ) -> Result < Response , AppError > {
184
+ let ( rpc_method, path_params, rest_map) = handle_custom_paths ( & plugin, & path, "GET" ) . await ?;
153
185
154
- verify_rune ( plugin. clone ( ) , rune, & rpc_method, & rpc_params) . await ?;
186
+ if rest_map. as_ref ( ) . map_or_else ( || true , |map| map. rune ) {
187
+ let rune = headers
188
+ . get ( "rune" )
189
+ . and_then ( |v| v. to_str ( ) . ok ( ) )
190
+ . map ( String :: from) ;
191
+ verify_rune (
192
+ & plugin. configuration ( ) . rpc_file ,
193
+ rune,
194
+ & rpc_method,
195
+ path_params. clone ( ) ,
196
+ )
197
+ . await ?;
198
+ }
155
199
156
- match call_rpc ( plugin, & rpc_method, rpc_params) . await {
157
- Ok ( result) => {
158
- let response_body = Json ( result) ;
159
- let response = ( StatusCode :: CREATED , response_body) . into_response ( ) ;
160
- Ok ( response)
161
- }
200
+ let content_type = get_content_type ( rest_map) ?;
201
+
202
+ match call_rpc ( & plugin. configuration ( ) . rpc_file , & rpc_method, path_params) . await {
203
+ Ok ( result) => Ok ( generate_response ( result, content_type) ) ,
162
204
Err ( err) => {
163
205
if let Some ( code) = err. code {
164
206
if code == -32601 {
@@ -178,11 +220,42 @@ pub async fn handle_notification(
178
220
plugin : Plugin < PluginState > ,
179
221
value : serde_json:: Value ,
180
222
) -> Result < ( ) , anyhow:: Error > {
223
+ log:: debug!( "notification: {}" , value. to_string( ) ) ;
181
224
if let Some ( sht) = value. get ( "shutdown" ) {
182
225
log:: info!( "Got shutdown notification: {}" , sht) ;
183
226
/* This seems to error when subscribing to "*" notifications */
184
227
_ = plugin. shutdown ( ) ;
185
228
process:: exit ( 0 ) ;
229
+ } else if let Some ( p_started) = value. get ( "plugin_started" ) {
230
+ let rpc_methods = get_plugin_methods ( p_started) ;
231
+
232
+ let manifests = get_clnrest_manifests ( & plugin. configuration ( ) . rpc_file ) . await ?;
233
+ let mut rest_paths = plugin. state ( ) . rest_paths . lock ( ) . unwrap ( ) ;
234
+ for rpc_method in rpc_methods. into_iter ( ) {
235
+ let clnrest_data = match manifests. get ( & rpc_method) {
236
+ Some ( c) => c. clone ( ) ,
237
+ None => continue ,
238
+ } ;
239
+ if let Entry :: Vacant ( entry) = rest_paths. entry ( clnrest_data. path . clone ( ) ) {
240
+ log:: info!(
241
+ "Registered custom path `{}` for `{}` via `{}`" ,
242
+ clnrest_data. path,
243
+ rpc_method,
244
+ clnrest_data. method
245
+ ) ;
246
+ entry. insert ( ClnrestMap {
247
+ content_type : clnrest_data. content_type ,
248
+ http_method : clnrest_data. method ,
249
+ rpc_method,
250
+ rune : clnrest_data. rune ,
251
+ } ) ;
252
+ }
253
+ }
254
+ } else if let Some ( p_stopped) = value. get ( "plugin_stopped" ) {
255
+ let rpc_methods = get_plugin_methods ( p_stopped) ;
256
+
257
+ let mut rest_paths = plugin. state ( ) . rest_paths . lock ( ) . unwrap ( ) ;
258
+ rest_paths. retain ( |_, v| !rpc_methods. contains ( & v. rpc_method ) )
186
259
}
187
260
match plugin. state ( ) . notification_sender . send ( value) . await {
188
261
Ok ( ( ) ) => Ok ( ( ) ) ,
@@ -213,7 +286,14 @@ pub async fn header_inspection_middleware(
213
286
. map ( String :: from) ;
214
287
215
288
if upgrade. is_some ( ) {
216
- match verify_rune ( plugin, rune, "listclnrest-notifications" , & json ! ( { } ) ) . await {
289
+ match verify_rune (
290
+ & plugin. configuration ( ) . rpc_file ,
291
+ rune,
292
+ "listclnrest-notifications" ,
293
+ None ,
294
+ )
295
+ . await
296
+ {
217
297
Ok ( ( ) ) => Ok ( next. run ( req) . await ) ,
218
298
Err ( e) => Err ( e) ,
219
299
}
0 commit comments