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 }