package filepb import ( "errors" "fmt" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" ) var ErrProtoNoVarName error = errors.New("name not in .proto") var ErrProtoVarNotString error = errors.New("name exists but is not a string") // Gemini AI can help author some pretty good protobuf code. // I never remember the syntax for 'reflect' on these things. // sets "Filename" if it exists in the protobuf func SetFilename(pb proto.Message, filename string) (bool, error) { msg := pb.ProtoReflect() // This is the entry point to the reflection API. descriptor := msg.Descriptor() // Get the message's descriptor, which contains metadata about its fields. fieldName := protoreflect.Name("Filename") fieldDescriptor := descriptor.Fields().ByName(fieldName) if fieldDescriptor == nil { fieldName = protoreflect.Name("filename") fieldDescriptor = descriptor.Fields().ByName(fieldName) } if fieldDescriptor == nil { return false, fmt.Errorf("fieldDescriptor == nil") } if fieldDescriptor.Kind() != protoreflect.StringKind { // The field exists but is not a string, so we can't return it as one. return false, fmt.Errorf("The field exists but is not a string") } valueToSet := protoreflect.ValueOfString(filename) // 6. If the field exists and is a string, get its value. // The value is returned as a protoreflect.Value. msg.Set(fieldDescriptor, valueToSet) // 7. Convert the protoreflect.Value to a native Go string. return true, nil } // this will try both "filename" and "Filename" func GetFilename(pb proto.Message) (string, error) { // 1. Get the protoreflect.Message interface from the message. // This is the entry point to the reflection API. msg := pb.ProtoReflect() // 2. Get the message's descriptor, which contains metadata about its fields. descriptor := msg.Descriptor() // 3. Find the specific field descriptor by its protobuf name ("Filename"). // Note: The field name must match the name in the .proto file. fieldName := protoreflect.Name("Filename") fieldDescriptor := descriptor.Fields().ByName(fieldName) // try upper case if fieldDescriptor == nil { fieldName = protoreflect.Name("filename") fieldDescriptor = descriptor.Fields().ByName(fieldName) // if fieldDescriptor == nil { // panic(".proto file: try 'Filename', not 'filename'? or maybe nomutex if pb.Marshal() fails") // } } // 4. Check if the field was found. If not, return false. if fieldDescriptor == nil { return "", ErrProtoNoVarName } // 5. (Optional but recommended) Verify the field is a string type. if fieldDescriptor.Kind() != protoreflect.StringKind { // The field exists but is not a string, so we can't return it as one. return "", ErrProtoVarNotString } // 6. If the field exists and is a string, get its value. // The value is returned as a protoreflect.Value. value := msg.Get(fieldDescriptor) // 7. Convert the protoreflect.Value to a native Go string. return value.String(), nil } // this will try both "filename" and "Filename" func GetString(pb proto.Message, varname string) (string, error) { // 1. Get the protoreflect.Message interface from the message. // This is the entry point to the reflection API. msg := pb.ProtoReflect() // 2. Get the message's descriptor, which contains metadata about its fields. descriptor := msg.Descriptor() // 3. Find the specific field descriptor by its protobuf name ("Filename"). // Note: The field name must match the name in the .proto file. fieldName := protoreflect.Name(varname) fieldDescriptor := descriptor.Fields().ByName(fieldName) // 4. Check if the field was found. If not, return false. if fieldDescriptor == nil { return "", ErrProtoNoVarName } // 5. (Optional but recommended) Verify the field is a string type. if fieldDescriptor.Kind() != protoreflect.StringKind { // The field exists but is not a string, so we can't return it as one. return "", ErrProtoVarNotString } // 6. If the field exists and is a string, get its value. // The value is returned as a protoreflect.Value. value := msg.Get(fieldDescriptor) // 7. Convert the protoreflect.Value to a native Go string. return value.String(), nil } // don't do this. use prototext.Format(pb) // duh. I'm dumb. I literally wasted my time + was being lazy so I // just asked asking Gemini AI to make some function for this // when, for years, I use prototext.Format() all over the place func printStrings(pb proto.Message) { // 1. Get the protoreflect.Message interface. msg := pb.ProtoReflect() // It's good practice to check if the message is valid. if !msg.IsValid() { fmt.Printf("Error: Provided protobuf message is not valid.") return } // 2. Get the message's descriptor. descriptor := msg.Descriptor() fmt.Printf("--- Listing String Fields in [%s] ---\n", descriptor.FullName()) // 3. Get the collection of all field descriptors for this message. fields := descriptor.Fields() // 4. Iterate over all the fields. for i := 0; i < fields.Len(); i++ { // Get the descriptor for the field at the current index. fieldDescriptor := fields.Get(i) // 5. Check if the field's kind is a string. if fieldDescriptor.Kind() == protoreflect.StringKind { // 6. If it is a string, get its name and value. fieldName := fieldDescriptor.Name() value := msg.Get(fieldDescriptor).String() // 7. Print the formatted result. // We add a check to see if the field is populated. An empty string // is a valid value, but you might only want to see set fields. if msg.Has(fieldDescriptor) { fmt.Printf(" %s: \"%s\"\n", fieldName, value) } } } }