@@ -9,23 +9,25 @@ use convert_case::{Case, Casing};
99use itertools:: Itertools ;
1010use spacetimedb_lib:: sats:: { self , AlgebraicType , Typespace } ;
1111use spacetimedb_lib:: { Identity , ProductTypeElement } ;
12- use spacetimedb_schema:: def:: { ModuleDef , ReducerDef } ;
12+ use spacetimedb_schema:: def:: { ModuleDef , ProcedureDef , ReducerDef } ;
1313use std:: fmt:: Write ;
1414
1515use super :: sql:: parse_req;
1616
1717pub fn cli ( ) -> clap:: Command {
1818 clap:: Command :: new ( "call" )
19- . about ( format ! ( "Invokes a reducer function in a database. {UNSTABLE_WARNING}" ) )
19+ . about ( format ! (
20+ "Invokes a function (reducer or procedure) in a database. {UNSTABLE_WARNING}"
21+ ) )
2022 . arg (
2123 Arg :: new ( "database" )
2224 . required ( true )
2325 . help ( "The database name or identity to use to invoke the call" ) ,
2426 )
2527 . arg (
26- Arg :: new ( "reducer_name " )
28+ Arg :: new ( "function_name " )
2729 . required ( true )
28- . help ( "The name of the reducer to call" ) ,
30+ . help ( "The name of the function to call" ) ,
2931 )
3032 . arg ( Arg :: new ( "arguments" ) . help ( "arguments formatted as JSON" ) . num_args ( 1 ..) )
3133 . arg ( common_args:: server ( ) . help ( "The nickname, host name or URL of the server hosting the database" ) )
@@ -34,9 +36,35 @@ pub fn cli() -> clap::Command {
3436 . after_help ( "Run `spacetime help call` for more detailed information.\n " )
3537}
3638
39+ enum CallDef < ' a > {
40+ Reducer ( & ' a ReducerDef ) ,
41+ Procedure ( & ' a ProcedureDef ) ,
42+ }
43+
44+ impl < ' a > CallDef < ' a > {
45+ fn params ( & self ) -> & ' a sats:: ProductType {
46+ match self {
47+ CallDef :: Reducer ( reducer_def) => & reducer_def. params ,
48+ CallDef :: Procedure ( procedure_def) => & procedure_def. params ,
49+ }
50+ }
51+ fn name ( & self ) -> & str {
52+ match self {
53+ CallDef :: Reducer ( reducer_def) => & reducer_def. name ,
54+ CallDef :: Procedure ( procedure_def) => & procedure_def. name ,
55+ }
56+ }
57+ fn kind ( & self ) -> & str {
58+ match self {
59+ CallDef :: Reducer ( _) => "reducer" ,
60+ CallDef :: Procedure ( _) => "procedure" ,
61+ }
62+ }
63+ }
64+
3765pub async fn exec ( config : Config , args : & ArgMatches ) -> Result < ( ) , Error > {
3866 eprintln ! ( "{UNSTABLE_WARNING}\n " ) ;
39- let reducer_name = args. get_one :: < String > ( "reducer_name " ) . unwrap ( ) ;
67+ let reducer_procedure_name = args. get_one :: < String > ( "function_name " ) . unwrap ( ) ;
4068 let arguments = args. get_many :: < String > ( "arguments" ) ;
4169
4270 let conn = parse_req ( config, args) . await ?;
@@ -47,14 +75,25 @@ pub async fn exec(config: Config, args: &ArgMatches) -> Result<(), Error> {
4775
4876 let module_def: ModuleDef = api. module_def ( ) . await ?. try_into ( ) ?;
4977
50- let reducer_def = module_def
51- . reducer ( & * * reducer_name)
52- . ok_or_else ( || anyhow:: Error :: msg ( no_such_reducer ( & database_identity, database, reducer_name, & module_def) ) ) ?;
78+ let call_def = match module_def. reducer ( & * * reducer_procedure_name) {
79+ Some ( reducer_def) => CallDef :: Reducer ( reducer_def) ,
80+ None => match module_def. procedure ( & * * reducer_procedure_name) {
81+ Some ( procedure_def) => CallDef :: Procedure ( procedure_def) ,
82+ None => {
83+ return Err ( anyhow:: Error :: msg ( no_such_reducer_or_procedure (
84+ & database_identity,
85+ database,
86+ reducer_procedure_name,
87+ & module_def,
88+ ) ) ) ;
89+ }
90+ } ,
91+ } ;
5392
5493 // String quote any arguments that should be quoted
5594 let arguments = arguments
5695 . unwrap_or_default ( )
57- . zip ( & * reducer_def . params . elements )
96+ . zip ( & call_def . params ( ) . elements )
5897 . map ( |( argument, element) | match & element. algebraic_type {
5998 AlgebraicType :: String if !argument. starts_with ( '\"' ) || !argument. ends_with ( '\"' ) => {
6099 format ! ( "\" {argument}\" " )
@@ -63,7 +102,7 @@ pub async fn exec(config: Config, args: &ArgMatches) -> Result<(), Error> {
63102 } ) ;
64103
65104 let arg_json = format ! ( "[{}]" , arguments. format( ", " ) ) ;
66- let res = api. call ( reducer_name , arg_json) . await ?;
105+ let res = api. call ( reducer_procedure_name , arg_json) . await ?;
67106
68107 if let Err ( e) = res. error_for_status_ref ( ) {
69108 let Ok ( response_text) = res. text ( ) . await else {
@@ -73,31 +112,34 @@ pub async fn exec(config: Config, args: &ArgMatches) -> Result<(), Error> {
73112
74113 let error = Err ( e) . context ( format ! ( "Response text: {response_text}" ) ) ;
75114
76- let error_msg = if response_text. starts_with ( "no such reducer" ) {
77- no_such_reducer ( & database_identity, database, reducer_name, & module_def)
78- } else if response_text. starts_with ( "invalid arguments" ) {
79- invalid_arguments ( & database_identity, database, & response_text, & module_def, reducer_def)
80- } else {
81- return error;
82- } ;
115+ let error_msg =
116+ if response_text. starts_with ( "no such reducer" ) || response_text. starts_with ( "no such procedure" ) {
117+ no_such_reducer_or_procedure ( & database_identity, database, reducer_procedure_name, & module_def)
118+ } else if response_text. starts_with ( "invalid arguments" ) {
119+ invalid_arguments ( & database_identity, database, & response_text, & module_def, call_def)
120+ } else {
121+ return error;
122+ } ;
83123
84124 return error. context ( error_msg) ;
85125 }
86126
127+ if let CallDef :: Procedure ( _) = call_def {
128+ let body = res. text ( ) . await ?;
129+ println ! ( "{body}" ) ;
130+ }
131+
87132 Ok ( ( ) )
88133}
89134
90135/// Returns an error message for when `reducer` is called with wrong arguments.
91- fn invalid_arguments (
92- identity : & Identity ,
93- db : & str ,
94- text : & str ,
95- module_def : & ModuleDef ,
96- reducer_def : & ReducerDef ,
97- ) -> String {
136+ fn invalid_arguments ( identity : & Identity , db : & str , text : & str , module_def : & ModuleDef , call_def : CallDef ) -> String {
98137 let mut error = format ! (
99- "Invalid arguments provided for reducer `{}` for database `{}` resolving to identity `{}`." ,
100- reducer_def. name, db, identity
138+ "Invalid arguments provided for {} `{}` for database `{}` resolving to identity `{}`." ,
139+ call_def. kind( ) ,
140+ call_def. name( ) ,
141+ db,
142+ identity
101143 ) ;
102144
103145 if let Some ( ( actual, expected) ) = find_actual_expected ( text) . filter ( |( a, e) | a != e) {
@@ -110,8 +152,9 @@ fn invalid_arguments(
110152
111153 write ! (
112154 error,
113- "\n \n The reducer has the following signature:\n \t {}" ,
114- ReducerSignature ( module_def. typespace( ) . with_type( reducer_def) )
155+ "\n \n The {} has the following signature:\n \t {}" ,
156+ call_def. kind( ) ,
157+ CallSignature ( module_def. typespace( ) . with_type( & call_def) )
115158 )
116159 . unwrap ( ) ;
117160
@@ -138,18 +181,18 @@ fn split_at_first_substring<'t>(text: &'t str, substring: &str) -> Option<(&'t s
138181}
139182
140183/// Provided the `schema_json` for the database,
141- /// returns the signature for a reducer with `reducer_name `.
142- struct ReducerSignature < ' a > ( sats:: WithTypespace < ' a , ReducerDef > ) ;
143- impl std:: fmt:: Display for ReducerSignature < ' _ > {
184+ /// returns the signature for a reducer OR procedure with `name `.
185+ struct CallSignature < ' a > ( sats:: WithTypespace < ' a , CallDef < ' a > > ) ;
186+ impl std:: fmt:: Display for CallSignature < ' _ > {
144187 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
145- let reducer_def = self . 0 . ty ( ) ;
188+ let call_def = self . 0 . ty ( ) ;
146189 let typespace = self . 0 . typespace ( ) ;
147190
148- write ! ( f, "{}(" , reducer_def . name) ?;
191+ write ! ( f, "{}(" , call_def . name( ) ) ?;
149192
150193 // Print the arguments to `args`.
151194 let mut comma = false ;
152- for arg in & * reducer_def . params . elements {
195+ for arg in & * call_def . params ( ) . elements {
153196 if comma {
154197 write ! ( f, ", " ) ?;
155198 }
@@ -164,51 +207,65 @@ impl std::fmt::Display for ReducerSignature<'_> {
164207 }
165208}
166209
167- /// Returns an error message for when `reducer` does not exist in `db`.
168- fn no_such_reducer ( database_identity : & Identity , db : & str , reducer : & str , module_def : & ModuleDef ) -> String {
169- let mut error =
170- format ! ( "No such reducer `{reducer}` for database `{db}` resolving to identity `{database_identity}`." ) ;
210+ /// Returns an error message for when `reducer` or `procedure` does not exist in `db`.
211+ fn no_such_reducer_or_procedure ( database_identity : & Identity , db : & str , name : & str , module_def : & ModuleDef ) -> String {
212+ let mut error = format ! (
213+ "No such reducer OR procedure `{name}` for database `{db}` resolving to identity `{database_identity}`."
214+ ) ;
171215
172- add_reducer_ctx_to_err ( & mut error, module_def, reducer ) ;
216+ add_reducer_procedure_ctx_to_err ( & mut error, module_def, name ) ;
173217
174218 error
175219}
176220
177- const REDUCER_PRINT_LIMIT : usize = 10 ;
221+ const CALL_PRINT_LIMIT : usize = 10 ;
178222
179223/// Provided the schema for the database,
180- /// decorate `error` with more helpful info about reducers.
181- fn add_reducer_ctx_to_err ( error : & mut String , module_def : & ModuleDef , reducer_name : & str ) {
182- let mut reducers = module_def
224+ /// decorate `error` with more helpful info about reducers and procedures .
225+ fn add_reducer_procedure_ctx_to_err ( error : & mut String , module_def : & ModuleDef , reducer_name : & str ) {
226+ let reducers = module_def
183227 . reducers ( )
184228 . filter ( |reducer| reducer. lifecycle . is_none ( ) )
185229 . map ( |reducer| & * reducer. name )
186230 . collect :: < Vec < _ > > ( ) ;
187231
232+ let procedures = module_def
233+ . procedures ( )
234+ . map ( |reducer| & * reducer. name )
235+ . collect :: < Vec < _ > > ( ) ;
236+
188237 if let Some ( best) = find_best_match_for_name ( & reducers, reducer_name, None ) {
189238 write ! ( error, "\n \n A reducer with a similar name exists: `{best}`" ) . unwrap ( ) ;
190- } else if reducers . is_empty ( ) {
191- write ! ( error, "\n \n The database has no reducers. " ) . unwrap ( ) ;
239+ } else if let Some ( best ) = find_best_match_for_name ( & procedures , reducer_name , None ) {
240+ write ! ( error, "\n \n A procedure with a similar name exists: `{best}` " ) . unwrap ( ) ;
192241 } else {
193- // Sort reducers by relevance.
194- reducers. sort_by_key ( |candidate| edit_distance ( reducer_name, candidate, usize:: MAX ) ) ;
195-
196- // Don't spam the user with too many entries.
197- let too_many_to_show = reducers. len ( ) > REDUCER_PRINT_LIMIT ;
198- let diff = reducers. len ( ) . abs_diff ( REDUCER_PRINT_LIMIT ) ;
199- reducers. truncate ( REDUCER_PRINT_LIMIT ) ;
200-
201- // List them.
202- write ! ( error, "\n \n Here are some existing reducers:" ) . unwrap ( ) ;
203- for candidate in reducers {
204- write ! ( error, "\n - {candidate}" ) . unwrap ( ) ;
205- }
242+ let mut list_similar = |mut list : Vec < & str > , name : & str , kind : & str | {
243+ if list. is_empty ( ) {
244+ write ! ( error, "\n \n The database has no {kind}s." ) . unwrap ( ) ;
245+ return ;
246+ }
247+ list. sort_by_key ( |candidate| edit_distance ( name, candidate, usize:: MAX ) ) ;
206248
207- // When some where not listed, note that are more.
208- if too_many_to_show {
209- let plural = if diff == 1 { "" } else { "s" } ;
210- write ! ( error, "\n ... ({diff} reducer{plural} not shown)" ) . unwrap ( ) ;
211- }
249+ // Don't spam the user with too many entries.
250+ let too_many_to_show = list. len ( ) > CALL_PRINT_LIMIT ;
251+ let diff = list. len ( ) . abs_diff ( CALL_PRINT_LIMIT ) ;
252+ list. truncate ( CALL_PRINT_LIMIT ) ;
253+
254+ // List them.
255+ write ! ( error, "\n \n Here are some existing {kind}s:" ) . unwrap ( ) ;
256+ for candidate in list {
257+ write ! ( error, "\n - {candidate}" ) . unwrap ( ) ;
258+ }
259+
260+ // When somewhere not listed, note that are more.
261+ if too_many_to_show {
262+ let plural = if diff == 1 { "" } else { "s" } ;
263+ write ! ( error, "\n ... ({diff} {kind}{plural} not shown)" ) . unwrap ( ) ;
264+ }
265+ } ;
266+
267+ list_similar ( reducers, reducer_name, "reducer" ) ;
268+ list_similar ( procedures, reducer_name, "procedure" ) ;
212269 }
213270}
214271
0 commit comments