package descfmt
import (
"fmt"
"io"
"reflect"
"strconv"
"strings"
"google.golang.org/protobuf/internal/detrand"
"google.golang.org/protobuf/internal/pragma"
"google.golang.org/protobuf/reflect/protoreflect"
)
type list interface {
Len () int
pragma .DoNotImplement
}
func FormatList (s fmt .State , r rune , vs list ) {
io .WriteString (s , formatListOpt (vs , true , r == 'v' && (s .Flag ('+' ) || s .Flag ('#' ))))
}
func formatListOpt (vs list , isRoot , allowMulti bool ) string {
start , end := "[" , "]"
if isRoot {
var name string
switch vs .(type ) {
case protoreflect .Names :
name = "Names"
case protoreflect .FieldNumbers :
name = "FieldNumbers"
case protoreflect .FieldRanges :
name = "FieldRanges"
case protoreflect .EnumRanges :
name = "EnumRanges"
case protoreflect .FileImports :
name = "FileImports"
case protoreflect .Descriptor :
name = reflect .ValueOf (vs ).MethodByName ("Get" ).Type ().Out (0 ).Name () + "s"
default :
name = reflect .ValueOf (vs ).Elem ().Type ().Name ()
}
start , end = name +"{" , "}"
}
var ss []string
switch vs := vs .(type ) {
case protoreflect .Names :
for i := 0 ; i < vs .Len (); i ++ {
ss = append (ss , fmt .Sprint (vs .Get (i )))
}
return start + joinStrings (ss , false ) + end
case protoreflect .FieldNumbers :
for i := 0 ; i < vs .Len (); i ++ {
ss = append (ss , fmt .Sprint (vs .Get (i )))
}
return start + joinStrings (ss , false ) + end
case protoreflect .FieldRanges :
for i := 0 ; i < vs .Len (); i ++ {
r := vs .Get (i )
if r [0 ]+1 == r [1 ] {
ss = append (ss , fmt .Sprintf ("%d" , r [0 ]))
} else {
ss = append (ss , fmt .Sprintf ("%d:%d" , r [0 ], r [1 ]))
}
}
return start + joinStrings (ss , false ) + end
case protoreflect .EnumRanges :
for i := 0 ; i < vs .Len (); i ++ {
r := vs .Get (i )
if r [0 ] == r [1 ] {
ss = append (ss , fmt .Sprintf ("%d" , r [0 ]))
} else {
ss = append (ss , fmt .Sprintf ("%d:%d" , r [0 ], int64 (r [1 ])+1 ))
}
}
return start + joinStrings (ss , false ) + end
case protoreflect .FileImports :
for i := 0 ; i < vs .Len (); i ++ {
var rs records
rv := reflect .ValueOf (vs .Get (i ))
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Path" ), "Path" },
{rv .MethodByName ("Package" ), "Package" },
{rv .MethodByName ("IsPublic" ), "IsPublic" },
{rv .MethodByName ("IsWeak" ), "IsWeak" },
}...)
ss = append (ss , "{" +rs .Join ()+"}" )
}
return start + joinStrings (ss , allowMulti ) + end
default :
_ , isEnumValue := vs .(protoreflect .EnumValueDescriptors )
for i := 0 ; i < vs .Len (); i ++ {
m := reflect .ValueOf (vs ).MethodByName ("Get" )
v := m .Call ([]reflect .Value {reflect .ValueOf (i )})[0 ].Interface ()
ss = append (ss , formatDescOpt (v .(protoreflect .Descriptor ), false , allowMulti && !isEnumValue , nil ))
}
return start + joinStrings (ss , allowMulti && isEnumValue ) + end
}
}
type methodAndName struct {
method reflect .Value
name string
}
func FormatDesc (s fmt .State , r rune , t protoreflect .Descriptor ) {
io .WriteString (s , formatDescOpt (t , true , r == 'v' && (s .Flag ('+' ) || s .Flag ('#' )), nil ))
}
func InternalFormatDescOptForTesting (t protoreflect .Descriptor , isRoot , allowMulti bool , record func (string )) string {
return formatDescOpt (t , isRoot , allowMulti , record )
}
func formatDescOpt (t protoreflect .Descriptor , isRoot , allowMulti bool , record func (string )) string {
rv := reflect .ValueOf (t )
rt := rv .MethodByName ("ProtoType" ).Type ().In (0 )
start , end := "{" , "}"
if isRoot {
start = rt .Name () + "{"
}
_ , isFile := t .(protoreflect .FileDescriptor )
rs := records {
allowMulti : allowMulti ,
record : record ,
}
if t .IsPlaceholder () {
if isFile {
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Path" ), "Path" },
{rv .MethodByName ("Package" ), "Package" },
{rv .MethodByName ("IsPlaceholder" ), "IsPlaceholder" },
}...)
} else {
rs .Append (rv , []methodAndName {
{rv .MethodByName ("FullName" ), "FullName" },
{rv .MethodByName ("IsPlaceholder" ), "IsPlaceholder" },
}...)
}
} else {
switch {
case isFile :
rs .Append (rv , methodAndName {rv .MethodByName ("Syntax" ), "Syntax" })
case isRoot :
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Syntax" ), "Syntax" },
{rv .MethodByName ("FullName" ), "FullName" },
}...)
default :
rs .Append (rv , methodAndName {rv .MethodByName ("Name" ), "Name" })
}
switch t := t .(type ) {
case protoreflect .FieldDescriptor :
accessors := []methodAndName {
{rv .MethodByName ("Number" ), "Number" },
{rv .MethodByName ("Cardinality" ), "Cardinality" },
{rv .MethodByName ("Kind" ), "Kind" },
{rv .MethodByName ("HasJSONName" ), "HasJSONName" },
{rv .MethodByName ("JSONName" ), "JSONName" },
{rv .MethodByName ("HasPresence" ), "HasPresence" },
{rv .MethodByName ("IsExtension" ), "IsExtension" },
{rv .MethodByName ("IsPacked" ), "IsPacked" },
{rv .MethodByName ("IsWeak" ), "IsWeak" },
{rv .MethodByName ("IsList" ), "IsList" },
{rv .MethodByName ("IsMap" ), "IsMap" },
{rv .MethodByName ("MapKey" ), "MapKey" },
{rv .MethodByName ("MapValue" ), "MapValue" },
{rv .MethodByName ("HasDefault" ), "HasDefault" },
{rv .MethodByName ("Default" ), "Default" },
{rv .MethodByName ("ContainingOneof" ), "ContainingOneof" },
{rv .MethodByName ("ContainingMessage" ), "ContainingMessage" },
{rv .MethodByName ("Message" ), "Message" },
{rv .MethodByName ("Enum" ), "Enum" },
}
for _ , s := range accessors {
switch s .name {
case "MapKey" :
if k := t .MapKey (); k != nil {
rs .recs = append (rs .recs , [2 ]string {"MapKey" , k .Kind ().String ()})
}
case "MapValue" :
if v := t .MapValue (); v != nil {
switch v .Kind () {
case protoreflect .EnumKind :
rs .AppendRecs ("MapValue" , [2 ]string {"MapValue" , string (v .Enum ().FullName ())})
case protoreflect .MessageKind , protoreflect .GroupKind :
rs .AppendRecs ("MapValue" , [2 ]string {"MapValue" , string (v .Message ().FullName ())})
default :
rs .AppendRecs ("MapValue" , [2 ]string {"MapValue" , v .Kind ().String ()})
}
}
case "ContainingOneof" :
if od := t .ContainingOneof (); od != nil {
rs .AppendRecs ("ContainingOneof" , [2 ]string {"Oneof" , string (od .Name ())})
}
case "ContainingMessage" :
if t .IsExtension () {
rs .AppendRecs ("ContainingMessage" , [2 ]string {"Extendee" , string (t .ContainingMessage ().FullName ())})
}
case "Message" :
if !t .IsMap () {
rs .Append (rv , s )
}
default :
rs .Append (rv , s )
}
}
case protoreflect .OneofDescriptor :
var ss []string
fs := t .Fields ()
for i := 0 ; i < fs .Len (); i ++ {
ss = append (ss , string (fs .Get (i ).Name ()))
}
if len (ss ) > 0 {
rs .AppendRecs ("Fields" , [2 ]string {"Fields" , "[" + joinStrings (ss , false ) + "]" })
}
case protoreflect .FileDescriptor :
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Path" ), "Path" },
{rv .MethodByName ("Package" ), "Package" },
{rv .MethodByName ("Imports" ), "Imports" },
{rv .MethodByName ("Messages" ), "Messages" },
{rv .MethodByName ("Enums" ), "Enums" },
{rv .MethodByName ("Extensions" ), "Extensions" },
{rv .MethodByName ("Services" ), "Services" },
}...)
case protoreflect .MessageDescriptor :
rs .Append (rv , []methodAndName {
{rv .MethodByName ("IsMapEntry" ), "IsMapEntry" },
{rv .MethodByName ("Fields" ), "Fields" },
{rv .MethodByName ("Oneofs" ), "Oneofs" },
{rv .MethodByName ("ReservedNames" ), "ReservedNames" },
{rv .MethodByName ("ReservedRanges" ), "ReservedRanges" },
{rv .MethodByName ("RequiredNumbers" ), "RequiredNumbers" },
{rv .MethodByName ("ExtensionRanges" ), "ExtensionRanges" },
{rv .MethodByName ("Messages" ), "Messages" },
{rv .MethodByName ("Enums" ), "Enums" },
{rv .MethodByName ("Extensions" ), "Extensions" },
}...)
case protoreflect .EnumDescriptor :
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Values" ), "Values" },
{rv .MethodByName ("ReservedNames" ), "ReservedNames" },
{rv .MethodByName ("ReservedRanges" ), "ReservedRanges" },
{rv .MethodByName ("IsClosed" ), "IsClosed" },
}...)
case protoreflect .EnumValueDescriptor :
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Number" ), "Number" },
}...)
case protoreflect .ServiceDescriptor :
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Methods" ), "Methods" },
}...)
case protoreflect .MethodDescriptor :
rs .Append (rv , []methodAndName {
{rv .MethodByName ("Input" ), "Input" },
{rv .MethodByName ("Output" ), "Output" },
{rv .MethodByName ("IsStreamingClient" ), "IsStreamingClient" },
{rv .MethodByName ("IsStreamingServer" ), "IsStreamingServer" },
}...)
}
if m := rv .MethodByName ("GoType" ); m .IsValid () {
rs .Append (rv , methodAndName {m , "GoType" })
}
}
return start + rs .Join () + end
}
type records struct {
recs [][2 ]string
allowMulti bool
record func (string )
}
func (rs *records ) AppendRecs (fieldName string , newRecs [2 ]string ) {
if rs .record != nil {
rs .record (fieldName )
}
rs .recs = append (rs .recs , newRecs )
}
func (rs *records ) Append (v reflect .Value , accessors ...methodAndName ) {
for _ , a := range accessors {
if rs .record != nil {
rs .record (a .name )
}
var rv reflect .Value
if a .method .IsValid () {
rv = a .method .Call (nil )[0 ]
}
if v .Kind () == reflect .Struct && !rv .IsValid () {
rv = v .FieldByName (a .name )
}
if !rv .IsValid () {
panic (fmt .Sprintf ("unknown accessor: %v.%s" , v .Type (), a .name ))
}
if _ , ok := rv .Interface ().(protoreflect .Value ); ok {
rv = rv .MethodByName ("Interface" ).Call (nil )[0 ]
if !rv .IsNil () {
rv = rv .Elem ()
}
}
var isZero bool
switch rv .Kind () {
case reflect .Interface , reflect .Slice :
isZero = rv .IsNil ()
case reflect .Bool :
isZero = rv .Bool () == false
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
isZero = rv .Int () == 0
case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
isZero = rv .Uint () == 0
case reflect .String :
isZero = rv .String () == ""
}
if n , ok := rv .Interface ().(list ); ok {
isZero = n .Len () == 0
}
if isZero {
continue
}
var s string
v := rv .Interface ()
switch v := v .(type ) {
case list :
s = formatListOpt (v , false , rs .allowMulti )
case protoreflect .FieldDescriptor , protoreflect .OneofDescriptor , protoreflect .EnumValueDescriptor , protoreflect .MethodDescriptor :
s = string (v .(protoreflect .Descriptor ).Name ())
case protoreflect .Descriptor :
s = string (v .FullName ())
case string :
s = strconv .Quote (v )
case []byte :
s = fmt .Sprintf ("%q" , v )
default :
s = fmt .Sprint (v )
}
rs .recs = append (rs .recs , [2 ]string {a .name , s })
}
}
func (rs *records ) Join () string {
var ss []string
if !rs .allowMulti {
for _ , r := range rs .recs {
ss = append (ss , r [0 ]+formatColon (0 )+r [1 ])
}
return joinStrings (ss , false )
}
var maxLen int
flush := func (i int ) {
for _ , r := range rs .recs [len (ss ):i ] {
ss = append (ss , r [0 ]+formatColon (maxLen -len (r [0 ]))+r [1 ])
}
maxLen = 0
}
for i , r := range rs .recs {
if isMulti := strings .Contains (r [1 ], "\n" ); isMulti {
flush (i )
ss = append (ss , r [0 ]+formatColon (0 )+strings .Join (strings .Split (r [1 ], "\n" ), "\n\t" ))
} else if maxLen < len (r [0 ]) {
maxLen = len (r [0 ])
}
}
flush (len (rs .recs ))
return joinStrings (ss , true )
}
func formatColon (padding int ) string {
if detrand .Bool () {
return ":" + strings .Repeat (" " , 1 +padding )
} else {
return ":" + strings .Repeat (" " , 1 +padding )
}
}
func joinStrings (ss []string , isMulti bool ) string {
if len (ss ) == 0 {
return ""
}
if isMulti {
return "\n\t" + strings .Join (ss , "\n\t" ) + "\n"
}
return strings .Join (ss , ", " )
}