Skip to content

Commit 9498145

Browse files
pieternclaude
andauthored
Add support for SDK native types in dyn conversion (#4385)
## Changes Implements conversion support for SDK native types across the dyn package conversion layer (FromTyped, ToTyped, Normalize). Supports three SDK types that use custom JSON marshaling: - duration.Duration: Protobuf duration format (e.g., "300s") - time.Time: RFC3339 timestamp format (e.g., "2023-12-25T10:30:00Z") - fieldmask.FieldMask: Comma-separated paths (e.g., "name,age,email") The implementation uses a generic approach with all SDK-specific code in sdk_native_types.go and sdk_native_types_test.go. ## Why The new Postgres APIs use these types and need the to/from conversion to work. Also see databricks/databricks-sdk-go#1271. --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 0840a20 commit 9498145

File tree

5 files changed

+529
-1
lines changed

5 files changed

+529
-1
lines changed

libs/dyn/convert/from_typed.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value,
5959
var err error
6060
switch srcv.Kind() {
6161
case reflect.Struct:
62-
v, err = fromTypedStruct(srcv, ref, options...)
62+
// Handle SDK native types using JSON marshaling.
63+
if slices.Contains(sdkNativeTypes, srcv.Type()) {
64+
v, err = fromTypedSDKNative(srcv, ref, options...)
65+
} else {
66+
v, err = fromTypedStruct(srcv, ref, options...)
67+
}
6368
case reflect.Map:
6469
v, err = fromTypedMap(srcv, ref)
6570
case reflect.Slice:

libs/dyn/convert/normalize.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ func (n normalizeOptions) normalizeType(typ reflect.Type, src dyn.Value, seen []
4343

4444
switch typ.Kind() {
4545
case reflect.Struct:
46+
// Handle SDK native types as strings since they use custom JSON marshaling.
47+
if slices.Contains(sdkNativeTypes, typ) {
48+
return n.normalizeString(reflect.TypeOf(""), src, path)
49+
}
4650
return n.normalizeStruct(typ, src, append(seen, typ), path)
4751
case reflect.Map:
4852
return n.normalizeMap(typ, src, append(seen, typ), path)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package convert
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"reflect"
7+
"slices"
8+
9+
"github.com/databricks/cli/libs/dyn"
10+
"github.com/databricks/cli/libs/dyn/dynvar"
11+
sdkduration "github.com/databricks/databricks-sdk-go/common/types/duration"
12+
sdkfieldmask "github.com/databricks/databricks-sdk-go/common/types/fieldmask"
13+
sdktime "github.com/databricks/databricks-sdk-go/common/types/time"
14+
)
15+
16+
// sdkNativeTypes is a list of SDK native types that use custom JSON marshaling
17+
// and should be treated as strings in dyn.Value. These types all implement
18+
// json.Marshaler and json.Unmarshaler interfaces.
19+
var sdkNativeTypes = []reflect.Type{
20+
reflect.TypeFor[sdkduration.Duration](), // Protobuf duration format (e.g., "300s")
21+
reflect.TypeFor[sdktime.Time](), // RFC3339 timestamp format (e.g., "2023-12-25T10:30:00Z")
22+
reflect.TypeFor[sdkfieldmask.FieldMask](), // Comma-separated paths (e.g., "name,age,email")
23+
}
24+
25+
// fromTypedSDKNative converts SDK native types to dyn.Value.
26+
// SDK native types (duration.Duration, time.Time, fieldmask.FieldMask) use
27+
// custom JSON marshaling with string representations.
28+
func fromTypedSDKNative(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) {
29+
// Check that the reference value is compatible or nil.
30+
switch ref.Kind() {
31+
case dyn.KindString:
32+
// Ignore pure variable references (e.g. ${var.foo}).
33+
if dynvar.IsPureVariableReference(ref.MustString()) {
34+
return ref, nil
35+
}
36+
case dyn.KindNil:
37+
// Allow nil reference.
38+
default:
39+
return dyn.InvalidValue, fmt.Errorf("cannot convert SDK native type to dynamic type %#v", ref.Kind().String())
40+
}
41+
42+
// Check for zero value first.
43+
if src.IsZero() && !slices.Contains(options, includeZeroValues) {
44+
return dyn.NilValue, nil
45+
}
46+
47+
// Use JSON marshaling since SDK native types implement json.Marshaler.
48+
jsonBytes, err := json.Marshal(src.Interface())
49+
if err != nil {
50+
return dyn.InvalidValue, err
51+
}
52+
53+
// All SDK native types marshal to JSON strings. Unmarshal to get the raw string value.
54+
// For example: duration.Duration(300s) -> JSON "300s" -> string "300s"
55+
var str string
56+
if err := json.Unmarshal(jsonBytes, &str); err != nil {
57+
return dyn.InvalidValue, err
58+
}
59+
60+
// Handle empty string as zero value.
61+
if str == "" && !slices.Contains(options, includeZeroValues) {
62+
return dyn.NilValue, nil
63+
}
64+
65+
return dyn.V(str), nil
66+
}
67+
68+
// toTypedSDKNative converts a dyn.Value to an SDK native type.
69+
// SDK native types (duration.Duration, time.Time, fieldmask.FieldMask) use
70+
// custom JSON marshaling with string representations.
71+
func toTypedSDKNative(dst reflect.Value, src dyn.Value) error {
72+
switch src.Kind() {
73+
case dyn.KindString:
74+
// Ignore pure variable references (e.g. ${var.foo}).
75+
if dynvar.IsPureVariableReference(src.MustString()) {
76+
dst.SetZero()
77+
return nil
78+
}
79+
// Use JSON unmarshaling since SDK native types implement json.Unmarshaler.
80+
// Marshal the string to create a valid JSON string literal for unmarshaling.
81+
jsonBytes, err := json.Marshal(src.MustString())
82+
if err != nil {
83+
return err
84+
}
85+
return json.Unmarshal(jsonBytes, dst.Addr().Interface())
86+
case dyn.KindNil:
87+
dst.SetZero()
88+
return nil
89+
default:
90+
// Fall through to the error case.
91+
}
92+
93+
return TypeError{
94+
value: src,
95+
msg: fmt.Sprintf("expected a string, found a %s", src.Kind()),
96+
}
97+
}

0 commit comments

Comments
 (0)