@@ -2,6 +2,7 @@ use jsonpath_lib::select;
22use lazy_static:: lazy_static;
33use regex:: { Regex , RegexBuilder } ;
44use serde_json:: Value ;
5+ use std:: borrow:: Cow ;
56use std:: { env, fmt, fs} ;
67
78mod cache;
@@ -48,13 +49,16 @@ pub struct Command {
4849pub enum CommandKind {
4950 Has ,
5051 Count ,
52+ Is ,
53+ Set ,
5154}
5255
5356impl CommandKind {
5457 fn validate ( & self , args : & [ String ] , command_num : usize , lineno : usize ) -> bool {
5558 let count = match self {
5659 CommandKind :: Has => ( 1 ..=3 ) . contains ( & args. len ( ) ) ,
57- CommandKind :: Count => 3 == args. len ( ) ,
60+ CommandKind :: Count | CommandKind :: Is => 3 == args. len ( ) ,
61+ CommandKind :: Set => 4 == args. len ( ) ,
5862 } ;
5963
6064 if !count {
@@ -83,6 +87,8 @@ impl fmt::Display for CommandKind {
8387 let text = match self {
8488 CommandKind :: Has => "has" ,
8589 CommandKind :: Count => "count" ,
90+ CommandKind :: Is => "is" ,
91+ CommandKind :: Set => "set" ,
8692 } ;
8793 write ! ( f, "{}" , text)
8894 }
@@ -127,6 +133,8 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
127133 let cmd = match cmd {
128134 "has" => CommandKind :: Has ,
129135 "count" => CommandKind :: Count ,
136+ "is" => CommandKind :: Is ,
137+ "set" => CommandKind :: Set ,
130138 _ => {
131139 print_err ( & format ! ( "Unrecognized command name `@{}`" , cmd) , lineno) ;
132140 errors = true ;
@@ -180,6 +188,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
180188/// Performs the actual work of ensuring a command passes. Generally assumes the command
181189/// is syntactically valid.
182190fn check_command ( command : Command , cache : & mut Cache ) -> Result < ( ) , CkError > {
191+ // FIXME: Be more granular about why, (e.g. syntax error, count not equal)
183192 let result = match command. kind {
184193 CommandKind :: Has => {
185194 match command. args . len ( ) {
@@ -188,23 +197,15 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
188197 // @has <path> <jsonpath> = check path exists
189198 2 => {
190199 let val = cache. get_value ( & command. args [ 0 ] ) ?;
191-
192- match select ( & val, & command. args [ 1 ] ) {
193- Ok ( results) => !results. is_empty ( ) ,
194- Err ( _) => false ,
195- }
200+ let results = select ( & val, & command. args [ 1 ] ) . unwrap ( ) ;
201+ !results. is_empty ( )
196202 }
197203 // @has <path> <jsonpath> <value> = check *any* item matched by path equals value
198204 3 => {
199205 let val = cache. get_value ( & command. args [ 0 ] ) ?;
200- match select ( & val, & command. args [ 1 ] ) {
201- Ok ( results) => {
202- let pat: Value = serde_json:: from_str ( & command. args [ 2 ] ) . unwrap ( ) ;
203-
204- !results. is_empty ( ) && results. into_iter ( ) . any ( |val| * val == pat)
205- }
206- Err ( _) => false ,
207- }
206+ let results = select ( & val, & command. args [ 1 ] ) . unwrap ( ) ;
207+ let pat = string_to_value ( & command. args [ 2 ] , cache) ;
208+ results. contains ( & pat. as_ref ( ) )
208209 }
209210 _ => unreachable ! ( ) ,
210211 }
@@ -215,9 +216,37 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
215216 let expected: usize = command. args [ 2 ] . parse ( ) . unwrap ( ) ;
216217
217218 let val = cache. get_value ( & command. args [ 0 ] ) ?;
218- match select ( & val, & command. args [ 1 ] ) {
219- Ok ( results) => results. len ( ) == expected,
220- Err ( _) => false ,
219+ let results = select ( & val, & command. args [ 1 ] ) . unwrap ( ) ;
220+ results. len ( ) == expected
221+ }
222+ CommandKind :: Is => {
223+ // @has <path> <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
224+ assert_eq ! ( command. args. len( ) , 3 ) ;
225+ let val = cache. get_value ( & command. args [ 0 ] ) ?;
226+ let results = select ( & val, & command. args [ 1 ] ) . unwrap ( ) ;
227+ let pat = string_to_value ( & command. args [ 2 ] , cache) ;
228+ results. len ( ) == 1 && results[ 0 ] == pat. as_ref ( )
229+ }
230+ CommandKind :: Set => {
231+ // @set <name> = <path> <jsonpath>
232+ assert_eq ! ( command. args. len( ) , 4 ) ;
233+ assert_eq ! ( command. args[ 1 ] , "=" , "Expected an `=`" ) ;
234+ let val = cache. get_value ( & command. args [ 2 ] ) ?;
235+ let results = select ( & val, & command. args [ 3 ] ) . unwrap ( ) ;
236+ assert_eq ! ( results. len( ) , 1 ) ;
237+ match results. len ( ) {
238+ 0 => false ,
239+ 1 => {
240+ let r = cache. variables . insert ( command. args [ 0 ] . clone ( ) , results[ 0 ] . clone ( ) ) ;
241+ assert ! ( r. is_none( ) , "Name collision: {} is duplicated" , command. args[ 0 ] ) ;
242+ true
243+ }
244+ _ => {
245+ panic ! (
246+ "Got multiple results in `@set` for `{}`: {:?}" ,
247+ & command. args[ 3 ] , results
248+ ) ;
249+ }
221250 }
222251 }
223252 } ;
@@ -247,3 +276,11 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
247276 Ok ( ( ) )
248277 }
249278}
279+
280+ fn string_to_value < ' a > ( s : & str , cache : & ' a Cache ) -> Cow < ' a , Value > {
281+ if s. starts_with ( "$" ) {
282+ Cow :: Borrowed ( & cache. variables [ & s[ 1 ..] ] )
283+ } else {
284+ Cow :: Owned ( serde_json:: from_str ( s) . unwrap ( ) )
285+ }
286+ }
0 commit comments