@@ -2,6 +2,7 @@ use jsonpath_lib::select;
2
2
use lazy_static:: lazy_static;
3
3
use regex:: { Regex , RegexBuilder } ;
4
4
use serde_json:: Value ;
5
+ use std:: borrow:: Cow ;
5
6
use std:: { env, fmt, fs} ;
6
7
7
8
mod cache;
@@ -48,13 +49,16 @@ pub struct Command {
48
49
pub enum CommandKind {
49
50
Has ,
50
51
Count ,
52
+ Is ,
53
+ Set ,
51
54
}
52
55
53
56
impl CommandKind {
54
57
fn validate ( & self , args : & [ String ] , command_num : usize , lineno : usize ) -> bool {
55
58
let count = match self {
56
59
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 ( ) ,
58
62
} ;
59
63
60
64
if !count {
@@ -83,6 +87,8 @@ impl fmt::Display for CommandKind {
83
87
let text = match self {
84
88
CommandKind :: Has => "has" ,
85
89
CommandKind :: Count => "count" ,
90
+ CommandKind :: Is => "is" ,
91
+ CommandKind :: Set => "set" ,
86
92
} ;
87
93
write ! ( f, "{}" , text)
88
94
}
@@ -127,6 +133,8 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
127
133
let cmd = match cmd {
128
134
"has" => CommandKind :: Has ,
129
135
"count" => CommandKind :: Count ,
136
+ "is" => CommandKind :: Is ,
137
+ "set" => CommandKind :: Set ,
130
138
_ => {
131
139
print_err ( & format ! ( "Unrecognized command name `@{}`" , cmd) , lineno) ;
132
140
errors = true ;
@@ -180,6 +188,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
180
188
/// Performs the actual work of ensuring a command passes. Generally assumes the command
181
189
/// is syntactically valid.
182
190
fn check_command ( command : Command , cache : & mut Cache ) -> Result < ( ) , CkError > {
191
+ // FIXME: Be more granular about why, (e.g. syntax error, count not equal)
183
192
let result = match command. kind {
184
193
CommandKind :: Has => {
185
194
match command. args . len ( ) {
@@ -188,23 +197,15 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
188
197
// @has <path> <jsonpath> = check path exists
189
198
2 => {
190
199
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 ( )
196
202
}
197
203
// @has <path> <jsonpath> <value> = check *any* item matched by path equals value
198
204
3 => {
199
205
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 ( ) )
208
209
}
209
210
_ => unreachable ! ( ) ,
210
211
}
@@ -215,9 +216,37 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
215
216
let expected: usize = command. args [ 2 ] . parse ( ) . unwrap ( ) ;
216
217
217
218
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
+ }
221
250
}
222
251
}
223
252
} ;
@@ -247,3 +276,11 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
247
276
Ok ( ( ) )
248
277
}
249
278
}
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