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  }