trpc.group/trpc-go/trpc-cmdline@v1.0.9/util/apidocs/property.go (about)

     1  // Tencent is pleased to support the open source community by making tRPC available.
     2  //
     3  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     4  // All rights reserved.
     5  //
     6  // If you have downloaded a copy of the tRPC source code from Tencent,
     7  // please note that tRPC source code is licensed under the  Apache 2.0 License,
     8  // A copy of the Apache 2.0 License is included in this file.
     9  
    10  package apidocs
    11  
    12  import (
    13  	"fmt"
    14  	"reflect"
    15  	"sort"
    16  	"strings"
    17  
    18  	"github.com/jhump/protoreflect/desc"
    19  	protobuf "google.golang.org/protobuf/types/descriptorpb"
    20  
    21  	"trpc.group/trpc-go/trpc-cmdline/params"
    22  
    23  	"trpc.group/trpc-go/trpc-cmdline/util/apidocs/x"
    24  )
    25  
    26  // PropertyStruct defines the structure of a single field of a data model in the api docs json.
    27  type PropertyStruct struct {
    28  	Title       string  `json:"title,omitempty"`       // Name of parameter.
    29  	Type        string  `json:"type,omitempty"`        // Type of parameter.
    30  	Format      string  `json:"format,omitempty"`      // Format of parameter.
    31  	Ref         string  `json:"$ref,omitempty"`        // References in parameter.
    32  	Description string  `json:"description,omitempty"` // Description of paramaeter.
    33  	Enum        []int32 `json:"enum,omitempty"`        // Possible values of the enum type.
    34  	// When type is array, specify the member type, i.e., the description of a single field value.
    35  	Items *PropertyStruct `json:"items,omitempty"`
    36  }
    37  
    38  // Properties Properties
    39  type Properties struct {
    40  	Elements map[string]PropertyStruct
    41  	Rank     map[string]int
    42  }
    43  
    44  // Put puts an element into an ordered map and records the order of the elements in Rank.
    45  func (props *Properties) Put(key string, value PropertyStruct) {
    46  	props.Elements[key] = value
    47  
    48  	if props.Rank != nil {
    49  		if _, ok := props.Rank[key]; !ok {
    50  			props.Rank[key] = len(props.Elements)
    51  		}
    52  	}
    53  }
    54  
    55  // UnmarshalJSON deserializes JSON data.
    56  func (props *Properties) UnmarshalJSON(b []byte) error {
    57  	return OrderedUnmarshalJSON(b, &props.Elements, &props.Rank)
    58  }
    59  
    60  // MarshalJSON serializes to JSON.
    61  func (props Properties) MarshalJSON() ([]byte, error) {
    62  	return OrderedMarshalJSON(props.Elements, props.Rank)
    63  }
    64  
    65  // orderedEach traverses in order.
    66  func (props *Properties) orderedEach(f func(k string, prop PropertyStruct)) {
    67  	if props == nil {
    68  		return
    69  	}
    70  
    71  	var keys []string
    72  	for k := range props.Elements {
    73  		keys = append(keys, k)
    74  	}
    75  
    76  	if props.Rank != nil {
    77  		sort.Slice(keys, func(i, j int) bool {
    78  			return props.Rank[keys[i]] < props.Rank[keys[j]]
    79  		})
    80  	} else {
    81  		sort.Strings(keys)
    82  	}
    83  
    84  	for _, k := range keys {
    85  		f(k, props.Elements[k])
    86  	}
    87  }
    88  
    89  func (p PropertyStruct) refs(searched map[string]bool) []string {
    90  	if searched[p.Ref] {
    91  		// Circular reference exists.
    92  		return nil
    93  	}
    94  
    95  	var refs []string
    96  	if len(p.Ref) > 0 {
    97  		refs = append(refs, GetNameByRef(p.Ref))
    98  		searched[p.Ref] = true
    99  	}
   100  
   101  	if p.Items == nil {
   102  		return refs
   103  	}
   104  
   105  	return append(refs, p.Items.refs(searched)...)
   106  }
   107  
   108  // NewPropertyFunc factory
   109  type NewPropertyFunc func(field *desc.FieldDescriptor, defs *Definitions) PropertyStruct
   110  
   111  // NewProperties new
   112  func NewProperties(option *params.Option, msg *desc.MessageDescriptor, defs *Definitions) *Properties {
   113  	if len(msg.GetFields()) == 0 {
   114  		return nil
   115  	}
   116  
   117  	// Get message's field information and fill in properties.
   118  	propertiesMap := &Properties{
   119  		Elements: make(map[string]PropertyStruct),
   120  	}
   121  
   122  	if option.OrderByPBName {
   123  		propertiesMap.Rank = make(map[string]int)
   124  	}
   125  
   126  	for _, field := range msg.GetFields() {
   127  		propertiesMap.Put(field.GetName(), NewProperty(field, defs))
   128  	}
   129  
   130  	return propertiesMap
   131  }
   132  
   133  // NewProperty new
   134  func NewProperty(field *desc.FieldDescriptor, defs *Definitions) PropertyStruct {
   135  	property := newPropertyFactory(field)(field, defs)
   136  
   137  	if property.Ref == "" || len(property.Title) == 0 {
   138  		property.Title = field.GetName()
   139  	}
   140  
   141  	if !field.IsRepeated() {
   142  		return property
   143  	}
   144  
   145  	p := property
   146  	property.Items = &PropertyStruct{
   147  		Type:   p.Type,
   148  		Format: p.Format,
   149  	}
   150  
   151  	if p.Ref != "" {
   152  		property.Items = &PropertyStruct{Ref: p.Ref}
   153  	}
   154  	property.Ref = ""
   155  	property.Type = "array"
   156  
   157  	return property
   158  }
   159  
   160  func newPropertyFactory(field *desc.FieldDescriptor) NewPropertyFunc {
   161  	isMsg := field.GetType() == protobuf.FieldDescriptorProto_TYPE_MESSAGE
   162  
   163  	switch {
   164  	case field.GetType() == protobuf.FieldDescriptorProto_TYPE_ENUM:
   165  		return newEnumProperty
   166  	case field.IsMap():
   167  		return newMapProperty
   168  	case isMsg:
   169  		return newMessageProperty
   170  	default:
   171  		return newBasicProperty
   172  	}
   173  }
   174  
   175  func newBasicProperty(field *desc.FieldDescriptor, defs *Definitions) PropertyStruct {
   176  	// Get comment of field.
   177  	var descriptions []string
   178  	field.GetSourceInfo().GetLeadingDetachedComments()
   179  	descriptions = append(descriptions, strings.TrimSpace(field.GetSourceInfo().GetLeadingComments()))
   180  	descriptions = append(descriptions, strings.TrimSpace(field.GetSourceInfo().GetTrailingComments()))
   181  
   182  	return PropertyStruct{
   183  		Type:        x.GetTypeStr(field.GetType()),
   184  		Format:      x.GetFormatStr(field.GetType()),
   185  		Description: strings.TrimSpace(strings.Join(descriptions, "\n")),
   186  	}
   187  }
   188  
   189  func newEnumProperty(field *desc.FieldDescriptor, defs *Definitions) PropertyStruct {
   190  	property := newBasicProperty(field, defs)
   191  	enums := field.GetEnumType().GetValues()
   192  	if len(enums) == 0 {
   193  		return property
   194  	}
   195  
   196  	for _, enum := range enums {
   197  		desc := fmt.Sprintf("%d - %s - %s",
   198  			enum.GetNumber(),
   199  			enum.GetName(),
   200  			strings.TrimSpace(enum.GetSourceInfo().GetTrailingComments()),
   201  		)
   202  		property.Enum = append(property.Enum, enum.GetNumber())
   203  		property.Description += " * " + desc + "\n"
   204  	}
   205  
   206  	return property
   207  }
   208  
   209  func newMessageProperty(field *desc.FieldDescriptor, defs *Definitions) PropertyStruct {
   210  	return PropertyStruct{
   211  		Ref:         RefName(field.GetMessageType().GetFullyQualifiedName()),
   212  		Description: field.GetSourceInfo().GetLeadingComments() + field.GetSourceInfo().GetTrailingComments(),
   213  	}
   214  }
   215  
   216  func newMapProperty(field *desc.FieldDescriptor, defs *Definitions) PropertyStruct {
   217  	name := strings.TrimSuffix(field.GetMessageType().GetFullyQualifiedName(), "entry")
   218  
   219  	mapValueField := field.GetMapValueType()
   220  	mapAdditionProperties := PropertyStruct{}
   221  	if mapValueField.GetType() == protobuf.FieldDescriptorProto_TYPE_MESSAGE {
   222  		// For map values that are message types, use reflection to get the actual type
   223  		rMapValue := reflect.ValueOf(mapValueField)
   224  		rProto := reflect.Indirect(rMapValue).FieldByName("proto")
   225  		rTypeName := reflect.Indirect(rProto).FieldByName("TypeName")
   226  		typeName := fmt.Sprint(rTypeName.Elem())[1:]
   227  		mapAdditionProperties.Ref = RefName(typeName)
   228  	} else {
   229  		mapAdditionProperties = newBasicProperty(mapValueField, defs)
   230  	}
   231  
   232  	defs.addAdditionalModel(name, mapAdditionProperties)
   233  	return PropertyStruct{
   234  		Type:        x.GetTypeStr(field.GetType()),
   235  		Ref:         RefName(name),
   236  		Description: strings.TrimSpace(field.GetSourceInfo().GetLeadingComments()),
   237  	}
   238  }
   239  
   240  // GetQueryParameter converts the given parameters into a query parameter string.
   241  func (p PropertyStruct) GetQueryParameter(name string) *ParametersStruct {
   242  	return &ParametersStruct{
   243  		Required:    false, // Set to non-required field by default.
   244  		Name:        name,
   245  		In:          "query",
   246  		Schema:      nil, // Query parameter should not have schema.
   247  		Type:        p.Type,
   248  		Format:      p.Format,
   249  		Description: p.Description,
   250  		Enum:        p.Enum,
   251  		Items:       p.Items,
   252  	}
   253  }
   254  
   255  // GetQueryParameters converts the given parameters into a query parameter string.
   256  func (p PropertyStruct) GetQueryParameters(name string, defs *Definitions,
   257  	searched map[string]bool) []*ParametersStruct {
   258  
   259  	var params []*ParametersStruct
   260  	if p.Type != "message" && p.Type != "object" && p.Type != "" {
   261  		return append(params, p.GetQueryParameter(name))
   262  	}
   263  	refName := GetNameByRef(p.Ref)
   264  	def := defs.getModel(refName)
   265  	if searched[refName] {
   266  		return append(params, p.GetQueryParameter(name))
   267  	}
   268  	searched[refName] = true
   269  	def.Properties.orderedEach(func(k string, prop PropertyStruct) {
   270  		params = append(params, prop.GetQueryParameters(name+"."+k, defs, searched)...)
   271  	})
   272  	delete(searched, refName)
   273  	return params
   274  }