mirror of https://github.com/databricks/cli.git
Compare `.Kind()` instead of direct equality checks on a `dyn.Value` (#1520)
## Changes This PR makes two changes: 1. In https://github.com/databricks/cli/pull/1510 we'll be adding multiple associated location metadata with a dyn.Value. The Go compiler does not allow comparing structs if they contain slice values (presumably due to multiple possible definitions for equality). In anticipation for adding a `[]dyn.Location` type field to `dyn.Value` this PR removes all direct comparisons of `dyn.Value` and instead relies on the kind. 2. Retain location metadata for values in convert.FromTyped. The change diff is exactly the same as https://github.com/databricks/cli/pull/1523. It's been combined with this PR because they both depend on each other to prevent test failures (forming a test failure deadlock). Go patch used: ``` @@ var x expression @@ -x == dyn.InvalidValue +x.Kind() == dyn.KindInvalid @@ var x expression @@ -x != dyn.InvalidValue +x.Kind() != dyn.KindInvalid @@ var x expression @@ -x == dyn.NilValue +x.Kind() == dyn.KindNil @@ var x expression @@ -x != dyn.NilValue +x.Kind() != dyn.KindNil ``` ## Tests Unit tests and integration tests pass.
This commit is contained in:
parent
dba6164a4c
commit
4d8eba04cd
|
@ -32,7 +32,7 @@ func (m *environmentsToTargets) Apply(ctx context.Context, b *bundle.Bundle) dia
|
||||||
targets := v.Get("targets")
|
targets := v.Get("targets")
|
||||||
|
|
||||||
// Return an error if both "environments" and "targets" are set.
|
// Return an error if both "environments" and "targets" are set.
|
||||||
if environments != dyn.InvalidValue && targets != dyn.InvalidValue {
|
if environments.Kind() != dyn.KindInvalid && targets.Kind() != dyn.KindInvalid {
|
||||||
return dyn.InvalidValue, fmt.Errorf(
|
return dyn.InvalidValue, fmt.Errorf(
|
||||||
"both 'environments' and 'targets' are specified; only 'targets' should be used: %s",
|
"both 'environments' and 'targets' are specified; only 'targets' should be used: %s",
|
||||||
environments.Location().String(),
|
environments.Location().String(),
|
||||||
|
@ -40,7 +40,7 @@ func (m *environmentsToTargets) Apply(ctx context.Context, b *bundle.Bundle) dia
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewrite "environments" to "targets".
|
// Rewrite "environments" to "targets".
|
||||||
if environments != dyn.InvalidValue && targets == dyn.InvalidValue {
|
if environments.Kind() != dyn.KindInvalid && targets.Kind() == dyn.KindInvalid {
|
||||||
nv, err := dyn.Set(v, "targets", environments)
|
nv, err := dyn.Set(v, "targets", environments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dyn.InvalidValue, err
|
return dyn.InvalidValue, err
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (m *mergeJobClusters) jobClusterKey(v dyn.Value) string {
|
||||||
|
|
||||||
func (m *mergeJobClusters) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
func (m *mergeJobClusters) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
||||||
if v == dyn.NilValue {
|
if v.Kind() == dyn.KindNil {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (m *mergeJobTasks) taskKeyString(v dyn.Value) string {
|
||||||
|
|
||||||
func (m *mergeJobTasks) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
func (m *mergeJobTasks) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
||||||
if v == dyn.NilValue {
|
if v.Kind() == dyn.KindNil {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ func (m *mergePipelineClusters) clusterLabel(v dyn.Value) string {
|
||||||
|
|
||||||
func (m *mergePipelineClusters) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
func (m *mergePipelineClusters) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
|
||||||
if v == dyn.NilValue {
|
if v.Kind() == dyn.KindNil {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,14 +53,20 @@ func (e errBothSpAndUserSpecified) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRunAs(b *bundle.Bundle) error {
|
func validateRunAs(b *bundle.Bundle) error {
|
||||||
runAs := b.Config.RunAs
|
neitherSpecifiedErr := fmt.Errorf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s", b.Config.GetLocation("run_as"))
|
||||||
|
// Error if neither service_principal_name nor user_name are specified, but the
|
||||||
// Error if neither service_principal_name nor user_name are specified
|
// run_as section is present.
|
||||||
if runAs.ServicePrincipalName == "" && runAs.UserName == "" {
|
if b.Config.Value().Get("run_as").Kind() == dyn.KindNil {
|
||||||
return fmt.Errorf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s", b.Config.GetLocation("run_as"))
|
return neitherSpecifiedErr
|
||||||
|
}
|
||||||
|
// Error if one or both of service_principal_name and user_name are specified,
|
||||||
|
// but with empty values.
|
||||||
|
if b.Config.RunAs.ServicePrincipalName == "" && b.Config.RunAs.UserName == "" {
|
||||||
|
return neitherSpecifiedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error if both service_principal_name and user_name are specified
|
// Error if both service_principal_name and user_name are specified
|
||||||
|
runAs := b.Config.RunAs
|
||||||
if runAs.UserName != "" && runAs.ServicePrincipalName != "" {
|
if runAs.UserName != "" && runAs.ServicePrincipalName != "" {
|
||||||
return errBothSpAndUserSpecified{
|
return errBothSpAndUserSpecified{
|
||||||
spName: runAs.ServicePrincipalName,
|
spName: runAs.ServicePrincipalName,
|
||||||
|
@ -163,8 +169,7 @@ func setPipelineOwnersToRunAsIdentity(b *bundle.Bundle) {
|
||||||
|
|
||||||
func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
|
func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
// Mutator is a no-op if run_as is not specified in the bundle
|
// Mutator is a no-op if run_as is not specified in the bundle
|
||||||
runAs := b.Config.RunAs
|
if b.Config.Value().Get("run_as").Kind() == dyn.KindInvalid {
|
||||||
if runAs == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -346,7 +346,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge `run_as`. This field must be overwritten if set, not merged.
|
// Merge `run_as`. This field must be overwritten if set, not merged.
|
||||||
if v := target.Get("run_as"); v != dyn.InvalidValue {
|
if v := target.Get("run_as"); v.Kind() != dyn.KindInvalid {
|
||||||
root, err = dyn.Set(root, "run_as", v)
|
root, err = dyn.Set(root, "run_as", v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -354,7 +354,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Below, we're setting fields on the bundle key, so make sure it exists.
|
// Below, we're setting fields on the bundle key, so make sure it exists.
|
||||||
if root.Get("bundle") == dyn.InvalidValue {
|
if root.Get("bundle").Kind() == dyn.KindInvalid {
|
||||||
root, err = dyn.Set(root, "bundle", dyn.NewValue(map[string]dyn.Value{}, dyn.Location{}))
|
root, err = dyn.Set(root, "bundle", dyn.NewValue(map[string]dyn.Value{}, dyn.Location{}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -362,7 +362,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge `mode`. This field must be overwritten if set, not merged.
|
// Merge `mode`. This field must be overwritten if set, not merged.
|
||||||
if v := target.Get("mode"); v != dyn.InvalidValue {
|
if v := target.Get("mode"); v.Kind() != dyn.KindInvalid {
|
||||||
root, err = dyn.SetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("mode")), v)
|
root, err = dyn.SetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("mode")), v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -370,7 +370,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge `compute_id`. This field must be overwritten if set, not merged.
|
// Merge `compute_id`. This field must be overwritten if set, not merged.
|
||||||
if v := target.Get("compute_id"); v != dyn.InvalidValue {
|
if v := target.Get("compute_id"); v.Kind() != dyn.KindInvalid {
|
||||||
root, err = dyn.SetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("compute_id")), v)
|
root, err = dyn.SetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("compute_id")), v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -378,7 +378,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge `git`.
|
// Merge `git`.
|
||||||
if v := target.Get("git"); v != dyn.InvalidValue {
|
if v := target.Get("git"); v.Kind() != dyn.KindInvalid {
|
||||||
ref, err := dyn.GetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("git")))
|
ref, err := dyn.GetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("git")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ref = dyn.NewValue(map[string]dyn.Value{}, dyn.Location{})
|
ref = dyn.NewValue(map[string]dyn.Value{}, dyn.Location{})
|
||||||
|
@ -391,7 +391,7 @@ func (r *Root) MergeTargetOverrides(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the branch was overridden, we need to clear the inferred flag.
|
// If the branch was overridden, we need to clear the inferred flag.
|
||||||
if branch := v.Get("branch"); branch != dyn.InvalidValue {
|
if branch := v.Get("branch"); branch.Kind() != dyn.KindInvalid {
|
||||||
out, err = dyn.SetByPath(out, dyn.NewPath(dyn.Key("inferred")), dyn.NewValue(false, dyn.Location{}))
|
out, err = dyn.SetByPath(out, dyn.NewPath(dyn.Key("inferred")), dyn.NewValue(false, dyn.Location{}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -419,7 +419,7 @@ func rewriteShorthands(v dyn.Value) (dyn.Value, error) {
|
||||||
// For each target, rewrite the variables block.
|
// For each target, rewrite the variables block.
|
||||||
return dyn.Map(v, "targets", dyn.Foreach(func(_ dyn.Path, target dyn.Value) (dyn.Value, error) {
|
return dyn.Map(v, "targets", dyn.Foreach(func(_ dyn.Path, target dyn.Value) (dyn.Value, error) {
|
||||||
// Confirm it has a variables block.
|
// Confirm it has a variables block.
|
||||||
if target.Get("variables") == dyn.InvalidValue {
|
if target.Get("variables").Kind() == dyn.KindInvalid {
|
||||||
return target, nil
|
return target, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,7 +464,7 @@ func validateVariableOverrides(root, target dyn.Value) (err error) {
|
||||||
var tv map[string]variable.Variable
|
var tv map[string]variable.Variable
|
||||||
|
|
||||||
// Collect variables from the root.
|
// Collect variables from the root.
|
||||||
if v := root.Get("variables"); v != dyn.InvalidValue {
|
if v := root.Get("variables"); v.Kind() != dyn.KindInvalid {
|
||||||
err = convert.ToTyped(&rv, v)
|
err = convert.ToTyped(&rv, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to collect variables from root: %w", err)
|
return fmt.Errorf("unable to collect variables from root: %w", err)
|
||||||
|
@ -472,7 +472,7 @@ func validateVariableOverrides(root, target dyn.Value) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect variables from the target.
|
// Collect variables from the target.
|
||||||
if v := target.Get("variables"); v != dyn.InvalidValue {
|
if v := target.Get("variables"); v.Kind() != dyn.KindInvalid {
|
||||||
err = convert.ToTyped(&tv, v)
|
err = convert.ToTyped(&tv, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to collect variables from target: %w", err)
|
return fmt.Errorf("unable to collect variables from target: %w", err)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
bundle:
|
||||||
|
name: "abc"
|
||||||
|
|
||||||
|
run_as:
|
||||||
|
service_principal_name: ""
|
|
@ -0,0 +1,5 @@
|
||||||
|
bundle:
|
||||||
|
name: "abc"
|
||||||
|
|
||||||
|
run_as:
|
||||||
|
user_name: ""
|
|
@ -0,0 +1,6 @@
|
||||||
|
bundle:
|
||||||
|
name: "abc"
|
||||||
|
|
||||||
|
run_as:
|
||||||
|
service_principal_name: ""
|
||||||
|
user_name: ""
|
|
@ -196,27 +196,53 @@ func TestRunAsErrorWhenBothUserAndSpSpecified(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunAsErrorNeitherUserOrSpSpecified(t *testing.T) {
|
func TestRunAsErrorNeitherUserOrSpSpecified(t *testing.T) {
|
||||||
b := load(t, "./run_as/not_allowed/neither_sp_nor_user")
|
tcases := []struct {
|
||||||
|
name string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty_run_as",
|
||||||
|
err: fmt.Sprintf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s:4:8", filepath.FromSlash("run_as/not_allowed/neither_sp_nor_user/empty_run_as/databricks.yml")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_sp",
|
||||||
|
err: fmt.Sprintf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s:5:3", filepath.FromSlash("run_as/not_allowed/neither_sp_nor_user/empty_sp/databricks.yml")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_user",
|
||||||
|
err: fmt.Sprintf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s:5:3", filepath.FromSlash("run_as/not_allowed/neither_sp_nor_user/empty_user/databricks.yml")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_user_and_sp",
|
||||||
|
err: fmt.Sprintf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s:5:3", filepath.FromSlash("run_as/not_allowed/neither_sp_nor_user/empty_user_and_sp/databricks.yml")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
for _, tc := range tcases {
|
||||||
bundle.ApplyFunc(ctx, b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
b.Config.Workspace.CurrentUser = &config.User{
|
|
||||||
User: &iam.User{
|
|
||||||
UserName: "my_service_principal",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
diags := bundle.Apply(ctx, b, mutator.SetRunAs())
|
bundlePath := fmt.Sprintf("./run_as/not_allowed/neither_sp_nor_user/%s", tc.name)
|
||||||
err := diags.Error()
|
b := load(t, bundlePath)
|
||||||
|
|
||||||
configPath := filepath.FromSlash("run_as/not_allowed/neither_sp_nor_user/databricks.yml")
|
ctx := context.Background()
|
||||||
assert.EqualError(t, err, fmt.Sprintf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s:4:8", configPath))
|
bundle.ApplyFunc(ctx, b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
|
b.Config.Workspace.CurrentUser = &config.User{
|
||||||
|
User: &iam.User{
|
||||||
|
UserName: "my_service_principal",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
diags := bundle.Apply(ctx, b, mutator.SetRunAs())
|
||||||
|
err := diags.Error()
|
||||||
|
assert.EqualError(t, err, tc.err)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunAsErrorNeitherUserOrSpSpecifiedAtTargetOverride(t *testing.T) {
|
func TestRunAsErrorNeitherUserOrSpSpecifiedAtTargetOverride(t *testing.T) {
|
||||||
b := loadTarget(t, "./run_as/not_allowed/neither_sp_nor_user_override", "development")
|
b := loadTarget(t, "./run_as/not_allowed/neither_sp_nor_user/override", "development")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
bundle.ApplyFunc(ctx, b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
bundle.ApplyFunc(ctx, b, func(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
|
@ -231,7 +257,7 @@ func TestRunAsErrorNeitherUserOrSpSpecifiedAtTargetOverride(t *testing.T) {
|
||||||
diags := bundle.Apply(ctx, b, mutator.SetRunAs())
|
diags := bundle.Apply(ctx, b, mutator.SetRunAs())
|
||||||
err := diags.Error()
|
err := diags.Error()
|
||||||
|
|
||||||
configPath := filepath.FromSlash("run_as/not_allowed/neither_sp_nor_user_override/override.yml")
|
configPath := filepath.FromSlash("run_as/not_allowed/neither_sp_nor_user/override/override.yml")
|
||||||
assert.EqualError(t, err, fmt.Sprintf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s:4:12", configPath))
|
assert.EqualError(t, err, fmt.Sprintf("run_as section must specify exactly one identity. Neither service_principal_name nor user_name is specified at %s:4:12", configPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value,
|
||||||
// Dereference pointer if necessary
|
// Dereference pointer if necessary
|
||||||
for srcv.Kind() == reflect.Pointer {
|
for srcv.Kind() == reflect.Pointer {
|
||||||
if srcv.IsNil() {
|
if srcv.IsNil() {
|
||||||
return dyn.NilValue, nil
|
return dyn.NilValue.WithLocation(ref.Location()), nil
|
||||||
}
|
}
|
||||||
srcv = srcv.Elem()
|
srcv = srcv.Elem()
|
||||||
|
|
||||||
|
@ -55,27 +55,35 @@ func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var v dyn.Value
|
||||||
|
var err error
|
||||||
switch srcv.Kind() {
|
switch srcv.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
return fromTypedStruct(srcv, ref, options...)
|
v, err = fromTypedStruct(srcv, ref, options...)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return fromTypedMap(srcv, ref)
|
v, err = fromTypedMap(srcv, ref)
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return fromTypedSlice(srcv, ref)
|
v, err = fromTypedSlice(srcv, ref)
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return fromTypedString(srcv, ref, options...)
|
v, err = fromTypedString(srcv, ref, options...)
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return fromTypedBool(srcv, ref, options...)
|
v, err = fromTypedBool(srcv, ref, options...)
|
||||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||||
return fromTypedInt(srcv, ref, options...)
|
v, err = fromTypedInt(srcv, ref, options...)
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return fromTypedFloat(srcv, ref, options...)
|
v, err = fromTypedFloat(srcv, ref, options...)
|
||||||
case reflect.Invalid:
|
case reflect.Invalid:
|
||||||
// If the value is untyped and not set (e.g. any type with nil value), we return nil.
|
// If the value is untyped and not set (e.g. any type with nil value), we return nil.
|
||||||
return dyn.NilValue, nil
|
v, err = dyn.NilValue, nil
|
||||||
|
default:
|
||||||
|
return dyn.InvalidValue, fmt.Errorf("unsupported type: %s", srcv.Kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
return dyn.InvalidValue, fmt.Errorf("unsupported type: %s", srcv.Kind())
|
// Ensure the location metadata is retained.
|
||||||
|
if err != nil {
|
||||||
|
return dyn.InvalidValue, err
|
||||||
|
}
|
||||||
|
return v.WithLocation(ref.Location()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
|
func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
|
||||||
|
@ -117,7 +125,7 @@ func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptio
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either if the key was set in the reference or the field is not zero-valued, we include it.
|
// Either if the key was set in the reference or the field is not zero-valued, we include it.
|
||||||
if ok || nv != dyn.NilValue {
|
if ok || nv.Kind() != dyn.KindNil {
|
||||||
out.Set(refk, nv)
|
out.Set(refk, nv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +135,7 @@ func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptio
|
||||||
// 2. The reference is a map (i.e. the struct was and still is empty).
|
// 2. The reference is a map (i.e. the struct was and still is empty).
|
||||||
// 3. The "includeZeroValues" option is set (i.e. the struct is a non-nil pointer).
|
// 3. The "includeZeroValues" option is set (i.e. the struct is a non-nil pointer).
|
||||||
if out.Len() > 0 || ref.Kind() == dyn.KindMap || slices.Contains(options, includeZeroValues) {
|
if out.Len() > 0 || ref.Kind() == dyn.KindMap || slices.Contains(options, includeZeroValues) {
|
||||||
return dyn.NewValue(out, ref.Location()), nil
|
return dyn.V(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, return nil.
|
// Otherwise, return nil.
|
||||||
|
@ -179,7 +187,7 @@ func fromTypedMap(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
||||||
out.Set(refk, nv)
|
out.Set(refk, nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dyn.NewValue(out, ref.Location()), nil
|
return dyn.V(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromTypedSlice(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
func fromTypedSlice(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
||||||
|
@ -206,7 +214,7 @@ func fromTypedSlice(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
||||||
refv := ref.Index(i)
|
refv := ref.Index(i)
|
||||||
|
|
||||||
// Use nil reference if there is no reference for this index.
|
// Use nil reference if there is no reference for this index.
|
||||||
if refv == dyn.InvalidValue {
|
if refv.Kind() == dyn.KindInvalid {
|
||||||
refv = dyn.NilValue
|
refv = dyn.NilValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +227,7 @@ func fromTypedSlice(src reflect.Value, ref dyn.Value) (dyn.Value, error) {
|
||||||
out[i] = nv
|
out[i] = nv
|
||||||
}
|
}
|
||||||
|
|
||||||
return dyn.NewValue(out, ref.Location()), nil
|
return dyn.V(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromTypedString(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
|
func fromTypedString(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
|
||||||
|
|
|
@ -49,7 +49,7 @@ func TestFromTypedStructPointerZeroFields(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dyn.NilValue, nv)
|
assert.Equal(t, dyn.NilValue, nv)
|
||||||
|
|
||||||
// For an initialized pointer with a nil reference we expect a nil.
|
// For an initialized pointer with a nil reference we expect an empty map.
|
||||||
src = &Tmp{}
|
src = &Tmp{}
|
||||||
nv, err = FromTyped(src, dyn.NilValue)
|
nv, err = FromTyped(src, dyn.NilValue)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -103,7 +103,7 @@ func TestFromTypedStructSetFields(t *testing.T) {
|
||||||
}), nv)
|
}), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedStructSetFieldsRetainLocationIfUnchanged(t *testing.T) {
|
func TestFromTypedStructSetFieldsRetainLocation(t *testing.T) {
|
||||||
type Tmp struct {
|
type Tmp struct {
|
||||||
Foo string `json:"foo"`
|
Foo string `json:"foo"`
|
||||||
Bar string `json:"bar"`
|
Bar string `json:"bar"`
|
||||||
|
@ -122,11 +122,9 @@ func TestFromTypedStructSetFieldsRetainLocationIfUnchanged(t *testing.T) {
|
||||||
nv, err := FromTyped(src, ref)
|
nv, err := FromTyped(src, ref)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Assert foo has retained its location.
|
// Assert foo and bar have retained their location.
|
||||||
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv.Get("foo"))
|
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv.Get("foo"))
|
||||||
|
assert.Equal(t, dyn.NewValue("qux", dyn.Location{File: "bar"}), nv.Get("bar"))
|
||||||
// Assert bar lost its location (because it was overwritten).
|
|
||||||
assert.Equal(t, dyn.NewValue("qux", dyn.Location{}), nv.Get("bar"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedStringMapWithZeroValue(t *testing.T) {
|
func TestFromTypedStringMapWithZeroValue(t *testing.T) {
|
||||||
|
@ -354,7 +352,7 @@ func TestFromTypedMapNonEmpty(t *testing.T) {
|
||||||
}), nv)
|
}), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedMapNonEmptyRetainLocationIfUnchanged(t *testing.T) {
|
func TestFromTypedMapNonEmptyRetainLocation(t *testing.T) {
|
||||||
var src = map[string]string{
|
var src = map[string]string{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"bar": "qux",
|
"bar": "qux",
|
||||||
|
@ -368,11 +366,9 @@ func TestFromTypedMapNonEmptyRetainLocationIfUnchanged(t *testing.T) {
|
||||||
nv, err := FromTyped(src, ref)
|
nv, err := FromTyped(src, ref)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Assert foo has retained its location.
|
// Assert foo and bar have retained their locations.
|
||||||
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv.Get("foo"))
|
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv.Get("foo"))
|
||||||
|
assert.Equal(t, dyn.NewValue("qux", dyn.Location{File: "bar"}), nv.Get("bar"))
|
||||||
// Assert bar lost its location (because it was overwritten).
|
|
||||||
assert.Equal(t, dyn.NewValue("qux", dyn.Location{}), nv.Get("bar"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedMapFieldWithZeroValue(t *testing.T) {
|
func TestFromTypedMapFieldWithZeroValue(t *testing.T) {
|
||||||
|
@ -429,7 +425,7 @@ func TestFromTypedSliceNonEmpty(t *testing.T) {
|
||||||
}), nv)
|
}), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedSliceNonEmptyRetainLocationIfUnchanged(t *testing.T) {
|
func TestFromTypedSliceNonEmptyRetainLocation(t *testing.T) {
|
||||||
var src = []string{
|
var src = []string{
|
||||||
"foo",
|
"foo",
|
||||||
"bar",
|
"bar",
|
||||||
|
@ -437,17 +433,15 @@ func TestFromTypedSliceNonEmptyRetainLocationIfUnchanged(t *testing.T) {
|
||||||
|
|
||||||
ref := dyn.V([]dyn.Value{
|
ref := dyn.V([]dyn.Value{
|
||||||
dyn.NewValue("foo", dyn.Location{File: "foo"}),
|
dyn.NewValue("foo", dyn.Location{File: "foo"}),
|
||||||
dyn.NewValue("baz", dyn.Location{File: "baz"}),
|
dyn.NewValue("bar", dyn.Location{File: "bar"}),
|
||||||
})
|
})
|
||||||
|
|
||||||
nv, err := FromTyped(src, ref)
|
nv, err := FromTyped(src, ref)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Assert foo has retained its location.
|
// Assert foo and bar have retained their locations.
|
||||||
assert.Equal(t, dyn.NewValue("foo", dyn.Location{File: "foo"}), nv.Index(0))
|
assert.Equal(t, dyn.NewValue("foo", dyn.Location{File: "foo"}), nv.Index(0))
|
||||||
|
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "bar"}), nv.Index(1))
|
||||||
// Assert bar lost its location (because it was overwritten).
|
|
||||||
assert.Equal(t, dyn.NewValue("bar", dyn.Location{}), nv.Index(1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedStringEmpty(t *testing.T) {
|
func TestFromTypedStringEmpty(t *testing.T) {
|
||||||
|
@ -482,12 +476,20 @@ func TestFromTypedStringNonEmptyOverwrite(t *testing.T) {
|
||||||
assert.Equal(t, dyn.V("new"), nv)
|
assert.Equal(t, dyn.V("new"), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedStringRetainsLocationsIfUnchanged(t *testing.T) {
|
func TestFromTypedStringRetainsLocations(t *testing.T) {
|
||||||
var src string = "foo"
|
|
||||||
var ref = dyn.NewValue("foo", dyn.Location{File: "foo"})
|
var ref = dyn.NewValue("foo", dyn.Location{File: "foo"})
|
||||||
|
|
||||||
|
// case: value has not been changed
|
||||||
|
var src string = "foo"
|
||||||
nv, err := FromTyped(src, ref)
|
nv, err := FromTyped(src, ref)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dyn.NewValue("foo", dyn.Location{File: "foo"}), nv)
|
assert.Equal(t, dyn.NewValue("foo", dyn.Location{File: "foo"}), nv)
|
||||||
|
|
||||||
|
// case: value has been changed
|
||||||
|
src = "bar"
|
||||||
|
nv, err = FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.NewValue("bar", dyn.Location{File: "foo"}), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedStringTypeError(t *testing.T) {
|
func TestFromTypedStringTypeError(t *testing.T) {
|
||||||
|
@ -529,12 +531,20 @@ func TestFromTypedBoolNonEmptyOverwrite(t *testing.T) {
|
||||||
assert.Equal(t, dyn.V(true), nv)
|
assert.Equal(t, dyn.V(true), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedBoolRetainsLocationsIfUnchanged(t *testing.T) {
|
func TestFromTypedBoolRetainsLocations(t *testing.T) {
|
||||||
var src bool = true
|
|
||||||
var ref = dyn.NewValue(true, dyn.Location{File: "foo"})
|
var ref = dyn.NewValue(true, dyn.Location{File: "foo"})
|
||||||
|
|
||||||
|
// case: value has not been changed
|
||||||
|
var src bool = true
|
||||||
nv, err := FromTyped(src, ref)
|
nv, err := FromTyped(src, ref)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dyn.NewValue(true, dyn.Location{File: "foo"}), nv)
|
assert.Equal(t, dyn.NewValue(true, dyn.Location{File: "foo"}), nv)
|
||||||
|
|
||||||
|
// case: value has been changed
|
||||||
|
src = false
|
||||||
|
nv, err = FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.NewValue(false, dyn.Location{File: "foo"}), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedBoolVariableReference(t *testing.T) {
|
func TestFromTypedBoolVariableReference(t *testing.T) {
|
||||||
|
@ -584,12 +594,20 @@ func TestFromTypedIntNonEmptyOverwrite(t *testing.T) {
|
||||||
assert.Equal(t, dyn.V(int64(1234)), nv)
|
assert.Equal(t, dyn.V(int64(1234)), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedIntRetainsLocationsIfUnchanged(t *testing.T) {
|
func TestFromTypedIntRetainsLocations(t *testing.T) {
|
||||||
var src int = 1234
|
|
||||||
var ref = dyn.NewValue(1234, dyn.Location{File: "foo"})
|
var ref = dyn.NewValue(1234, dyn.Location{File: "foo"})
|
||||||
|
|
||||||
|
// case: value has not been changed
|
||||||
|
var src int = 1234
|
||||||
nv, err := FromTyped(src, ref)
|
nv, err := FromTyped(src, ref)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dyn.NewValue(1234, dyn.Location{File: "foo"}), nv)
|
assert.Equal(t, dyn.NewValue(1234, dyn.Location{File: "foo"}), nv)
|
||||||
|
|
||||||
|
// case: value has been changed
|
||||||
|
src = 1235
|
||||||
|
nv, err = FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.NewValue(int64(1235), dyn.Location{File: "foo"}), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedIntVariableReference(t *testing.T) {
|
func TestFromTypedIntVariableReference(t *testing.T) {
|
||||||
|
@ -639,12 +657,21 @@ func TestFromTypedFloatNonEmptyOverwrite(t *testing.T) {
|
||||||
assert.Equal(t, dyn.V(1.23), nv)
|
assert.Equal(t, dyn.V(1.23), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedFloatRetainsLocationsIfUnchanged(t *testing.T) {
|
func TestFromTypedFloatRetainsLocations(t *testing.T) {
|
||||||
var src float64 = 1.23
|
var src float64
|
||||||
var ref = dyn.NewValue(1.23, dyn.Location{File: "foo"})
|
var ref = dyn.NewValue(1.23, dyn.Location{File: "foo"})
|
||||||
|
|
||||||
|
// case: value has not been changed
|
||||||
|
src = 1.23
|
||||||
nv, err := FromTyped(src, ref)
|
nv, err := FromTyped(src, ref)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dyn.NewValue(1.23, dyn.Location{File: "foo"}), nv)
|
assert.Equal(t, dyn.NewValue(1.23, dyn.Location{File: "foo"}), nv)
|
||||||
|
|
||||||
|
// case: value has been changed
|
||||||
|
src = 1.24
|
||||||
|
nv, err = FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.NewValue(1.24, dyn.Location{File: "foo"}), nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromTypedFloatVariableReference(t *testing.T) {
|
func TestFromTypedFloatVariableReference(t *testing.T) {
|
||||||
|
@ -705,3 +732,35 @@ func TestFromTypedAnyNil(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dyn.NilValue, nv)
|
assert.Equal(t, dyn.NilValue, nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFromTypedNilPointerRetainsLocations(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var src *Tmp
|
||||||
|
ref := dyn.NewValue(nil, dyn.Location{File: "foobar"})
|
||||||
|
|
||||||
|
nv, err := FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.NewValue(nil, dyn.Location{File: "foobar"}), nv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromTypedNilMapRetainsLocation(t *testing.T) {
|
||||||
|
var src map[string]string
|
||||||
|
ref := dyn.NewValue(nil, dyn.Location{File: "foobar"})
|
||||||
|
|
||||||
|
nv, err := FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.NewValue(nil, dyn.Location{File: "foobar"}), nv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromTypedNilSliceRetainsLocation(t *testing.T) {
|
||||||
|
var src []string
|
||||||
|
ref := dyn.NewValue(nil, dyn.Location{File: "foobar"})
|
||||||
|
|
||||||
|
nv, err := FromTyped(src, ref)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, dyn.NewValue(nil, dyn.Location{File: "foobar"}), nv)
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func ToTyped(dst any, src dyn.Value) error {
|
||||||
for dstv.Kind() == reflect.Pointer {
|
for dstv.Kind() == reflect.Pointer {
|
||||||
// If the source value is nil and the destination is a settable pointer,
|
// If the source value is nil and the destination is a settable pointer,
|
||||||
// set the destination to nil. Also see `end_to_end_test.go`.
|
// set the destination to nil. Also see `end_to_end_test.go`.
|
||||||
if dstv.CanSet() && src == dyn.NilValue {
|
if dstv.CanSet() && src.Kind() == dyn.KindNil {
|
||||||
dstv.SetZero()
|
dstv.SetZero()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue