Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions libs/dyn/convert/from_typed.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value,
}
}

// Handle SDK native types using JSON marshaling.
// Check for Invalid kind first to avoid panic when calling Type() on invalid value.
if srcv.Kind() != reflect.Invalid && isSDKNativeType(srcv.Type()) {
v, err := fromTypedSDKNative(srcv, ref, options...)
if err != nil {
return dyn.InvalidValue, err
}
return v.WithLocations(ref.Locations()), nil
}

var v dyn.Value
var err error
switch srcv.Kind() {
Expand Down
5 changes: 5 additions & 0 deletions libs/dyn/convert/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ func (n normalizeOptions) normalizeType(typ reflect.Type, src dyn.Value, seen []
typ = typ.Elem()
}

// Handle SDK native types as strings since they use custom JSON marshaling.
if isSDKNativeType(typ) {
return n.normalizeString(reflect.TypeOf(""), src, path)
}

switch typ.Kind() {
case reflect.Struct:
return n.normalizeStruct(typ, src, append(seen, typ), path)
Expand Down
92 changes: 92 additions & 0 deletions libs/dyn/convert/sdk_native_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package convert

import (
"encoding/json"
"fmt"
"reflect"
"slices"

"github.com/databricks/cli/libs/dyn"
sdkduration "github.com/databricks/databricks-sdk-go/common/types/duration"
sdkfieldmask "github.com/databricks/databricks-sdk-go/common/types/fieldmask"
sdktime "github.com/databricks/databricks-sdk-go/common/types/time"
)

// sdkNativeTypes is a list of SDK native types that use custom JSON marshaling
// and should be treated as strings in dyn.Value. These types all implement
// json.Marshaler and json.Unmarshaler interfaces.
var sdkNativeTypes = []reflect.Type{
reflect.TypeFor[sdkduration.Duration](), // Protobuf duration format (e.g., "300s")
reflect.TypeFor[sdktime.Time](), // RFC3339 timestamp format (e.g., "2023-12-25T10:30:00Z")
reflect.TypeFor[sdkfieldmask.FieldMask](), // Comma-separated paths (e.g., "name,age,email")
}

// isSDKNativeType checks if the given type is one of the SDK's native types
// that use custom JSON marshaling and should be treated as strings.
func isSDKNativeType(typ reflect.Type) bool {
for typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
for _, sdkType := range sdkNativeTypes {
if typ == sdkType {
return true
}
}
return false
}

// fromTypedSDKNative converts SDK native types to dyn.Value.
// SDK native types (duration.Duration, time.Time, fieldmask.FieldMask) use
// custom JSON marshaling with string representations.
func fromTypedSDKNative(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
// Check for zero value first.
if src.IsZero() && !slices.Contains(options, includeZeroValues) {
return dyn.NilValue, nil
}

// Use JSON marshaling since SDK native types implement json.Marshaler.
jsonBytes, err := json.Marshal(src.Interface())
if err != nil {
return dyn.InvalidValue, err
}

// All SDK native types marshal to JSON strings. Unmarshal to get the raw string value.
// For example: duration.Duration(300s) -> JSON "300s" -> string "300s"
var str string
if err := json.Unmarshal(jsonBytes, &str); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be a variable reference?

return dyn.InvalidValue, err
}

// Handle empty string as zero value.
if str == "" && !slices.Contains(options, includeZeroValues) {
return dyn.NilValue, nil
}

return dyn.V(str), nil
}

// toTypedSDKNative converts a dyn.Value to an SDK native type.
// SDK native types (duration.Duration, time.Time, fieldmask.FieldMask) use
// custom JSON marshaling with string representations.
func toTypedSDKNative(dst reflect.Value, src dyn.Value) error {
switch src.Kind() {
case dyn.KindString:
// Use JSON unmarshaling since SDK native types implement json.Unmarshaler.
// Marshal the string to create a valid JSON string literal for unmarshaling.
jsonBytes, err := json.Marshal(src.MustString())
if err != nil {
return err
}
return json.Unmarshal(jsonBytes, dst.Addr().Interface())
case dyn.KindNil:
dst.SetZero()
return nil
default:
// Fall through to the error case.
}

return TypeError{
value: src,
msg: fmt.Sprintf("expected a string, found a %s", src.Kind()),
}
}
Loading