@@ -3,10 +3,15 @@ package cli
3
3
import (
4
4
"fmt"
5
5
"log/slog"
6
+ "strconv"
7
+ "strings"
6
8
7
9
pythonv1 "buf.build/gen/go/stealthrocket/dispatch-proto/protocolbuffers/go/dispatch/sdk/python/v1"
8
- "google.golang.org/protobuf/proto"
9
10
"google.golang.org/protobuf/types/known/anypb"
11
+ "google.golang.org/protobuf/types/known/durationpb"
12
+ "google.golang.org/protobuf/types/known/emptypb"
13
+ "google.golang.org/protobuf/types/known/structpb"
14
+ "google.golang.org/protobuf/types/known/timestamppb"
10
15
"google.golang.org/protobuf/types/known/wrapperspb"
11
16
)
12
17
@@ -15,51 +20,130 @@ func anyString(any *anypb.Any) string {
15
20
return "nil"
16
21
}
17
22
18
- var s string
19
- var err error
20
- switch any .TypeUrl {
21
- case "buf.build/stealthrocket/dispatch-proto/dispatch.sdk.python.v1.Pickled" :
22
- var pickled proto.Message
23
- pickled , err = any .UnmarshalNew ()
24
- if err == nil {
25
- if p , ok := pickled .(* pythonv1.Pickled ); ok {
26
- s , err = pythonPickleString (p .PickledValue )
27
- } else {
28
- err = fmt .Errorf ("invalid pickled message: %T" , p )
29
- }
23
+ m , err := any .UnmarshalNew ()
24
+ if err != nil {
25
+ return unsupportedAny (any , err )
26
+ }
27
+
28
+ switch mm := m .(type ) {
29
+ case * wrapperspb.BytesValue :
30
+ // The Python SDK originally wrapped pickled values in a
31
+ // wrapperspb.BytesValue. Try to unpickle the bytes first,
32
+ // and return literal bytes if they cannot be unpickled.
33
+ s , err := pythonPickleString (mm .Value )
34
+ if err != nil {
35
+ s = fmt .Sprintf ("bytes(%s)" , truncateBytes (mm .Value ))
36
+ }
37
+ return s
38
+
39
+ case * wrapperspb.Int32Value :
40
+ return strconv .FormatInt (int64 (mm .Value ), 10 )
41
+
42
+ case * wrapperspb.Int64Value :
43
+ return strconv .FormatInt (mm .Value , 10 )
44
+
45
+ case * wrapperspb.UInt32Value :
46
+ return strconv .FormatUint (uint64 (mm .Value ), 10 )
47
+
48
+ case * wrapperspb.UInt64Value :
49
+ return strconv .FormatUint (mm .Value , 10 )
50
+
51
+ case * wrapperspb.StringValue :
52
+ return fmt .Sprintf ("%q" , mm .Value )
53
+
54
+ case * wrapperspb.BoolValue :
55
+ return strconv .FormatBool (mm .Value )
56
+
57
+ case * wrapperspb.FloatValue :
58
+ return fmt .Sprintf ("%v" , mm .Value )
59
+
60
+ case * wrapperspb.DoubleValue :
61
+ return fmt .Sprintf ("%v" , mm .Value )
62
+
63
+ case * emptypb.Empty :
64
+ return "empty()"
65
+
66
+ case * timestamppb.Timestamp :
67
+ return mm .AsTime ().String ()
68
+
69
+ case * durationpb.Duration :
70
+ return mm .AsDuration ().String ()
71
+
72
+ case * structpb.Struct :
73
+ return structpbStructString (mm )
74
+
75
+ case * structpb.ListValue :
76
+ return structpbListString (mm )
77
+
78
+ case * structpb.Value :
79
+ return structpbValueString (mm )
80
+
81
+ case * pythonv1.Pickled :
82
+ s , err := pythonPickleString (mm .PickledValue )
83
+ if err != nil {
84
+ return unsupportedAny (any , fmt .Errorf ("pickle error: %w" , err ))
30
85
}
31
- case "type.googleapis.com/google.protobuf.BytesValue" :
32
- s , err = anyBytesString ( any )
86
+ return s
87
+
33
88
default :
34
- // TODO: support unpacking other types of serialized values
35
- err = fmt .Errorf ("not implemented: %s" , any .TypeUrl )
89
+ return unsupportedAny (any , fmt .Errorf ("not implemented: %T" , m ))
36
90
}
37
- if err != nil {
38
- slog .Debug ("cannot parse input/output value" , "error" , err )
39
- return fmt .Sprintf ("%s(?)" , any .TypeUrl )
91
+ }
92
+
93
+ func structpbStructString (s * structpb.Struct ) string {
94
+ var b strings.Builder
95
+ b .WriteByte ('{' )
96
+ i := 0
97
+ for name , value := range s .Fields {
98
+ if i > 0 {
99
+ b .WriteString (", " )
100
+ }
101
+ b .WriteString (fmt .Sprintf ("%q" , name ))
102
+ b .WriteString (": " )
103
+ b .WriteString (structpbValueString (value ))
104
+ i ++
40
105
}
41
- return s
106
+ b .WriteByte ('}' )
107
+ return b .String ()
42
108
}
43
109
44
- func anyBytesString (any * anypb.Any ) (string , error ) {
45
- m , err := anypb .UnmarshalNew (any , proto.UnmarshalOptions {})
46
- if err != nil {
47
- return "" , err
110
+ func structpbListString (s * structpb.ListValue ) string {
111
+ var b strings.Builder
112
+ b .WriteByte ('[' )
113
+ for i , value := range s .Values {
114
+ if i > 0 {
115
+ b .WriteString (", " )
116
+ }
117
+ b .WriteString (structpbValueString (value ))
48
118
}
49
- bv , ok := m .(* wrapperspb.BytesValue )
50
- if ! ok {
51
- return "" , fmt .Errorf ("invalid bytes value: %T" , m )
119
+ b .WriteByte (']' )
120
+ return b .String ()
121
+ }
122
+
123
+ func structpbValueString (s * structpb.Value ) string {
124
+ switch v := s .Kind .(type ) {
125
+ case * structpb.Value_StructValue :
126
+ return structpbStructString (v .StructValue )
127
+ case * structpb.Value_ListValue :
128
+ return structpbListString (v .ListValue )
129
+ case * structpb.Value_BoolValue :
130
+ return strconv .FormatBool (v .BoolValue )
131
+ case * structpb.Value_NumberValue :
132
+ return fmt .Sprintf ("%v" , v .NumberValue )
133
+ case * structpb.Value_StringValue :
134
+ return fmt .Sprintf ("%q" , v .StringValue )
135
+ case * structpb.Value_NullValue :
136
+ return "null"
137
+ default :
138
+ panic ("unreachable" )
52
139
}
53
- b := bv . Value
140
+ }
54
141
55
- // The Python SDK originally wrapped pickled values in a
56
- // wrapperspb.BytesValue. Try to unpickle the bytes first,
57
- // and return literal bytes if they cannot be unpickled.
58
- s , err := pythonPickleString (b )
142
+ func unsupportedAny (any * anypb.Any , err error ) string {
59
143
if err != nil {
60
- s = string ( truncateBytes ( b ) )
144
+ slog . Debug ( "cannot parse input/output value" , "error" , err )
61
145
}
62
- return s , nil
146
+ return fmt . Sprintf ( "%s(?)" , any . TypeUrl )
63
147
}
64
148
65
149
func truncateBytes (b []byte ) []byte {
0 commit comments