github.com/jhump/protoreflect@v1.16.0/desc/builder/service.go (about) 1 package builder 2 3 import ( 4 "fmt" 5 6 "google.golang.org/protobuf/proto" 7 "google.golang.org/protobuf/types/descriptorpb" 8 9 "github.com/jhump/protoreflect/desc" 10 "github.com/jhump/protoreflect/desc/internal" 11 ) 12 13 // ServiceBuilder is a builder used to construct a desc.ServiceDescriptor. 14 // 15 // To create a new ServiceBuilder, use NewService. 16 type ServiceBuilder struct { 17 baseBuilder 18 19 Options *descriptorpb.ServiceOptions 20 21 methods []*MethodBuilder 22 symbols map[string]*MethodBuilder 23 } 24 25 // NewService creates a new ServiceBuilder for a service with the given name. 26 func NewService(name string) *ServiceBuilder { 27 return &ServiceBuilder{ 28 baseBuilder: baseBuilderWithName(name), 29 symbols: map[string]*MethodBuilder{}, 30 } 31 } 32 33 // FromService returns a ServiceBuilder that is effectively a copy of the given 34 // descriptor. 35 // 36 // Note that it is not just the given service that is copied but its entire 37 // file. So the caller can get the parent element of the returned builder and 38 // the result would be a builder that is effectively a copy of the service 39 // descriptor's parent file. 40 // 41 // This means that service builders created from descriptors do not need to be 42 // explicitly assigned to a file in order to preserve the original service's 43 // package name. 44 func FromService(sd *desc.ServiceDescriptor) (*ServiceBuilder, error) { 45 if fb, err := FromFile(sd.GetFile()); err != nil { 46 return nil, err 47 } else if sb, ok := fb.findFullyQualifiedElement(sd.GetFullyQualifiedName()).(*ServiceBuilder); ok { 48 return sb, nil 49 } else { 50 return nil, fmt.Errorf("could not find service %s after converting file %q to builder", sd.GetFullyQualifiedName(), sd.GetFile().GetName()) 51 } 52 } 53 54 func fromService(sd *desc.ServiceDescriptor) (*ServiceBuilder, error) { 55 sb := NewService(sd.GetName()) 56 sb.Options = sd.GetServiceOptions() 57 setComments(&sb.comments, sd.GetSourceInfo()) 58 59 for _, mtd := range sd.GetMethods() { 60 if mtb, err := fromMethod(mtd); err != nil { 61 return nil, err 62 } else if err := sb.TryAddMethod(mtb); err != nil { 63 return nil, err 64 } 65 } 66 67 return sb, nil 68 } 69 70 // SetName changes this service's name, returning the service builder for method 71 // chaining. If the given new name is not valid (e.g. TrySetName would have 72 // returned an error) then this method will panic. 73 func (sb *ServiceBuilder) SetName(newName string) *ServiceBuilder { 74 if err := sb.TrySetName(newName); err != nil { 75 panic(err) 76 } 77 return sb 78 } 79 80 // TrySetName changes this service's name. It will return an error if the given 81 // new name is not a valid protobuf identifier or if the parent file builder 82 // already has an element with the given name. 83 func (sb *ServiceBuilder) TrySetName(newName string) error { 84 return sb.baseBuilder.setName(sb, newName) 85 } 86 87 // SetComments sets the comments associated with the service. This method 88 // returns the service builder, for method chaining. 89 func (sb *ServiceBuilder) SetComments(c Comments) *ServiceBuilder { 90 sb.comments = c 91 return sb 92 } 93 94 // GetChildren returns any builders assigned to this service builder. These will 95 // be the service's methods. 96 func (sb *ServiceBuilder) GetChildren() []Builder { 97 var ch []Builder 98 for _, mtb := range sb.methods { 99 ch = append(ch, mtb) 100 } 101 return ch 102 } 103 104 func (sb *ServiceBuilder) findChild(name string) Builder { 105 return sb.symbols[name] 106 } 107 108 func (sb *ServiceBuilder) removeChild(b Builder) { 109 if p, ok := b.GetParent().(*ServiceBuilder); !ok || p != sb { 110 return 111 } 112 sb.methods = deleteBuilder(b.GetName(), sb.methods).([]*MethodBuilder) 113 delete(sb.symbols, b.GetName()) 114 b.setParent(nil) 115 } 116 117 func (sb *ServiceBuilder) renamedChild(b Builder, oldName string) error { 118 if p, ok := b.GetParent().(*ServiceBuilder); !ok || p != sb { 119 return nil 120 } 121 122 if err := sb.addSymbol(b.(*MethodBuilder)); err != nil { 123 return err 124 } 125 delete(sb.symbols, oldName) 126 return nil 127 } 128 129 func (sb *ServiceBuilder) addSymbol(b *MethodBuilder) error { 130 if _, ok := sb.symbols[b.GetName()]; ok { 131 return fmt.Errorf("service %s already contains method named %q", GetFullyQualifiedName(sb), b.GetName()) 132 } 133 sb.symbols[b.GetName()] = b 134 return nil 135 } 136 137 // GetMethod returns the method with the given name. If no such method exists in 138 // the service, nil is returned. 139 func (sb *ServiceBuilder) GetMethod(name string) *MethodBuilder { 140 return sb.symbols[name] 141 } 142 143 // RemoveMethod removes the method with the given name. If no such method exists 144 // in the service, this is a no-op. This returns the service builder, for method 145 // chaining. 146 func (sb *ServiceBuilder) RemoveMethod(name string) *ServiceBuilder { 147 sb.TryRemoveMethod(name) 148 return sb 149 } 150 151 // TryRemoveMethod removes the method with the given name and returns false if 152 // the service has no such method. 153 func (sb *ServiceBuilder) TryRemoveMethod(name string) bool { 154 if mtb, ok := sb.symbols[name]; ok { 155 sb.removeChild(mtb) 156 return true 157 } 158 return false 159 } 160 161 // AddMethod adds the given method to this servuce. If an error prevents the 162 // method from being added, this method panics. This returns the service 163 // builder, for method chaining. 164 func (sb *ServiceBuilder) AddMethod(mtb *MethodBuilder) *ServiceBuilder { 165 if err := sb.TryAddMethod(mtb); err != nil { 166 panic(err) 167 } 168 return sb 169 } 170 171 // TryAddMethod adds the given field to this service, returning any error that 172 // prevents the field from being added (such as a name collision with another 173 // method already added to the service). 174 func (sb *ServiceBuilder) TryAddMethod(mtb *MethodBuilder) error { 175 if err := sb.addSymbol(mtb); err != nil { 176 return err 177 } 178 Unlink(mtb) 179 mtb.setParent(sb) 180 sb.methods = append(sb.methods, mtb) 181 return nil 182 } 183 184 // SetOptions sets the service options for this service and returns the service, 185 // for method chaining. 186 func (sb *ServiceBuilder) SetOptions(options *descriptorpb.ServiceOptions) *ServiceBuilder { 187 sb.Options = options 188 return sb 189 } 190 191 func (sb *ServiceBuilder) buildProto(path []int32, sourceInfo *descriptorpb.SourceCodeInfo) (*descriptorpb.ServiceDescriptorProto, error) { 192 addCommentsTo(sourceInfo, path, &sb.comments) 193 194 methods := make([]*descriptorpb.MethodDescriptorProto, 0, len(sb.methods)) 195 for _, mtb := range sb.methods { 196 path := append(path, internal.Service_methodsTag, int32(len(methods))) 197 if mtd, err := mtb.buildProto(path, sourceInfo); err != nil { 198 return nil, err 199 } else { 200 methods = append(methods, mtd) 201 } 202 } 203 204 return &descriptorpb.ServiceDescriptorProto{ 205 Name: proto.String(sb.name), 206 Options: sb.Options, 207 Method: methods, 208 }, nil 209 } 210 211 // Build constructs a service descriptor based on the contents of this service 212 // builder. If there are any problems constructing the descriptor, including 213 // resolving symbols referenced by the builder or failing to meet certain 214 // validation rules, an error is returned. 215 func (sb *ServiceBuilder) Build() (*desc.ServiceDescriptor, error) { 216 sd, err := sb.BuildDescriptor() 217 if err != nil { 218 return nil, err 219 } 220 return sd.(*desc.ServiceDescriptor), nil 221 } 222 223 // BuildDescriptor constructs a service descriptor based on the contents of this 224 // service builder. Most usages will prefer Build() instead, whose return type 225 // is a concrete descriptor type. This method is present to satisfy the Builder 226 // interface. 227 func (sb *ServiceBuilder) BuildDescriptor() (desc.Descriptor, error) { 228 return doBuild(sb, BuilderOptions{}) 229 } 230 231 // MethodBuilder is a builder used to construct a desc.MethodDescriptor. A 232 // method builder *must* be added to a service before calling its Build() 233 // method. 234 // 235 // To create a new MethodBuilder, use NewMethod. 236 type MethodBuilder struct { 237 baseBuilder 238 239 Options *descriptorpb.MethodOptions 240 ReqType *RpcType 241 RespType *RpcType 242 } 243 244 // NewMethod creates a new MethodBuilder for a method with the given name and 245 // request and response types. 246 func NewMethod(name string, req, resp *RpcType) *MethodBuilder { 247 return &MethodBuilder{ 248 baseBuilder: baseBuilderWithName(name), 249 ReqType: req, 250 RespType: resp, 251 } 252 } 253 254 // FromMethod returns a MethodBuilder that is effectively a copy of the given 255 // descriptor. 256 // 257 // Note that it is not just the given method that is copied but its entire file. 258 // So the caller can get the parent element of the returned builder and the 259 // result would be a builder that is effectively a copy of the method 260 // descriptor's parent service. 261 // 262 // This means that method builders created from descriptors do not need to be 263 // explicitly assigned to a file in order to preserve the original method's 264 // package name. 265 func FromMethod(mtd *desc.MethodDescriptor) (*MethodBuilder, error) { 266 if fb, err := FromFile(mtd.GetFile()); err != nil { 267 return nil, err 268 } else if mtb, ok := fb.findFullyQualifiedElement(mtd.GetFullyQualifiedName()).(*MethodBuilder); ok { 269 return mtb, nil 270 } else { 271 return nil, fmt.Errorf("could not find method %s after converting file %q to builder", mtd.GetFullyQualifiedName(), mtd.GetFile().GetName()) 272 } 273 } 274 275 func fromMethod(mtd *desc.MethodDescriptor) (*MethodBuilder, error) { 276 req := RpcTypeImportedMessage(mtd.GetInputType(), mtd.IsClientStreaming()) 277 resp := RpcTypeImportedMessage(mtd.GetOutputType(), mtd.IsServerStreaming()) 278 mtb := NewMethod(mtd.GetName(), req, resp) 279 mtb.Options = mtd.GetMethodOptions() 280 setComments(&mtb.comments, mtd.GetSourceInfo()) 281 282 return mtb, nil 283 } 284 285 // SetName changes this method's name, returning the method builder for method 286 // chaining. If the given new name is not valid (e.g. TrySetName would have 287 // returned an error) then this method will panic. 288 func (mtb *MethodBuilder) SetName(newName string) *MethodBuilder { 289 if err := mtb.TrySetName(newName); err != nil { 290 panic(err) 291 } 292 return mtb 293 } 294 295 // TrySetName changes this method's name. It will return an error if the given 296 // new name is not a valid protobuf identifier or if the parent service builder 297 // already has a method with the given name. 298 func (mtb *MethodBuilder) TrySetName(newName string) error { 299 return mtb.baseBuilder.setName(mtb, newName) 300 } 301 302 // SetComments sets the comments associated with the method. This method 303 // returns the method builder, for method chaining. 304 func (mtb *MethodBuilder) SetComments(c Comments) *MethodBuilder { 305 mtb.comments = c 306 return mtb 307 } 308 309 // GetChildren returns nil, since methods cannot have child elements. It is 310 // present to satisfy the Builder interface. 311 func (mtb *MethodBuilder) GetChildren() []Builder { 312 // methods do not have children 313 return nil 314 } 315 316 func (mtb *MethodBuilder) findChild(name string) Builder { 317 // methods do not have children 318 return nil 319 } 320 321 func (mtb *MethodBuilder) removeChild(b Builder) { 322 // methods do not have children 323 } 324 325 func (mtb *MethodBuilder) renamedChild(b Builder, oldName string) error { 326 // methods do not have children 327 return nil 328 } 329 330 // SetOptions sets the method options for this method and returns the method, 331 // for method chaining. 332 func (mtb *MethodBuilder) SetOptions(options *descriptorpb.MethodOptions) *MethodBuilder { 333 mtb.Options = options 334 return mtb 335 } 336 337 // SetRequestType changes the request type for the method and then returns the 338 // method builder, for method chaining. 339 func (mtb *MethodBuilder) SetRequestType(t *RpcType) *MethodBuilder { 340 mtb.ReqType = t 341 return mtb 342 } 343 344 // SetResponseType changes the response type for the method and then returns the 345 // method builder, for method chaining. 346 func (mtb *MethodBuilder) SetResponseType(t *RpcType) *MethodBuilder { 347 mtb.RespType = t 348 return mtb 349 } 350 351 func (mtb *MethodBuilder) buildProto(path []int32, sourceInfo *descriptorpb.SourceCodeInfo) (*descriptorpb.MethodDescriptorProto, error) { 352 addCommentsTo(sourceInfo, path, &mtb.comments) 353 354 mtd := &descriptorpb.MethodDescriptorProto{ 355 Name: proto.String(mtb.name), 356 Options: mtb.Options, 357 InputType: proto.String("." + mtb.ReqType.GetTypeName()), 358 OutputType: proto.String("." + mtb.RespType.GetTypeName()), 359 } 360 if mtb.ReqType.IsStream { 361 mtd.ClientStreaming = proto.Bool(true) 362 } 363 if mtb.RespType.IsStream { 364 mtd.ServerStreaming = proto.Bool(true) 365 } 366 367 return mtd, nil 368 } 369 370 // Build constructs a method descriptor based on the contents of this method 371 // builder. If there are any problems constructing the descriptor, including 372 // resolving symbols referenced by the builder or failing to meet certain 373 // validation rules, an error is returned. 374 func (mtb *MethodBuilder) Build() (*desc.MethodDescriptor, error) { 375 mtd, err := mtb.BuildDescriptor() 376 if err != nil { 377 return nil, err 378 } 379 return mtd.(*desc.MethodDescriptor), nil 380 } 381 382 // BuildDescriptor constructs a method descriptor based on the contents of this 383 // method builder. Most usages will prefer Build() instead, whose return type is 384 // a concrete descriptor type. This method is present to satisfy the Builder 385 // interface. 386 func (mtb *MethodBuilder) BuildDescriptor() (desc.Descriptor, error) { 387 return doBuild(mtb, BuilderOptions{}) 388 }