github.com/cloudwego/kitex@v0.9.0/pkg/generic/thriftidl_provider.go (about)

     1  /*
     2   * Copyright 2021 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package generic
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"path/filepath"
    23  	"sync"
    24  
    25  	dthrift "github.com/cloudwego/dynamicgo/thrift"
    26  	"github.com/cloudwego/thriftgo/parser"
    27  
    28  	"github.com/cloudwego/kitex/pkg/generic/descriptor"
    29  	"github.com/cloudwego/kitex/pkg/generic/thrift"
    30  	"github.com/cloudwego/kitex/pkg/klog"
    31  )
    32  
    33  var (
    34  	_ Closer = &ThriftContentProvider{}
    35  	_ Closer = &ThriftContentWithAbsIncludePathProvider{}
    36  )
    37  
    38  type thriftFileProvider struct {
    39  	closeOnce sync.Once
    40  	svcs      chan *descriptor.ServiceDescriptor
    41  	opts      *ProviderOption
    42  }
    43  
    44  // NewThriftFileProvider create a ThriftIDLProvider by given path and include dirs
    45  func NewThriftFileProvider(path string, includeDirs ...string) (DescriptorProvider, error) {
    46  	p := &thriftFileProvider{
    47  		svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel
    48  		opts: &ProviderOption{DynamicGoEnabled: false},
    49  	}
    50  	svc, err := newServiceDescriptorFromPath(path, includeDirs...)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	p.svcs <- svc
    55  	return p, nil
    56  }
    57  
    58  // NewThriftFileProviderWithDynamicGo create a ThriftIDLProvider with dynamicgo by given path and include dirs
    59  func NewThriftFileProviderWithDynamicGo(path string, includeDirs ...string) (DescriptorProvider, error) {
    60  	p := &thriftFileProvider{
    61  		svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel
    62  		opts: &ProviderOption{DynamicGoEnabled: true},
    63  	}
    64  
    65  	svc, err := newServiceDescriptorFromPath(path, includeDirs...)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	// ServiceDescriptor of dynamicgo
    71  	dOpts := dthrift.Options{EnableThriftBase: true}
    72  	dsvc, err := dOpts.NewDescritorFromPath(context.Background(), path, includeDirs...)
    73  	if err != nil {
    74  		// fall back to the original way (without dynamicgo)
    75  		p.opts.DynamicGoEnabled = false
    76  		p.svcs <- svc
    77  		klog.CtxWarnf(context.Background(), "KITEX: failed to get dynamicgo service descriptor, fall back to the original way, error=%s", err)
    78  		return p, nil
    79  	}
    80  	svc.DynamicGoDsc = dsvc
    81  
    82  	p.svcs <- svc
    83  	return p, nil
    84  }
    85  
    86  func newServiceDescriptorFromPath(path string, includeDirs ...string) (*descriptor.ServiceDescriptor, error) {
    87  	tree, err := parser.ParseFile(path, includeDirs, true)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	svc, err := thrift.Parse(tree, thrift.DefaultParseMode())
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	return svc, nil
    96  }
    97  
    98  // maybe watch the file change and reparse
    99  // p.thrifts <- some change
   100  // func (p *thriftFileProvider) watchAndUpdate() {}
   101  
   102  func (p *thriftFileProvider) Provide() <-chan *descriptor.ServiceDescriptor {
   103  	return p.svcs
   104  }
   105  
   106  // Close the sending chan.
   107  func (p *thriftFileProvider) Close() error {
   108  	p.closeOnce.Do(func() {
   109  		close(p.svcs)
   110  	})
   111  	return nil
   112  }
   113  
   114  func (p *thriftFileProvider) Option() ProviderOption {
   115  	return *p.opts
   116  }
   117  
   118  // ThriftContentProvider provide descriptor from contents
   119  type ThriftContentProvider struct {
   120  	closeOnce sync.Once
   121  	svcs      chan *descriptor.ServiceDescriptor
   122  	opts      *ProviderOption
   123  }
   124  
   125  var _ DescriptorProvider = (*ThriftContentProvider)(nil)
   126  
   127  const defaultMainIDLPath = "main.thrift"
   128  
   129  // NewThriftContentProvider builder
   130  func NewThriftContentProvider(main string, includes map[string]string) (*ThriftContentProvider, error) {
   131  	p := &ThriftContentProvider{
   132  		svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel
   133  		opts: &ProviderOption{DynamicGoEnabled: false},
   134  	}
   135  	svc, err := newServiceDescriptorFromContent(defaultMainIDLPath, main, includes, false)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	p.svcs <- svc
   141  	return p, nil
   142  }
   143  
   144  // NewThriftContentProviderWithDynamicGo builder
   145  func NewThriftContentProviderWithDynamicGo(main string, includes map[string]string) (*ThriftContentProvider, error) {
   146  	p := &ThriftContentProvider{
   147  		svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel
   148  		opts: &ProviderOption{DynamicGoEnabled: true},
   149  	}
   150  	svc, err := newServiceDescriptorFromContent(defaultMainIDLPath, main, includes, false)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	p.newDynamicGoDsc(svc, defaultMainIDLPath, main, includes)
   156  
   157  	p.svcs <- svc
   158  	return p, nil
   159  }
   160  
   161  // UpdateIDL ...
   162  func (p *ThriftContentProvider) UpdateIDL(main string, includes map[string]string) error {
   163  	var svc *descriptor.ServiceDescriptor
   164  	tree, err := ParseContent(defaultMainIDLPath, main, includes, false)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	svc, err = thrift.Parse(tree, thrift.DefaultParseMode())
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	if p.opts.DynamicGoEnabled {
   174  		p.newDynamicGoDsc(svc, defaultMainIDLPath, main, includes)
   175  	}
   176  
   177  	select {
   178  	case <-p.svcs:
   179  	default:
   180  	}
   181  	select {
   182  	case p.svcs <- svc:
   183  	default:
   184  	}
   185  	return nil
   186  }
   187  
   188  // Provide ...
   189  func (p *ThriftContentProvider) Provide() <-chan *descriptor.ServiceDescriptor {
   190  	return p.svcs
   191  }
   192  
   193  // Close the sending chan.
   194  func (p *ThriftContentProvider) Close() error {
   195  	p.closeOnce.Do(func() {
   196  		close(p.svcs)
   197  	})
   198  	return nil
   199  }
   200  
   201  // Option ...
   202  func (p *ThriftContentProvider) Option() ProviderOption {
   203  	return *p.opts
   204  }
   205  
   206  func (p *ThriftContentProvider) newDynamicGoDsc(svc *descriptor.ServiceDescriptor, path, content string, includes map[string]string) {
   207  	if err := newDynamicGoDscFromContent(svc, path, content, includes, false); err != nil {
   208  		p.opts.DynamicGoEnabled = false
   209  	}
   210  }
   211  
   212  func parseIncludes(tree *parser.Thrift, parsed map[string]*parser.Thrift, sources map[string]string, isAbsIncludePath bool) (err error) {
   213  	for _, i := range tree.Includes {
   214  		p := i.Path
   215  		if isAbsIncludePath {
   216  			p = absPath(tree.Filename, i.Path)
   217  		}
   218  		ref, ok := parsed[p] // avoid infinite recursion
   219  		if ok {
   220  			i.Reference = ref
   221  			continue
   222  		}
   223  		if src, ok := sources[p]; !ok {
   224  			return fmt.Errorf("miss include path: %s for file: %s", p, tree.Filename)
   225  		} else {
   226  			if ref, err = parser.ParseString(p, src); err != nil {
   227  				return
   228  			}
   229  		}
   230  		parsed[p] = ref
   231  		i.Reference = ref
   232  		if err = parseIncludes(ref, parsed, sources, isAbsIncludePath); err != nil {
   233  			return
   234  		}
   235  	}
   236  	return nil
   237  }
   238  
   239  // path := /a/b/c.thrift
   240  // includePath := ../d.thrift
   241  // result := /a/d.thrift
   242  func absPath(path, includePath string) string {
   243  	if filepath.IsAbs(includePath) {
   244  		return includePath
   245  	}
   246  	return filepath.Join(filepath.Dir(path), includePath)
   247  }
   248  
   249  // ParseContent parses the IDL from path and content using provided includes
   250  func ParseContent(path, content string, includes map[string]string, isAbsIncludePath bool) (*parser.Thrift, error) {
   251  	if src := includes[path]; src != "" && src != content {
   252  		return nil, fmt.Errorf("provided main IDL content conflicts with includes: %q", path)
   253  	}
   254  	tree, err := parser.ParseString(path, content)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	parsed := make(map[string]*parser.Thrift)
   259  	parsed[path] = tree
   260  	if err := parseIncludes(tree, parsed, includes, isAbsIncludePath); err != nil {
   261  		return nil, err
   262  	}
   263  	if cir := parser.CircleDetect(tree); cir != "" {
   264  		return tree, fmt.Errorf("IDL circular dependency: %s", cir)
   265  	}
   266  	return tree, nil
   267  }
   268  
   269  // ThriftContentWithAbsIncludePathProvider ...
   270  type ThriftContentWithAbsIncludePathProvider struct {
   271  	closeOnce sync.Once
   272  	svcs      chan *descriptor.ServiceDescriptor
   273  	opts      *ProviderOption
   274  }
   275  
   276  var _ DescriptorProvider = (*ThriftContentWithAbsIncludePathProvider)(nil)
   277  
   278  // NewThriftContentWithAbsIncludePathProvider create abs include path DescriptorProvider
   279  func NewThriftContentWithAbsIncludePathProvider(mainIDLPath string, includes map[string]string) (*ThriftContentWithAbsIncludePathProvider, error) {
   280  	p := &ThriftContentWithAbsIncludePathProvider{
   281  		svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel
   282  		opts: &ProviderOption{DynamicGoEnabled: false},
   283  	}
   284  	mainIDLContent, ok := includes[mainIDLPath]
   285  	if !ok {
   286  		return nil, fmt.Errorf("miss main IDL content for main IDL path: %s", mainIDLPath)
   287  	}
   288  	svc, err := newServiceDescriptorFromContent(mainIDLPath, mainIDLContent, includes, true)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	p.svcs <- svc
   294  	return p, nil
   295  }
   296  
   297  // NewThriftContentWithAbsIncludePathProviderWithDynamicGo create abs include path DescriptorProvider with dynamicgo
   298  func NewThriftContentWithAbsIncludePathProviderWithDynamicGo(mainIDLPath string, includes map[string]string) (*ThriftContentWithAbsIncludePathProvider, error) {
   299  	p := &ThriftContentWithAbsIncludePathProvider{
   300  		svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel
   301  		opts: &ProviderOption{DynamicGoEnabled: true},
   302  	}
   303  	mainIDLContent, ok := includes[mainIDLPath]
   304  	if !ok {
   305  		return nil, fmt.Errorf("miss main IDL content for main IDL path: %s", mainIDLPath)
   306  	}
   307  	svc, err := newServiceDescriptorFromContent(mainIDLPath, mainIDLContent, includes, true)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	p.newDynamicGoDsc(svc, mainIDLPath, mainIDLContent, includes)
   313  
   314  	p.svcs <- svc
   315  	return p, nil
   316  }
   317  
   318  // UpdateIDL update idl by given args
   319  func (p *ThriftContentWithAbsIncludePathProvider) UpdateIDL(mainIDLPath string, includes map[string]string) error {
   320  	mainIDLContent, ok := includes[mainIDLPath]
   321  	if !ok {
   322  		return fmt.Errorf("miss main IDL content for main IDL path: %s", mainIDLPath)
   323  	}
   324  	var svc *descriptor.ServiceDescriptor
   325  	tree, err := ParseContent(mainIDLPath, mainIDLContent, includes, true)
   326  	if err != nil {
   327  		return err
   328  	}
   329  	svc, err = thrift.Parse(tree, thrift.DefaultParseMode())
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	if p.opts.DynamicGoEnabled {
   335  		p.newDynamicGoDsc(svc, mainIDLPath, mainIDLContent, includes)
   336  	}
   337  
   338  	// drain the channel
   339  	select {
   340  	case <-p.svcs:
   341  	default:
   342  	}
   343  	select {
   344  	case p.svcs <- svc:
   345  	default:
   346  	}
   347  	return nil
   348  }
   349  
   350  // Provide ...
   351  func (p *ThriftContentWithAbsIncludePathProvider) Provide() <-chan *descriptor.ServiceDescriptor {
   352  	return p.svcs
   353  }
   354  
   355  // Close the sending chan.
   356  func (p *ThriftContentWithAbsIncludePathProvider) Close() error {
   357  	p.closeOnce.Do(func() {
   358  		close(p.svcs)
   359  	})
   360  	return nil
   361  }
   362  
   363  // Option ...
   364  func (p *ThriftContentWithAbsIncludePathProvider) Option() ProviderOption {
   365  	return *p.opts
   366  }
   367  
   368  func (p *ThriftContentWithAbsIncludePathProvider) newDynamicGoDsc(svc *descriptor.ServiceDescriptor, path, content string, includes map[string]string) {
   369  	if err := newDynamicGoDscFromContent(svc, path, content, includes, true); err != nil {
   370  		p.opts.DynamicGoEnabled = false
   371  	}
   372  }
   373  
   374  func newServiceDescriptorFromContent(path, content string, includes map[string]string, isAbsIncludePath bool) (*descriptor.ServiceDescriptor, error) {
   375  	tree, err := ParseContent(path, content, includes, isAbsIncludePath)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	svc, err := thrift.Parse(tree, thrift.DefaultParseMode())
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	return svc, nil
   384  }
   385  
   386  func newDynamicGoDscFromContent(svc *descriptor.ServiceDescriptor, path, content string, includes map[string]string, isAbsIncludePath bool) error {
   387  	// ServiceDescriptor of dynamicgo
   388  	dOpts := dthrift.Options{EnableThriftBase: true}
   389  	dsvc, err := dOpts.NewDescritorFromContent(context.Background(), path, content, includes, isAbsIncludePath)
   390  	if err != nil {
   391  		klog.CtxWarnf(context.Background(), "KITEX: failed to get dynamicgo service descriptor, fall back to the original way, error=%s", err)
   392  		return err
   393  	}
   394  	svc.DynamicGoDsc = dsvc
   395  	return nil
   396  }