diff --git a/automation/types/expr.go b/automation/types/expr.go index e11f2e400..c3f5e9406 100644 --- a/automation/types/expr.go +++ b/automation/types/expr.go @@ -160,7 +160,7 @@ func (set ExprSet) Eval(ctx context.Context, in *expr.Vars) (*expr.Vars, error) if e.typ == nil { typedValue, _ = expr.NewAny(value) } else if typedValue, err = e.typ.Cast(value); err != nil { - return nil, fmt.Errorf("cannot cast value on %s to type %s", e.Target, typedValue.Type()) + return nil, fmt.Errorf("cannot cast value %T to %s (target %s)", value, e.typ.Type(), e.Target) } } diff --git a/compose/automation/expr_types.go b/compose/automation/expr_types.go index ffad3b105..19a6cb7fc 100644 --- a/compose/automation/expr_types.go +++ b/compose/automation/expr_types.go @@ -53,9 +53,11 @@ func CastToComposeRecord(val interface{}) (out *types.Record, err error) { return assignToComposeRecord(out, k, v) }) } - switch val := expr.UntypedValue(val).(type) { case *types.Record: + if val.Values == nil { + val.Values = types.RecordValueSet{} + } return val, nil default: return nil, fmt.Errorf("unable to cast type %T to %T", val, out) @@ -221,6 +223,7 @@ func assignToComposeRecordValues(res *types.RecordValueSet, pp []string, val int return fmt.Errorf("empty path used for assigning record values") } + k := pp[0] rv := &types.RecordValue{Name: k} @@ -231,6 +234,48 @@ func assignToComposeRecordValues(res *types.RecordValueSet, pp []string, val int rv.Value = utval.Format(time.RFC3339) case *time.Time: rv.Value = utval.Format(time.RFC3339) + case []expr.TypedValue: // expr.Array + // Handle situation where array of values is assigned to a single (multi-value) field + // @todo this should use field context (when available) to determinate if we're actually + // setting array to a multi-value field + + if len(pp) == 2 { + // Tying to assign an array of values to a single value; that will not work + return fmt.Errorf("can not assign array of values to a single value in a record value set") + } + + for p, v := range utval { + rv = &types.RecordValue{Name: k, Place: uint(p)} + rv.Value, err = cast.ToStringE(v.Get()) + if err != nil { + return + } + + *res = res.Set(rv) + } + + return nil + case []interface{}: // expr.Any + // Handle situation where array of values is assigned to a single (multi-value) field + // @todo this should use field context (when available) to determinate if we're actually + // setting array to a multi-value field + + if len(pp) == 2 { + // Tying to assign an array of values to a single value; that will not work + return fmt.Errorf("can not assign array of values to a single value in a record value set") + } + + for p, v := range utval { + rv = &types.RecordValue{Name: k, Place: uint(p)} + rv.Value, err = cast.ToStringE(v) + if err != nil { + return + } + + *res = res.Set(rv) + } + + return nil default: rv.Value, err = cast.ToStringE(utval) } diff --git a/compose/automation/expr_types_test.go b/compose/automation/expr_types_test.go index ab0c847bb..b642e9696 100644 --- a/compose/automation/expr_types_test.go +++ b/compose/automation/expr_types_test.go @@ -179,4 +179,20 @@ func TestAssignToComposeRecordValues(t *testing.T) { req.False(target.Has("a", 0)) req.True(target.Has("b", 0)) }) + + t.Run("assign multipl values", func(t *testing.T) { + var ( + req = require.New(t) + target = types.RecordValueSet{} + ) + + req.Error(assignToComposeRecordValues(&target, []string{"a", "2"}, expr.Must(expr.NewAny([]interface{}{"1", "2"})))) + req.Len(target, 0) + + req.NoError(assignToComposeRecordValues(&target, []string{"a"}, expr.Must(expr.NewAny([]interface{}{"1", "2"})))) + req.Len(target, 2) + + req.NoError(assignToComposeRecordValues(&target, []string{"a"}, expr.Must(expr.NewAny([]string{"1", "2"})))) + req.Len(target, 2) + }) }