github.com/bakjos/protoreflect@v1.9.2/desc/builder/service.go (about)

     1  package builder
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/golang/protobuf/proto"
     7  	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
     8  
     9  	"github.com/bakjos/protoreflect/desc"
    10  	"github.com/bakjos/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 *dpb.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 *dpb.ServiceOptions) *ServiceBuilder {
   187  	sb.Options = options
   188  	return sb
   189  }
   190  
   191  func (sb *ServiceBuilder) buildProto(path []int32, sourceInfo *dpb.SourceCodeInfo) (*dpb.ServiceDescriptorProto, error) {
   192  	addCommentsTo(sourceInfo, path, &sb.comments)
   193  
   194  	methods := make([]*dpb.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 &dpb.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  *dpb.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 *dpb.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 *dpb.SourceCodeInfo) (*dpb.MethodDescriptorProto, error) {
   352  	addCommentsTo(sourceInfo, path, &mtb.comments)
   353  
   354  	mtd := &dpb.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  }