go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/starlarkproto/values_singular.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package starlarkproto 16 17 import ( 18 "fmt" 19 "math" 20 21 "go.starlark.net/starlark" 22 23 "google.golang.org/protobuf/reflect/protoreflect" 24 25 "go.chromium.org/luci/starlark/typed" 26 ) 27 28 // toStarlarkSingular converts 'v' to starlark, based on type in 'fd'. 29 // 30 // This is Proto => Starlark converter. Ignores 'repeated' qualifier. 31 // 32 // Panics if type of 'v' doesn't match 'fd'. 33 func toStarlarkSingular(l *Loader, fd protoreflect.FieldDescriptor, v protoreflect.Value) starlark.Value { 34 // See https://godoc.org/google.golang.org/protobuf/reflect/protoreflect#Kind 35 // Also https://developers.google.com/protocol-buffers/docs/proto#scalar 36 37 switch fd.Kind() { 38 case protoreflect.BoolKind: 39 return starlark.Bool(v.Bool()) 40 41 case protoreflect.EnumKind: 42 return starlark.MakeInt(int(v.Enum())) 43 44 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: 45 return starlark.MakeInt64(v.Int()) 46 47 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: 48 return starlark.MakeUint64(v.Uint()) 49 50 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: 51 return starlark.MakeInt64(v.Int()) 52 53 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: 54 return starlark.MakeUint64(v.Uint()) 55 56 case protoreflect.FloatKind, protoreflect.DoubleKind: 57 return starlark.Float(v.Float()) 58 59 case protoreflect.StringKind: 60 return starlark.String(v.String()) 61 62 case protoreflect.BytesKind: 63 return starlark.String(v.Bytes()) 64 65 case protoreflect.MessageKind, protoreflect.GroupKind: 66 typ := l.MessageType(fd.Message()) 67 if v.IsValid() { 68 return typ.MessageFromProto(v.Message().Interface()) 69 } 70 return typ.Message() 71 72 default: 73 panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind())) 74 } 75 } 76 77 // toProtoSingular converts 'v' to a protobuf value described by 'fd'. 78 // 79 // This is Starlark => Proto converter. 80 // 81 // Assumes type checks have been done already (this is responsibility of 82 // 'converter' function, see below). 83 // 84 // Panics on type mismatch. 85 func toProtoSingular(fd protoreflect.FieldDescriptor, v starlark.Value) protoreflect.Value { 86 // See https://godoc.org/google.golang.org/protobuf/reflect/protoreflect#Kind 87 // Also https://developers.google.com/protocol-buffers/docs/proto#scalar 88 switch kind := fd.Kind(); kind { 89 case protoreflect.BoolKind: 90 return protoreflect.ValueOf(bool(v.(starlark.Bool))) 91 92 case protoreflect.EnumKind: 93 return protoreflect.ValueOf(protoreflect.EnumNumber(starlarkToInt(v, kind, math.MinInt32, math.MaxInt32))) 94 95 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: 96 return protoreflect.ValueOf(int32(starlarkToInt(v, kind, math.MinInt32, math.MaxInt32))) 97 98 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: 99 return protoreflect.ValueOf(uint32(starlarkToUint(v, kind, math.MaxUint32))) 100 101 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: 102 return protoreflect.ValueOf(int64(starlarkToInt(v, kind, math.MinInt64, math.MaxInt64))) 103 104 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: 105 return protoreflect.ValueOf(uint64(starlarkToUint(v, kind, math.MaxUint64))) 106 107 case protoreflect.FloatKind: 108 return protoreflect.ValueOf(float32(v.(starlark.Float))) 109 110 case protoreflect.DoubleKind: 111 return protoreflect.ValueOf(float64(v.(starlark.Float))) 112 113 case protoreflect.StringKind: 114 return protoreflect.ValueOf(v.(starlark.String).GoString()) 115 116 case protoreflect.BytesKind: 117 return protoreflect.ValueOf([]byte(v.(starlark.String).GoString())) 118 119 case protoreflect.MessageKind, protoreflect.GroupKind: 120 return protoreflect.ValueOf(v.(*Message).ToProto()) 121 122 default: 123 panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind())) 124 } 125 } 126 127 // Extracts int64 from 'v' checking its range. Panics on mismatches. 128 func starlarkToInt(v starlark.Value, k protoreflect.Kind, min, max int64) int64 { 129 si, ok := v.(starlark.Int) 130 if !ok { 131 panic(fmt.Errorf("internal error: got %s, expect %s", v.Type(), k)) 132 } 133 i, ok := si.Int64() 134 if !ok || i < min || i > max { 135 panic(fmt.Errorf("internal error: %s doesn't fit into %s field", v, k)) 136 } 137 return i 138 } 139 140 // Extract uint64 from 'v' checking its range. Panics on mismatches. 141 func starlarkToUint(v starlark.Value, k protoreflect.Kind, max uint64) uint64 { 142 si, ok := v.(starlark.Int) 143 if !ok { 144 panic(fmt.Errorf("internal error: got %s, expect %s", v.Type(), k)) 145 } 146 i, ok := si.Uint64() 147 if !ok || i > max { 148 panic(fmt.Errorf("internal error: %s doesn't fit into %s field", v, k)) 149 } 150 return i 151 } 152 153 // converter returns typed.Converter that converts arbitrary Starlark values to 154 // another Starlark values of a scalar (i.e. non-repeated, non-dict) type 155 // described by the descriptor, if such conversion is allowed. 156 // 157 // This is Starlark => Starlark converter. Such converter is involved when 158 // executing following Starlark statements: 159 // 160 // msg.scalar = <some starlark value> 161 // msg.repeated[123] = <some starlark value> 162 // msg.dict[<some starlark value>] = <some starlark value> 163 // 164 // It ensures that *Message fields at all times conform to the proto message 165 // schema. 166 // 167 // Some notable conversion rules: 168 // - converter([u]int(32|64)) checks int fits within the corresponding range. 169 // - converter(float[32|64]) implicitly converts ints to floats. 170 // - converter(Message) implicitly converts dicts and Nones to messages. 171 // 172 // The following invariant holds (and relied upon by 'assign'): for all possible 173 // 'fd' and all possible 'x' the following doesn't panic: 174 // 175 // v, err := converter(l, fd).Convert(x) 176 // if err != nil { 177 // return err // type of 'x' is incompatible with 'fd' 178 // } 179 // p := toProtoSingular(fd, v) // compatible values can be converted to proto 180 func converter(l *Loader, fd protoreflect.FieldDescriptor) typed.Converter { 181 // Note: primitive type converters need "stable" addresses, since converters 182 // are compared by identity when checking type compatibility. So we use 183 // globals for them. For Message types, "stable" addresses are guaranteed by 184 // the loader, which caches types internally. 185 switch fd.Kind() { 186 case protoreflect.BoolKind: 187 return &boolConverter 188 189 case protoreflect.EnumKind, protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: 190 return &int32Converter 191 192 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: 193 return &uint32Converter 194 195 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: 196 return &int64Converter 197 198 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: 199 return &uint64Converter 200 201 case protoreflect.FloatKind, protoreflect.DoubleKind: 202 return &floatConverter 203 204 case protoreflect.StringKind, protoreflect.BytesKind: 205 return &stringConverter 206 207 case protoreflect.MessageKind, protoreflect.GroupKind: 208 return l.MessageType(fd.Message()).Converter() 209 210 default: 211 panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind())) 212 } 213 } 214 215 // Converters into built-in types. 216 217 var ( 218 boolConverter = simpleConverter{ 219 tp: "bool", 220 cb: func(x starlark.Value) (starlark.Value, error) { 221 if _, ok := x.(starlark.Bool); ok { 222 return x, nil 223 } 224 return nil, fmt.Errorf("got %s, want bool", x.Type()) 225 }, 226 } 227 228 floatConverter = simpleConverter{ 229 tp: "float", 230 cb: func(x starlark.Value) (starlark.Value, error) { 231 if _, ok := x.(starlark.Float); ok { 232 return x, nil 233 } 234 if i, ok := x.(starlark.Int); ok { 235 return i.Float(), nil 236 } 237 return nil, fmt.Errorf("got %s, want float", x.Type()) 238 }, 239 } 240 241 stringConverter = simpleConverter{ 242 tp: "string", 243 cb: func(x starlark.Value) (starlark.Value, error) { 244 if _, ok := x.(starlark.String); ok { 245 return x, nil 246 } 247 return nil, fmt.Errorf("got %s, want string", x.Type()) 248 }, 249 } 250 251 int32Converter = intConverter{ 252 tp: "int32", 253 min: math.MinInt32, 254 max: math.MaxInt32, 255 } 256 257 uint32Converter = uintConverter{ 258 tp: "uint32", 259 max: math.MaxUint32, 260 } 261 262 int64Converter = intConverter{ 263 tp: "int64", 264 min: math.MinInt64, 265 max: math.MaxInt64, 266 } 267 268 uint64Converter = uintConverter{ 269 tp: "uint64", 270 max: math.MaxUint64, 271 } 272 ) 273 274 type simpleConverter struct { 275 tp string 276 cb func(x starlark.Value) (starlark.Value, error) 277 } 278 279 func (c *simpleConverter) Type() string { return c.tp } 280 func (c *simpleConverter) Convert(x starlark.Value) (starlark.Value, error) { return c.cb(x) } 281 282 type intConverter struct { 283 tp string 284 min int64 285 max int64 286 } 287 288 func (c *intConverter) Type() string { return c.tp } 289 290 func (c *intConverter) Convert(x starlark.Value) (starlark.Value, error) { 291 si, ok := x.(starlark.Int) 292 if !ok { 293 return nil, fmt.Errorf("got %s, want int", x.Type()) 294 } 295 if i, ok := si.Int64(); !ok || i < c.min || i > c.max { 296 return nil, fmt.Errorf("%s doesn't fit into %s", x, c.tp) 297 } 298 return si, nil 299 } 300 301 type uintConverter struct { 302 tp string 303 max uint64 304 } 305 306 func (c *uintConverter) Type() string { return c.tp } 307 308 func (c *uintConverter) Convert(x starlark.Value) (starlark.Value, error) { 309 si, ok := x.(starlark.Int) 310 if !ok { 311 return nil, fmt.Errorf("got %s, want int", x.Type()) 312 } 313 if i, ok := si.Uint64(); !ok || i > c.max { 314 return nil, fmt.Errorf("%s doesn't fit into %s", x, c.tp) 315 } 316 return si, nil 317 }