go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/flag/protomsgsliceflag.go (about) 1 // Copyright 2020 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 flag 16 17 import ( 18 "flag" 19 "fmt" 20 "reflect" 21 "strings" 22 23 "google.golang.org/protobuf/encoding/protojson" 24 "google.golang.org/protobuf/proto" 25 ) 26 27 var protoMsgBaseType = reflect.TypeOf((*proto.Message)(nil)).Elem() 28 29 type messageSliceFlag struct { 30 // msgSliceVal is a slice of pointers to concrete proto message structs. 31 msgSliceVal reflect.Value 32 // refMsg is a zero value concrete message struct instance that is used as 33 // reference to create new instance for deserialization purposes. 34 refMsg proto.Message 35 } 36 37 // MessageSliceFlag returns a new flag for a slice of pointer to concrete proto 38 // message struct which implements the proto.Message interface. Expect input to 39 // be of type *[]*B where B is concrete struct of proto message like pb.Build. 40 // A flag value must be a JSON string of the provided concrete proto message. 41 func MessageSliceFlag(msgSlicePtr any) flag.Getter { 42 slicePtrVal := reflect.ValueOf(msgSlicePtr) 43 assertKind(slicePtrVal.Type(), reflect.Ptr) 44 45 sliceType := slicePtrVal.Elem().Type() 46 assertKind(sliceType, reflect.Slice) 47 48 sliceElemType := sliceType.Elem() 49 if !sliceElemType.Implements(protoMsgBaseType) { 50 panic(fmt.Sprintf("Expect elements of the slice to implement interface: %s, however, got type: %s", protoMsgBaseType, sliceElemType)) 51 } 52 // type that implement proto.message should be of pointer. 53 assertKind(sliceElemType, reflect.Ptr) 54 55 protoMsgStructType := sliceElemType.Elem() 56 assertKind(protoMsgStructType, reflect.Struct) 57 58 // reflect new return ptr to the message struct which implements the 59 // proto.Message interface 60 refMsg := reflect.New(protoMsgStructType).Interface().(proto.Message) 61 62 return &messageSliceFlag{ 63 msgSliceVal: slicePtrVal.Elem(), 64 refMsg: refMsg, 65 } 66 } 67 68 // String returns all messages serailzed in JSON and separated by a new line. 69 // Empty string will be returned if flag is a zero value. 70 func (m messageSliceFlag) String() string { 71 if !m.msgSliceVal.IsValid() { 72 return "" 73 } 74 var sb strings.Builder 75 for i := 0; i < m.msgSliceVal.Len(); i++ { 76 msg := m.msgSliceVal.Index(i).Interface().(proto.Message) 77 78 if buf, err := protojson.Marshal(msg); err != nil { 79 panic(fmt.Errorf("failed to marshal a message: %s", err)) 80 } else { 81 sb.Write(buf) 82 } 83 sb.WriteString("\n") 84 } 85 return sb.String() 86 } 87 88 // Set deserializes the JSON datagram of proto message and appends it 89 // to the slice. 90 func (m *messageSliceFlag) Set(val string) error { 91 newMsg := proto.Clone(m.refMsg) 92 if err := protojson.Unmarshal([]byte(val), newMsg); err != nil { 93 return err 94 } 95 m.msgSliceVal.Set(reflect.Append(m.msgSliceVal, reflect.ValueOf(newMsg))) 96 return nil 97 } 98 99 // Get retrieves the raw flag value. 100 func (m *messageSliceFlag) Get() any { 101 return m.msgSliceVal.Interface() 102 } 103 104 // assertKind panics when the given actual type does not match the expected 105 // kind. 106 func assertKind(actual reflect.Type, expected reflect.Kind) { 107 if actual.Kind() != expected { 108 panic(fmt.Sprintf("Expect kind: %s, however, got type: %s of kind %s", 109 expected, actual, actual.Kind())) 110 } 111 }