github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/svc/codegen/httpclient.go (about)

     1  package codegen
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"github.com/iancoleman/strcase"
     7  	"github.com/sirupsen/logrus"
     8  	"github.com/unionj-cloud/go-doudou/cmd/internal/astutils"
     9  	v3helper "github.com/unionj-cloud/go-doudou/cmd/internal/openapi/v3"
    10  	"github.com/unionj-cloud/go-doudou/toolkit/copier"
    11  	"github.com/unionj-cloud/go-doudou/version"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"text/template"
    16  )
    17  
    18  var clientTmpl = `/**
    19  * Generated by go-doudou {{.Version}}.
    20  * Don't edit!
    21  */
    22  package client
    23  
    24  import (
    25  	"context"
    26  	"encoding/json"
    27  	"github.com/go-resty/resty/v2"
    28  	"github.com/pkg/errors"
    29  	"github.com/unionj-cloud/go-doudou/toolkit/fileutils"
    30  	"github.com/unionj-cloud/go-doudou/toolkit/stringutils"
    31  	"github.com/unionj-cloud/go-doudou/framework/registry"
    32  	ddhttp "github.com/unionj-cloud/go-doudou/framework/http"
    33  	v3 "github.com/unionj-cloud/go-doudou/toolkit/openapi/v3"
    34  	"github.com/opentracing-contrib/go-stdlib/nethttp"
    35  	"github.com/opentracing/opentracing-go"
    36  	"io"
    37  	"net/http"
    38  	"mime/multipart"
    39  	"net/url"
    40  	"os"
    41  	"path/filepath"
    42  	"strings"
    43  	"{{.VoPackage}}"
    44  )
    45  
    46  type {{.Meta.Name}}Client struct {
    47  	provider registry.IServiceProvider
    48  	client   *resty.Client
    49  	rootPath string
    50  }
    51  
    52  func (receiver *{{.Meta.Name}}Client) SetRootPath(rootPath string) {
    53  	receiver.rootPath = rootPath
    54  }
    55  
    56  func (receiver *{{.Meta.Name}}Client) SetProvider(provider registry.IServiceProvider) {
    57  	receiver.provider = provider
    58  }
    59  
    60  func (receiver *{{.Meta.Name}}Client) SetClient(client *resty.Client) {
    61  	receiver.client = client
    62  }
    63  
    64  {{- range $m := .Meta.Methods }}
    65  	func (receiver *{{$.Meta.Name}}Client) {{$m.Name}}(ctx context.Context, _headers map[string]string, {{- range $i, $p := $m.Params}}
    66  	{{- if ne $p.Type "context.Context" }}
    67  	{{- $p.Name}} {{$p.Type}},
    68  	{{- end }}
    69      {{- end }}) (_resp *resty.Response, {{- range $i, $r := $m.Results}}
    70                       {{- if $i}},{{end}}
    71                       {{- $r.Name}} {{$r.Type}}
    72                       {{- end }}) {
    73  		var _err error
    74  		_urlValues := url.Values{}
    75  		_req := receiver.client.R()
    76  		if len(_headers) > 0 {
    77  			_req.SetHeaders(_headers)
    78  		}
    79  		_req.SetContext(ctx)
    80  		{{- range $p := $m.Params }}
    81  		{{- if or (eq $p.Type "*multipart.FileHeader") (eq $p.Type "[]*multipart.FileHeader") }}
    82  		{{- if contains $p.Type "["}}
    83  		for _, _fh := range {{$p.Name}} {
    84  			_f, _err := _fh.Open()
    85  			if _err != nil {
    86  				{{- range $r := $m.Results }}
    87  					{{- if eq $r.Type "error" }}
    88  				{{ $r.Name }} = errors.Wrap(_err, "error")
    89  					{{- end }}
    90  				{{- end }}
    91  				return
    92  			}
    93  			_req.SetFileReader("{{$p.Name}}", _fh.Filename, _f)
    94  		}
    95  		{{- else}}
    96  		if _f, _err := {{$p.Name}}.Open(); _err != nil {
    97  			{{- range $r := $m.Results }}
    98  				{{- if eq $r.Type "error" }}
    99  			{{ $r.Name }} = errors.Wrap(_err, "error")
   100  				{{- end }}
   101  			{{- end }}
   102  			return
   103  		} else {
   104  			_req.SetFileReader("{{$p.Name}}", {{$p.Name}}.Filename, _f)
   105  		}
   106  		{{- end}}
   107  		{{- else if or (eq $p.Type "v3.FileModel") (eq $p.Type "*v3.FileModel") (eq $p.Type "[]v3.FileModel") (eq $p.Type "*[]v3.FileModel") (eq $p.Type "...v3.FileModel") }}
   108  		{{- if isSlice $p.Type }}
   109  		{{- if isOptional $p.Type }}
   110  		if {{$p.Name}} != nil {
   111  			{{- if isVarargs $p.Type }}
   112  			for _, _f := range {{$p.Name}} {
   113  			{{- else }}
   114  			for _, _f := range *{{$p.Name}} {
   115  			{{- end }}
   116  				_req.SetFileReader("{{$p.Name}}", _f.Filename, _f.Reader)
   117  			}
   118  		}
   119  		{{- else }}
   120  		if len({{$p.Name}}) == 0 {
   121  			{{- range $r := $m.Results }}
   122  				{{- if eq $r.Type "error" }}
   123  			{{ $r.Name }} = errors.New("at least one file should be uploaded for parameter {{$p.Name}}")
   124  				{{- end }}
   125  			{{- end }}
   126  			return
   127  		}
   128  		for _, _f := range {{$p.Name}} {
   129  			_req.SetFileReader("{{$p.Name}}", _f.Filename, _f.Reader)
   130  		}
   131  		{{- end }}
   132  		{{- else }}
   133  		{{- if isOptional $p.Type }}
   134  		if {{$p.Name}} != nil { 
   135  			_req.SetFileReader("{{$p.Name}}", {{$p.Name}}.Filename, {{$p.Name}}.Reader)
   136  		}
   137  		{{- else }}
   138  		_req.SetFileReader("{{$p.Name}}", {{$p.Name}}.Filename, {{$p.Name}}.Reader)
   139  		{{- end }}
   140  		{{- end }}
   141  		{{- else if eq $p.Type "context.Context" }}
   142  		{{- else if not (isBuiltin $p)}}
   143  		_req.SetBody({{$p.Name}})
   144  		{{- else if isSlice $p.Type }}
   145  		{{- if isOptional $p.Type }}
   146  		if {{$p.Name}} != nil { 
   147  			{{- if isVarargs $p.Type }}
   148  			for _, _item := range {{$p.Name}} {
   149  			{{- else }}
   150  			for _, _item := range *{{$p.Name}} {
   151  			{{- end }}
   152  				{{- if IsEnum $p }}
   153  				_urlValues.Add("{{$p.Name}}", _item.StringGetter())
   154  				{{- else }}
   155  				_urlValues.Add("{{$p.Name}}", fmt.Sprintf("%v", _item))
   156  				{{- end }}
   157  			}
   158  		}
   159  		{{- else }}
   160  		if len({{$p.Name}}) == 0 {
   161  			{{- range $r := $m.Results }}
   162  				{{- if eq $r.Type "error" }}
   163  			{{ $r.Name }} = errors.New("size of parameter {{$p.Name}} should be greater than zero")
   164  				{{- end }}
   165  			{{- end }}
   166  			return
   167  		}
   168  		for _, _item := range {{$p.Name}} {
   169  			{{- if IsEnum $p }}
   170  			_urlValues.Add("{{$p.Name}}", _item.StringGetter())
   171  			{{- else }}
   172  			_urlValues.Add("{{$p.Name}}", fmt.Sprintf("%v", _item))
   173  			{{- end }}
   174  		}
   175  		{{- end}}
   176  		{{- else }}
   177  		{{- if isOptional $p.Type }}
   178  		if {{$p.Name}} != nil { 
   179              {{- if IsEnum $p }}
   180  			_urlValues.Set("{{$p.Name}}", {{$p.Name}}.StringGetter())
   181  			{{- else }}
   182  			_urlValues.Set("{{$p.Name}}", fmt.Sprintf("%v", *{{$p.Name}}))
   183  			{{- end }}
   184  		}
   185  		{{- else }}
   186  		{{- if IsEnum $p }}
   187  		_urlValues.Set("{{$p.Name}}", {{$p.Name}}.StringGetter())
   188  		{{- else }}
   189  		_urlValues.Set("{{$p.Name}}", fmt.Sprintf("%v", {{$p.Name}}))
   190  		{{- end }}
   191  		{{- end }}
   192  		{{- end }}
   193  		{{- end }}
   194  
   195  		{{- range $r := $m.Results }}
   196  			{{- if eq $r.Type "*os.File" }}
   197  				_req.SetDoNotParseResponse(true)
   198  			{{- end }}
   199  		{{- end }}
   200  
   201  		{{- if eq $.RoutePatternStrategy 1}}
   202  		_path := "/{{$.Meta.Name | lower}}/{{$m.Name | noSplitPattern}}"
   203  		{{- else }}
   204  		_path := "/{{$m.Name | pattern}}"
   205  		{{- end }}
   206  
   207  		{{- if eq ($m.Name | httpMethod) "GET" }}
   208  		_resp, _err = _req.SetQueryParamsFromValues(_urlValues).
   209  			Get(_path)
   210  		{{- else }}
   211  		if _req.Body != nil {
   212  			_req.SetQueryParamsFromValues(_urlValues)
   213  		} else {
   214  			_req.SetFormDataFromValues(_urlValues)
   215  		}
   216  		_resp, _err = _req.{{$m.Name | restyMethod}}(_path)
   217  		{{- end }}
   218  		if _err != nil {
   219  			{{- range $r := $m.Results }}
   220  				{{- if eq $r.Type "error" }}
   221  			{{ $r.Name }} = errors.Wrap(_err, "error")
   222  				{{- end }}
   223  			{{- end }}
   224  			return
   225  		}
   226  		if _resp.IsError() {
   227  			{{- range $r := $m.Results }}
   228  				{{- if eq $r.Type "error" }}
   229  			{{ $r.Name }} = errors.New(_resp.String())
   230  				{{- end }}
   231  			{{- end }}
   232  			return
   233  		}
   234  		{{- $done := false }}
   235  		{{- range $r := $m.Results }}
   236  			{{- if eq $r.Type "*os.File" }}
   237  				_disp := _resp.Header().Get("Content-Disposition")
   238  				_file := strings.TrimPrefix(_disp, "attachment; filename=")
   239  				_output := os.TempDir()
   240  				if stringutils.IsNotEmpty(_output) {
   241  					_file = _output + string(filepath.Separator) + _file
   242  				}
   243  				_file = filepath.Clean(_file)
   244  				if _err = fileutils.CreateDirectory(filepath.Dir(_file)); _err != nil {
   245  					{{- range $r := $m.Results }}
   246  						{{- if eq $r.Type "error" }}
   247  					{{ $r.Name }} = errors.Wrap(_err, "error")
   248  						{{- end }}
   249  					{{- end }}
   250  					return
   251  				}
   252  				_outFile, _err := os.Create(_file)
   253  				if _err != nil {
   254  					{{- range $r := $m.Results }}
   255  						{{- if eq $r.Type "error" }}
   256  					{{ $r.Name }} = errors.Wrap(_err, "error")
   257  						{{- end }}
   258  					{{- end }}
   259  					return
   260  				}
   261  				defer _outFile.Close()
   262  				defer _resp.RawBody().Close()
   263  				_, _err = io.Copy(_outFile, _resp.RawBody())
   264  				if _err != nil {
   265  					{{- range $r := $m.Results }}
   266  						{{- if eq $r.Type "error" }}
   267  					{{ $r.Name }} = errors.Wrap(_err, "error")
   268  						{{- end }}
   269  					{{- end }}
   270  					return
   271  				}
   272  				{{ $r.Name }} = _outFile
   273  				return
   274  				{{- $done = true }}	
   275  			{{- end }}
   276  		{{- end }}
   277  		{{- if not $done }}
   278  			var _result struct {
   279  				{{- range $r := $m.Results }}
   280  				{{- if ne $r.Type "error" }}
   281  				{{ $r.Name | toCamel }} {{ $r.Type }} ` + "`" + `json:"{{ $r.Name | convertCase }}"` + "`" + `
   282  				{{- end }}
   283  				{{- end }}
   284  			}
   285  			if _err = json.Unmarshal(_resp.Body(), &_result); _err != nil {
   286  				{{- range $r := $m.Results }}
   287  					{{- if eq $r.Type "error" }}
   288  				{{ $r.Name }} = errors.Wrap(_err, "error")
   289  					{{- end }}
   290  				{{- end }}
   291  				return
   292  			}
   293  			return _resp, {{range $i, $r := $m.Results }}{{- if $i}},{{end}}{{ if eq $r.Type "error" }}nil{{else}}_result.{{ $r.Name | toCamel }}{{end}}{{- end }}
   294  		{{- end }}    
   295  	}
   296  {{- end }}
   297  
   298  func New{{.Meta.Name}}Client(opts ...ddhttp.DdClientOption) *{{.Meta.Name}}Client {
   299  	{{- if .Env }}
   300  	defaultProvider := ddhttp.NewServiceProvider("{{.Env}}")
   301  	{{- else }}
   302  	defaultProvider := ddhttp.NewServiceProvider("{{.Meta.Name | toUpper}}")
   303  	{{- end }}
   304  	defaultClient := ddhttp.NewClient()
   305  
   306  	svcClient := &{{.Meta.Name}}Client{
   307  		provider: defaultProvider,
   308  		client:   defaultClient,
   309  	}
   310  
   311  	for _, opt := range opts {
   312  		opt(svcClient)
   313  	}
   314  
   315  	svcClient.client.OnBeforeRequest(func(_ *resty.Client, request *resty.Request) error {
   316  		request.URL = svcClient.provider.SelectServer() + svcClient.rootPath + request.URL
   317  		return nil
   318  	})
   319  
   320  	svcClient.client.SetPreRequestHook(func(_ *resty.Client, request *http.Request) error {
   321  		traceReq, _ := nethttp.TraceRequest(opentracing.GlobalTracer(), request,
   322  			nethttp.OperationName(fmt.Sprintf("HTTP %s: %s", request.Method, request.URL.Path)))
   323  		*request = *traceReq
   324  		return nil
   325  	})
   326  
   327  	svcClient.client.OnAfterResponse(func(_ *resty.Client, response *resty.Response) error {
   328  		nethttp.TracerFromRequest(response.Request.RawRequest).Finish()
   329  		return nil
   330  	})
   331  
   332  	return svcClient
   333  }
   334  `
   335  
   336  func restyMethod(method string) string {
   337  	return strings.Title(strings.ToLower(httpMethod(method)))
   338  }
   339  
   340  // GenGoClient generates golang http client code from result of parsing svc.go file in project root path
   341  func GenGoClient(dir string, ic astutils.InterfaceCollector, env string, routePatternStrategy int, caseconvertor func(string) string) {
   342  	var (
   343  		err        error
   344  		clientfile string
   345  		f          *os.File
   346  		tpl        *template.Template
   347  		sqlBuf     bytes.Buffer
   348  		clientDir  string
   349  		fi         os.FileInfo
   350  		source     string
   351  		modfile    string
   352  		modName    string
   353  		firstLine  string
   354  		modf       *os.File
   355  		meta       astutils.InterfaceMeta
   356  	)
   357  	clientDir = filepath.Join(dir, "client")
   358  	if err = MkdirAll(clientDir, os.ModePerm); err != nil {
   359  		panic(err)
   360  	}
   361  
   362  	clientfile = filepath.Join(clientDir, "client.go")
   363  	fi, err = Stat(clientfile)
   364  	if err != nil && !os.IsNotExist(err) {
   365  		panic(err)
   366  	}
   367  	if fi != nil {
   368  		logrus.Warningln("file client.go will be overwritten")
   369  	}
   370  	if f, err = Create(clientfile); err != nil {
   371  		panic(err)
   372  	}
   373  	defer f.Close()
   374  
   375  	_ = copier.DeepCopy(ic.Interfaces[0], &meta)
   376  
   377  	modfile = filepath.Join(dir, "go.mod")
   378  	if modf, err = Open(modfile); err != nil {
   379  		panic(err)
   380  	}
   381  	reader := bufio.NewReader(modf)
   382  	firstLine, _ = reader.ReadString('\n')
   383  	modName = strings.TrimSpace(strings.TrimPrefix(firstLine, "module"))
   384  
   385  	funcMap := make(map[string]interface{})
   386  	funcMap["toLowerCamel"] = strcase.ToLowerCamel
   387  	funcMap["toCamel"] = strcase.ToCamel
   388  	funcMap["httpMethod"] = httpMethod
   389  	funcMap["pattern"] = pattern
   390  	funcMap["lower"] = strings.ToLower
   391  	funcMap["contains"] = strings.Contains
   392  	funcMap["isBuiltin"] = v3helper.IsBuiltin
   393  	funcMap["restyMethod"] = restyMethod
   394  	funcMap["toUpper"] = strings.ToUpper
   395  	funcMap["noSplitPattern"] = noSplitPattern
   396  	funcMap["isOptional"] = v3helper.IsOptional
   397  	funcMap["convertCase"] = caseconvertor
   398  	funcMap["isSlice"] = v3helper.IsSlice
   399  	funcMap["isVarargs"] = v3helper.IsVarargs
   400  	funcMap["IsEnum"] = v3helper.IsEnum
   401  	if tpl, err = template.New("client.go.tmpl").Funcs(funcMap).Parse(clientTmpl); err != nil {
   402  		panic(err)
   403  	}
   404  	if err = tpl.Execute(&sqlBuf, struct {
   405  		VoPackage            string
   406  		Meta                 astutils.InterfaceMeta
   407  		Env                  string
   408  		RoutePatternStrategy int
   409  		Version              string
   410  	}{
   411  		VoPackage:            modName + "/vo",
   412  		Meta:                 meta,
   413  		Env:                  env,
   414  		RoutePatternStrategy: routePatternStrategy,
   415  		Version:              version.Release,
   416  	}); err != nil {
   417  		panic(err)
   418  	}
   419  	source = strings.TrimSpace(sqlBuf.String())
   420  	astutils.FixImport([]byte(source), clientfile)
   421  }