blob: 299815e0566ca8caa4c951e27af8cd19329e9989 [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package starlarkproto
import (
"fmt"
"math"
"go.starlark.net/starlark"
"google.golang.org/protobuf/reflect/protoreflect"
"go.chromium.org/luci/starlark/typed"
)
// toStarlarkSingular converts 'v' to starlark, based on type in 'fd'.
//
// This is Proto => Starlark converter. Ignores 'repeated' qualifier.
//
// Panics if type of 'v' doesn't match 'fd'.
func toStarlarkSingular(l *Loader, fd protoreflect.FieldDescriptor, v protoreflect.Value) starlark.Value {
// See https://godoc.org/google.golang.org/protobuf/reflect/protoreflect#Kind
// Also https://developers.google.com/protocol-buffers/docs/proto#scalar
switch fd.Kind() {
case protoreflect.BoolKind:
return starlark.Bool(v.Bool())
case protoreflect.EnumKind:
return starlark.MakeInt(int(v.Enum()))
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
return starlark.MakeInt64(v.Int())
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
return starlark.MakeUint64(v.Uint())
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
return starlark.MakeInt64(v.Int())
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
return starlark.MakeUint64(v.Uint())
case protoreflect.FloatKind, protoreflect.DoubleKind:
return starlark.Float(v.Float())
case protoreflect.StringKind:
return starlark.String(v.String())
case protoreflect.BytesKind:
return starlark.String(v.Bytes())
case protoreflect.MessageKind, protoreflect.GroupKind:
typ := l.MessageType(fd.Message())
if v.IsValid() {
return typ.MessageFromProto(v.Message().Interface())
}
return typ.Message()
default:
panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind()))
}
}
// toProtoSingular converts 'v' to a protobuf value described by 'fd'.
//
// This is Starlark => Proto converter.
//
// Assumes type checks have been done already (this is responsibility of
// 'converter' function, see below).
//
// Panics on type mismatch.
func toProtoSingular(fd protoreflect.FieldDescriptor, v starlark.Value) protoreflect.Value {
// See https://godoc.org/google.golang.org/protobuf/reflect/protoreflect#Kind
// Also https://developers.google.com/protocol-buffers/docs/proto#scalar
switch kind := fd.Kind(); kind {
case protoreflect.BoolKind:
return protoreflect.ValueOf(bool(v.(starlark.Bool)))
case protoreflect.EnumKind:
return protoreflect.ValueOf(protoreflect.EnumNumber(starlarkToInt(v, kind, math.MinInt32, math.MaxInt32)))
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
return protoreflect.ValueOf(int32(starlarkToInt(v, kind, math.MinInt32, math.MaxInt32)))
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
return protoreflect.ValueOf(uint32(starlarkToUint(v, kind, math.MaxUint32)))
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
return protoreflect.ValueOf(int64(starlarkToInt(v, kind, math.MinInt64, math.MaxInt64)))
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
return protoreflect.ValueOf(uint64(starlarkToUint(v, kind, math.MaxUint64)))
case protoreflect.FloatKind:
return protoreflect.ValueOf(float32(v.(starlark.Float)))
case protoreflect.DoubleKind:
return protoreflect.ValueOf(float64(v.(starlark.Float)))
case protoreflect.StringKind:
return protoreflect.ValueOf(v.(starlark.String).GoString())
case protoreflect.BytesKind:
return protoreflect.ValueOf([]byte(v.(starlark.String).GoString()))
case protoreflect.MessageKind, protoreflect.GroupKind:
return protoreflect.ValueOf(v.(*Message).ToProto())
default:
panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind()))
}
}
// Extracts int64 from 'v' checking its range. Panics on mismatches.
func starlarkToInt(v starlark.Value, k protoreflect.Kind, min, max int64) int64 {
si, ok := v.(starlark.Int)
if !ok {
panic(fmt.Errorf("internal error: got %s, expect %s", v.Type(), k))
}
i, ok := si.Int64()
if !ok || i < min || i > max {
panic(fmt.Errorf("internal error: %s doesn't fit into %s field", v, k))
}
return i
}
// Extract uint64 from 'v' checking its range. Panics on mismatches.
func starlarkToUint(v starlark.Value, k protoreflect.Kind, max uint64) uint64 {
si, ok := v.(starlark.Int)
if !ok {
panic(fmt.Errorf("internal error: got %s, expect %s", v.Type(), k))
}
i, ok := si.Uint64()
if !ok || i > max {
panic(fmt.Errorf("internal error: %s doesn't fit into %s field", v, k))
}
return i
}
// converter returns typed.Converter that converts arbitrary Starlark values to
// another Starlark values of a scalar (i.e. non-repeated, non-dict) type
// described by the descriptor, if such conversion is allowed.
//
// This is Starlark => Starlark converter. Such converter is involved when
// executing following Starlark statements:
// msg.scalar = <some starlark value>
// msg.repeated[123] = <some starlark value>
// msg.dict[<some starlark value>] = <some starlark value>
//
// It ensures that *Message fields at all times conform to the proto message
// schema.
//
// Some notable conversion rules:
// * converter([u]int(32|64)) checks int fits within the corresponding range.
// * converter(float[32|64]) implicitly converts ints to floats.
// * converter(Message) implicitly converts dicts and Nones to messages.
//
// The following invariant holds (and relied upon by 'assign'): for all possible
// 'fd' and all possible 'x' the following doesn't panic:
//
// v, err := converter(l, fd).Convert(x)
// if err != nil {
// return err // type of 'x' is incompatible with 'fd'
// }
// p := toProtoSingular(fd, v) // compatible values can be converted to proto
func converter(l *Loader, fd protoreflect.FieldDescriptor) typed.Converter {
// Note: primitive type converters need "stable" addresses, since converters
// are compared by identity when checking type compatibility. So we use
// globals for them. For Message types, "stable" addresses are guaranteed by
// the loader, which caches types internally.
switch fd.Kind() {
case protoreflect.BoolKind:
return &boolConverter
case protoreflect.EnumKind, protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
return &int32Converter
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
return &uint32Converter
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
return &int64Converter
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
return &uint64Converter
case protoreflect.FloatKind, protoreflect.DoubleKind:
return &floatConverter
case protoreflect.StringKind, protoreflect.BytesKind:
return &stringConverter
case protoreflect.MessageKind, protoreflect.GroupKind:
return l.MessageType(fd.Message()).Converter()
default:
panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind()))
}
}
// Converters into built-in types.
var (
boolConverter = simpleConverter{
tp: "bool",
cb: func(x starlark.Value) (starlark.Value, error) {
if _, ok := x.(starlark.Bool); ok {
return x, nil
}
return nil, fmt.Errorf("got %s, want bool", x.Type())
},
}
floatConverter = simpleConverter{
tp: "float",
cb: func(x starlark.Value) (starlark.Value, error) {
if _, ok := x.(starlark.Float); ok {
return x, nil
}
if i, ok := x.(starlark.Int); ok {
return i.Float(), nil
}
return nil, fmt.Errorf("got %s, want float", x.Type())
},
}
stringConverter = simpleConverter{
tp: "string",
cb: func(x starlark.Value) (starlark.Value, error) {
if _, ok := x.(starlark.String); ok {
return x, nil
}
return nil, fmt.Errorf("got %s, want string", x.Type())
},
}
int32Converter = intConverter{
tp: "int32",
min: math.MinInt32,
max: math.MaxInt32,
}
uint32Converter = uintConverter{
tp: "uint32",
max: math.MaxUint32,
}
int64Converter = intConverter{
tp: "int64",
min: math.MinInt64,
max: math.MaxInt64,
}
uint64Converter = uintConverter{
tp: "uint64",
max: math.MaxUint64,
}
)
type simpleConverter struct {
tp string
cb func(x starlark.Value) (starlark.Value, error)
}
func (c *simpleConverter) Type() string { return c.tp }
func (c *simpleConverter) Convert(x starlark.Value) (starlark.Value, error) { return c.cb(x) }
type intConverter struct {
tp string
min int64
max int64
}
func (c *intConverter) Type() string { return c.tp }
func (c *intConverter) Convert(x starlark.Value) (starlark.Value, error) {
si, ok := x.(starlark.Int)
if !ok {
return nil, fmt.Errorf("got %s, want int", x.Type())
}
if i, ok := si.Int64(); !ok || i < c.min || i > c.max {
return nil, fmt.Errorf("%s doesn't fit into %s", x, c.tp)
}
return si, nil
}
type uintConverter struct {
tp string
max uint64
}
func (c *uintConverter) Type() string { return c.tp }
func (c *uintConverter) Convert(x starlark.Value) (starlark.Value, error) {
si, ok := x.(starlark.Int)
if !ok {
return nil, fmt.Errorf("got %s, want int", x.Type())
}
if i, ok := si.Uint64(); !ok || i > c.max {
return nil, fmt.Errorf("%s doesn't fit into %s", x, c.tp)
}
return si, nil
}