go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/lsp.go (about) 1 // Copyright 2023 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 datastore 16 17 import ( 18 "fmt" 19 20 "google.golang.org/protobuf/proto" 21 22 pb "go.chromium.org/luci/gae/service/datastore/internal/protos/datastore" 23 ) 24 25 // loadLegacyLSP deserializes a "local structure property" into a property map. 26 // 27 // It supports only basic set of property types (e.g. no geo-points or keys). 28 func loadLegacyLSP(blob []byte) (PropertyMap, error) { 29 // Key and EntityGroup are marked `required` in the proto, but they aren't 30 // actually populate by Python code. 31 ent := pb.EntityProto{} 32 if err := (proto.UnmarshalOptions{AllowPartial: true}).Unmarshal(blob, &ent); err != nil { 33 return nil, err 34 } 35 36 switch { 37 case ent.Key != nil: 38 return nil, fmt.Errorf("unexpectedly populated `key` in %s", &ent) 39 case ent.EntityGroup != nil: 40 return nil, fmt.Errorf("unexpectedly populated `entity_group` in %s", &ent) 41 case ent.Owner != nil: 42 return nil, fmt.Errorf("unexpectedly populated `owner` in %s", &ent) 43 case ent.Kind != nil: 44 return nil, fmt.Errorf("unexpectedly populated `kind` in %s", &ent) 45 case ent.KindUri != nil: 46 return nil, fmt.Errorf("unexpectedly populated `kind_uri` in %s", &ent) 47 } 48 49 pmap := make(PropertyMap, len(ent.Property)+len(ent.RawProperty)) 50 51 addOne := func(p *pb.Property) error { 52 name, val, err := decodeLSPProp(p) 53 if err != nil { 54 return err 55 } 56 switch cur := pmap[name].(type) { 57 case PropertySlice: 58 pmap[name] = append(cur, val) 59 case Property: 60 pmap[name] = PropertySlice{cur, val} 61 case nil: 62 if p.GetMultiple() { 63 pmap[name] = PropertySlice{val} 64 } else { 65 pmap[name] = val 66 } 67 default: 68 panic("impossible") 69 } 70 return nil 71 } 72 73 for _, prop := range ent.Property { 74 if err := addOne(prop); err != nil { 75 return nil, err 76 } 77 } 78 for _, prop := range ent.RawProperty { 79 if err := addOne(prop); err != nil { 80 return nil, err 81 } 82 } 83 return pmap, nil 84 } 85 86 func decodeLSPProp(p *pb.Property) (name string, prop Property, err error) { 87 if name = p.GetName(); name == "" { 88 err = fmt.Errorf("there's a property without a name") 89 return 90 } 91 92 v := p.Value 93 if v == nil { 94 err = fmt.Errorf("property %q has no value", name) 95 return 96 } 97 98 switch { 99 case v.Int64Value != nil: 100 prop = MkPropertyNI(*v.Int64Value) 101 case v.BooleanValue != nil: 102 prop = MkPropertyNI(*v.BooleanValue) 103 case v.StringValue != nil && propertyIsBytes(p.GetMeaning()): 104 prop = MkPropertyNI([]byte(*v.StringValue)) 105 case v.StringValue != nil: 106 prop = MkPropertyNI(*v.StringValue) 107 case v.DoubleValue != nil: 108 prop = MkPropertyNI(*v.DoubleValue) 109 case v.Pointvalue != nil || v.Referencevalue != nil || v.Uservalue != nil: 110 err = fmt.Errorf("unsupported LSP property: %s", p) 111 default: 112 prop = MkPropertyNI(nil) 113 } 114 115 return 116 } 117 118 func propertyIsBytes(m pb.Property_Meaning) bool { 119 return m == pb.Property_BLOB || m == pb.Property_BYTESTRING || m == pb.Property_ENTITY_PROTO 120 }