github.com/viant/toolbox@v0.34.5/data/parser_test.go (about) 1 package data 2 3 import ( 4 "fmt" 5 "github.com/stretchr/testify/assert" 6 "github.com/viant/toolbox" 7 "sort" 8 "strings" 9 "testing" 10 ) 11 12 func IndexOf(source interface{}, state Map) (interface{}, error) { 13 if !toolbox.IsSlice(source) { 14 return nil, fmt.Errorf("expected arguments but had: %T", source) 15 } 16 args := toolbox.AsSlice(source) 17 if len(args) != 2 { 18 return nil, fmt.Errorf("expected 2 arguments but had: %v", len(args)) 19 } 20 21 collection := toolbox.AsSlice(args[0]) 22 for i, candidate := range collection { 23 if candidate == args[1] || toolbox.AsString(candidate) == toolbox.AsString(args[1]) { 24 return i, nil 25 } 26 } 27 return -1, nil 28 } 29 30 func TestParseExpression(t *testing.T) { 31 var useCases = []struct { 32 description string 33 aMap Map 34 expression string 35 expected interface{} 36 }{ 37 { 38 description: "simple variable", 39 aMap: Map(map[string]interface{}{ 40 "k1": 123, 41 }), 42 expression: "$k1", 43 expected: 123, 44 }, 45 { 46 description: "simple enclosed variable", 47 aMap: Map(map[string]interface{}{ 48 "k1": 123, 49 }), 50 expression: "${k1}", 51 expected: 123, 52 }, 53 54 { 55 description: "simple embedding", 56 aMap: Map(map[string]interface{}{ 57 "k1": 123, 58 }), 59 expression: "abc $k1 xyz", 60 expected: "abc 123 xyz", 61 }, 62 63 { 64 description: "simple embedding", 65 aMap: Map(map[string]interface{}{ 66 "k1": 123, 67 }), 68 expression: "abc $k1/xyz", 69 expected: "abc 123/xyz", 70 }, 71 72 { 73 description: "double embedding", 74 aMap: Map(map[string]interface{}{ 75 "k1": 123, 76 "k2": 88, 77 }), 78 expression: "abc $k1 xyz $k2 ", 79 expected: "abc 123 xyz 88 ", 80 }, 81 82 { 83 description: "enclosing ", 84 aMap: Map(map[string]interface{}{ 85 "k1": 123, 86 "k2": 88, 87 }), 88 expression: "abc ${k1} xyz $k2 ", 89 expected: "abc 123 xyz 88 ", 90 }, 91 92 { 93 description: "enclosing and partialy unexpanded", 94 aMap: Map(map[string]interface{}{ 95 "k1": 123, 96 "k2": 88, 97 }), 98 expression: " $z1 abc ${k1} xyz $k2 ", 99 expected: " $z1 abc 123 xyz 88 ", 100 }, 101 102 { 103 description: "sub key access", 104 aMap: Map(map[string]interface{}{ 105 "k2": map[string]interface{}{ 106 "z": 111, 107 "x": 333, 108 }, 109 }), 110 expression: "abc ${k2.z} xyz $k2.x/ ", 111 expected: "abc 111 xyz 333/ ", 112 }, 113 114 { 115 description: "slice & nested access", 116 aMap: Map(map[string]interface{}{ 117 "array": []interface{}{ 118 map[string]interface{}{ 119 "z": 111, 120 "x": map[string]interface{}{ 121 "k": 444, 122 }, 123 "y": []interface{}{"a", "b"}, 124 }, 125 }, 126 }), 127 expression: "abc $array[0].z $array[0].y[0]* !${array[0].x.k}#$array[0].x.k", 128 expected: "abc 111 a* !444#444", 129 }, 130 131 { 132 description: "slice with index variable", 133 aMap: Map(map[string]interface{}{ 134 "i": 1, 135 "array": []interface{}{ 136 111, 222, 333, 137 }, 138 }), 139 expression: "$array[$i]", 140 expected: 222, 141 }, 142 { 143 description: "slice with index variable", 144 aMap: Map(map[string]interface{}{ 145 "i": 2, 146 "array": []interface{}{ 147 111, 222, 333, 148 }, 149 }), 150 expression: "$array[${i}]", 151 expected: 333, 152 }, 153 { 154 description: "slice with index variable", 155 aMap: Map(map[string]interface{}{ 156 "i": 2, 157 "array": []interface{}{ 158 111, 222, 333, 159 }, 160 }), 161 expression: "${array[${i}]}", 162 expected: 333, 163 }, 164 { 165 description: "variable func", 166 aMap: Map(map[string]interface{}{ 167 "f": func(key interface{}, state Map) (interface{}, error) { 168 return "test " + toolbox.AsString(key), nil 169 }, 170 }), 171 expression: "$f(123)", 172 expected: "test 123", 173 }, 174 { 175 description: "variable func", 176 aMap: Map(map[string]interface{}{ 177 "f": func(key interface{}, state Map) (interface{}, error) { 178 return "test " + toolbox.AsString(key), nil 179 }, 180 }), 181 expression: "a $f(123) b", 182 expected: "a test 123 b", 183 }, 184 { 185 description: "variable func", 186 aMap: Map(map[string]interface{}{ 187 "f": func(key interface{}, state Map) (interface{}, error) { 188 return "test " + toolbox.AsString(key), nil 189 }, 190 }), 191 expression: "a ${f(123)} b", 192 expected: "a test 123 b", 193 }, 194 195 { 196 description: "variable func with unexpanded variables", 197 aMap: Map(map[string]interface{}{ 198 "f": func(key interface{}, state Map) (interface{}, error) { 199 return "test " + toolbox.AsString(key), nil 200 }, 201 }), 202 expression: "${a()} ${f(123)} $b()", 203 expected: "${a()} test 123 $b()", 204 }, 205 206 { 207 description: "variable func with slice arguments", 208 aMap: Map(map[string]interface{}{ 209 "f": func(args interface{}, state Map) (interface{}, error) { 210 211 aSlice := toolbox.AsSlice(args) 212 textSlice := []string{} 213 for _, item := range aSlice { 214 textSlice = append(textSlice, toolbox.AsString(item)) 215 } 216 return strings.Join(textSlice, ":"), nil 217 }, 218 }), 219 expression: `! $f(["a", "b", "c"]) !`, 220 expected: "! a:b:c !", 221 }, 222 { 223 description: "variable func with aMap arguments", 224 aMap: Map(map[string]interface{}{ 225 "f": func(args interface{}, state Map) (interface{}, error) { 226 aMap := toolbox.AsMap(args) 227 aSlice := []string{} 228 for k, v := range aMap { 229 aSlice = append(aSlice, toolbox.AsString(fmt.Sprintf("%v->%v", k, v))) 230 } 231 sort.Strings(aSlice) 232 return strings.Join(aSlice, ":"), nil 233 }, 234 }), 235 expression: `! $f({"a":1, "b":2, "c":3}) !`, 236 expected: "! a->1:b->2:c->3 !", 237 }, 238 239 { 240 description: "slice element shift", 241 aMap: Map(map[string]interface{}{ 242 "s": []interface{}{3, 2, 1}, 243 }), 244 expression: `! $<-s ${<-s} !`, 245 expected: "! 3 2 !", 246 }, 247 { 248 description: "element inc", 249 aMap: Map(map[string]interface{}{ 250 "i": 2, 251 "j": 5, 252 }), 253 expression: `!${i++}/${i}/${++i}!`, 254 expected: "!2/3/4!", 255 }, 256 257 { 258 description: "basic arithmetic", 259 aMap: Map(map[string]interface{}{ 260 "i": 1, 261 "j": 2, 262 "k": 0.4, 263 }), 264 expression: `${(i + j) / 2}`, 265 expected: 1.5, 266 }, 267 { 268 description: "enclosed basic arithmetic", 269 aMap: Map(map[string]interface{}{ 270 "i": 1, 271 "j": 2, 272 "k": 0.4, 273 }), 274 expression: `z${(i + j) / 2}z`, 275 expected: "z1.5z", 276 }, 277 { 278 description: "multi arithmetic", 279 aMap: Map(map[string]interface{}{ 280 "i": 1, 281 "j": 2, 282 "k": 0.4, 283 }), 284 expression: `${10 + 1 - 2}`, 285 expected: 9, 286 }, 287 { 288 description: "sub attribute arithmetic", 289 aMap: Map(map[string]interface{}{ 290 "i": 1, 291 "j": 2, 292 "k": map[string]interface{}{ 293 "z": 0.4, 294 }, 295 "s": []interface{}{10}, 296 }), 297 expression: `${k.z * s[0]}`, 298 expected: 4, 299 }, 300 { 301 description: "unexpanded ", 302 aMap: Map(map[string]interface{}{ 303 "index": 1, 304 }), 305 expression: `${index}*`, 306 expected: "1*", 307 }, 308 309 { 310 description: "unexpanded ", 311 aMap: Map(map[string]interface{}{ 312 "i": 1, 313 }), 314 expression: `[]Orders,`, 315 expected: "[]Orders,", 316 }, 317 318 { 319 description: "unexpanded tags", 320 aMap: Map(map[string]interface{}{ 321 "tag": "Root", 322 "tagId": "Root", 323 }), 324 expression: `[]Orders,Id,Name,LineItems,SubTotal`, 325 expected: "[]Orders,Id,Name,LineItems,SubTotal", 326 }, 327 { 328 description: "unexpanded dolar", 329 aMap: Map(map[string]interface{}{ 330 "tag": "Root", 331 }), 332 expression: `$`, 333 expected: "$", 334 }, 335 { 336 description: "unexpanded enclosed dolar", 337 aMap: Map(map[string]interface{}{ 338 "tag": "Root", 339 }), 340 expression: `a/$/z`, 341 expected: "a/$/z", 342 }, 343 { 344 description: "udf with text argument", 345 aMap: Map(map[string]interface{}{ 346 "r": func(key interface{}, state Map) (interface{}, error) { 347 return true, nil 348 }, 349 }), 350 expression: `$r(use_cases/001_event_processing_use_case/skip.txt):true`, 351 expected: "true:true", 352 }, 353 354 { 355 description: "int conversion", 356 aMap: Map(map[string]interface{}{ 357 "AsInt": func(source interface{}, state Map) (interface{}, error) { 358 return toolbox.AsInt(source), nil 359 }, 360 }), 361 expression: `z $AsInt(3434)`, 362 expected: "z 3434", 363 }, 364 365 { 366 description: "int conversion", 367 aMap: Map(map[string]interface{}{ 368 "dailyCap": 100, 369 "overallCap": 2, 370 "AsFloat": func(source interface{}, state Map) (interface{}, error) { 371 return toolbox.AsFloat(source), nil 372 }, 373 }), 374 expression: `{ 375 "DAILY_CAP": "$AsFloat($dailyCap)" 376 }`, 377 expected: "{\n\t\t \"DAILY_CAP\": \"100\"\n\t\t}", 378 }, 379 380 { 381 description: "post increment", 382 aMap: map[string]interface{}{ 383 "i": 0, 384 "z": 3, 385 }, 386 expression: "$i++ $i $z++ $z", 387 expected: "0 1 3 4", 388 }, 389 390 { 391 description: "pre increment", 392 aMap: map[string]interface{}{ 393 "i": 10, 394 "z": 20, 395 }, 396 expression: "$++i $i $++z $z", 397 expected: "11 11 21 21", 398 }, 399 400 { 401 description: "arguments as text glitch", 402 aMap: map[string]interface{}{ 403 "f": func(source interface{}, state Map) (interface{}, error) { 404 return source, nil 405 }, 406 }, 407 expression: "#$f(554257_popularmechanics.com)#", 408 expected: "#554257_popularmechanics.com#", 409 }, 410 411 { 412 description: "embedded UDF expression", 413 aMap: map[string]interface{}{ 414 "IndexOf": IndexOf, 415 "collection": []interface{}{"abc", "xtz"}, 416 "key": "abc", 417 }, 418 expression: `$IndexOf($collection, $key)`, 419 expected: 0, 420 }, 421 { 422 description: "embedded UDF expression with literal", 423 aMap: map[string]interface{}{ 424 "IndexOf": IndexOf, 425 "collection": []interface{}{"abc", "xtz"}, 426 "key": "abc", 427 }, 428 expression: `$IndexOf($collection, xtz)`, 429 expected: 1, 430 }, 431 432 { 433 description: "multi udf neating", 434 aMap: map[string]interface{}{ 435 "IndexOf": IndexOf, 436 "collection": []interface{}{"abc", "xtz"}, 437 "key": "abc", 438 }, 439 expression: `$IndexOf($collection, xtz)`, 440 expected: 1, 441 }, 442 { 443 description: "unresolved expression", 444 aMap: map[string]interface{}{ 445 "IndexOf": IndexOf, 446 "collection": []interface{}{"abc", "xtz"}, 447 "key": "abc", 448 }, 449 expression: `${$appPath}/hello/main.zip`, 450 expected: `${$appPath}/hello/main.zip`, 451 }, 452 { 453 description: "resolved expression", 454 aMap: map[string]interface{}{ 455 "appPath": "/abc/a", 456 }, 457 expression: `${appPath}/hello/main.zip`, 458 expected: `/abc/a/hello/main.zip`, 459 }, 460 461 { 462 description: "byte extraction", 463 aMap: map[string]interface{}{ 464 "Payload": []byte{ 465 34, 466 72, 467 101, 468 108, 469 108, 470 111, 471 32, 472 87, 473 111, 474 114, 475 108, 476 100, 477 34, 478 }, 479 "AsString": func(source interface{}, state Map) (interface{}, error) { 480 return toolbox.AsString(source), nil 481 }, 482 }, 483 expression: `$AsString($Payload)`, 484 expected: `"Hello World"`, 485 }, 486 } 487 488 //$Join($AsCollection($Cat($env.APP_HOME/app-config/schema/go/3.json)), “,”) 489 490 for _, useCase := range useCases { 491 var expandHandler = func(expression string, isUDF bool, argument interface{}) (interface{}, bool) { 492 result, has := useCase.aMap.GetValue(string(expression[1:])) 493 if isUDF { 494 if udf, ok := result.(func(interface{}, Map) (interface{}, error)); ok { 495 expandedArgs := useCase.aMap.expandArgumentsExpressions(argument) 496 if toolbox.IsString(expandedArgs) && toolbox.IsStructuredJSON(toolbox.AsString(expandedArgs)) { 497 if evaluated, err := toolbox.JSONToInterface(toolbox.AsString(expandedArgs)); err == nil { 498 expandedArgs = evaluated 499 } 500 } 501 result, err := udf(expandedArgs, nil) 502 return result, err == nil 503 } 504 } 505 return result, has 506 } 507 actual := Parse(useCase.expression, expandHandler) 508 if !assert.Equal(t, useCase.expected, actual, useCase.description) { 509 fmt.Printf("!%v!\n", actual) 510 } 511 } 512 }