@@ -3,10 +3,15 @@ package cli
33import (
44 "fmt"
55 "log/slog"
6+ "strconv"
7+ "strings"
68
79 pythonv1 "buf.build/gen/go/stealthrocket/dispatch-proto/protocolbuffers/go/dispatch/sdk/python/v1"
8- "google.golang.org/protobuf/proto"
910 "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"
1015 "google.golang.org/protobuf/types/known/wrapperspb"
1116)
1217
@@ -15,51 +20,130 @@ func anyString(any *anypb.Any) string {
1520 return "nil"
1621 }
1722
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 ))
3085 }
31- case "type.googleapis.com/google.protobuf.BytesValue" :
32- s , err = anyBytesString ( any )
86+ return s
87+
3388 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 ))
3690 }
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 ++
40105 }
41- return s
106+ b .WriteByte ('}' )
107+ return b .String ()
42108}
43109
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 ))
48118 }
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" )
52139 }
53- b := bv . Value
140+ }
54141
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 {
59143 if err != nil {
60- s = string ( truncateBytes ( b ) )
144+ slog . Debug ( "cannot parse input/output value" , "error" , err )
61145 }
62- return s , nil
146+ return fmt . Sprintf ( "%s(?)" , any . TypeUrl )
63147}
64148
65149func truncateBytes (b []byte ) []byte {
0 commit comments