github.com/aavshr/aws-sdk-go@v1.41.3/service/dynamodb/expression/expression_test.go (about)

     1  //go:build go1.7
     2  // +build go1.7
     3  
     4  package expression
     5  
     6  import (
     7  	"reflect"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/aavshr/aws-sdk-go/aws"
    12  	"github.com/aavshr/aws-sdk-go/service/dynamodb"
    13  )
    14  
    15  type exprErrorMode string
    16  
    17  const (
    18  	noExpressionError exprErrorMode = ""
    19  	// invalidEscChar error will occer if the escape char '$' is either followed
    20  	// by an unsupported character or if the escape char is the last character
    21  	invalidEscChar = "invalid escape"
    22  	// outOfRange error will occur if there are more escaped chars than there are
    23  	// actual values to be aliased.
    24  	outOfRange = "out of range"
    25  	// invalidBuilderOperand error will occur if an invalid operand is used
    26  	// as input for Build()
    27  	invalidExpressionBuildOperand = "BuildOperand error"
    28  	// unsetBuilder error will occur if Build() is called on an unset Builder
    29  	unsetBuilder = "unset parameter: Builder"
    30  	// unsetConditionBuilder error will occur if an unset ConditionBuilder is
    31  	// used in WithCondition()
    32  	unsetConditionBuilder = "unset parameter: ConditionBuilder"
    33  )
    34  
    35  func TestBuild(t *testing.T) {
    36  	cases := []struct {
    37  		name     string
    38  		input    Builder
    39  		expected Expression
    40  		err      exprErrorMode
    41  	}{
    42  		{
    43  			name:  "condition",
    44  			input: NewBuilder().WithCondition(Name("foo").Equal(Value(5))),
    45  			expected: Expression{
    46  				expressionMap: map[expressionType]string{
    47  					condition: "#0 = :0",
    48  				},
    49  				namesMap: map[string]*string{
    50  					"#0": aws.String("foo"),
    51  				},
    52  				valuesMap: map[string]*dynamodb.AttributeValue{
    53  					":0": {
    54  						N: aws.String("5"),
    55  					},
    56  				},
    57  			},
    58  		},
    59  		{
    60  			name:  "projection",
    61  			input: NewBuilder().WithProjection(NamesList(Name("foo"), Name("bar"), Name("baz"))),
    62  			expected: Expression{
    63  				expressionMap: map[expressionType]string{
    64  					projection: "#0, #1, #2",
    65  				},
    66  				namesMap: map[string]*string{
    67  					"#0": aws.String("foo"),
    68  					"#1": aws.String("bar"),
    69  					"#2": aws.String("baz"),
    70  				},
    71  			},
    72  		},
    73  		{
    74  			name:  "keyCondition",
    75  			input: NewBuilder().WithKeyCondition(Key("foo").Equal(Value(5))),
    76  			expected: Expression{
    77  				expressionMap: map[expressionType]string{
    78  					keyCondition: "#0 = :0",
    79  				},
    80  				namesMap: map[string]*string{
    81  					"#0": aws.String("foo"),
    82  				},
    83  				valuesMap: map[string]*dynamodb.AttributeValue{
    84  					":0": {
    85  						N: aws.String("5"),
    86  					},
    87  				},
    88  			},
    89  		},
    90  		{
    91  			name:  "filter",
    92  			input: NewBuilder().WithFilter(Name("foo").Equal(Value(5))),
    93  			expected: Expression{
    94  				expressionMap: map[expressionType]string{
    95  					filter: "#0 = :0",
    96  				},
    97  				namesMap: map[string]*string{
    98  					"#0": aws.String("foo"),
    99  				},
   100  				valuesMap: map[string]*dynamodb.AttributeValue{
   101  					":0": {
   102  						N: aws.String("5"),
   103  					},
   104  				},
   105  			},
   106  		},
   107  		{
   108  			name:  "update",
   109  			input: NewBuilder().WithUpdate(Set(Name("foo"), (Value(5)))),
   110  			expected: Expression{
   111  				expressionMap: map[expressionType]string{
   112  					update: "SET #0 = :0\n",
   113  				},
   114  				namesMap: map[string]*string{
   115  					"#0": aws.String("foo"),
   116  				},
   117  				valuesMap: map[string]*dynamodb.AttributeValue{
   118  					":0": {
   119  						N: aws.String("5"),
   120  					},
   121  				},
   122  			},
   123  		},
   124  		{
   125  			name: "compound",
   126  			input: NewBuilder().
   127  				WithCondition(Name("foo").Equal(Value(5))).
   128  				WithFilter(Name("bar").LessThan(Value(6))).
   129  				WithProjection(NamesList(Name("foo"), Name("bar"), Name("baz"))).
   130  				WithKeyCondition(Key("foo").Equal(Value(5))).
   131  				WithUpdate(Set(Name("foo"), Value(5))),
   132  			expected: Expression{
   133  				expressionMap: map[expressionType]string{
   134  					condition:    "#0 = :0",
   135  					filter:       "#1 < :1",
   136  					projection:   "#0, #1, #2",
   137  					keyCondition: "#0 = :2",
   138  					update:       "SET #0 = :3\n",
   139  				},
   140  				namesMap: map[string]*string{
   141  					"#0": aws.String("foo"),
   142  					"#1": aws.String("bar"),
   143  					"#2": aws.String("baz"),
   144  				},
   145  				valuesMap: map[string]*dynamodb.AttributeValue{
   146  					":0": {
   147  						N: aws.String("5"),
   148  					},
   149  					":1": {
   150  						N: aws.String("6"),
   151  					},
   152  					":2": {
   153  						N: aws.String("5"),
   154  					},
   155  					":3": {
   156  						N: aws.String("5"),
   157  					},
   158  				},
   159  			},
   160  		},
   161  		{
   162  			name:  "invalid Builder",
   163  			input: NewBuilder().WithCondition(Name("").Equal(Value(5))),
   164  			err:   invalidExpressionBuildOperand,
   165  		},
   166  		{
   167  			name:  "unset Builder",
   168  			input: Builder{},
   169  			err:   unsetBuilder,
   170  		},
   171  	}
   172  	for _, c := range cases {
   173  		t.Run(c.name, func(t *testing.T) {
   174  			actual, err := c.input.Build()
   175  			if c.err != noExpressionError {
   176  				if err == nil {
   177  					t.Errorf("expect error %q, got no error", c.err)
   178  				} else {
   179  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   180  						t.Errorf("expect %q error message to be in %q", e, a)
   181  					}
   182  				}
   183  			} else {
   184  				if err != nil {
   185  					t.Errorf("expect no error, got unexpected Error %q", err)
   186  				}
   187  
   188  				if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   189  					t.Errorf("expect %v, got %v", e, a)
   190  				}
   191  			}
   192  		})
   193  	}
   194  }
   195  
   196  func TestCondition(t *testing.T) {
   197  	cases := []struct {
   198  		name     string
   199  		input    Builder
   200  		expected *string
   201  		err      exprErrorMode
   202  	}{
   203  		{
   204  			name: "condition",
   205  			input: Builder{
   206  				expressionMap: map[expressionType]treeBuilder{
   207  					condition: Name("foo").Equal(Value(5)),
   208  				},
   209  			},
   210  			expected: aws.String("#0 = :0"),
   211  		},
   212  		{
   213  			name:  "unset builder",
   214  			input: Builder{},
   215  			err:   unsetBuilder,
   216  		},
   217  	}
   218  	for _, c := range cases {
   219  		t.Run(c.name, func(t *testing.T) {
   220  			expr, err := c.input.Build()
   221  			if c.err != noExpressionError {
   222  				if err == nil {
   223  					t.Errorf("expect error %q, got no error", c.err)
   224  				} else {
   225  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   226  						t.Errorf("expect %q error message to be in %q", e, a)
   227  					}
   228  				}
   229  			} else {
   230  				if err != nil {
   231  					t.Errorf("expect no error, got unexpected Error %q", err)
   232  				}
   233  			}
   234  			actual := expr.Condition()
   235  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   236  				t.Errorf("expect %v, got %v", e, a)
   237  			}
   238  		})
   239  	}
   240  }
   241  
   242  func TestFilter(t *testing.T) {
   243  	cases := []struct {
   244  		name     string
   245  		input    Builder
   246  		expected *string
   247  		err      exprErrorMode
   248  	}{
   249  		{
   250  			name: "filter",
   251  			input: Builder{
   252  				expressionMap: map[expressionType]treeBuilder{
   253  					filter: Name("foo").Equal(Value(5)),
   254  				},
   255  			},
   256  			expected: aws.String("#0 = :0"),
   257  		},
   258  		{
   259  			name:  "unset builder",
   260  			input: Builder{},
   261  			err:   unsetBuilder,
   262  		},
   263  	}
   264  	for _, c := range cases {
   265  		t.Run(c.name, func(t *testing.T) {
   266  			expr, err := c.input.Build()
   267  			if c.err != noExpressionError {
   268  				if err == nil {
   269  					t.Errorf("expect error %q, got no error", c.err)
   270  				} else {
   271  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   272  						t.Errorf("expect %q error message to be in %q", e, a)
   273  					}
   274  				}
   275  			} else {
   276  				if err != nil {
   277  					t.Errorf("expect no error, got unexpected Error %q", err)
   278  				}
   279  			}
   280  			actual := expr.Filter()
   281  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   282  				t.Errorf("expect %v, got %v", e, a)
   283  			}
   284  		})
   285  	}
   286  }
   287  
   288  func TestProjection(t *testing.T) {
   289  	cases := []struct {
   290  		name     string
   291  		input    Builder
   292  		expected *string
   293  		err      exprErrorMode
   294  	}{
   295  		{
   296  			name: "projection",
   297  			input: Builder{
   298  				expressionMap: map[expressionType]treeBuilder{
   299  					projection: NamesList(Name("foo"), Name("bar"), Name("baz")),
   300  				},
   301  			},
   302  			expected: aws.String("#0, #1, #2"),
   303  		},
   304  		{
   305  			name:  "unset builder",
   306  			input: Builder{},
   307  			err:   unsetBuilder,
   308  		},
   309  	}
   310  	for _, c := range cases {
   311  		t.Run(c.name, func(t *testing.T) {
   312  			expr, err := c.input.Build()
   313  			if c.err != noExpressionError {
   314  				if err == nil {
   315  					t.Errorf("expect error %q, got no error", c.err)
   316  				} else {
   317  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   318  						t.Errorf("expect %q error message to be in %q", e, a)
   319  					}
   320  				}
   321  			} else {
   322  				if err != nil {
   323  					t.Errorf("expect no error, got unexpected Error %q", err)
   324  				}
   325  			}
   326  			actual := expr.Projection()
   327  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   328  				t.Errorf("expect %v, got %v", e, a)
   329  			}
   330  		})
   331  	}
   332  }
   333  
   334  func TestKeyCondition(t *testing.T) {
   335  	cases := []struct {
   336  		name     string
   337  		input    Builder
   338  		expected *string
   339  		err      exprErrorMode
   340  	}{
   341  		{
   342  			name: "keyCondition",
   343  			input: Builder{
   344  				expressionMap: map[expressionType]treeBuilder{
   345  					keyCondition: KeyConditionBuilder{
   346  						operandList: []OperandBuilder{
   347  							KeyBuilder{
   348  								key: "foo",
   349  							},
   350  							ValueBuilder{
   351  								value: 5,
   352  							},
   353  						},
   354  						mode: equalKeyCond,
   355  					},
   356  				},
   357  			},
   358  			expected: aws.String("#0 = :0"),
   359  		},
   360  		{
   361  			name:  "empty builder",
   362  			input: Builder{},
   363  			err:   unsetBuilder,
   364  		},
   365  	}
   366  	for _, c := range cases {
   367  		t.Run(c.name, func(t *testing.T) {
   368  			expr, err := c.input.Build()
   369  			if c.err != noExpressionError {
   370  				if err == nil {
   371  					t.Errorf("expect error %q, got no error", c.err)
   372  				} else {
   373  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   374  						t.Errorf("expect %q error message to be in %q", e, a)
   375  					}
   376  				}
   377  			} else {
   378  				if err != nil {
   379  					t.Errorf("expect no error, got unexpected Error %q", err)
   380  				}
   381  			}
   382  			actual := expr.KeyCondition()
   383  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   384  				t.Errorf("expect %v, got %v", e, a)
   385  			}
   386  		})
   387  	}
   388  }
   389  
   390  func TestUpdate(t *testing.T) {
   391  	cases := []struct {
   392  		name     string
   393  		input    Builder
   394  		expected *string
   395  		err      exprErrorMode
   396  	}{
   397  		{
   398  			name: "update",
   399  			input: Builder{
   400  				expressionMap: map[expressionType]treeBuilder{
   401  					update: UpdateBuilder{
   402  						operationList: map[operationMode][]operationBuilder{
   403  							setOperation: {
   404  								{
   405  									name: NameBuilder{
   406  										name: "foo",
   407  									},
   408  									value: ValueBuilder{
   409  										value: 5,
   410  									},
   411  									mode: setOperation,
   412  								},
   413  							},
   414  						},
   415  					},
   416  				},
   417  			},
   418  			expected: aws.String("SET #0 = :0\n"),
   419  		},
   420  		{
   421  			name: "multiple sets",
   422  			input: Builder{
   423  				expressionMap: map[expressionType]treeBuilder{
   424  					update: UpdateBuilder{
   425  						operationList: map[operationMode][]operationBuilder{
   426  							setOperation: {
   427  								{
   428  									name: NameBuilder{
   429  										name: "foo",
   430  									},
   431  									value: ValueBuilder{
   432  										value: 5,
   433  									},
   434  									mode: setOperation,
   435  								},
   436  								{
   437  									name: NameBuilder{
   438  										name: "bar",
   439  									},
   440  									value: ValueBuilder{
   441  										value: 6,
   442  									},
   443  									mode: setOperation,
   444  								},
   445  								{
   446  									name: NameBuilder{
   447  										name: "baz",
   448  									},
   449  									value: ValueBuilder{
   450  										value: 7,
   451  									},
   452  									mode: setOperation,
   453  								},
   454  							},
   455  						},
   456  					},
   457  				},
   458  			},
   459  			expected: aws.String("SET #0 = :0, #1 = :1, #2 = :2\n"),
   460  		},
   461  		{
   462  			name:  "unset builder",
   463  			input: Builder{},
   464  			err:   unsetBuilder,
   465  		},
   466  	}
   467  	for _, c := range cases {
   468  		t.Run(c.name, func(t *testing.T) {
   469  			expr, err := c.input.Build()
   470  			if c.err != noExpressionError {
   471  				if err == nil {
   472  					t.Errorf("expect error %q, got no error", c.err)
   473  				} else {
   474  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   475  						t.Errorf("expect %q error message to be in %q", e, a)
   476  					}
   477  				}
   478  			} else {
   479  				if err != nil {
   480  					t.Errorf("expect no error, got unexpected Error %q", err)
   481  				}
   482  			}
   483  			actual := expr.Update()
   484  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   485  				t.Errorf("expect %v, got %v", e, a)
   486  			}
   487  		})
   488  	}
   489  }
   490  
   491  func TestNames(t *testing.T) {
   492  	cases := []struct {
   493  		name     string
   494  		input    Builder
   495  		expected map[string]*string
   496  		err      exprErrorMode
   497  	}{
   498  		{
   499  			name: "projection",
   500  			input: Builder{
   501  				expressionMap: map[expressionType]treeBuilder{
   502  					projection: NamesList(Name("foo"), Name("bar"), Name("baz")),
   503  				},
   504  			},
   505  			expected: map[string]*string{
   506  				"#0": aws.String("foo"),
   507  				"#1": aws.String("bar"),
   508  				"#2": aws.String("baz"),
   509  			},
   510  		},
   511  		{
   512  			name: "aggregate",
   513  			input: Builder{
   514  				expressionMap: map[expressionType]treeBuilder{
   515  					condition: ConditionBuilder{
   516  						operandList: []OperandBuilder{
   517  							NameBuilder{
   518  								name: "foo",
   519  							},
   520  							ValueBuilder{
   521  								value: 5,
   522  							},
   523  						},
   524  						mode: equalCond,
   525  					},
   526  					filter: ConditionBuilder{
   527  						operandList: []OperandBuilder{
   528  							NameBuilder{
   529  								name: "bar",
   530  							},
   531  							ValueBuilder{
   532  								value: 6,
   533  							},
   534  						},
   535  						mode: lessThanCond,
   536  					},
   537  					projection: ProjectionBuilder{
   538  						names: []NameBuilder{
   539  							{
   540  								name: "foo",
   541  							},
   542  							{
   543  								name: "bar",
   544  							},
   545  							{
   546  								name: "baz",
   547  							},
   548  						},
   549  					},
   550  				},
   551  			},
   552  			expected: map[string]*string{
   553  				"#0": aws.String("foo"),
   554  				"#1": aws.String("bar"),
   555  				"#2": aws.String("baz"),
   556  			},
   557  		},
   558  		{
   559  			name:  "unset",
   560  			input: Builder{},
   561  			err:   unsetBuilder,
   562  		},
   563  		{
   564  			name:  "unset ConditionBuilder",
   565  			input: NewBuilder().WithCondition(ConditionBuilder{}),
   566  			err:   unsetConditionBuilder,
   567  		},
   568  	}
   569  	for _, c := range cases {
   570  		t.Run(c.name, func(t *testing.T) {
   571  			expr, err := c.input.Build()
   572  			if c.err != noExpressionError {
   573  				if err == nil {
   574  					t.Errorf("expect error %q, got no error", c.err)
   575  				} else {
   576  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   577  						t.Errorf("expect %q error message to be in %q", e, a)
   578  					}
   579  				}
   580  			} else {
   581  				if err != nil {
   582  					t.Errorf("expect no error, got unexpected Error %q", err)
   583  				}
   584  			}
   585  			actual := expr.Names()
   586  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   587  				t.Errorf("expect %v, got %v", e, a)
   588  			}
   589  		})
   590  	}
   591  }
   592  
   593  func TestValues(t *testing.T) {
   594  	cases := []struct {
   595  		name     string
   596  		input    Builder
   597  		expected map[string]*dynamodb.AttributeValue
   598  		err      exprErrorMode
   599  	}{
   600  		{
   601  			name: "empty lists become null",
   602  			input: Builder{
   603  				expressionMap: map[expressionType]treeBuilder{
   604  					update: Name("groups").Equal(Value([]string{})),
   605  				},
   606  			},
   607  			expected: map[string]*dynamodb.AttributeValue{
   608  				":0": {
   609  					NULL: aws.Bool(true),
   610  				},
   611  			},
   612  		},
   613  		{
   614  			name: "dynamodb.AttributeValue values are used directly",
   615  			input: Builder{
   616  				expressionMap: map[expressionType]treeBuilder{
   617  					update: Name("key").Equal(Value(dynamodb.AttributeValue{
   618  						S: aws.String("value"),
   619  					})),
   620  				},
   621  			},
   622  			expected: map[string]*dynamodb.AttributeValue{
   623  				":0": {
   624  					S: aws.String("value"),
   625  				},
   626  			},
   627  		},
   628  		{
   629  			name: "condition",
   630  			input: Builder{
   631  				expressionMap: map[expressionType]treeBuilder{
   632  					condition: Name("foo").Equal(Value(5)),
   633  				},
   634  			},
   635  			expected: map[string]*dynamodb.AttributeValue{
   636  				":0": {
   637  					N: aws.String("5"),
   638  				},
   639  			},
   640  		},
   641  		{
   642  			name: "aggregate",
   643  			input: Builder{
   644  				expressionMap: map[expressionType]treeBuilder{
   645  					condition: ConditionBuilder{
   646  						operandList: []OperandBuilder{
   647  							NameBuilder{
   648  								name: "foo",
   649  							},
   650  							ValueBuilder{
   651  								value: 5,
   652  							},
   653  						},
   654  						mode: equalCond,
   655  					},
   656  					filter: ConditionBuilder{
   657  						operandList: []OperandBuilder{
   658  							NameBuilder{
   659  								name: "bar",
   660  							},
   661  							ValueBuilder{
   662  								value: 6,
   663  							},
   664  						},
   665  						mode: lessThanCond,
   666  					},
   667  					projection: ProjectionBuilder{
   668  						names: []NameBuilder{
   669  							{
   670  								name: "foo",
   671  							},
   672  							{
   673  								name: "bar",
   674  							},
   675  							{
   676  								name: "baz",
   677  							},
   678  						},
   679  					},
   680  				},
   681  			},
   682  			expected: map[string]*dynamodb.AttributeValue{
   683  				":0": {
   684  					N: aws.String("5"),
   685  				},
   686  				":1": {
   687  					N: aws.String("6"),
   688  				},
   689  			},
   690  		},
   691  		{
   692  			name:  "unset",
   693  			input: Builder{},
   694  			err:   unsetBuilder,
   695  		},
   696  	}
   697  	for _, c := range cases {
   698  		t.Run(c.name, func(t *testing.T) {
   699  			expr, err := c.input.Build()
   700  			if c.err != noExpressionError {
   701  				if err == nil {
   702  					t.Errorf("expect error %q, got no error", c.err)
   703  				} else {
   704  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   705  						t.Errorf("expect %q error message to be in %q", e, a)
   706  					}
   707  				}
   708  			} else {
   709  				if err != nil {
   710  					t.Errorf("expect no error, got unexpected Error %q", err)
   711  				}
   712  			}
   713  			actual := expr.Values()
   714  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
   715  				t.Errorf("expect %v, got %v", e, a)
   716  			}
   717  		})
   718  	}
   719  }
   720  
   721  func TestBuildChildTrees(t *testing.T) {
   722  	cases := []struct {
   723  		name              string
   724  		input             Builder
   725  		expectedaliasList aliasList
   726  		expectedStringMap map[expressionType]string
   727  		err               exprErrorMode
   728  	}{
   729  		{
   730  			name: "aggregate",
   731  			input: Builder{
   732  				expressionMap: map[expressionType]treeBuilder{
   733  					condition: ConditionBuilder{
   734  						operandList: []OperandBuilder{
   735  							NameBuilder{
   736  								name: "foo",
   737  							},
   738  							ValueBuilder{
   739  								value: 5,
   740  							},
   741  						},
   742  						mode: equalCond,
   743  					},
   744  					filter: ConditionBuilder{
   745  						operandList: []OperandBuilder{
   746  							NameBuilder{
   747  								name: "bar",
   748  							},
   749  							ValueBuilder{
   750  								value: 6,
   751  							},
   752  						},
   753  						mode: lessThanCond,
   754  					},
   755  					projection: ProjectionBuilder{
   756  						names: []NameBuilder{
   757  							{
   758  								name: "foo",
   759  							},
   760  							{
   761  								name: "bar",
   762  							},
   763  							{
   764  								name: "baz",
   765  							},
   766  						},
   767  					},
   768  				},
   769  			},
   770  			expectedaliasList: aliasList{
   771  				namesList: []string{"foo", "bar", "baz"},
   772  				valuesList: []dynamodb.AttributeValue{
   773  					{
   774  						N: aws.String("5"),
   775  					},
   776  					{
   777  						N: aws.String("6"),
   778  					},
   779  				},
   780  			},
   781  			expectedStringMap: map[expressionType]string{
   782  				condition:  "#0 = :0",
   783  				filter:     "#1 < :1",
   784  				projection: "#0, #1, #2",
   785  			},
   786  		},
   787  		{
   788  			name:              "unset",
   789  			input:             Builder{},
   790  			expectedaliasList: aliasList{},
   791  			expectedStringMap: map[expressionType]string{},
   792  		},
   793  	}
   794  	for _, c := range cases {
   795  		t.Run(c.name, func(t *testing.T) {
   796  			actualAL, actualSM, err := c.input.buildChildTrees()
   797  			if c.err != noExpressionError {
   798  				if err == nil {
   799  					t.Errorf("expect error %q, got no error", c.err)
   800  				} else {
   801  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   802  						t.Errorf("expect %q error message to be in %q", e, a)
   803  					}
   804  				}
   805  			} else {
   806  				if err != nil {
   807  					t.Errorf("expect no error, got unexpected Error %q", err)
   808  				}
   809  			}
   810  			if e, a := c.expectedaliasList, actualAL; !reflect.DeepEqual(a, e) {
   811  				t.Errorf("expect %v, got %v", e, a)
   812  			}
   813  			if e, a := c.expectedStringMap, actualSM; !reflect.DeepEqual(a, e) {
   814  				t.Errorf("expect %v, got %v", e, a)
   815  			}
   816  		})
   817  	}
   818  }
   819  
   820  func TestBuildExpressionString(t *testing.T) {
   821  	cases := []struct {
   822  		name               string
   823  		input              exprNode
   824  		expectedNames      map[string]*string
   825  		expectedValues     map[string]*dynamodb.AttributeValue
   826  		expectedExpression string
   827  		err                exprErrorMode
   828  	}{
   829  		{
   830  			name: "basic name",
   831  			input: exprNode{
   832  				names:   []string{"foo"},
   833  				fmtExpr: "$n",
   834  			},
   835  
   836  			expectedValues: map[string]*dynamodb.AttributeValue{},
   837  			expectedNames: map[string]*string{
   838  				"#0": aws.String("foo"),
   839  			},
   840  			expectedExpression: "#0",
   841  		},
   842  		{
   843  			name: "basic value",
   844  			input: exprNode{
   845  				values: []dynamodb.AttributeValue{
   846  					{
   847  						N: aws.String("5"),
   848  					},
   849  				},
   850  				fmtExpr: "$v",
   851  			},
   852  			expectedNames: map[string]*string{},
   853  			expectedValues: map[string]*dynamodb.AttributeValue{
   854  				":0": {
   855  					N: aws.String("5"),
   856  				},
   857  			},
   858  			expectedExpression: ":0",
   859  		},
   860  		{
   861  			name: "nested path",
   862  			input: exprNode{
   863  				names:   []string{"foo", "bar"},
   864  				fmtExpr: "$n.$n",
   865  			},
   866  
   867  			expectedValues: map[string]*dynamodb.AttributeValue{},
   868  			expectedNames: map[string]*string{
   869  				"#0": aws.String("foo"),
   870  				"#1": aws.String("bar"),
   871  			},
   872  			expectedExpression: "#0.#1",
   873  		},
   874  		{
   875  			name: "nested path with index",
   876  			input: exprNode{
   877  				names:   []string{"foo", "bar", "baz"},
   878  				fmtExpr: "$n.$n[0].$n",
   879  			},
   880  			expectedValues: map[string]*dynamodb.AttributeValue{},
   881  			expectedNames: map[string]*string{
   882  				"#0": aws.String("foo"),
   883  				"#1": aws.String("bar"),
   884  				"#2": aws.String("baz"),
   885  			},
   886  			expectedExpression: "#0.#1[0].#2",
   887  		},
   888  		{
   889  			name: "basic size",
   890  			input: exprNode{
   891  				names:   []string{"foo"},
   892  				fmtExpr: "size ($n)",
   893  			},
   894  			expectedValues: map[string]*dynamodb.AttributeValue{},
   895  			expectedNames: map[string]*string{
   896  				"#0": aws.String("foo"),
   897  			},
   898  			expectedExpression: "size (#0)",
   899  		},
   900  		{
   901  			name: "duplicate path name",
   902  			input: exprNode{
   903  				names:   []string{"foo", "foo"},
   904  				fmtExpr: "$n.$n",
   905  			},
   906  			expectedValues: map[string]*dynamodb.AttributeValue{},
   907  			expectedNames: map[string]*string{
   908  				"#0": aws.String("foo"),
   909  			},
   910  			expectedExpression: "#0.#0",
   911  		},
   912  		{
   913  			name: "equal expression",
   914  			input: exprNode{
   915  				children: []exprNode{
   916  					{
   917  						names:   []string{"foo"},
   918  						fmtExpr: "$n",
   919  					},
   920  					{
   921  						values: []dynamodb.AttributeValue{
   922  							{
   923  								N: aws.String("5"),
   924  							},
   925  						},
   926  						fmtExpr: "$v",
   927  					},
   928  				},
   929  				fmtExpr: "$c = $c",
   930  			},
   931  
   932  			expectedNames: map[string]*string{
   933  				"#0": aws.String("foo"),
   934  			},
   935  			expectedValues: map[string]*dynamodb.AttributeValue{
   936  				":0": {
   937  					N: aws.String("5"),
   938  				},
   939  			},
   940  			expectedExpression: "#0 = :0",
   941  		},
   942  		{
   943  			name: "missing char after $",
   944  			input: exprNode{
   945  				names:   []string{"foo", "foo"},
   946  				fmtExpr: "$n.$",
   947  			},
   948  			err: invalidEscChar,
   949  		},
   950  		{
   951  			name: "names out of range",
   952  			input: exprNode{
   953  				names:   []string{"foo"},
   954  				fmtExpr: "$n.$n",
   955  			},
   956  			err: outOfRange,
   957  		},
   958  		{
   959  			name: "values out of range",
   960  			input: exprNode{
   961  				fmtExpr: "$v",
   962  			},
   963  			err: outOfRange,
   964  		},
   965  		{
   966  			name: "children out of range",
   967  			input: exprNode{
   968  				fmtExpr: "$c",
   969  			},
   970  			err: outOfRange,
   971  		},
   972  		{
   973  			name: "invalid escape char",
   974  			input: exprNode{
   975  				fmtExpr: "$!",
   976  			},
   977  			err: invalidEscChar,
   978  		},
   979  		{
   980  			name:               "unset exprNode",
   981  			input:              exprNode{},
   982  			expectedExpression: "",
   983  		},
   984  	}
   985  
   986  	for _, c := range cases {
   987  		t.Run(c.name, func(t *testing.T) {
   988  			expr, err := c.input.buildExpressionString(&aliasList{})
   989  			if c.err != noExpressionError {
   990  				if err == nil {
   991  					t.Errorf("expect error %q, got no error", c.err)
   992  				} else {
   993  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
   994  						t.Errorf("expect %q error message to be in %q", e, a)
   995  					}
   996  				}
   997  			} else {
   998  				if err != nil {
   999  					t.Errorf("expect no error, got unexpected Error %q", err)
  1000  				}
  1001  
  1002  				if e, a := c.expectedExpression, expr; !reflect.DeepEqual(a, e) {
  1003  					t.Errorf("expect %v, got %v", e, a)
  1004  				}
  1005  			}
  1006  		})
  1007  	}
  1008  }
  1009  
  1010  func TestReturnExpression(t *testing.T) {
  1011  	cases := []struct {
  1012  		name     string
  1013  		input    Expression
  1014  		expected *string
  1015  	}{
  1016  		{
  1017  			name: "projection exists",
  1018  			input: Expression{
  1019  				expressionMap: map[expressionType]string{
  1020  					projection: "#0, #1, #2",
  1021  				},
  1022  			},
  1023  			expected: aws.String("#0, #1, #2"),
  1024  		},
  1025  		{
  1026  			name: "projection not exists",
  1027  			input: Expression{
  1028  				expressionMap: map[expressionType]string{},
  1029  			},
  1030  			expected: nil,
  1031  		},
  1032  	}
  1033  	for _, c := range cases {
  1034  		t.Run(c.name, func(t *testing.T) {
  1035  			actual := c.input.returnExpression(projection)
  1036  			if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
  1037  				t.Errorf("expect %v, got %v", e, a)
  1038  			}
  1039  		})
  1040  	}
  1041  }
  1042  
  1043  func TestAliasValue(t *testing.T) {
  1044  	cases := []struct {
  1045  		name     string
  1046  		input    *aliasList
  1047  		expected string
  1048  		err      exprErrorMode
  1049  	}{
  1050  		{
  1051  			name:     "first item",
  1052  			input:    &aliasList{},
  1053  			expected: ":0",
  1054  		},
  1055  		{
  1056  			name: "fifth item",
  1057  			input: &aliasList{
  1058  				valuesList: []dynamodb.AttributeValue{
  1059  					{},
  1060  					{},
  1061  					{},
  1062  					{},
  1063  				},
  1064  			},
  1065  			expected: ":4",
  1066  		},
  1067  	}
  1068  
  1069  	for _, c := range cases {
  1070  		t.Run(c.name, func(t *testing.T) {
  1071  			str, err := c.input.aliasValue(dynamodb.AttributeValue{})
  1072  
  1073  			if c.err != noExpressionError {
  1074  				if err == nil {
  1075  					t.Errorf("expect error %q, got no error", c.err)
  1076  				} else {
  1077  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  1078  						t.Errorf("expect %q error message to be in %q", e, a)
  1079  					}
  1080  				}
  1081  			} else {
  1082  				if err != nil {
  1083  					t.Errorf("expect no error, got unexpected Error %q", err)
  1084  				}
  1085  
  1086  				if e, a := c.expected, str; e != a {
  1087  					t.Errorf("expect %v, got %v", e, a)
  1088  				}
  1089  			}
  1090  		})
  1091  	}
  1092  }
  1093  
  1094  func TestAliasPath(t *testing.T) {
  1095  	cases := []struct {
  1096  		name      string
  1097  		inputList *aliasList
  1098  		inputName string
  1099  		expected  string
  1100  		err       exprErrorMode
  1101  	}{
  1102  		{
  1103  			name:      "new unique item",
  1104  			inputList: &aliasList{},
  1105  			inputName: "foo",
  1106  			expected:  "#0",
  1107  		},
  1108  		{
  1109  			name: "duplicate item",
  1110  			inputList: &aliasList{
  1111  				namesList: []string{
  1112  					"foo",
  1113  					"bar",
  1114  				},
  1115  			},
  1116  			inputName: "foo",
  1117  			expected:  "#0",
  1118  		},
  1119  	}
  1120  
  1121  	for _, c := range cases {
  1122  		t.Run(c.name, func(t *testing.T) {
  1123  			str, err := c.inputList.aliasPath(c.inputName)
  1124  
  1125  			if c.err != noExpressionError {
  1126  				if err == nil {
  1127  					t.Errorf("expect error %q, got no error", c.err)
  1128  				} else {
  1129  					if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
  1130  						t.Errorf("expect %q error message to be in %q", e, a)
  1131  					}
  1132  				}
  1133  			} else {
  1134  				if err != nil {
  1135  					t.Errorf("expect no error, got unexpected Error %q", err)
  1136  				}
  1137  
  1138  				if e, a := c.expected, str; e != a {
  1139  					t.Errorf("expect %v, got %v", e, a)
  1140  				}
  1141  			}
  1142  		})
  1143  	}
  1144  }