├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── internal └── generate │ ├── find.go │ ├── find_test.go │ ├── generate.go │ ├── generate_test.go │ ├── hello │ ├── another │ │ └── hello.go │ ├── embed │ │ └── parser.go │ ├── hello.go │ └── otel │ │ ├── otel.go │ │ └── sdk │ │ └── sdk.go │ ├── import.go │ ├── import_test.go │ ├── interface_finder.go │ ├── loader.go │ ├── template.go │ └── template_test.go ├── main.go ├── otelwrap ├── command.go ├── command_test.go └── testdata │ └── generic_handler ├── revive.toml └── tools.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: otelwrap 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-go@v2 13 | with: 14 | go-version: 1.19 15 | - name: Install Tools 16 | run: make install-tools 17 | - name: Lint 18 | run: make lint 19 | - name: Test 20 | run: make test 21 | - name: Test Race 22 | run: make test-race 23 | - name: Convert coverage.out to coverage.lcov 24 | uses: jandelgado/gcov2lcov-action@v1.0.6 25 | - name: Coveralls 26 | uses: coverallsapp/github-action@v1.1.2 27 | with: 28 | github-token: ${{ secrets.github_token }} 29 | path-to-lcov: coverage.lcov 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /coverage.out -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Ta Quang Tung 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint test install-tools install 2 | 3 | lint: 4 | go fmt ./... 5 | go vet ./... 6 | revive -config revive.toml -formatter friendly ./... 7 | 8 | test: 9 | go test -covermode=count -coverprofile=coverage.out ./... 10 | 11 | test-race: 12 | go test -race -count=1 ./... 13 | 14 | install-tools: 15 | go install github.com/mgechev/revive 16 | 17 | install: 18 | go install -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OtelWrap: code generation tool for Go OpenTelemetry 2 | 3 | [![otelwrap](https://github.com/QuangTung97/otelwrap/actions/workflows/go.yml/badge.svg)](https://github.com/QuangTung97/otelwrap/actions/workflows/go.yml) 4 | [![Coverage Status](https://coveralls.io/repos/github/QuangTung97/otelwrap/badge.svg?branch=master)](https://coveralls.io/github/QuangTung97/otelwrap?branch=master) 5 | 6 | ### What is OtelWrap? 7 | 8 | **OtelWrap** is a tool that generates a decorator implementation of any interfaces that can be used for instrumentation 9 | with Go OpenTelemetry library. Inspired by https://github.com/matryer/moq 10 | 11 | Supporting: 12 | 13 | * Any interface and any method with **context.Context** as the first parameter. 14 | * Detecting **error** return and set the span's error status accordingly. 15 | * Only tested using **go generate**. 16 | * Interface embedding. 17 | 18 | ### Installing 19 | 20 | Using conventional **tools.go** file for pinning version in **go.mod** / **go.sum**. 21 | 22 | ```go 23 | // +build tools 24 | 25 | package tools 26 | 27 | import ( 28 | _ "github.com/QuangTung97/otelwrap" 29 | ) 30 | ``` 31 | 32 | And then download and install the binary with commands: 33 | 34 | ```shell 35 | $ go mod tidy 36 | $ go install github.com/QuangTung97/otelwrap 37 | ``` 38 | 39 | ### Usage 40 | 41 | ``` 42 | otelwrap [flags] -source-dir interface [interface2 interface3 ...] 43 | --out string (required) 44 | output file 45 | ``` 46 | 47 | Using **go generate**: 48 | 49 | ```go 50 | package example 51 | 52 | import "context" 53 | 54 | //go:generate otelwrap --out interface_wrappers.go . MyInterface 55 | 56 | type MyInterface interface { 57 | Method1(ctx context.Context) error 58 | Method2(ctx context.Context, x int) 59 | Method3() 60 | } 61 | ``` 62 | 63 | The run ``go generate ./...`` in your module. The generated file looks like: 64 | 65 | ```go 66 | // Code generated by otelwrap; DO NOT EDIT. 67 | // github.com/QuangTung97/otelwrap 68 | 69 | package example 70 | 71 | import ( 72 | "context" 73 | "go.opentelemetry.io/otel/codes" 74 | "go.opentelemetry.io/otel/trace" 75 | ) 76 | 77 | // MyInterfaceWrapper wraps OpenTelemetry's span 78 | type MyInterfaceWrapper struct { 79 | MyInterface 80 | tracer trace.Tracer 81 | prefix string 82 | } 83 | 84 | // NewMyInterfaceWrapper creates a wrapper 85 | func NewMyInterfaceWrapper(wrapped MyInterface, tracer trace.Tracer, prefix string) *MyInterfaceWrapper { 86 | return &MyInterfaceWrapper{ 87 | MyInterface: wrapped, 88 | tracer: tracer, 89 | prefix: prefix, 90 | } 91 | } 92 | 93 | // Method1 ... 94 | func (w *MyInterfaceWrapper) Method1(ctx context.Context) (err error) { 95 | ctx, span := w.tracer.Start(ctx, w.prefix+"Method1") 96 | defer span.End() 97 | 98 | err = w.MyInterface.Method1(ctx) 99 | if err != nil { 100 | span.RecordError(err) 101 | span.SetStatus(codes.Error, err.Error()) 102 | } 103 | return err 104 | } 105 | 106 | // Method2 ... 107 | func (w *MyInterfaceWrapper) Method2(ctx context.Context, x int) { 108 | ctx, span := w.tracer.Start(ctx, w.prefix+"Method2") 109 | defer span.End() 110 | 111 | w.MyInterface.Method2(ctx, x) 112 | } 113 | ``` 114 | 115 | To use the generated struct, simply wraps the original implementation. The generated code is very easy to read. 116 | 117 | ```go 118 | package example 119 | 120 | import "go.opentelemetry.io/otel" 121 | 122 | func InitMyInterface() MyInterface { 123 | original := NewMyInterfaceImpl() 124 | return NewMyInterfaceWrapper(original, otel.GetTracerProvider().Tracer("example"), "prefix") 125 | } 126 | 127 | 128 | ``` 129 | 130 | Can also generate for interfaces in other packages: 131 | 132 | ```go 133 | package example 134 | 135 | import "path/to/another" 136 | 137 | var _ another.Interface1 // not necessary, only for keeping the import statement 138 | 139 | //go:generate otelwrap --out interface_wrappers.go . another.Interface1 another.Interface2 140 | ``` 141 | 142 | Or generate to another package: 143 | 144 | ```go 145 | package example 146 | 147 | import "context" 148 | 149 | //go:generate otelwrap --out ../another/interface_wrappers.go . MyInterface 150 | 151 | type MyInterface interface { 152 | Method1(ctx context.Context) error 153 | Method2(ctx context.Context, x int) 154 | Method3() 155 | } 156 | ``` 157 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/QuangTung97/otelwrap 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/mgechev/revive v1.3.1 7 | github.com/spf13/cobra v1.2.1 8 | github.com/stretchr/testify v1.8.2 9 | golang.org/x/tools v0.7.0 10 | ) 11 | 12 | require ( 13 | github.com/BurntSushi/toml v1.2.1 // indirect 14 | github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/fatih/color v1.15.0 // indirect 17 | github.com/fatih/structtag v1.2.0 // indirect 18 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 19 | github.com/mattn/go-colorable v0.1.13 // indirect 20 | github.com/mattn/go-isatty v0.0.17 // indirect 21 | github.com/mattn/go-runewidth v0.0.9 // indirect 22 | github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect 23 | github.com/mitchellh/go-homedir v1.1.0 // indirect 24 | github.com/olekukonko/tablewriter v0.0.5 // indirect 25 | github.com/pkg/errors v0.9.1 // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/spf13/pflag v1.0.5 // indirect 28 | golang.org/x/mod v0.9.0 // indirect 29 | golang.org/x/sys v0.6.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 22 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 23 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 24 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 25 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 26 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 27 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 28 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 29 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 30 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 31 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 32 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 33 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 34 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 35 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 36 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 37 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 38 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 39 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 40 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 41 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 42 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 43 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 44 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 45 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 46 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 47 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 48 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 49 | github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= 50 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 51 | github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= 52 | github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= 53 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 54 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 55 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 56 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 57 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 58 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 59 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 60 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 61 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 62 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 63 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 64 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 65 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 66 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 67 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 68 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 69 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 70 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 71 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 72 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 73 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 74 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 75 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 76 | github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= 77 | github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= 78 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 79 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 80 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 81 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 82 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 83 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 84 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 85 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 86 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 87 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 88 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 89 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 90 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 91 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 92 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 93 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 94 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 95 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 96 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 97 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 98 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 99 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 100 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 101 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 102 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 103 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 104 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 105 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 106 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 107 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 108 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 109 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 110 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 111 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 112 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 113 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 114 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 115 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 116 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 117 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 118 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 119 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 120 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 121 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 122 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 123 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 124 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 125 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 126 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 127 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 128 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 129 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 130 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 131 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 132 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 133 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 134 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 135 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 136 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 137 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 138 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 139 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 140 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 141 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 142 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 143 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 144 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 145 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 146 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 147 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 148 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 149 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 150 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 151 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 152 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 153 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 154 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 155 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 156 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 157 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 158 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 159 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 160 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 161 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 162 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 163 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 164 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 165 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 166 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 167 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 168 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 169 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 170 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 171 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 172 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 173 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 174 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 175 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 176 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 177 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 178 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 179 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 180 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 181 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 182 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 183 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 184 | github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 185 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 186 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 187 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 188 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 189 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 190 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 191 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 192 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 193 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 194 | github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= 195 | github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= 196 | github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4= 197 | github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I= 198 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 199 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 200 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 201 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 202 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 203 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 204 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 205 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 206 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 207 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 208 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 209 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 210 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 211 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 212 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 213 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 214 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 215 | github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 216 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 217 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 218 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 219 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 220 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 221 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 222 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 223 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 224 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 225 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 226 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 227 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 228 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 229 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 230 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 231 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 232 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 233 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 234 | github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= 235 | github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= 236 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 237 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 238 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 239 | github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= 240 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 241 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 242 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 243 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 244 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 245 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 246 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 247 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 248 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 249 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 250 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 251 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 252 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 253 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 254 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 255 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 256 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 257 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 258 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 259 | go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= 260 | go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= 261 | go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= 262 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 263 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 264 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 265 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 266 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 267 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 268 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 269 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 270 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 271 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 272 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 273 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 274 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 275 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 276 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 277 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 278 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 279 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 280 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 281 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 282 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 283 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 284 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 285 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 286 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 287 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 288 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 289 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 290 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 291 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 292 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 293 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 294 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 295 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 296 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 297 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 298 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 299 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 300 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 301 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 302 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 303 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 304 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 305 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 306 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 307 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 308 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 309 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 310 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 311 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 312 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 313 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 314 | golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= 315 | golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 316 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 317 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 318 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 319 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 320 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 321 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 322 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 323 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 324 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 325 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 326 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 327 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 328 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 329 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 330 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 331 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 332 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 333 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 334 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 335 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 336 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 337 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 338 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 339 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 340 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 341 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 342 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 343 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 344 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 345 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 346 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 347 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 348 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 349 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 350 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 351 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 352 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 353 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 354 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 355 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 356 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 357 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 358 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 359 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 360 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 361 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 362 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 363 | golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 364 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 365 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 366 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 367 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 368 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 369 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 370 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 371 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 372 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 373 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 374 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 375 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 376 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 377 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 378 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 379 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 380 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 381 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 382 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 383 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 384 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 385 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 386 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 387 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 388 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 389 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 390 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 391 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 392 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 393 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 394 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 395 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 396 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 397 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 398 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 399 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 400 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 401 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 402 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 411 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 412 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 413 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 417 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 418 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 419 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 420 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 421 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 422 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 423 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 424 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 425 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 426 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 427 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 428 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 429 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 430 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 431 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 432 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 433 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 434 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 435 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 436 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 437 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 438 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 439 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 440 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 441 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 442 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 443 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 444 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 445 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 446 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 447 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 448 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 449 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 450 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 451 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 452 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 453 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 454 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 455 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 456 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 457 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 458 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 459 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 460 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 461 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 462 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 463 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 464 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 465 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 466 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 467 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 468 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 469 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 470 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 471 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 472 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 473 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 474 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 475 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 476 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 477 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 478 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 479 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 480 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 481 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 482 | golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= 483 | golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= 484 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 485 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 486 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 487 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 488 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 489 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 490 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 491 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 492 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 493 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 494 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 495 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 496 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 497 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 498 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 499 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 500 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 501 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 502 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 503 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 504 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 505 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 506 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 507 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 508 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 509 | google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 510 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 511 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 512 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 513 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 514 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 515 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 516 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 517 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 518 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 519 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 520 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 521 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 522 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 523 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 524 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 525 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 526 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 527 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 528 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 529 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 530 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 531 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 532 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 533 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 534 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 535 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 536 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 537 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 538 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 539 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 540 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 541 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 542 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 543 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 544 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 545 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 546 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 547 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 548 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 549 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 550 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 551 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 552 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 553 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 554 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 555 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 556 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 557 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 558 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 559 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 560 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 561 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 562 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 563 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 564 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 565 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 566 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 567 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 568 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 569 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 570 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 571 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 572 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 573 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 574 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 575 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 576 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 577 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 578 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 579 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 580 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 581 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 582 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 583 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 584 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 585 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 586 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 587 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 588 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 589 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 590 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 591 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 592 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 593 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 594 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 595 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 596 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 597 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 598 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 599 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 600 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 601 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 602 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 603 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 604 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 605 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 606 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 607 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 608 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 609 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 610 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 611 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 612 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 613 | -------------------------------------------------------------------------------- /internal/generate/find.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "errors" 5 | "go/parser" 6 | "go/token" 7 | "io" 8 | "os" 9 | "path" 10 | ) 11 | 12 | // FindResult ... 13 | type FindResult struct { 14 | SrcPkgName string 15 | DestPkgPath string 16 | } 17 | 18 | // ErrNotFound ... 19 | var ErrNotFound = errors.New("generate: not found") 20 | 21 | // FindPackage ... 22 | func FindPackage(filePath string, pkgName string) (FindResult, error) { 23 | srcFile, err := os.Open(filePath) 24 | if err != nil { 25 | return FindResult{}, err 26 | } 27 | defer func() { 28 | _ = srcFile.Close() 29 | }() 30 | 31 | data, err := io.ReadAll(srcFile) 32 | if err != nil { 33 | return FindResult{}, err 34 | } 35 | 36 | fset := token.NewFileSet() 37 | f, err := parser.ParseFile(fset, filePath, string(data), 0) 38 | if err != nil { 39 | return FindResult{}, err 40 | } 41 | for _, importSpec := range f.Imports { 42 | importPath := importSpec.Path.Value 43 | importPath = importPath[1 : len(importPath)-1] 44 | usedName := path.Base(importPath) 45 | if importSpec.Name != nil { 46 | usedName = importSpec.Name.Name 47 | } 48 | if usedName == pkgName { 49 | return FindResult{ 50 | SrcPkgName: f.Name.Name, 51 | DestPkgPath: importPath, 52 | }, nil 53 | } 54 | } 55 | return FindResult{}, ErrNotFound 56 | } 57 | -------------------------------------------------------------------------------- /internal/generate/find_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestFindPackage(t *testing.T) { 9 | result, err := FindPackage("./hello/hello.go", "otelgo") 10 | assert.Equal(t, nil, err) 11 | assert.Equal(t, FindResult{ 12 | SrcPkgName: "hello", 13 | DestPkgPath: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel", 14 | }, result) 15 | } 16 | 17 | func TestFindPackage_Not_Found(t *testing.T) { 18 | result, err := FindPackage("./hello/hello.go", "random") 19 | assert.Equal(t, ErrNotFound, err) 20 | assert.Equal(t, FindResult{}, result) 21 | } 22 | -------------------------------------------------------------------------------- /internal/generate/generate.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/token" 7 | "go/types" 8 | "golang.org/x/tools/go/packages" 9 | "io" 10 | "os" 11 | "sort" 12 | "strings" 13 | ) 14 | 15 | type recognizedType int 16 | 17 | const ( 18 | recognizedTypeUnknown recognizedType = iota 19 | recognizedTypeContext 20 | recognizedTypeError 21 | 22 | // only for generating 23 | recognizedTypeSpan 24 | ) 25 | 26 | // tupleTypePkg for replacing package names 27 | type tupleTypePkg struct { 28 | path string 29 | begin int 30 | end int 31 | } 32 | 33 | type tupleType struct { 34 | name string 35 | typeStr string 36 | recognized recognizedType 37 | isVariadic bool 38 | 39 | pkgList []tupleTypePkg 40 | } 41 | 42 | type methodType struct { 43 | name string 44 | params []tupleType 45 | results []tupleType 46 | } 47 | 48 | type importInfo struct { 49 | name string 50 | path string 51 | } 52 | 53 | type interfaceInfo struct { 54 | name string 55 | methods []methodType 56 | } 57 | 58 | type packageTypeInfo struct { 59 | name string 60 | path string 61 | 62 | imports []importInfo 63 | interfaces []interfaceInfo 64 | } 65 | 66 | func getRecognizedType(field *ast.Field, info *types.Info) recognizedType { 67 | fieldType := info.TypeOf(field.Type) 68 | namedType, ok := fieldType.(*types.Named) 69 | if ok { 70 | name := namedType.Obj().Name() 71 | pkg := namedType.Obj().Pkg() 72 | if name == "Context" && pkg != nil && pkg.Path() == "context" { 73 | return recognizedTypeContext 74 | } 75 | if name == "error" && pkg == nil { 76 | return recognizedTypeError 77 | } 78 | } 79 | return recognizedTypeUnknown 80 | } 81 | 82 | type tupleVisitor struct { 83 | begin token.Pos 84 | info *types.Info 85 | 86 | pkgList []tupleTypePkg 87 | 88 | packageBegin int 89 | packageEnd int 90 | foundPkg bool 91 | } 92 | 93 | func (v *tupleVisitor) Visit(node ast.Node) ast.Visitor { 94 | ident, ok := node.(*ast.Ident) 95 | if !ok { 96 | return v 97 | } 98 | object, ok := v.info.Uses[ident] 99 | if !ok { 100 | return v 101 | } 102 | _, ok = object.(*types.PkgName) 103 | if ok { 104 | v.packageBegin = int(ident.Pos() - v.begin) 105 | v.packageEnd = int(ident.End() - v.begin) 106 | v.foundPkg = true 107 | return v 108 | } 109 | 110 | pkg := object.Pkg() 111 | if pkg == nil { 112 | return v 113 | } 114 | 115 | var pkgInfo tupleTypePkg 116 | if v.foundPkg { 117 | v.foundPkg = false 118 | pkgInfo = tupleTypePkg{ 119 | path: pkg.Path(), 120 | begin: v.packageBegin, 121 | end: v.packageEnd, 122 | } 123 | } else { 124 | identBegin := int(ident.Pos() - v.begin) 125 | pkgInfo = tupleTypePkg{ 126 | path: pkg.Path(), 127 | begin: identBegin, 128 | end: identBegin, 129 | } 130 | } 131 | 132 | v.pkgList = append(v.pkgList, pkgInfo) 133 | return v 134 | } 135 | 136 | func fieldListToTupleList( 137 | fileList *ast.FieldList, fset *token.FileSet, 138 | fileMap map[string]string, info *types.Info, 139 | ) []tupleType { 140 | if fileList == nil { 141 | return nil 142 | } 143 | 144 | var tuples []tupleType 145 | for _, field := range fileList.List { 146 | begin := field.Type.Pos() 147 | end := field.Type.End() 148 | file := fset.File(begin) 149 | 150 | filename := file.Name() 151 | typeStr := fileMap[filename][file.Offset(begin):file.Offset(end)] 152 | 153 | isVariadic := false 154 | _, ok := field.Type.(*ast.Ellipsis) 155 | if ok { 156 | isVariadic = true 157 | } 158 | 159 | visitor := &tupleVisitor{begin: field.Type.Pos(), info: info} 160 | ast.Walk(visitor, field.Type) 161 | 162 | recognized := getRecognizedType(field, info) 163 | tupleTemplate := tupleType{ 164 | typeStr: typeStr, 165 | recognized: recognized, 166 | isVariadic: isVariadic, 167 | 168 | pkgList: visitor.pkgList, 169 | } 170 | 171 | for _, resultName := range field.Names { 172 | tuple := tupleTemplate 173 | tuple.name = resultName.Name 174 | tuples = append(tuples, tuple) 175 | } 176 | if len(field.Names) == 0 { 177 | tuples = append(tuples, tupleTemplate) 178 | } 179 | } 180 | return tuples 181 | } 182 | 183 | func readFiles(files []string) map[string]string { 184 | fileMap := map[string]string{} 185 | for _, filename := range files { 186 | file, err := os.Open(filename) 187 | if err != nil { 188 | panic(err) 189 | } 190 | 191 | data, err := io.ReadAll(file) 192 | if err != nil { 193 | panic(err) 194 | } 195 | fileMap[filename] = string(data) 196 | _ = file.Close() 197 | } 198 | return fileMap 199 | } 200 | 201 | func findInterfaceTypeForDecl(interfaceName string, syntax *ast.File) *ast.TypeSpec { 202 | for _, decl := range syntax.Decls { 203 | genDecl, ok := decl.(*ast.GenDecl) 204 | if !ok { 205 | continue 206 | } 207 | for _, spec := range genDecl.Specs { 208 | typeSpec, ok := spec.(*ast.TypeSpec) 209 | if !ok { 210 | continue 211 | } 212 | if typeSpec.Name.Name == interfaceName { 213 | return typeSpec 214 | } 215 | } 216 | } 217 | return nil 218 | } 219 | 220 | func findInterfaceTypeSpec( 221 | interfaceName string, syntaxFiles []*ast.File, 222 | ) *ast.TypeSpec { 223 | for _, syntax := range syntaxFiles { 224 | typeSpec := findInterfaceTypeForDecl(interfaceName, syntax) 225 | if typeSpec != nil { 226 | return typeSpec 227 | } 228 | } 229 | return nil 230 | } 231 | 232 | func findInterfaceAST(typeSpec *ast.TypeSpec) *ast.InterfaceType { 233 | interfaceType, ok := typeSpec.Type.(*ast.InterfaceType) 234 | if !ok { 235 | return nil 236 | } 237 | return interfaceType 238 | } 239 | 240 | type emptyStruct = struct{} 241 | 242 | type importVisitorData struct { 243 | rootPackagePath string 244 | 245 | existedImports map[string]emptyStruct 246 | imports []importInfo 247 | } 248 | 249 | type importVisitor struct { 250 | info *types.Info 251 | data *importVisitorData 252 | } 253 | 254 | func newImportVisitorData(rootPackagePath string) *importVisitorData { 255 | return &importVisitorData{ 256 | rootPackagePath: rootPackagePath, 257 | existedImports: map[string]emptyStruct{}, 258 | imports: nil, 259 | } 260 | } 261 | 262 | func newImportVisitor(info *types.Info, visitorData *importVisitorData) *importVisitor { 263 | return &importVisitor{ 264 | info: info, 265 | data: visitorData, 266 | } 267 | } 268 | 269 | func (v *importVisitorData) append(imports []importInfo) { 270 | for _, importDetail := range imports { 271 | if importDetail.path == v.rootPackagePath { 272 | continue 273 | } 274 | 275 | if _, existed := v.existedImports[importDetail.path]; existed { 276 | continue 277 | } 278 | 279 | v.existedImports[importDetail.path] = emptyStruct{} 280 | v.imports = append(v.imports, importDetail) 281 | } 282 | } 283 | 284 | func (v *importVisitor) Visit(node ast.Node) ast.Visitor { 285 | ident, ok := node.(*ast.Ident) 286 | if !ok { 287 | return v 288 | } 289 | object, ok := v.info.Uses[ident] 290 | if !ok { 291 | return v 292 | } 293 | 294 | basicType, ok := object.Type().(*types.Basic) 295 | if ok { 296 | if basicType.Kind() == types.Invalid { 297 | return v 298 | } 299 | } 300 | 301 | if object.Pkg() == nil { 302 | return v 303 | } 304 | 305 | pkgInfo := object.Pkg() 306 | pkgPath := pkgInfo.Path() 307 | 308 | v.data.append([]importInfo{ 309 | { 310 | name: pkgInfo.Name(), 311 | path: pkgPath, 312 | }, 313 | }) 314 | return v 315 | } 316 | 317 | type embeddedInterface struct { 318 | name string 319 | pkgPath string 320 | } 321 | 322 | func getEmbeddedInterfaceForTypeExpr(typeExpr ast.Expr, foundPkg *packages.Package) (embeddedInterface, bool) { 323 | selector, ok := typeExpr.(*ast.SelectorExpr) 324 | if !ok { 325 | ident, ok := typeExpr.(*ast.Ident) 326 | if !ok { 327 | return embeddedInterface{}, false 328 | } 329 | object, ok := foundPkg.TypesInfo.Uses[ident] 330 | if !ok { 331 | return embeddedInterface{}, false 332 | } 333 | return embeddedInterface{ 334 | name: ident.Name, 335 | pkgPath: object.Pkg().Path(), 336 | }, true 337 | } 338 | 339 | packageIdent, ok := selector.X.(*ast.Ident) 340 | if !ok { 341 | return embeddedInterface{}, false 342 | } 343 | 344 | object, ok := foundPkg.TypesInfo.Uses[packageIdent] 345 | if !ok { 346 | return embeddedInterface{}, false 347 | } 348 | 349 | pkg, ok := object.(*types.PkgName) 350 | if !ok { 351 | return embeddedInterface{}, false 352 | } 353 | 354 | return embeddedInterface{ 355 | name: selector.Sel.Name, 356 | pkgPath: pkg.Imported().Path(), 357 | }, true 358 | } 359 | 360 | func checkAndFindPackageForInterfaces( 361 | pkgList []*packages.Package, interfaceNames ...string, 362 | ) (*packages.Package, error) { 363 | var foundPkg *packages.Package 364 | for _, pkg := range pkgList { 365 | if pkg.Types.Scope().Lookup(interfaceNames[0]) != nil { 366 | foundPkg = pkg 367 | break 368 | } 369 | } 370 | if foundPkg == nil { 371 | return nil, fmt.Errorf("can not find interface '%s'", interfaceNames[0]) 372 | } 373 | 374 | for _, interfaceName := range interfaceNames[1:] { 375 | if foundPkg.Types.Scope().Lookup(interfaceName) == nil { 376 | return nil, fmt.Errorf("can not find interface '%s'", interfaceName) 377 | } 378 | } 379 | return foundPkg, nil 380 | } 381 | 382 | func sortImportInfos(imports []importInfo) []importInfo { 383 | var stdImports []importInfo 384 | var otherImports []importInfo 385 | for _, importDetail := range imports { 386 | if strings.ContainsRune(importDetail.path, '.') { 387 | otherImports = append(otherImports, importDetail) 388 | } else { 389 | stdImports = append(stdImports, importDetail) 390 | } 391 | } 392 | 393 | sort.Slice(stdImports, func(i, j int) bool { 394 | return stdImports[i].path < stdImports[j].path 395 | }) 396 | 397 | sort.Slice(otherImports, func(i, j int) bool { 398 | return otherImports[i].path < otherImports[j].path 399 | }) 400 | 401 | result := stdImports 402 | result = append(result, otherImports...) 403 | return result 404 | } 405 | 406 | func loadPackageTypeData(pattern string, interfaceNames ...string) (packageTypeInfo, error) { 407 | loaded := loadedPackages{} 408 | foundPkg, err := loaded.loadPackageForInterfaces(pattern, interfaceNames...) 409 | if err != nil { 410 | fmt.Println("loadPackageForInterfaces", err) 411 | return packageTypeInfo{}, err 412 | } 413 | 414 | visitorData := newImportVisitorData(foundPkg.pkg.PkgPath) 415 | 416 | var interfaces []interfaceInfo 417 | for _, interfaceName := range interfaceNames { 418 | finder := newInterfaceInfoFinder(loaded, visitorData) 419 | 420 | info, err := finder.getInterfaceInfo(interfaceName, foundPkg) 421 | if err != nil { 422 | fmt.Println("getInterfaceInfo", err) 423 | return packageTypeInfo{}, err 424 | } 425 | interfaces = append(interfaces, info) 426 | } 427 | 428 | return packageTypeInfo{ 429 | name: foundPkg.pkg.Name, 430 | path: foundPkg.pkg.PkgPath, 431 | imports: sortImportInfos(visitorData.imports), 432 | interfaces: interfaces, 433 | }, nil 434 | } 435 | -------------------------------------------------------------------------------- /internal/generate/generate_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "errors" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | const rootPackagePath = "github.com/QuangTung97/otelwrap/internal/generate" 10 | 11 | func pkgListContext() []tupleTypePkg { 12 | return []tupleTypePkg{ 13 | { 14 | path: "context", 15 | begin: 0, 16 | end: len("context"), 17 | }, 18 | } 19 | } 20 | 21 | func TestLoadPackageTypeInfo(t *testing.T) { 22 | info, err := loadPackageTypeData("./hello", "Processor") 23 | assert.Equal(t, nil, err) 24 | 25 | interface1 := interfaceInfo{ 26 | name: "Processor", 27 | methods: []methodType{ 28 | { 29 | name: "StartTimer", 30 | params: []tupleType{ 31 | { 32 | name: "ctx", 33 | typeStr: "context.Context", 34 | recognized: recognizedTypeContext, 35 | pkgList: pkgListContext(), 36 | }, 37 | { 38 | name: "d", 39 | typeStr: "int32", 40 | }, 41 | }, 42 | }, 43 | { 44 | name: "Scan", 45 | params: []tupleType{ 46 | { 47 | name: "ctx", 48 | typeStr: "context.Context", 49 | recognized: recognizedTypeContext, 50 | pkgList: pkgListContext(), 51 | }, 52 | { 53 | name: "n", 54 | typeStr: "int", 55 | }, 56 | }, 57 | results: []tupleType{ 58 | { 59 | typeStr: "error", 60 | recognized: recognizedTypeError, 61 | }, 62 | }, 63 | }, 64 | { 65 | name: "Convert", 66 | params: []tupleType{ 67 | { 68 | name: "ctx", 69 | typeStr: "context.Context", 70 | recognized: recognizedTypeContext, 71 | pkgList: pkgListContext(), 72 | }, 73 | { 74 | name: "d", 75 | typeStr: "time.Duration", 76 | pkgList: []tupleTypePkg{ 77 | { 78 | path: "time", 79 | begin: 0, 80 | end: len("time"), 81 | }, 82 | }, 83 | }, 84 | }, 85 | }, 86 | { 87 | name: "SetInfo", 88 | params: []tupleType{ 89 | { 90 | name: "ctx", 91 | typeStr: "context.Context", 92 | recognized: recognizedTypeContext, 93 | pkgList: pkgListContext(), 94 | }, 95 | { 96 | name: "info", 97 | typeStr: "ScannerInfo", 98 | pkgList: []tupleTypePkg{ 99 | { 100 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/embed", 101 | begin: 0, 102 | end: 0, 103 | }, 104 | }, 105 | }, 106 | }, 107 | }, 108 | { 109 | name: "Compute", 110 | params: []tupleType{ 111 | { 112 | name: "ctx", 113 | typeStr: "context.Context", 114 | recognized: recognizedTypeContext, 115 | pkgList: pkgListContext(), 116 | }, 117 | { 118 | name: "x", 119 | typeStr: "string", 120 | }, 121 | }, 122 | results: []tupleType{ 123 | { 124 | typeStr: "error", 125 | recognized: recognizedTypeError, 126 | }, 127 | }, 128 | }, 129 | { 130 | name: "DoA", 131 | params: []tupleType{ 132 | { 133 | name: "ctx", 134 | typeStr: "context.Context", 135 | recognized: recognizedTypeContext, 136 | pkgList: pkgListContext(), 137 | }, 138 | { 139 | name: "n", 140 | typeStr: "int", 141 | }, 142 | }, 143 | results: []tupleType{ 144 | { 145 | typeStr: "error", 146 | recognized: recognizedTypeError, 147 | }, 148 | }, 149 | }, 150 | { 151 | name: "Handle", 152 | params: []tupleType{ 153 | { 154 | name: "ctx", 155 | typeStr: "context.Context", 156 | recognized: recognizedTypeContext, 157 | pkgList: pkgListContext(), 158 | }, 159 | { 160 | name: "u", 161 | typeStr: "*User", 162 | pkgList: []tupleTypePkg{ 163 | { 164 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 165 | begin: 1, 166 | end: 1, 167 | }, 168 | }, 169 | }, 170 | }, 171 | results: []tupleType{ 172 | { 173 | typeStr: "error", 174 | recognized: recognizedTypeError, 175 | }, 176 | }, 177 | }, 178 | { 179 | name: "Get", 180 | params: []tupleType{ 181 | { 182 | name: "ctx", 183 | typeStr: "context.Context", 184 | recognized: recognizedTypeContext, 185 | pkgList: pkgListContext(), 186 | }, 187 | { 188 | name: "id", 189 | typeStr: "int64", 190 | }, 191 | { 192 | name: "content", 193 | typeStr: "otelgosdk.Content", 194 | pkgList: []tupleTypePkg{ 195 | { 196 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel/sdk", 197 | end: len("otelgosdk"), 198 | }, 199 | }, 200 | }, 201 | }, 202 | results: []tupleType{ 203 | { 204 | typeStr: "otelgo.Person", 205 | pkgList: []tupleTypePkg{ 206 | { 207 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel", 208 | end: len("otelgo"), 209 | }, 210 | }, 211 | }, 212 | { 213 | typeStr: "error", 214 | recognized: recognizedTypeError, 215 | }, 216 | }, 217 | }, 218 | { 219 | name: "NoName", 220 | params: []tupleType{ 221 | { 222 | typeStr: "context.Context", 223 | recognized: recognizedTypeContext, 224 | pkgList: pkgListContext(), 225 | }, 226 | { 227 | typeStr: "int", 228 | }, 229 | }, 230 | results: nil, 231 | }, 232 | { 233 | name: "ManyParams", 234 | params: []tupleType{ 235 | { 236 | name: "ctx", 237 | typeStr: "context.Context", 238 | recognized: recognizedTypeContext, 239 | pkgList: pkgListContext(), 240 | }, 241 | { 242 | name: "params", 243 | typeStr: "...string", 244 | isVariadic: true, 245 | }, 246 | }, 247 | results: nil, 248 | }, 249 | { 250 | name: "UseArray", 251 | params: []tupleType{ 252 | { 253 | name: "ctx", 254 | typeStr: "context.Context", 255 | recognized: recognizedTypeContext, 256 | pkgList: pkgListContext(), 257 | }, 258 | { 259 | name: "contents", 260 | typeStr: "[]*otelgosdk.Content", 261 | pkgList: []tupleTypePkg{ 262 | { 263 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel/sdk", 264 | begin: len("[]*"), 265 | end: len("[]*") + len("otelgosdk"), 266 | }, 267 | }, 268 | }, 269 | }, 270 | results: []tupleType{ 271 | { 272 | name: "", 273 | typeStr: "User", 274 | pkgList: []tupleTypePkg{ 275 | { 276 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 277 | }, 278 | }, 279 | }, 280 | { 281 | name: "", 282 | typeStr: "error", 283 | recognized: recognizedTypeError, 284 | }, 285 | }, 286 | }, 287 | { 288 | name: "UseMap", 289 | params: []tupleType{ 290 | { 291 | name: "ctx", 292 | typeStr: "context.Context", 293 | recognized: recognizedTypeContext, 294 | pkgList: pkgListContext(), 295 | }, 296 | { 297 | name: "m", 298 | typeStr: "map[otelgosdk.Content]otelgosdk.Content", 299 | pkgList: []tupleTypePkg{ 300 | { 301 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel/sdk", 302 | begin: len("map["), 303 | end: len("map[") + len("otelgosdk"), 304 | }, 305 | { 306 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel/sdk", 307 | begin: len("map[otelgosdk.Content]"), 308 | end: len("map[otelgosdk.Content]") + len("otelgosdk"), 309 | }, 310 | }, 311 | }, 312 | }, 313 | results: []tupleType{ 314 | { 315 | typeStr: "map[User]User", 316 | pkgList: []tupleTypePkg{ 317 | { 318 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 319 | begin: len("map["), 320 | end: len("map["), 321 | }, 322 | { 323 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 324 | begin: len("map[User]"), 325 | end: len("map[User]"), 326 | }, 327 | }, 328 | }, 329 | }, 330 | }, 331 | }, 332 | } 333 | 334 | assert.Equal(t, packageTypeInfo{ 335 | name: "hello", 336 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 337 | imports: []importInfo{ 338 | { 339 | path: "context", 340 | name: "context", 341 | }, 342 | { 343 | path: "time", 344 | name: "time", 345 | }, 346 | { 347 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/embed", 348 | name: "embed", 349 | }, 350 | { 351 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel", 352 | name: "otelgo", 353 | }, 354 | { 355 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel/sdk", 356 | name: "otelgo", 357 | }, 358 | }, 359 | interfaces: []interfaceInfo{interface1}, 360 | }, info) 361 | } 362 | 363 | func TestLoadPackageTypeInfo_For_Type_Alias(t *testing.T) { 364 | info, err := loadPackageTypeData("./hello", "SimpleAlias") 365 | assert.Equal(t, nil, err) 366 | 367 | interface1 := interfaceInfo{ 368 | name: "SimpleAlias", 369 | methods: []methodType{ 370 | { 371 | name: "Scan", 372 | params: []tupleType{ 373 | { 374 | name: "ctx", 375 | typeStr: "context.Context", 376 | recognized: recognizedTypeContext, 377 | pkgList: pkgListContext(), 378 | }, 379 | { 380 | name: "n", 381 | typeStr: "int", 382 | }, 383 | }, 384 | results: []tupleType{ 385 | { 386 | name: "", 387 | typeStr: "error", 388 | recognized: recognizedTypeError, 389 | }, 390 | }, 391 | }, 392 | { 393 | name: "Convert", 394 | params: []tupleType{ 395 | { 396 | name: "ctx", 397 | typeStr: "context.Context", 398 | recognized: recognizedTypeContext, 399 | pkgList: pkgListContext(), 400 | }, 401 | { 402 | name: "d", 403 | typeStr: "time.Duration", 404 | pkgList: []tupleTypePkg{ 405 | { 406 | path: "time", 407 | begin: 0, 408 | end: len("time"), 409 | }, 410 | }, 411 | }, 412 | }, 413 | results: nil, 414 | }, 415 | { 416 | name: "SetInfo", 417 | params: []tupleType{ 418 | { 419 | name: "ctx", 420 | typeStr: "context.Context", 421 | recognized: recognizedTypeContext, 422 | pkgList: pkgListContext(), 423 | }, 424 | { 425 | name: "info", 426 | typeStr: "ScannerInfo", 427 | pkgList: []tupleTypePkg{ 428 | { 429 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/embed", 430 | }, 431 | }, 432 | }, 433 | }, 434 | results: nil, 435 | }, 436 | { 437 | name: "Handle", 438 | params: []tupleType{ 439 | { 440 | name: "ctx", 441 | typeStr: "context.Context", 442 | recognized: recognizedTypeContext, 443 | pkgList: pkgListContext(), 444 | }, 445 | { 446 | name: "u", 447 | typeStr: "*User", 448 | pkgList: []tupleTypePkg{ 449 | { 450 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 451 | begin: 1, 452 | end: 1, 453 | }, 454 | }, 455 | }, 456 | }, 457 | results: []tupleType{ 458 | { 459 | name: "", 460 | typeStr: "error", 461 | recognized: recognizedTypeError, 462 | }, 463 | }, 464 | }, 465 | { 466 | name: "Variadic", 467 | params: []tupleType{ 468 | { 469 | name: "ctx", 470 | typeStr: "context.Context", 471 | recognized: recognizedTypeContext, 472 | pkgList: pkgListContext(), 473 | }, 474 | { 475 | name: "names", 476 | typeStr: "...string", 477 | isVariadic: true, 478 | }, 479 | }, 480 | }, 481 | }, 482 | } 483 | 484 | assert.Equal(t, packageTypeInfo{ 485 | name: "hello", 486 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 487 | imports: []importInfo{ 488 | { 489 | path: "context", 490 | name: "context", 491 | }, 492 | { 493 | path: "time", 494 | name: "time", 495 | }, 496 | { 497 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/embed", 498 | name: "embed", 499 | }, 500 | }, 501 | interfaces: []interfaceInfo{interface1}, 502 | }, info) 503 | } 504 | 505 | func TestLoadPackageTypeInfo_Not_Found(t *testing.T) { 506 | info, err := loadPackageTypeData("./hello", "RandomInterface") 507 | assert.Equal(t, errors.New("can not find interface 'RandomInterface'"), err) 508 | assert.Equal(t, packageTypeInfo{}, info) 509 | } 510 | 511 | func TestLoadPackageTypeInfo_Not_Found_Second_Interface(t *testing.T) { 512 | info, err := loadPackageTypeData("./hello", "Simple", "AnotherInterface") 513 | assert.Equal(t, errors.New("can not find interface 'AnotherInterface'"), err) 514 | assert.Equal(t, packageTypeInfo{}, info) 515 | } 516 | 517 | func TestLoadPackageTypeInfo_Not_An_Interface(t *testing.T) { 518 | info, err := loadPackageTypeData("./hello", "User") 519 | assert.Equal(t, errors.New("name 'User' is not an interface"), err) 520 | assert.Equal(t, packageTypeInfo{}, info) 521 | } 522 | 523 | func TestLoadPackageTypeInfo_Interface_With_Underscore(t *testing.T) { 524 | info, err := loadPackageTypeData("./hello", "InterfaceWithUnderscore") 525 | assert.Equal(t, nil, err) 526 | 527 | methods := []methodType{ 528 | { 529 | name: "GetName", 530 | params: []tupleType{ 531 | { 532 | name: "ctx", 533 | typeStr: "context.Context", 534 | recognized: recognizedTypeContext, 535 | pkgList: pkgListContext(), 536 | }, 537 | { 538 | name: "_", 539 | typeStr: "string", 540 | }, 541 | { 542 | name: "_", 543 | typeStr: "int", 544 | }, 545 | }, 546 | results: []tupleType{ 547 | { 548 | name: "_", 549 | typeStr: "int32", 550 | }, 551 | { 552 | name: "_", 553 | typeStr: "error", 554 | recognized: recognizedTypeError, 555 | }, 556 | }, 557 | }, 558 | } 559 | 560 | assert.Equal(t, packageTypeInfo{ 561 | name: "hello", 562 | path: rootPackagePath + "/hello", 563 | imports: []importInfo{ 564 | { 565 | name: "context", 566 | path: "context", 567 | }, 568 | }, 569 | interfaces: []interfaceInfo{ 570 | { 571 | name: "InterfaceWithUnderscore", 572 | methods: methods, 573 | }, 574 | }, 575 | }, info) 576 | } 577 | 578 | func TestLoadPackageTypeInfo_For_Type_Alias__In_Another_Package(t *testing.T) { 579 | info, err := loadPackageTypeData("./hello/another", "HandlerAlias") 580 | assert.Equal(t, nil, err) 581 | 582 | interface1 := interfaceInfo{ 583 | name: "HandlerAlias", 584 | methods: []methodType{ 585 | { 586 | name: "Process", 587 | params: []tupleType{ 588 | { 589 | name: "ctx", 590 | typeStr: "context.Context", 591 | recognized: recognizedTypeContext, 592 | pkgList: pkgListContext(), 593 | }, 594 | { 595 | name: "n", 596 | typeStr: "int", 597 | }, 598 | }, 599 | results: []tupleType{ 600 | { 601 | name: "", 602 | typeStr: "error", 603 | recognized: recognizedTypeError, 604 | }, 605 | }, 606 | }, 607 | }, 608 | } 609 | 610 | assert.Equal(t, packageTypeInfo{ 611 | name: "another", 612 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/another", 613 | imports: []importInfo{ 614 | { 615 | name: "context", 616 | path: "context", 617 | }, 618 | }, 619 | interfaces: []interfaceInfo{interface1}, 620 | }, info) 621 | } 622 | 623 | func TestLoadPackageTypeInfo_For_InterfaceType_With_Generic_Params_And_Returns(t *testing.T) { 624 | info, err := loadPackageTypeData("./hello", "GenericHandler") 625 | assert.Equal(t, nil, err) 626 | 627 | interface1 := interfaceInfo{ 628 | name: "GenericHandler", 629 | methods: []methodType{ 630 | { 631 | name: "GetNull", 632 | params: []tupleType{ 633 | { 634 | name: "ctx", 635 | typeStr: "context.Context", 636 | recognized: recognizedTypeContext, 637 | pkgList: pkgListContext(), 638 | }, 639 | { 640 | name: "info", 641 | typeStr: "Null[otelgo.AnotherInfo]", 642 | pkgList: []tupleTypePkg{ 643 | { 644 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 645 | begin: 0, 646 | end: 0, 647 | }, 648 | { 649 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel", 650 | begin: len("Null["), 651 | end: len("Null[otelgo"), 652 | }, 653 | }, 654 | }, 655 | }, 656 | results: []tupleType{ 657 | { 658 | name: "", 659 | typeStr: "Null[otelgo.Person]", 660 | pkgList: []tupleTypePkg{ 661 | { 662 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 663 | begin: 0, 664 | end: 0, 665 | }, 666 | { 667 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel", 668 | begin: len("Null["), 669 | end: len("Null[otelgo"), 670 | }, 671 | }, 672 | }, 673 | { 674 | name: "", 675 | typeStr: "error", 676 | recognized: recognizedTypeError, 677 | }, 678 | }, 679 | }, 680 | }, 681 | } 682 | 683 | assert.Equal(t, packageTypeInfo{ 684 | name: "hello", 685 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello", 686 | imports: []importInfo{ 687 | { 688 | name: "context", 689 | path: "context", 690 | }, 691 | { 692 | name: "otelgo", 693 | path: "github.com/QuangTung97/otelwrap/internal/generate/hello/otel", 694 | }, 695 | }, 696 | interfaces: []interfaceInfo{interface1}, 697 | }, info) 698 | } 699 | -------------------------------------------------------------------------------- /internal/generate/hello/another/hello.go: -------------------------------------------------------------------------------- 1 | package another 2 | 3 | import "github.com/QuangTung97/otelwrap/internal/generate/hello" 4 | 5 | // HandlerAlias ... 6 | type HandlerAlias = hello.Handler 7 | -------------------------------------------------------------------------------- /internal/generate/hello/embed/parser.go: -------------------------------------------------------------------------------- 1 | package embed 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // ScannerInfo ... 9 | type ScannerInfo struct { 10 | Name string 11 | } 12 | 13 | // Scanner ... 14 | type Scanner interface { 15 | Scan(ctx context.Context, n int) error 16 | Convert(ctx context.Context, d time.Duration) 17 | SetInfo(ctx context.Context, info ScannerInfo) 18 | } 19 | 20 | // Parser ... 21 | type Parser interface { 22 | Compute(ctx context.Context, x string) error 23 | } 24 | -------------------------------------------------------------------------------- /internal/generate/hello/hello.go: -------------------------------------------------------------------------------- 1 | package hello 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "github.com/QuangTung97/otelwrap/internal/generate/hello/embed" 7 | otelgo "github.com/QuangTung97/otelwrap/internal/generate/hello/otel" 8 | otelgosdk "github.com/QuangTung97/otelwrap/internal/generate/hello/otel/sdk" 9 | "time" 10 | ) 11 | 12 | // User ... 13 | type User struct { 14 | ID int64 15 | Name string 16 | CreatedAt time.Time 17 | IsValid sql.NullBool 18 | } 19 | 20 | // Timer ... 21 | type Timer interface { 22 | StartTimer(ctx context.Context, d int32) 23 | } 24 | 25 | // Processor ... 26 | type Processor interface { 27 | Timer 28 | embed.Scanner 29 | embed.Parser 30 | 31 | DoA(ctx context.Context, n int) error 32 | Handle(ctx context.Context, u *User) error 33 | Get(ctx context.Context, id int64, content otelgosdk.Content) (otelgo.Person, error) 34 | NoName(context.Context, int) 35 | ManyParams(ctx context.Context, params ...string) 36 | UseArray(ctx context.Context, contents []*otelgosdk.Content) (User, error) 37 | UseMap(ctx context.Context, m map[otelgosdk.Content]otelgosdk.Content) map[User]User 38 | } 39 | 40 | // Simple ... 41 | type Simple interface { 42 | embed.Scanner 43 | 44 | Handle(ctx context.Context, u *User) error 45 | Variadic(ctx context.Context, names ...string) 46 | } 47 | 48 | // SimpleAlias ... 49 | type SimpleAlias = Simple 50 | 51 | // InterfaceWithUnderscore ... 52 | type InterfaceWithUnderscore interface { 53 | GetName(ctx context.Context, _ string, _ int) (_ int32, _ error) 54 | } 55 | 56 | // Handler ... 57 | type Handler interface { 58 | Process(ctx context.Context, n int) error 59 | } 60 | 61 | // Null ... 62 | type Null[T any] struct { 63 | Valid bool 64 | Data T 65 | } 66 | 67 | // GenericHandler ... 68 | type GenericHandler interface { 69 | GetNull(ctx context.Context, info Null[otelgo.AnotherInfo]) (Null[otelgo.Person], error) 70 | } 71 | -------------------------------------------------------------------------------- /internal/generate/hello/otel/otel.go: -------------------------------------------------------------------------------- 1 | package otelgo 2 | 3 | // Person ... 4 | type Person struct { 5 | ID int64 6 | } 7 | 8 | // AnotherInfo ... 9 | type AnotherInfo struct { 10 | Name string 11 | } 12 | -------------------------------------------------------------------------------- /internal/generate/hello/otel/sdk/sdk.go: -------------------------------------------------------------------------------- 1 | package otelgo 2 | 3 | // Content ... 4 | type Content struct { 5 | Value string 6 | } 7 | -------------------------------------------------------------------------------- /internal/generate/import.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | ) 7 | 8 | type importer struct { 9 | importClauses []importClause 10 | importPaths map[string]int 11 | usedNames map[string]int 12 | } 13 | 14 | type importClause struct { 15 | aliasName string 16 | path string 17 | usedName string 18 | } 19 | 20 | func newImporter() *importer { 21 | return &importer{ 22 | importPaths: map[string]int{}, 23 | usedNames: map[string]int{}, 24 | } 25 | } 26 | 27 | type addConfig struct { 28 | prefix string 29 | } 30 | 31 | type addOption func(opts *addConfig) 32 | 33 | func withPreferPrefix(prefix string) addOption { 34 | return func(conf *addConfig) { 35 | conf.prefix = prefix 36 | } 37 | } 38 | 39 | func computeImporterConfig(options ...addOption) addConfig { 40 | conf := addConfig{} 41 | for _, o := range options { 42 | o(&conf) 43 | } 44 | return conf 45 | } 46 | 47 | func (i *importer) add(importDetail importInfo, options ...addOption) { 48 | conf := computeImporterConfig(options...) 49 | 50 | index, ok := i.importPaths[importDetail.path] 51 | if ok { 52 | return 53 | } 54 | 55 | clause := importClause{ 56 | usedName: importDetail.name, 57 | path: importDetail.path, 58 | } 59 | 60 | index, ok = i.usedNames[importDetail.name] 61 | if ok { 62 | dir := path.Dir(importDetail.path) 63 | 64 | var newName string 65 | if dir == "." { 66 | newName = "std" + importDetail.name 67 | } else { 68 | if conf.prefix == "" { 69 | base := path.Base(dir) 70 | newName = base[:1] + importDetail.name 71 | } else { 72 | newName = conf.prefix + importDetail.name 73 | } 74 | } 75 | 76 | prevNewName := newName 77 | for suffix := 1; ; suffix++ { 78 | _, existed := i.usedNames[newName] 79 | if !existed { 80 | break 81 | } 82 | newName = fmt.Sprintf("%s%d", prevNewName, suffix) 83 | } 84 | 85 | clause.aliasName = newName 86 | clause.usedName = newName 87 | } 88 | 89 | index = len(i.importClauses) 90 | 91 | i.importPaths[clause.path] = index 92 | i.usedNames[clause.usedName] = index 93 | 94 | i.importClauses = append(i.importClauses, clause) 95 | } 96 | 97 | func (i *importer) getImports() []importClause { 98 | result := make([]importClause, len(i.importClauses)) 99 | copy(result, i.importClauses) 100 | return result 101 | } 102 | 103 | func (i *importer) chosenName(importPath string) string { 104 | index, ok := i.importPaths[importPath] 105 | if !ok { 106 | return "" 107 | } 108 | return i.importClauses[index].usedName 109 | } 110 | -------------------------------------------------------------------------------- /internal/generate/import_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestImporter_Same_Path(t *testing.T) { 9 | i := newImporter() 10 | i.add(importInfo{ 11 | name: "stderrors", 12 | path: "errors", 13 | }) 14 | 15 | assert.Equal(t, []importClause{ 16 | { 17 | path: "errors", 18 | usedName: "stderrors", 19 | }, 20 | }, i.getImports()) 21 | assert.Equal(t, "stderrors", i.chosenName("errors")) 22 | assert.Equal(t, "", i.chosenName("context")) 23 | 24 | i.add(importInfo{ 25 | name: "context", 26 | path: "context", 27 | }) 28 | 29 | assert.Equal(t, []importClause{ 30 | {path: "errors", usedName: "stderrors"}, 31 | {path: "context", usedName: "context"}, 32 | }, i.getImports()) 33 | assert.Equal(t, "context", i.chosenName("context")) 34 | 35 | i.add(importInfo{ 36 | name: "errors", 37 | path: "errors", 38 | }) 39 | 40 | assert.Equal(t, []importClause{ 41 | {path: "errors", usedName: "stderrors"}, 42 | {aliasName: "", path: "context", usedName: "context"}, 43 | }, i.getImports()) 44 | assert.Equal(t, "stderrors", i.chosenName("errors")) 45 | } 46 | 47 | func TestImporter_Same_UsedName(t *testing.T) { 48 | i := newImporter() 49 | i.add(importInfo{ 50 | name: "codes", 51 | path: "grpc/codes", 52 | }) 53 | i.add(importInfo{ 54 | name: "codes", 55 | path: "domain/codes", 56 | }) 57 | 58 | assert.Equal(t, []importClause{ 59 | { 60 | aliasName: "", 61 | path: "grpc/codes", 62 | usedName: "codes", 63 | }, 64 | { 65 | aliasName: "dcodes", 66 | path: "domain/codes", 67 | usedName: "dcodes", 68 | }, 69 | }, i.getImports()) 70 | } 71 | 72 | func TestImporter_Same_UsedName_With_StdLib(t *testing.T) { 73 | i := newImporter() 74 | i.add(importInfo{ 75 | name: "codes", 76 | path: "grpc/codes", 77 | }) 78 | i.add(importInfo{ 79 | name: "codes", 80 | path: "codes", 81 | }) 82 | 83 | assert.Equal(t, []importClause{ 84 | { 85 | aliasName: "", 86 | path: "grpc/codes", 87 | usedName: "codes", 88 | }, 89 | { 90 | aliasName: "stdcodes", 91 | path: "codes", 92 | usedName: "stdcodes", 93 | }, 94 | }, i.getImports()) 95 | } 96 | 97 | func TestImporter_Same_UsedName_Path_Multi_Levels(t *testing.T) { 98 | i := newImporter() 99 | i.add(importInfo{ 100 | name: "codes", 101 | path: "grpc/codes", 102 | }) 103 | i.add(importInfo{ 104 | name: "codes", 105 | path: "sample/hello/codes", 106 | }) 107 | 108 | assert.Equal(t, []importClause{ 109 | { 110 | aliasName: "", 111 | path: "grpc/codes", 112 | usedName: "codes", 113 | }, 114 | { 115 | aliasName: "hcodes", 116 | path: "sample/hello/codes", 117 | usedName: "hcodes", 118 | }, 119 | }, i.getImports()) 120 | } 121 | 122 | func TestImporter_Same_UsedName_New_Name_Still_Existed(t *testing.T) { 123 | i := newImporter() 124 | i.add(importInfo{ 125 | name: "codes", 126 | path: "grpc/codes", 127 | }) 128 | i.add(importInfo{ 129 | name: "codes", 130 | path: "sample/hello/codes", 131 | }) 132 | i.add(importInfo{ 133 | name: "codes", 134 | path: "another/hello/codes", 135 | }) 136 | 137 | assert.Equal(t, []importClause{ 138 | { 139 | aliasName: "", 140 | path: "grpc/codes", 141 | usedName: "codes", 142 | }, 143 | { 144 | aliasName: "hcodes", 145 | path: "sample/hello/codes", 146 | usedName: "hcodes", 147 | }, 148 | { 149 | aliasName: "hcodes1", 150 | path: "another/hello/codes", 151 | usedName: "hcodes1", 152 | }, 153 | }, i.getImports()) 154 | } 155 | 156 | func TestImporter_Same_UsedName_New_Name_Still_Existed_Suffix_2(t *testing.T) { 157 | i := newImporter() 158 | i.add(importInfo{ 159 | name: "codes", 160 | path: "grpc/codes", 161 | }) 162 | i.add(importInfo{ 163 | name: "hcodes", 164 | path: "sample/hello/codes", 165 | }) 166 | i.add(importInfo{ 167 | name: "hcodes1", 168 | path: "another/hello/codes", 169 | }) 170 | i.add(importInfo{ 171 | name: "codes", 172 | path: "else/hello/codes", 173 | }) 174 | 175 | assert.Equal(t, []importClause{ 176 | { 177 | aliasName: "", 178 | path: "grpc/codes", 179 | usedName: "codes", 180 | }, 181 | { 182 | path: "sample/hello/codes", 183 | usedName: "hcodes", 184 | }, 185 | { 186 | path: "another/hello/codes", 187 | usedName: "hcodes1", 188 | }, 189 | { 190 | aliasName: "hcodes2", 191 | path: "else/hello/codes", 192 | usedName: "hcodes2", 193 | }, 194 | }, i.getImports()) 195 | } 196 | 197 | func TestImporter_Same_UsedName_With_Prefer_Prefix(t *testing.T) { 198 | i := newImporter() 199 | i.add(importInfo{ 200 | name: "codes", 201 | path: "sample/codes", 202 | }) 203 | i.add(importInfo{ 204 | name: "codes", 205 | path: "opentelemetry/codes", 206 | }, withPreferPrefix("otel")) 207 | 208 | assert.Equal(t, []importClause{ 209 | { 210 | aliasName: "", 211 | path: "sample/codes", 212 | usedName: "codes", 213 | }, 214 | { 215 | aliasName: "otelcodes", 216 | path: "opentelemetry/codes", 217 | usedName: "otelcodes", 218 | }, 219 | }, i.getImports()) 220 | } 221 | -------------------------------------------------------------------------------- /internal/generate/interface_finder.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | ) 7 | 8 | type interfaceInfoFinder struct { 9 | methods []methodType 10 | loaded loadedPackages 11 | visitorData *importVisitorData 12 | } 13 | 14 | func newInterfaceInfoFinder(loaded loadedPackages, visitorData *importVisitorData) *interfaceInfoFinder { 15 | return &interfaceInfoFinder{ 16 | loaded: loaded, 17 | visitorData: visitorData, 18 | } 19 | } 20 | 21 | func (f *interfaceInfoFinder) getInterfaceHandleTypeAlias( 22 | typeSpec *ast.TypeSpec, interfaceName string, foundPkg loadedPackage, 23 | ) error { 24 | embed, ok := getEmbeddedInterfaceForTypeExpr(typeSpec.Type, foundPkg.pkg) 25 | if !ok { 26 | return fmt.Errorf("name '%s' is not an interface", interfaceName) 27 | } 28 | 29 | embeddedPkg, err := f.loaded.loadPackageForInterfaces(embed.pkgPath, embed.name) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return f.getInterfaceInfoRecursive(embed.name, embeddedPkg) 35 | } 36 | 37 | func (f *interfaceInfoFinder) getInterfaceInfoRecursive( 38 | interfaceName string, 39 | foundPkg loadedPackage, 40 | ) error { 41 | typeSpec := findInterfaceTypeSpec(interfaceName, foundPkg.pkg.Syntax) 42 | if typeSpec == nil { 43 | return fmt.Errorf("name '%s' is not a type spec", interfaceName) 44 | } 45 | 46 | interfaceType := findInterfaceAST(typeSpec) 47 | if interfaceType == nil { 48 | return f.getInterfaceHandleTypeAlias(typeSpec, interfaceName, foundPkg) 49 | } 50 | 51 | visitor := newImportVisitor(foundPkg.pkg.TypesInfo, f.visitorData) 52 | 53 | for _, field := range interfaceType.Methods.List { 54 | funcType, ok := field.Type.(*ast.FuncType) 55 | if !ok { 56 | embed, ok := getEmbeddedInterfaceForTypeExpr(field.Type, foundPkg.pkg) 57 | if !ok { 58 | continue 59 | } 60 | 61 | embeddedPkg, err := f.loaded.loadPackageForInterfaces(embed.pkgPath, embed.name) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | err = f.getInterfaceInfoRecursive(embed.name, embeddedPkg) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | continue 72 | } 73 | 74 | ast.Walk(visitor, field) 75 | 76 | params := fieldListToTupleList(funcType.Params, foundPkg.pkg.Fset, foundPkg.fileMap, foundPkg.pkg.TypesInfo) 77 | results := fieldListToTupleList(funcType.Results, foundPkg.pkg.Fset, foundPkg.fileMap, foundPkg.pkg.TypesInfo) 78 | 79 | f.methods = append(f.methods, methodType{ 80 | name: field.Names[0].Name, 81 | params: params, 82 | results: results, 83 | }) 84 | } 85 | 86 | return nil 87 | } 88 | 89 | func (f *interfaceInfoFinder) getInterfaceInfo( 90 | interfaceName string, 91 | foundPkg loadedPackage, 92 | ) (interfaceInfo, error) { 93 | err := f.getInterfaceInfoRecursive(interfaceName, foundPkg) 94 | if err != nil { 95 | return interfaceInfo{}, err 96 | } 97 | 98 | return interfaceInfo{ 99 | name: interfaceName, 100 | methods: f.methods, 101 | }, nil 102 | } 103 | -------------------------------------------------------------------------------- /internal/generate/loader.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import "golang.org/x/tools/go/packages" 4 | 5 | type loadedPackage struct { 6 | fileMap map[string]string 7 | pkg *packages.Package 8 | } 9 | 10 | type loadedPackages map[string]loadedPackage 11 | 12 | const loadPackageMode = packages.NeedName | packages.NeedSyntax | packages.NeedCompiledGoFiles | 13 | packages.NeedTypes | packages.NeedTypesInfo 14 | 15 | func (loaded loadedPackages) loadPackageForInterfaces( 16 | pattern string, interfaceNames ...string, 17 | ) (loadedPackage, error) { 18 | if pkg, existed := loaded[pattern]; existed { 19 | _, err := checkAndFindPackageForInterfaces([]*packages.Package{pkg.pkg}, interfaceNames...) 20 | if err != nil { 21 | return loadedPackage{}, err 22 | } 23 | return pkg, nil 24 | } 25 | 26 | pkgList, err := packages.Load(&packages.Config{ 27 | Mode: loadPackageMode, 28 | }, pattern) 29 | if err != nil { 30 | return loadedPackage{}, err 31 | } 32 | 33 | foundPkg, err := checkAndFindPackageForInterfaces(pkgList, interfaceNames...) 34 | if err != nil { 35 | return loadedPackage{}, err 36 | } 37 | 38 | result := loadedPackage{ 39 | pkg: foundPkg, 40 | fileMap: readFiles(foundPkg.CompiledGoFiles), 41 | } 42 | loaded[foundPkg.PkgPath] = result 43 | return result, nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/generate/template.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path" 7 | "strings" 8 | "text/template" 9 | ) 10 | 11 | var templateString = ` 12 | package {{ .PackageName }} 13 | 14 | import ( 15 | {{- range .Imports }} 16 | {{ . }}{{ end }} 17 | ) 18 | {{ range $interface := .Interfaces }} 19 | // {{ .StructName }} wraps OpenTelemetry's span 20 | type {{ .StructName }} struct { 21 | {{ .Name }} 22 | tracer {{ .ChosenOtelTracer }} 23 | prefix string 24 | } 25 | 26 | // New{{ .StructName }} creates a wrapper 27 | func New{{ .StructName }}(wrapped {{ .Name}}, tracer {{ .ChosenOtelTracer }}, prefix string) *{{ .StructName }} { 28 | return &{{ .StructName }}{ 29 | {{ .UsedName }}: wrapped, 30 | tracer: tracer, 31 | prefix: prefix, 32 | } 33 | } 34 | {{ range .Methods }} 35 | // {{ .Name }} ... 36 | func (w *{{ $interface.StructName }}) {{ .Name }}{{ .ParamsString }}{{ .ResultsString }}{ 37 | {{ .CtxName }}, {{ .SpanName }} := w.tracer.Start({{ .CtxName }}, w.prefix + "{{ .Name }}") 38 | defer {{ .SpanName }}.End() 39 | 40 | {{ if .WithReturn -}} 41 | {{ .ResultsRecvString }} = w.{{ $interface.UsedName }}.{{ .Name }}({{ .ArgsString }}) 42 | {{ if .WithError -}} 43 | if {{ .ErrString }} != nil { 44 | {{ .SpanName }}.RecordError({{ .ErrString }}) 45 | {{ .SpanName }}.SetStatus({{ .ChosenOtelCodes }}, {{ .ErrString }}.Error()) 46 | } 47 | {{- end }} 48 | return {{ .ResultsRecvString }} 49 | {{- else -}} 50 | w.{{ $interface.UsedName }}.{{ .Name }}({{ .ArgsString }}) 51 | {{- end }} 52 | } 53 | {{ end -}} 54 | {{ end -}} 55 | ` 56 | 57 | func initTemplate() *template.Template { 58 | tmpl, err := template.New("otelwrap").Parse(templateString) 59 | if err != nil { 60 | panic(err) 61 | } 62 | return tmpl 63 | } 64 | 65 | var resultTemplate = initTemplate() 66 | 67 | type templateMethod struct { 68 | Name string 69 | CtxName string 70 | SpanName string 71 | 72 | ParamsString string 73 | ResultsString string 74 | ArgsString string 75 | 76 | WithReturn bool 77 | WithError bool 78 | ResultsRecvString string 79 | ErrString string 80 | ChosenOtelCodes string 81 | } 82 | 83 | type templateInterface struct { 84 | Name string 85 | UsedName string 86 | StructName string 87 | Methods []templateMethod 88 | ChosenOtelTracer string 89 | } 90 | 91 | type templatePackageInfo struct { 92 | PackageName string 93 | Imports []string 94 | Interfaces []templateInterface 95 | } 96 | 97 | type templateMethodVariables struct { 98 | variables map[string]recognizedType 99 | } 100 | 101 | type templateInterfaceVariables struct { 102 | name string 103 | methods []templateMethodVariables 104 | } 105 | 106 | type templateVariables struct { 107 | globalVariables map[string]emptyStruct 108 | interfaces []templateInterfaceVariables 109 | } 110 | 111 | func collectVariables(info packageTypeInfo) templateVariables { 112 | global := map[string]struct{}{} 113 | global[info.name] = struct{}{} 114 | 115 | for _, importDetail := range info.imports { 116 | global[importDetail.name] = struct{}{} 117 | } 118 | 119 | interfaces := make([]templateInterfaceVariables, 0, len(info.interfaces)) 120 | for _, interfaceDetail := range info.interfaces { 121 | global[interfaceDetail.name] = struct{}{} 122 | 123 | var methods []templateMethodVariables 124 | for _, method := range interfaceDetail.methods { 125 | variables := map[string]recognizedType{ 126 | method.name: recognizedTypeUnknown, 127 | } 128 | 129 | for _, param := range method.params { 130 | variables[param.name] = param.recognized 131 | } 132 | 133 | methods = append(methods, templateMethodVariables{ 134 | variables: variables, 135 | }) 136 | } 137 | 138 | interfaces = append(interfaces, templateInterfaceVariables{ 139 | name: interfaceDetail.name, 140 | methods: methods, 141 | }) 142 | } 143 | 144 | return templateVariables{ 145 | globalVariables: global, 146 | interfaces: interfaces, 147 | } 148 | } 149 | 150 | func nameIsEmpty(name string) bool { 151 | return name == "" || name == "_" 152 | } 153 | 154 | func assignVariableNamesForFields( 155 | global map[string]struct{}, 156 | local map[string]recognizedType, 157 | fieldList []tupleType, 158 | startPosition int, 159 | ) { 160 | for i, field := range fieldList { 161 | _, globalExisted := global[field.name] 162 | if !nameIsEmpty(field.name) && !globalExisted && field.name != "w" { 163 | continue 164 | } 165 | 166 | varName := getVariableName( 167 | global, local, 168 | i-startPosition, field.recognized, 169 | ) 170 | fieldList[i].name = varName 171 | local[varName] = field.recognized 172 | } 173 | } 174 | 175 | func assignVariableNamesForMethod( 176 | global map[string]struct{}, 177 | local map[string]recognizedType, 178 | method methodType, 179 | ) { 180 | assignVariableNamesForFields(global, local, method.params, 1) 181 | assignVariableNamesForFields(global, local, method.results, 0) 182 | } 183 | 184 | func assignVariableNames(info packageTypeInfo) packageTypeInfo { 185 | variables := collectVariables(info) 186 | 187 | for interfaceIndex, interfaceDetail := range info.interfaces { 188 | for methodIndex, method := range interfaceDetail.methods { 189 | local := variables.interfaces[interfaceIndex].methods[methodIndex].variables 190 | assignVariableNamesForMethod(variables.globalVariables, local, method) 191 | } 192 | } 193 | return info 194 | } 195 | 196 | func getNextVariableName(name string, index int) string { 197 | if index == 0 { 198 | return name 199 | } 200 | return fmt.Sprintf("%s%d", name, index) 201 | } 202 | 203 | func getVariableName( 204 | global map[string]struct{}, 205 | local map[string]recognizedType, 206 | index int, expectedType recognizedType, 207 | ) string { 208 | var recommendedName string 209 | switch expectedType { 210 | case recognizedTypeContext: 211 | recommendedName = "ctx" 212 | case recognizedTypeError: 213 | recommendedName = "err" 214 | case recognizedTypeSpan: 215 | recommendedName = "span" 216 | default: 217 | ch := 'a' + index 218 | recommendedName = fmt.Sprintf("%c", ch) 219 | } 220 | 221 | for retryIndex := 0; ; retryIndex++ { 222 | name := getNextVariableName(recommendedName, retryIndex) 223 | if _, existed := global[name]; existed { 224 | continue 225 | } 226 | if _, existed := local[name]; existed { 227 | continue 228 | } 229 | return name 230 | } 231 | } 232 | 233 | func replacePackageName(typeStr string, pkgList []tupleTypePkg, importController *importer) string { 234 | var buf strings.Builder 235 | 236 | var fromOffset int 237 | 238 | replace := func(pkg tupleTypePkg, newName string) { 239 | _, _ = buf.WriteString(typeStr[fromOffset:pkg.begin]) 240 | _, _ = buf.WriteString(newName) 241 | fromOffset = pkg.end 242 | } 243 | 244 | for _, pkg := range pkgList { 245 | chosenName := importController.chosenName(pkg.path) 246 | if pkg.begin == pkg.end && chosenName != "" { 247 | replace(pkg, chosenName+".") 248 | } else { 249 | replace(pkg, chosenName) 250 | } 251 | } 252 | 253 | _, _ = buf.WriteString(typeStr[fromOffset:]) 254 | 255 | return buf.String() 256 | } 257 | 258 | func generateFieldListString(fields []tupleType, importController *importer) string { 259 | var fieldList []string 260 | 261 | for _, f := range fields { 262 | modifiedTypeStr := replacePackageName(f.typeStr, f.pkgList, importController) 263 | s := fmt.Sprintf("%s %s", f.name, modifiedTypeStr) 264 | fieldList = append(fieldList, s) 265 | } 266 | 267 | return strings.Join(fieldList, ", ") 268 | } 269 | 270 | func generateArgsString(fields []tupleType) string { 271 | var args []string 272 | for _, field := range fields { 273 | name := field.name 274 | if field.isVariadic { 275 | name = name + "..." 276 | } 277 | args = append(args, name) 278 | } 279 | return strings.Join(args, ", ") 280 | } 281 | 282 | const ( 283 | otelTracePkgPath = "go.opentelemetry.io/otel/trace" 284 | otelCodesPkgPath = "go.opentelemetry.io/otel/codes" 285 | ) 286 | 287 | func generateCodeForMethod( 288 | global map[string]struct{}, 289 | local map[string]recognizedType, 290 | method methodType, 291 | importController *importer, 292 | ) templateMethod { 293 | paramsStr := generateFieldListString(method.params, importController) 294 | paramsStr = fmt.Sprintf("(%s)", paramsStr) 295 | 296 | ctxName := "" 297 | for _, param := range method.params { 298 | if param.recognized == recognizedTypeContext { 299 | ctxName = param.name 300 | break 301 | } 302 | } 303 | 304 | var resultsStr string 305 | if len(method.results) == 0 { 306 | resultsStr = " " 307 | } else { 308 | resultsStr = generateFieldListString(method.results, importController) 309 | resultsStr = fmt.Sprintf(" (%s) ", resultsStr) 310 | } 311 | 312 | errStr := "" 313 | var recvVars []string 314 | for _, result := range method.results { 315 | recvVars = append(recvVars, result.name) 316 | if result.recognized == recognizedTypeError { 317 | errStr = result.name 318 | } 319 | } 320 | 321 | spanName := getVariableName(global, local, 0, recognizedTypeSpan) 322 | 323 | return templateMethod{ 324 | Name: method.name, 325 | CtxName: ctxName, 326 | SpanName: spanName, 327 | 328 | ParamsString: paramsStr, 329 | ResultsString: resultsStr, 330 | ArgsString: generateArgsString(method.params), 331 | 332 | WithReturn: resultsStr != " ", 333 | WithError: errStr != "", 334 | ResultsRecvString: strings.Join(recvVars, ", "), 335 | ErrString: errStr, 336 | ChosenOtelCodes: replacePackageName("codes.Error", []tupleTypePkg{ 337 | { 338 | path: otelCodesPkgPath, 339 | begin: 0, 340 | end: len("codes"), 341 | }, 342 | }, importController), 343 | } 344 | } 345 | 346 | //revive:disable-next-line:flag-parameter 347 | func importControllerAddImports(importController *importer, imports []importInfo, addOtelCodes bool) { 348 | for _, importDetail := range imports { 349 | importController.add(importDetail) 350 | } 351 | 352 | importController.add(importInfo{ 353 | path: otelTracePkgPath, 354 | name: "trace", 355 | }, withPreferPrefix("otel")) 356 | 357 | if addOtelCodes { 358 | importController.add(importInfo{ 359 | path: otelCodesPkgPath, 360 | name: "codes", 361 | }, withPreferPrefix("otel")) 362 | } 363 | } 364 | 365 | type generateConfig struct { 366 | inAnotherPackage bool 367 | pkgName string 368 | } 369 | 370 | // Option ... 371 | type Option func(conf *generateConfig) 372 | 373 | // WithInAnotherPackage ... 374 | func WithInAnotherPackage(packageName string) Option { 375 | return func(conf *generateConfig) { 376 | conf.inAnotherPackage = true 377 | conf.pkgName = packageName 378 | } 379 | } 380 | 381 | func computeGenerateConfig(options ...Option) generateConfig { 382 | conf := generateConfig{ 383 | inAnotherPackage: false, 384 | } 385 | for _, o := range options { 386 | o(&conf) 387 | } 388 | return conf 389 | } 390 | 391 | func containsErrorReturns(info packageTypeInfo) bool { 392 | for _, interfaceDetail := range info.interfaces { 393 | for _, method := range interfaceDetail.methods { 394 | for _, result := range method.results { 395 | if result.recognized == recognizedTypeError { 396 | return true 397 | } 398 | } 399 | } 400 | } 401 | return false 402 | } 403 | 404 | func generateCode(writer io.Writer, info packageTypeInfo, options ...Option) error { 405 | conf := computeGenerateConfig(options...) 406 | 407 | importController := newImporter() 408 | if conf.inAnotherPackage { 409 | importController.add(importInfo{ 410 | path: info.path, 411 | name: path.Base(info.path), 412 | }) 413 | } 414 | addOtelCodes := containsErrorReturns(info) 415 | importControllerAddImports(importController, info.imports, addOtelCodes) 416 | 417 | controllerImports := importController.getImports() 418 | newImports := make([]importInfo, 0, len(controllerImports)) 419 | for _, clause := range controllerImports { 420 | newImports = append(newImports, importInfo{ 421 | name: clause.usedName, 422 | path: clause.path, 423 | }) 424 | } 425 | info.imports = newImports 426 | 427 | variables := collectVariables(info) 428 | info = assignVariableNames(info) 429 | 430 | global := variables.globalVariables 431 | 432 | var interfaces []templateInterface 433 | for interfaceIndex, interfaceDetail := range info.interfaces { 434 | var methods []templateMethod 435 | for methodIndex, method := range interfaceDetail.methods { 436 | if len(method.params) == 0 || method.params[0].recognized != recognizedTypeContext { 437 | continue 438 | } 439 | local := variables.interfaces[interfaceIndex].methods[methodIndex].variables 440 | methods = append(methods, generateCodeForMethod(global, local, method, importController)) 441 | } 442 | 443 | embeddedInterfaceName := replacePackageName(interfaceDetail.name, 444 | []tupleTypePkg{ 445 | { 446 | path: info.path, 447 | begin: 0, 448 | end: 0, 449 | }, 450 | }, 451 | importController, 452 | ) 453 | interfaces = append(interfaces, templateInterface{ 454 | Name: embeddedInterfaceName, 455 | UsedName: interfaceDetail.name, 456 | StructName: interfaceDetail.name + "Wrapper", 457 | Methods: methods, 458 | ChosenOtelTracer: replacePackageName("trace.Tracer", []tupleTypePkg{ 459 | { 460 | path: otelTracePkgPath, 461 | begin: 0, 462 | end: len("trace"), 463 | }, 464 | }, importController), 465 | }) 466 | } 467 | 468 | var importStmts []string 469 | for _, clause := range importController.getImports() { 470 | if clause.aliasName == "" { 471 | importStmts = append(importStmts, fmt.Sprintf(`"%s"`, clause.path)) 472 | } else { 473 | importStmts = append(importStmts, fmt.Sprintf(`%s "%s"`, clause.aliasName, clause.path)) 474 | } 475 | } 476 | 477 | packageName := info.name 478 | if conf.pkgName != "" { 479 | packageName = conf.pkgName 480 | } 481 | 482 | return resultTemplate.Execute(writer, templatePackageInfo{ 483 | PackageName: packageName, 484 | Imports: importStmts, 485 | Interfaces: interfaces, 486 | }) 487 | } 488 | 489 | // LoadAndGenerate ... 490 | func LoadAndGenerate(w io.Writer, pattern string, interfaceNames []string, options ...Option) error { 491 | info, err := loadPackageTypeData(pattern, interfaceNames...) 492 | if err != nil { 493 | return err 494 | } 495 | err = generateCode(w, info, options...) 496 | if err != nil { 497 | fmt.Println("generateCode", err) 498 | return err 499 | } 500 | return nil 501 | } 502 | -------------------------------------------------------------------------------- /internal/generate/template_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "bytes" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAssignVariableNames(t *testing.T) { 10 | result := assignVariableNames(packageTypeInfo{ 11 | name: "example", 12 | imports: []importInfo{ 13 | { 14 | path: "context", 15 | name: "context", 16 | }, 17 | }, 18 | interfaces: []interfaceInfo{ 19 | { 20 | name: "Generator", 21 | methods: []methodType{ 22 | { 23 | name: "Hello", 24 | params: []tupleType{ 25 | { 26 | typeStr: "context.Context", 27 | recognized: recognizedTypeContext, 28 | }, 29 | { 30 | typeStr: "int64", 31 | }, 32 | { 33 | typeStr: "string", 34 | }, 35 | }, 36 | results: []tupleType{ 37 | { 38 | typeStr: "bool", 39 | }, 40 | { 41 | typeStr: "error", 42 | recognized: recognizedTypeError, 43 | }, 44 | }, 45 | }, 46 | { 47 | name: "DoA", 48 | params: []tupleType{ 49 | { 50 | name: "ctx", 51 | typeStr: "context.Context", 52 | recognized: recognizedTypeContext, 53 | }, 54 | { 55 | name: "n", 56 | typeStr: "int64", 57 | }, 58 | }, 59 | results: []tupleType{ 60 | { 61 | name: "err", 62 | typeStr: "error", 63 | recognized: recognizedTypeError, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | }) 71 | assert.Equal(t, packageTypeInfo{ 72 | name: "example", 73 | imports: []importInfo{ 74 | { 75 | path: "context", 76 | name: "context", 77 | }, 78 | }, 79 | interfaces: []interfaceInfo{ 80 | { 81 | name: "Generator", 82 | methods: []methodType{ 83 | { 84 | name: "Hello", 85 | params: []tupleType{ 86 | { 87 | name: "ctx", 88 | typeStr: "context.Context", 89 | recognized: recognizedTypeContext, 90 | }, 91 | { 92 | name: "a", 93 | typeStr: "int64", 94 | }, 95 | { 96 | name: "b", 97 | typeStr: "string", 98 | }, 99 | }, 100 | results: []tupleType{ 101 | { 102 | name: "a1", 103 | typeStr: "bool", 104 | }, 105 | { 106 | name: "err", 107 | typeStr: "error", 108 | recognized: recognizedTypeError, 109 | }, 110 | }, 111 | }, 112 | { 113 | name: "DoA", 114 | params: []tupleType{ 115 | { 116 | name: "ctx", 117 | typeStr: "context.Context", 118 | recognized: recognizedTypeContext, 119 | }, 120 | { 121 | name: "n", 122 | typeStr: "int64", 123 | }, 124 | }, 125 | results: []tupleType{ 126 | { 127 | name: "err", 128 | typeStr: "error", 129 | recognized: recognizedTypeError, 130 | }, 131 | }, 132 | }, 133 | }, 134 | }, 135 | }, 136 | }, result) 137 | } 138 | 139 | func TestCollectVariables(t *testing.T) { 140 | result := collectVariables(packageTypeInfo{ 141 | name: "example", 142 | imports: []importInfo{ 143 | { 144 | path: "context", 145 | name: "context", 146 | }, 147 | }, 148 | interfaces: []interfaceInfo{ 149 | { 150 | name: "Generator", 151 | methods: []methodType{ 152 | { 153 | name: "Hello", 154 | params: []tupleType{ 155 | { 156 | name: "ctx", 157 | typeStr: "context.Context", 158 | recognized: recognizedTypeContext, 159 | }, 160 | { 161 | name: "id", 162 | typeStr: "int64", 163 | }, 164 | }, 165 | }, 166 | }, 167 | }, 168 | }, 169 | }) 170 | assert.Equal(t, templateVariables{ 171 | globalVariables: map[string]struct{}{ 172 | "example": {}, 173 | "context": {}, 174 | "Generator": {}, 175 | }, 176 | interfaces: []templateInterfaceVariables{ 177 | { 178 | name: "Generator", 179 | methods: []templateMethodVariables{ 180 | { 181 | variables: map[string]recognizedType{ 182 | "Hello": recognizedTypeUnknown, 183 | "ctx": recognizedTypeContext, 184 | "id": recognizedTypeUnknown, 185 | }, 186 | }, 187 | }, 188 | }, 189 | }, 190 | }, result) 191 | } 192 | 193 | func TestGetVariableName(t *testing.T) { 194 | name := getVariableName( 195 | map[string]struct{}{}, 196 | map[string]recognizedType{}, 0, recognizedTypeUnknown) 197 | assert.Equal(t, "a", name) 198 | 199 | name = getVariableName( 200 | map[string]struct{}{}, 201 | map[string]recognizedType{}, 1, recognizedTypeUnknown) 202 | assert.Equal(t, "b", name) 203 | 204 | name = getVariableName( 205 | map[string]struct{}{}, 206 | map[string]recognizedType{}, 0, recognizedTypeContext) 207 | assert.Equal(t, "ctx", name) 208 | 209 | name = getVariableName( 210 | map[string]struct{}{}, 211 | map[string]recognizedType{}, 0, recognizedTypeError) 212 | assert.Equal(t, "err", name) 213 | 214 | name = getVariableName( 215 | map[string]struct{}{}, 216 | map[string]recognizedType{ 217 | "ctx": recognizedTypeContext, 218 | }, 0, recognizedTypeContext) 219 | assert.Equal(t, "ctx1", name) 220 | 221 | name = getVariableName( 222 | map[string]struct{}{ 223 | "ctx": {}, 224 | }, 225 | map[string]recognizedType{ 226 | "ctx1": recognizedTypeContext, 227 | }, 0, recognizedTypeContext) 228 | assert.Equal(t, "ctx2", name) 229 | } 230 | 231 | func TestGenerateCode(t *testing.T) { 232 | var buf bytes.Buffer 233 | err := generateCode(&buf, packageTypeInfo{ 234 | name: "example", 235 | imports: []importInfo{ 236 | { 237 | path: "context", 238 | name: "context", 239 | }, 240 | { 241 | path: "time", 242 | name: "time", 243 | }, 244 | }, 245 | interfaces: []interfaceInfo{ 246 | { 247 | name: "Handler", 248 | methods: []methodType{ 249 | { 250 | name: "Hello", 251 | params: []tupleType{ 252 | { 253 | name: "ctx", 254 | typeStr: "context.Context", 255 | recognized: recognizedTypeContext, 256 | pkgList: pkgListContext(), 257 | }, 258 | { 259 | name: "n", 260 | typeStr: "int", 261 | }, 262 | { 263 | name: "createdAt", 264 | typeStr: "time.Time", 265 | pkgList: []tupleTypePkg{ 266 | { 267 | path: "time", 268 | begin: 0, 269 | end: len("time"), 270 | }, 271 | }, 272 | }, 273 | }, 274 | results: []tupleType{ 275 | { 276 | name: "", 277 | typeStr: "error", 278 | recognized: recognizedTypeError, 279 | }, 280 | }, 281 | }, 282 | { 283 | name: "WithReturn", 284 | params: []tupleType{ 285 | { 286 | name: "rootCtx", 287 | typeStr: "context.Context", 288 | recognized: recognizedTypeContext, 289 | }, 290 | { 291 | name: "n", 292 | typeStr: "int", 293 | }, 294 | { 295 | name: "span", 296 | typeStr: "string", 297 | }, 298 | }, 299 | results: []tupleType{ 300 | { 301 | name: "count", 302 | typeStr: "int64", 303 | }, 304 | { 305 | name: "err", 306 | typeStr: "error", 307 | recognized: recognizedTypeError, 308 | }, 309 | }, 310 | }, 311 | }, 312 | }, 313 | }, 314 | }) 315 | assert.Equal(t, nil, err) 316 | assert.Equal(t, ` 317 | package example 318 | 319 | import ( 320 | "context" 321 | "time" 322 | "go.opentelemetry.io/otel/trace" 323 | "go.opentelemetry.io/otel/codes" 324 | ) 325 | 326 | // HandlerWrapper wraps OpenTelemetry's span 327 | type HandlerWrapper struct { 328 | Handler 329 | tracer trace.Tracer 330 | prefix string 331 | } 332 | 333 | // NewHandlerWrapper creates a wrapper 334 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 335 | return &HandlerWrapper{ 336 | Handler: wrapped, 337 | tracer: tracer, 338 | prefix: prefix, 339 | } 340 | } 341 | 342 | // Hello ... 343 | func (w *HandlerWrapper) Hello(ctx context.Context, n int, createdAt time.Time) (err error) { 344 | ctx, span := w.tracer.Start(ctx, w.prefix + "Hello") 345 | defer span.End() 346 | 347 | err = w.Handler.Hello(ctx, n, createdAt) 348 | if err != nil { 349 | span.RecordError(err) 350 | span.SetStatus(codes.Error, err.Error()) 351 | } 352 | return err 353 | } 354 | 355 | // WithReturn ... 356 | func (w *HandlerWrapper) WithReturn(rootCtx context.Context, n int, span string) (count int64, err error) { 357 | rootCtx, span1 := w.tracer.Start(rootCtx, w.prefix + "WithReturn") 358 | defer span1.End() 359 | 360 | count, err = w.Handler.WithReturn(rootCtx, n, span) 361 | if err != nil { 362 | span1.RecordError(err) 363 | span1.SetStatus(codes.Error, err.Error()) 364 | } 365 | return count, err 366 | } 367 | `, buf.String()) 368 | } 369 | 370 | //revive:disable:line-length-limit 371 | func TestGenerateCode_W_In_Param(t *testing.T) { 372 | var buf bytes.Buffer 373 | err := generateCode(&buf, packageTypeInfo{ 374 | name: "example", 375 | imports: []importInfo{ 376 | { 377 | path: "context", 378 | name: "context", 379 | }, 380 | { 381 | path: "time", 382 | name: "time", 383 | }, 384 | { 385 | path: "sample/codes", 386 | name: "codes", 387 | }, 388 | { 389 | path: "sample/trace", 390 | name: "trace", 391 | }, 392 | }, 393 | interfaces: []interfaceInfo{ 394 | { 395 | name: "Handler", 396 | methods: []methodType{ 397 | { 398 | name: "Hello", 399 | params: []tupleType{ 400 | { 401 | name: "ctx", 402 | typeStr: "context.Context", 403 | recognized: recognizedTypeContext, 404 | pkgList: pkgListContext(), 405 | }, 406 | { 407 | name: "n", 408 | typeStr: "int", 409 | }, 410 | { 411 | name: "createdAt", 412 | typeStr: "time.Time", 413 | pkgList: []tupleTypePkg{ 414 | { 415 | path: "time", 416 | begin: 0, 417 | end: len("time"), 418 | }, 419 | }, 420 | }, 421 | { 422 | name: "value", 423 | typeStr: "*codes.Hello", 424 | pkgList: []tupleTypePkg{ 425 | { 426 | path: "sample/codes", 427 | begin: 1, 428 | end: 1 + len("codes"), 429 | }, 430 | }, 431 | }, 432 | { 433 | name: "t", 434 | typeStr: "*trace.Hello", 435 | pkgList: []tupleTypePkg{ 436 | { 437 | path: "sample/trace", 438 | begin: 1, 439 | end: 1 + len("trace"), 440 | }, 441 | }, 442 | }, 443 | }, 444 | results: []tupleType{ 445 | { 446 | name: "", 447 | typeStr: "error", 448 | recognized: recognizedTypeError, 449 | }, 450 | }, 451 | }, 452 | { 453 | name: "UseW", 454 | params: []tupleType{ 455 | { 456 | name: "ctx", 457 | typeStr: "context.Context", 458 | recognized: recognizedTypeContext, 459 | pkgList: pkgListContext(), 460 | }, 461 | { 462 | name: "w", 463 | typeStr: "int64", 464 | }, 465 | }, 466 | results: []tupleType{ 467 | { 468 | typeStr: "int", 469 | }, 470 | { 471 | typeStr: "error", 472 | recognized: recognizedTypeError, 473 | }, 474 | }, 475 | }, 476 | { 477 | name: "ReturnW", 478 | params: []tupleType{ 479 | { 480 | name: "ctx", 481 | typeStr: "context.Context", 482 | recognized: recognizedTypeContext, 483 | pkgList: pkgListContext(), 484 | }, 485 | }, 486 | results: []tupleType{ 487 | { 488 | name: "w", 489 | typeStr: "context.Context", 490 | recognized: recognizedTypeContext, 491 | pkgList: pkgListContext(), 492 | }, 493 | { 494 | typeStr: "error", 495 | recognized: recognizedTypeError, 496 | }, 497 | }, 498 | }, 499 | }, 500 | }, 501 | }, 502 | }) 503 | assert.Equal(t, nil, err) 504 | assert.Equal(t, ` 505 | package example 506 | 507 | import ( 508 | "context" 509 | "time" 510 | "sample/codes" 511 | "sample/trace" 512 | oteltrace "go.opentelemetry.io/otel/trace" 513 | otelcodes "go.opentelemetry.io/otel/codes" 514 | ) 515 | 516 | // HandlerWrapper wraps OpenTelemetry's span 517 | type HandlerWrapper struct { 518 | Handler 519 | tracer oteltrace.Tracer 520 | prefix string 521 | } 522 | 523 | // NewHandlerWrapper creates a wrapper 524 | func NewHandlerWrapper(wrapped Handler, tracer oteltrace.Tracer, prefix string) *HandlerWrapper { 525 | return &HandlerWrapper{ 526 | Handler: wrapped, 527 | tracer: tracer, 528 | prefix: prefix, 529 | } 530 | } 531 | 532 | // Hello ... 533 | func (w *HandlerWrapper) Hello(ctx context.Context, n int, createdAt time.Time, value *codes.Hello, t *trace.Hello) (err error) { 534 | ctx, span := w.tracer.Start(ctx, w.prefix + "Hello") 535 | defer span.End() 536 | 537 | err = w.Handler.Hello(ctx, n, createdAt, value, t) 538 | if err != nil { 539 | span.RecordError(err) 540 | span.SetStatus(otelcodes.Error, err.Error()) 541 | } 542 | return err 543 | } 544 | 545 | // UseW ... 546 | func (w *HandlerWrapper) UseW(ctx context.Context, a int64) (a1 int, err error) { 547 | ctx, span := w.tracer.Start(ctx, w.prefix + "UseW") 548 | defer span.End() 549 | 550 | a1, err = w.Handler.UseW(ctx, a) 551 | if err != nil { 552 | span.RecordError(err) 553 | span.SetStatus(otelcodes.Error, err.Error()) 554 | } 555 | return a1, err 556 | } 557 | 558 | // ReturnW ... 559 | func (w *HandlerWrapper) ReturnW(ctx context.Context) (ctx1 context.Context, err error) { 560 | ctx, span := w.tracer.Start(ctx, w.prefix + "ReturnW") 561 | defer span.End() 562 | 563 | ctx1, err = w.Handler.ReturnW(ctx) 564 | if err != nil { 565 | span.RecordError(err) 566 | span.SetStatus(otelcodes.Error, err.Error()) 567 | } 568 | return ctx1, err 569 | } 570 | `, buf.String()) 571 | } 572 | 573 | //revive:enable:line-length-limit 574 | 575 | func TestGenerateCode_Without_Arg_Name(t *testing.T) { 576 | var buf bytes.Buffer 577 | err := generateCode(&buf, packageTypeInfo{ 578 | name: "example", 579 | imports: []importInfo{ 580 | { 581 | path: "context", 582 | name: "context", 583 | }, 584 | }, 585 | interfaces: []interfaceInfo{ 586 | { 587 | name: "Handler", 588 | methods: []methodType{ 589 | { 590 | name: "WithoutName", 591 | params: []tupleType{ 592 | { 593 | typeStr: "context.Context", 594 | recognized: recognizedTypeContext, 595 | pkgList: pkgListContext(), 596 | }, 597 | { 598 | typeStr: "int", 599 | }, 600 | }, 601 | results: []tupleType{ 602 | { 603 | name: "", 604 | typeStr: "string", 605 | }, 606 | { 607 | name: "", 608 | typeStr: "error", 609 | recognized: recognizedTypeError, 610 | }, 611 | }, 612 | }, 613 | }, 614 | }, 615 | }, 616 | }) 617 | assert.Equal(t, nil, err) 618 | assert.Equal(t, ` 619 | package example 620 | 621 | import ( 622 | "context" 623 | "go.opentelemetry.io/otel/trace" 624 | "go.opentelemetry.io/otel/codes" 625 | ) 626 | 627 | // HandlerWrapper wraps OpenTelemetry's span 628 | type HandlerWrapper struct { 629 | Handler 630 | tracer trace.Tracer 631 | prefix string 632 | } 633 | 634 | // NewHandlerWrapper creates a wrapper 635 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 636 | return &HandlerWrapper{ 637 | Handler: wrapped, 638 | tracer: tracer, 639 | prefix: prefix, 640 | } 641 | } 642 | 643 | // WithoutName ... 644 | func (w *HandlerWrapper) WithoutName(ctx context.Context, a int) (a1 string, err error) { 645 | ctx, span := w.tracer.Start(ctx, w.prefix + "WithoutName") 646 | defer span.End() 647 | 648 | a1, err = w.Handler.WithoutName(ctx, a) 649 | if err != nil { 650 | span.RecordError(err) 651 | span.SetStatus(codes.Error, err.Error()) 652 | } 653 | return a1, err 654 | } 655 | `, buf.String()) 656 | } 657 | 658 | func TestGenerateCode_Trace_As_Var_Name(t *testing.T) { 659 | var buf bytes.Buffer 660 | err := generateCode(&buf, packageTypeInfo{ 661 | name: "example", 662 | imports: []importInfo{ 663 | { 664 | path: "context", 665 | name: "context", 666 | }, 667 | }, 668 | interfaces: []interfaceInfo{ 669 | { 670 | name: "Handler", 671 | methods: []methodType{ 672 | { 673 | name: "WithoutName", 674 | params: []tupleType{ 675 | { 676 | typeStr: "context.Context", 677 | recognized: recognizedTypeContext, 678 | pkgList: pkgListContext(), 679 | }, 680 | { 681 | name: "trace", 682 | typeStr: "int", 683 | }, 684 | }, 685 | results: []tupleType{ 686 | { 687 | name: "", 688 | typeStr: "error", 689 | recognized: recognizedTypeError, 690 | }, 691 | }, 692 | }, 693 | }, 694 | }, 695 | }, 696 | }) 697 | assert.Equal(t, nil, err) 698 | assert.Equal(t, ` 699 | package example 700 | 701 | import ( 702 | "context" 703 | "go.opentelemetry.io/otel/trace" 704 | "go.opentelemetry.io/otel/codes" 705 | ) 706 | 707 | // HandlerWrapper wraps OpenTelemetry's span 708 | type HandlerWrapper struct { 709 | Handler 710 | tracer trace.Tracer 711 | prefix string 712 | } 713 | 714 | // NewHandlerWrapper creates a wrapper 715 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 716 | return &HandlerWrapper{ 717 | Handler: wrapped, 718 | tracer: tracer, 719 | prefix: prefix, 720 | } 721 | } 722 | 723 | // WithoutName ... 724 | func (w *HandlerWrapper) WithoutName(ctx context.Context, a int) (err error) { 725 | ctx, span := w.tracer.Start(ctx, w.prefix + "WithoutName") 726 | defer span.End() 727 | 728 | err = w.Handler.WithoutName(ctx, a) 729 | if err != nil { 730 | span.RecordError(err) 731 | span.SetStatus(codes.Error, err.Error()) 732 | } 733 | return err 734 | } 735 | `, buf.String()) 736 | } 737 | 738 | func TestGenerateCode_Use_Type_In_Current_Package(t *testing.T) { 739 | var buf bytes.Buffer 740 | err := generateCode(&buf, packageTypeInfo{ 741 | name: "example", 742 | path: "hello/example", 743 | imports: []importInfo{ 744 | { 745 | path: "context", 746 | name: "context", 747 | }, 748 | }, 749 | interfaces: []interfaceInfo{ 750 | { 751 | name: "Handler", 752 | methods: []methodType{ 753 | { 754 | name: "WithoutName", 755 | params: []tupleType{ 756 | { 757 | typeStr: "context.Context", 758 | recognized: recognizedTypeContext, 759 | pkgList: pkgListContext(), 760 | }, 761 | { 762 | name: "u", 763 | typeStr: "*User", 764 | pkgList: []tupleTypePkg{ 765 | { 766 | path: "hello/example", 767 | begin: 1, 768 | end: 1, 769 | }, 770 | }, 771 | }, 772 | }, 773 | }, 774 | }, 775 | }, 776 | }, 777 | }) 778 | assert.Equal(t, nil, err) 779 | assert.Equal(t, ` 780 | package example 781 | 782 | import ( 783 | "context" 784 | "go.opentelemetry.io/otel/trace" 785 | ) 786 | 787 | // HandlerWrapper wraps OpenTelemetry's span 788 | type HandlerWrapper struct { 789 | Handler 790 | tracer trace.Tracer 791 | prefix string 792 | } 793 | 794 | // NewHandlerWrapper creates a wrapper 795 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 796 | return &HandlerWrapper{ 797 | Handler: wrapped, 798 | tracer: tracer, 799 | prefix: prefix, 800 | } 801 | } 802 | 803 | // WithoutName ... 804 | func (w *HandlerWrapper) WithoutName(ctx context.Context, u *User) { 805 | ctx, span := w.tracer.Start(ctx, w.prefix + "WithoutName") 806 | defer span.End() 807 | 808 | w.Handler.WithoutName(ctx, u) 809 | } 810 | `, buf.String()) 811 | } 812 | 813 | func TestGenerateCode_To_Another_Package(t *testing.T) { 814 | var buf bytes.Buffer 815 | err := generateCode(&buf, packageTypeInfo{ 816 | name: "example", 817 | path: "hello/example", 818 | imports: []importInfo{ 819 | { 820 | path: "context", 821 | name: "context", 822 | }, 823 | }, 824 | interfaces: []interfaceInfo{ 825 | { 826 | name: "Handler", 827 | methods: []methodType{ 828 | { 829 | name: "WithoutName", 830 | params: []tupleType{ 831 | { 832 | typeStr: "context.Context", 833 | recognized: recognizedTypeContext, 834 | pkgList: pkgListContext(), 835 | }, 836 | { 837 | name: "u", 838 | typeStr: "*User", 839 | pkgList: []tupleTypePkg{ 840 | { 841 | path: "hello/example", 842 | begin: 1, 843 | end: 1, 844 | }, 845 | }, 846 | }, 847 | }, 848 | }, 849 | }, 850 | }, 851 | }, 852 | }, WithInAnotherPackage("example_wrapper")) 853 | assert.Equal(t, nil, err) 854 | assert.Equal(t, ` 855 | package example_wrapper 856 | 857 | import ( 858 | "hello/example" 859 | "context" 860 | "go.opentelemetry.io/otel/trace" 861 | ) 862 | 863 | // HandlerWrapper wraps OpenTelemetry's span 864 | type HandlerWrapper struct { 865 | example.Handler 866 | tracer trace.Tracer 867 | prefix string 868 | } 869 | 870 | // NewHandlerWrapper creates a wrapper 871 | func NewHandlerWrapper(wrapped example.Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 872 | return &HandlerWrapper{ 873 | Handler: wrapped, 874 | tracer: tracer, 875 | prefix: prefix, 876 | } 877 | } 878 | 879 | // WithoutName ... 880 | func (w *HandlerWrapper) WithoutName(ctx context.Context, u *example.User) { 881 | ctx, span := w.tracer.Start(ctx, w.prefix + "WithoutName") 882 | defer span.End() 883 | 884 | w.Handler.WithoutName(ctx, u) 885 | } 886 | `, buf.String()) 887 | } 888 | 889 | func TestGenerateCode_To_Another_Package_Return_Error(t *testing.T) { 890 | var buf bytes.Buffer 891 | err := generateCode(&buf, packageTypeInfo{ 892 | name: "example", 893 | path: "hello/example", 894 | imports: []importInfo{ 895 | { 896 | path: "context", 897 | name: "context", 898 | }, 899 | }, 900 | interfaces: []interfaceInfo{ 901 | { 902 | name: "Handler", 903 | methods: []methodType{ 904 | { 905 | name: "HelloWorld", 906 | params: []tupleType{ 907 | { 908 | typeStr: "context.Context", 909 | recognized: recognizedTypeContext, 910 | pkgList: pkgListContext(), 911 | }, 912 | { 913 | name: "u", 914 | typeStr: "*User", 915 | pkgList: []tupleTypePkg{ 916 | { 917 | path: "hello/example", 918 | begin: 1, 919 | end: 1, 920 | }, 921 | }, 922 | }, 923 | }, 924 | results: []tupleType{ 925 | { 926 | typeStr: "error", 927 | recognized: recognizedTypeError, 928 | }, 929 | }, 930 | }, 931 | { 932 | name: "WithoutContext", 933 | params: []tupleType{ 934 | { 935 | name: "n", 936 | typeStr: "int", 937 | }, 938 | }, 939 | results: []tupleType{ 940 | { 941 | typeStr: "error", 942 | recognized: recognizedTypeError, 943 | }, 944 | }, 945 | }, 946 | }, 947 | }, 948 | }, 949 | }, WithInAnotherPackage("example_wrapper")) 950 | assert.Equal(t, nil, err) 951 | assert.Equal(t, ` 952 | package example_wrapper 953 | 954 | import ( 955 | "hello/example" 956 | "context" 957 | "go.opentelemetry.io/otel/trace" 958 | "go.opentelemetry.io/otel/codes" 959 | ) 960 | 961 | // HandlerWrapper wraps OpenTelemetry's span 962 | type HandlerWrapper struct { 963 | example.Handler 964 | tracer trace.Tracer 965 | prefix string 966 | } 967 | 968 | // NewHandlerWrapper creates a wrapper 969 | func NewHandlerWrapper(wrapped example.Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 970 | return &HandlerWrapper{ 971 | Handler: wrapped, 972 | tracer: tracer, 973 | prefix: prefix, 974 | } 975 | } 976 | 977 | // HelloWorld ... 978 | func (w *HandlerWrapper) HelloWorld(ctx context.Context, u *example.User) (err error) { 979 | ctx, span := w.tracer.Start(ctx, w.prefix + "HelloWorld") 980 | defer span.End() 981 | 982 | err = w.Handler.HelloWorld(ctx, u) 983 | if err != nil { 984 | span.RecordError(err) 985 | span.SetStatus(codes.Error, err.Error()) 986 | } 987 | return err 988 | } 989 | `, buf.String()) 990 | } 991 | 992 | func TestGenerateCode_Multiple_Interfaces(t *testing.T) { 993 | var buf bytes.Buffer 994 | err := generateCode(&buf, packageTypeInfo{ 995 | name: "example", 996 | path: "hello/example", 997 | imports: []importInfo{ 998 | { 999 | path: "context", 1000 | name: "context", 1001 | }, 1002 | }, 1003 | interfaces: []interfaceInfo{ 1004 | { 1005 | name: "Handler", 1006 | methods: []methodType{ 1007 | { 1008 | name: "HelloWorld", 1009 | params: []tupleType{ 1010 | { 1011 | typeStr: "context.Context", 1012 | recognized: recognizedTypeContext, 1013 | pkgList: pkgListContext(), 1014 | }, 1015 | { 1016 | name: "u", 1017 | typeStr: "*User", 1018 | pkgList: []tupleTypePkg{ 1019 | { 1020 | path: "hello/example", 1021 | begin: 1, 1022 | end: 1, 1023 | }, 1024 | }, 1025 | }, 1026 | }, 1027 | }, 1028 | }, 1029 | }, 1030 | { 1031 | name: "IRepo", 1032 | methods: []methodType{ 1033 | { 1034 | name: "GetUser", 1035 | params: []tupleType{ 1036 | { 1037 | typeStr: "context.Context", 1038 | recognized: recognizedTypeContext, 1039 | pkgList: pkgListContext(), 1040 | }, 1041 | { 1042 | name: "id", 1043 | typeStr: "int", 1044 | }, 1045 | }, 1046 | }, 1047 | }, 1048 | }, 1049 | }, 1050 | }) 1051 | assert.Equal(t, nil, err) 1052 | assert.Equal(t, ` 1053 | package example 1054 | 1055 | import ( 1056 | "context" 1057 | "go.opentelemetry.io/otel/trace" 1058 | ) 1059 | 1060 | // HandlerWrapper wraps OpenTelemetry's span 1061 | type HandlerWrapper struct { 1062 | Handler 1063 | tracer trace.Tracer 1064 | prefix string 1065 | } 1066 | 1067 | // NewHandlerWrapper creates a wrapper 1068 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 1069 | return &HandlerWrapper{ 1070 | Handler: wrapped, 1071 | tracer: tracer, 1072 | prefix: prefix, 1073 | } 1074 | } 1075 | 1076 | // HelloWorld ... 1077 | func (w *HandlerWrapper) HelloWorld(ctx context.Context, u *User) { 1078 | ctx, span := w.tracer.Start(ctx, w.prefix + "HelloWorld") 1079 | defer span.End() 1080 | 1081 | w.Handler.HelloWorld(ctx, u) 1082 | } 1083 | 1084 | // IRepoWrapper wraps OpenTelemetry's span 1085 | type IRepoWrapper struct { 1086 | IRepo 1087 | tracer trace.Tracer 1088 | prefix string 1089 | } 1090 | 1091 | // NewIRepoWrapper creates a wrapper 1092 | func NewIRepoWrapper(wrapped IRepo, tracer trace.Tracer, prefix string) *IRepoWrapper { 1093 | return &IRepoWrapper{ 1094 | IRepo: wrapped, 1095 | tracer: tracer, 1096 | prefix: prefix, 1097 | } 1098 | } 1099 | 1100 | // GetUser ... 1101 | func (w *IRepoWrapper) GetUser(ctx context.Context, id int) { 1102 | ctx, span := w.tracer.Start(ctx, w.prefix + "GetUser") 1103 | defer span.End() 1104 | 1105 | w.IRepo.GetUser(ctx, id) 1106 | } 1107 | `, buf.String()) 1108 | } 1109 | 1110 | func TestGenerateCode_With_Variadic_Params(t *testing.T) { 1111 | var buf bytes.Buffer 1112 | err := generateCode(&buf, packageTypeInfo{ 1113 | name: "example", 1114 | path: "hello/example", 1115 | imports: []importInfo{ 1116 | { 1117 | path: "context", 1118 | name: "context", 1119 | }, 1120 | }, 1121 | interfaces: []interfaceInfo{ 1122 | { 1123 | name: "Handler", 1124 | methods: []methodType{ 1125 | { 1126 | name: "ManyParams", 1127 | params: []tupleType{ 1128 | { 1129 | typeStr: "context.Context", 1130 | recognized: recognizedTypeContext, 1131 | pkgList: pkgListContext(), 1132 | }, 1133 | { 1134 | name: "names", 1135 | typeStr: "...string", 1136 | isVariadic: true, 1137 | }, 1138 | }, 1139 | results: []tupleType{ 1140 | { 1141 | typeStr: "error", 1142 | recognized: recognizedTypeError, 1143 | }, 1144 | }, 1145 | }, 1146 | }, 1147 | }, 1148 | }, 1149 | }) 1150 | assert.Equal(t, nil, err) 1151 | assert.Equal(t, ` 1152 | package example 1153 | 1154 | import ( 1155 | "context" 1156 | "go.opentelemetry.io/otel/trace" 1157 | "go.opentelemetry.io/otel/codes" 1158 | ) 1159 | 1160 | // HandlerWrapper wraps OpenTelemetry's span 1161 | type HandlerWrapper struct { 1162 | Handler 1163 | tracer trace.Tracer 1164 | prefix string 1165 | } 1166 | 1167 | // NewHandlerWrapper creates a wrapper 1168 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 1169 | return &HandlerWrapper{ 1170 | Handler: wrapped, 1171 | tracer: tracer, 1172 | prefix: prefix, 1173 | } 1174 | } 1175 | 1176 | // ManyParams ... 1177 | func (w *HandlerWrapper) ManyParams(ctx context.Context, names ...string) (err error) { 1178 | ctx, span := w.tracer.Start(ctx, w.prefix + "ManyParams") 1179 | defer span.End() 1180 | 1181 | err = w.Handler.ManyParams(ctx, names...) 1182 | if err != nil { 1183 | span.RecordError(err) 1184 | span.SetStatus(codes.Error, err.Error()) 1185 | } 1186 | return err 1187 | } 1188 | `, buf.String()) 1189 | } 1190 | 1191 | func TestGenerateCode_With_Underscore(t *testing.T) { 1192 | var buf bytes.Buffer 1193 | err := generateCode(&buf, packageTypeInfo{ 1194 | name: "example", 1195 | path: "hello/example", 1196 | imports: []importInfo{ 1197 | { 1198 | path: "context", 1199 | name: "context", 1200 | }, 1201 | }, 1202 | interfaces: []interfaceInfo{ 1203 | { 1204 | name: "Handler", 1205 | methods: []methodType{ 1206 | { 1207 | name: "GetName", 1208 | params: []tupleType{ 1209 | { 1210 | typeStr: "context.Context", 1211 | recognized: recognizedTypeContext, 1212 | pkgList: pkgListContext(), 1213 | }, 1214 | { 1215 | name: "_", 1216 | typeStr: "string", 1217 | }, 1218 | }, 1219 | results: []tupleType{ 1220 | { 1221 | name: "_", 1222 | typeStr: "error", 1223 | recognized: recognizedTypeError, 1224 | }, 1225 | }, 1226 | }, 1227 | }, 1228 | }, 1229 | }, 1230 | }) 1231 | assert.Equal(t, nil, err) 1232 | assert.Equal(t, ` 1233 | package example 1234 | 1235 | import ( 1236 | "context" 1237 | "go.opentelemetry.io/otel/trace" 1238 | "go.opentelemetry.io/otel/codes" 1239 | ) 1240 | 1241 | // HandlerWrapper wraps OpenTelemetry's span 1242 | type HandlerWrapper struct { 1243 | Handler 1244 | tracer trace.Tracer 1245 | prefix string 1246 | } 1247 | 1248 | // NewHandlerWrapper creates a wrapper 1249 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 1250 | return &HandlerWrapper{ 1251 | Handler: wrapped, 1252 | tracer: tracer, 1253 | prefix: prefix, 1254 | } 1255 | } 1256 | 1257 | // GetName ... 1258 | func (w *HandlerWrapper) GetName(ctx context.Context, a string) (err error) { 1259 | ctx, span := w.tracer.Start(ctx, w.prefix + "GetName") 1260 | defer span.End() 1261 | 1262 | err = w.Handler.GetName(ctx, a) 1263 | if err != nil { 1264 | span.RecordError(err) 1265 | span.SetStatus(codes.Error, err.Error()) 1266 | } 1267 | return err 1268 | } 1269 | `, buf.String()) 1270 | } 1271 | 1272 | func TestGenerateCode_With_Only_Non_Error_Methods(t *testing.T) { 1273 | var buf bytes.Buffer 1274 | err := generateCode(&buf, packageTypeInfo{ 1275 | name: "example", 1276 | path: "hello/example", 1277 | imports: []importInfo{ 1278 | { 1279 | path: "context", 1280 | name: "context", 1281 | }, 1282 | }, 1283 | interfaces: []interfaceInfo{ 1284 | { 1285 | name: "Handler", 1286 | methods: []methodType{ 1287 | { 1288 | name: "GetName", 1289 | params: []tupleType{ 1290 | { 1291 | typeStr: "context.Context", 1292 | recognized: recognizedTypeContext, 1293 | pkgList: pkgListContext(), 1294 | }, 1295 | }, 1296 | results: []tupleType{ 1297 | { 1298 | name: "", 1299 | typeStr: "int64", 1300 | }, 1301 | }, 1302 | }, 1303 | }, 1304 | }, 1305 | }, 1306 | }) 1307 | assert.Equal(t, nil, err) 1308 | assert.Equal(t, ` 1309 | package example 1310 | 1311 | import ( 1312 | "context" 1313 | "go.opentelemetry.io/otel/trace" 1314 | ) 1315 | 1316 | // HandlerWrapper wraps OpenTelemetry's span 1317 | type HandlerWrapper struct { 1318 | Handler 1319 | tracer trace.Tracer 1320 | prefix string 1321 | } 1322 | 1323 | // NewHandlerWrapper creates a wrapper 1324 | func NewHandlerWrapper(wrapped Handler, tracer trace.Tracer, prefix string) *HandlerWrapper { 1325 | return &HandlerWrapper{ 1326 | Handler: wrapped, 1327 | tracer: tracer, 1328 | prefix: prefix, 1329 | } 1330 | } 1331 | 1332 | // GetName ... 1333 | func (w *HandlerWrapper) GetName(ctx context.Context) (a int64) { 1334 | ctx, span := w.tracer.Start(ctx, w.prefix + "GetName") 1335 | defer span.End() 1336 | 1337 | a = w.Handler.GetName(ctx) 1338 | 1339 | return a 1340 | } 1341 | `, buf.String()) 1342 | } 1343 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/QuangTung97/otelwrap/otelwrap" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | cmd := &cobra.Command{ 13 | Use: "otelwrap", 14 | RunE: func(cmd *cobra.Command, args []string) error { 15 | if len(args) < 2 { 16 | return errors.New("missing directory and interface list") 17 | } 18 | if args[0] != "." { 19 | return errors.New("only support '.' as directory") 20 | } 21 | 22 | out, err := cmd.Flags().GetString("out") 23 | if err != nil { 24 | return err 25 | } 26 | if out == "" { 27 | return errors.New("missing 'out' flag") 28 | } 29 | 30 | pkgName, err := cmd.Flags().GetString("pkg") 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return otelwrap.RunCommand(otelwrap.CommandArgs{ 36 | Dir: args[0], 37 | SrcFileName: os.Getenv("GOFILE"), 38 | InterfaceNames: args[1:], 39 | InAnother: otelwrap.CheckInAnother(out), 40 | PkgName: pkgName, 41 | }, out) 42 | }, 43 | } 44 | cmd.Flags().String("out", "", "required, output file name") 45 | cmd.Flags().String("pkg", "", "package name if specified interface is in another package") 46 | 47 | err := cmd.Execute() 48 | if err != nil { 49 | fmt.Println("ERROR:", err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /otelwrap/command.go: -------------------------------------------------------------------------------- 1 | package otelwrap 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "github.com/QuangTung97/otelwrap/internal/generate" 9 | "github.com/QuangTung97/otelwrap/internal/generate/hello" 10 | "go/format" 11 | "io" 12 | "os" 13 | "path" 14 | "strings" 15 | ) 16 | 17 | // ========================================= 18 | // For Testing Only 19 | // ========================================= 20 | 21 | // Sample for testing 22 | type Sample interface { 23 | Get(ctx context.Context) (int, error) 24 | Check() (bool, error) 25 | } 26 | 27 | // Repo for testing 28 | type Repo interface { 29 | Update(ctx context.Context, id int) error 30 | } 31 | 32 | // HandlerAlias ... 33 | type HandlerAlias = hello.Handler 34 | 35 | // ========================================= 36 | 37 | // CommandArgs ... 38 | type CommandArgs struct { 39 | Dir string 40 | SrcFileName string 41 | InterfaceNames []string 42 | InAnother bool 43 | PkgName string 44 | } 45 | 46 | func splitPackageNameFromInterfaceNames(interfaceNames []string) (string, []string, error) { 47 | values := strings.Split(interfaceNames[0], ".") 48 | if len(values) == 1 { 49 | for _, interfaceName := range interfaceNames[1:] { 50 | values = strings.Split(interfaceName, ".") 51 | if len(values) > 1 { 52 | return "", nil, errors.New("can not have mixed interface names") 53 | } 54 | } 55 | return "", interfaceNames, nil 56 | } 57 | 58 | packageName := values[0] 59 | result := make([]string, 0, len(interfaceNames)) 60 | for _, interfaceName := range interfaceNames { 61 | values = strings.Split(interfaceName, ".") 62 | if len(values) != 2 || values[0] != packageName { 63 | return "", nil, errors.New("can not have mixed interface names") 64 | } 65 | result = append(result, values[1]) 66 | } 67 | return packageName, result, nil 68 | } 69 | 70 | func findAndGenerate(w io.Writer, args CommandArgs) error { 71 | packageName, interfaceNames, err := splitPackageNameFromInterfaceNames(args.InterfaceNames) 72 | if err != nil { 73 | fmt.Println("splitPackageNameFromInterfaceNames", err) 74 | return err 75 | } 76 | 77 | if len(packageName) == 0 { 78 | if args.InAnother { 79 | return generate.LoadAndGenerate(w, 80 | ".", interfaceNames, 81 | generate.WithInAnotherPackage(args.PkgName), 82 | ) 83 | } 84 | return generate.LoadAndGenerate(w, 85 | ".", interfaceNames, 86 | ) 87 | } 88 | 89 | filePath := path.Join(args.Dir, args.SrcFileName) 90 | findResult, err := generate.FindPackage(filePath, packageName) 91 | if err != nil { 92 | fmt.Println("FindPackage", err) 93 | return err 94 | } 95 | 96 | return generate.LoadAndGenerate(w, 97 | findResult.DestPkgPath, interfaceNames, 98 | generate.WithInAnotherPackage(findResult.SrcPkgName), 99 | ) 100 | } 101 | 102 | // RunCommand ... 103 | func RunCommand(args CommandArgs, outFile string) error { 104 | var buf bytes.Buffer 105 | _, _ = buf.WriteString(`// Code generated by otelwrap; DO NOT EDIT. 106 | // github.com/QuangTung97/otelwrap 107 | 108 | `) 109 | err := findAndGenerate(&buf, args) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | data, err := format.Source(buf.Bytes()) 115 | if err != nil { 116 | fmt.Println("format.Source", string(buf.Bytes()), err) 117 | return err 118 | } 119 | 120 | file, err := os.Create(outFile) 121 | if err != nil { 122 | return err 123 | } 124 | defer func() { 125 | _ = file.Close() 126 | }() 127 | 128 | _, err = file.Write(data) 129 | return err 130 | } 131 | 132 | // CheckInAnother ... 133 | func CheckInAnother(filename string) bool { 134 | dir := path.Dir(filename) 135 | return dir != "." 136 | } 137 | -------------------------------------------------------------------------------- /otelwrap/command_test.go: -------------------------------------------------------------------------------- 1 | package otelwrap 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "errors" 7 | "github.com/QuangTung97/otelwrap/internal/generate/hello" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | var _ hello.Simple 13 | 14 | func TestFindAndGenerate_Interface_From_Another(t *testing.T) { 15 | var buf bytes.Buffer 16 | err := findAndGenerate(&buf, CommandArgs{ 17 | Dir: ".", 18 | SrcFileName: "command_test.go", 19 | InterfaceNames: []string{"hello.Simple"}, 20 | }) 21 | assert.Equal(t, nil, err) 22 | expected := ` 23 | package otelwrap 24 | 25 | import ( 26 | "github.com/QuangTung97/otelwrap/internal/generate/hello" 27 | "context" 28 | "time" 29 | "github.com/QuangTung97/otelwrap/internal/generate/hello/embed" 30 | "go.opentelemetry.io/otel/trace" 31 | "go.opentelemetry.io/otel/codes" 32 | ) 33 | 34 | // SimpleWrapper wraps OpenTelemetry's span 35 | type SimpleWrapper struct { 36 | hello.Simple 37 | tracer trace.Tracer 38 | prefix string 39 | } 40 | 41 | // NewSimpleWrapper creates a wrapper 42 | func NewSimpleWrapper(wrapped hello.Simple, tracer trace.Tracer, prefix string) *SimpleWrapper { 43 | return &SimpleWrapper{ 44 | Simple: wrapped, 45 | tracer: tracer, 46 | prefix: prefix, 47 | } 48 | } 49 | 50 | // Scan ... 51 | func (w *SimpleWrapper) Scan(ctx context.Context, n int) (err error) { 52 | ctx, span := w.tracer.Start(ctx, w.prefix + "Scan") 53 | defer span.End() 54 | 55 | err = w.Simple.Scan(ctx, n) 56 | if err != nil { 57 | span.RecordError(err) 58 | span.SetStatus(codes.Error, err.Error()) 59 | } 60 | return err 61 | } 62 | 63 | // Convert ... 64 | func (w *SimpleWrapper) Convert(ctx context.Context, d time.Duration) { 65 | ctx, span := w.tracer.Start(ctx, w.prefix + "Convert") 66 | defer span.End() 67 | 68 | w.Simple.Convert(ctx, d) 69 | } 70 | 71 | // SetInfo ... 72 | func (w *SimpleWrapper) SetInfo(ctx context.Context, info embed.ScannerInfo) { 73 | ctx, span := w.tracer.Start(ctx, w.prefix + "SetInfo") 74 | defer span.End() 75 | 76 | w.Simple.SetInfo(ctx, info) 77 | } 78 | 79 | // Handle ... 80 | func (w *SimpleWrapper) Handle(ctx context.Context, u *hello.User) (err error) { 81 | ctx, span := w.tracer.Start(ctx, w.prefix + "Handle") 82 | defer span.End() 83 | 84 | err = w.Simple.Handle(ctx, u) 85 | if err != nil { 86 | span.RecordError(err) 87 | span.SetStatus(codes.Error, err.Error()) 88 | } 89 | return err 90 | } 91 | 92 | // Variadic ... 93 | func (w *SimpleWrapper) Variadic(ctx context.Context, names ...string) { 94 | ctx, span := w.tracer.Start(ctx, w.prefix + "Variadic") 95 | defer span.End() 96 | 97 | w.Simple.Variadic(ctx, names...) 98 | } 99 | ` 100 | assert.Equal(t, expected, buf.String()) 101 | } 102 | 103 | func TestFindAndGenerate_Same_Package_Not_Found(t *testing.T) { 104 | var buf bytes.Buffer 105 | err := findAndGenerate(&buf, CommandArgs{ 106 | Dir: ".", 107 | SrcFileName: "command_test.go", 108 | InterfaceNames: []string{"Example"}, 109 | }) 110 | assert.Equal(t, errors.New("can not find interface 'Example'"), err) 111 | } 112 | 113 | func TestFindAndGenerate_Same_Package_OK(t *testing.T) { 114 | var buf bytes.Buffer 115 | err := findAndGenerate(&buf, CommandArgs{ 116 | Dir: ".", 117 | SrcFileName: "command_test.go", 118 | InterfaceNames: []string{"Sample"}, 119 | }) 120 | assert.Equal(t, nil, err) 121 | expected := ` 122 | package otelwrap 123 | 124 | import ( 125 | "context" 126 | "go.opentelemetry.io/otel/trace" 127 | "go.opentelemetry.io/otel/codes" 128 | ) 129 | 130 | // SampleWrapper wraps OpenTelemetry's span 131 | type SampleWrapper struct { 132 | Sample 133 | tracer trace.Tracer 134 | prefix string 135 | } 136 | 137 | // NewSampleWrapper creates a wrapper 138 | func NewSampleWrapper(wrapped Sample, tracer trace.Tracer, prefix string) *SampleWrapper { 139 | return &SampleWrapper{ 140 | Sample: wrapped, 141 | tracer: tracer, 142 | prefix: prefix, 143 | } 144 | } 145 | 146 | // Get ... 147 | func (w *SampleWrapper) Get(ctx context.Context) (a int, err error) { 148 | ctx, span := w.tracer.Start(ctx, w.prefix + "Get") 149 | defer span.End() 150 | 151 | a, err = w.Sample.Get(ctx) 152 | if err != nil { 153 | span.RecordError(err) 154 | span.SetStatus(codes.Error, err.Error()) 155 | } 156 | return a, err 157 | } 158 | ` 159 | assert.Equal(t, expected, buf.String()) 160 | } 161 | 162 | func TestFindAndGenerate_Export_In_Another(t *testing.T) { 163 | var buf bytes.Buffer 164 | err := findAndGenerate(&buf, CommandArgs{ 165 | Dir: ".", 166 | InterfaceNames: []string{"Sample", "Repo"}, 167 | InAnother: true, 168 | PkgName: "another", 169 | }) 170 | assert.Equal(t, nil, err) 171 | expected := ` 172 | package another 173 | 174 | import ( 175 | "github.com/QuangTung97/otelwrap/otelwrap" 176 | "context" 177 | "go.opentelemetry.io/otel/trace" 178 | "go.opentelemetry.io/otel/codes" 179 | ) 180 | 181 | // SampleWrapper wraps OpenTelemetry's span 182 | type SampleWrapper struct { 183 | otelwrap.Sample 184 | tracer trace.Tracer 185 | prefix string 186 | } 187 | 188 | // NewSampleWrapper creates a wrapper 189 | func NewSampleWrapper(wrapped otelwrap.Sample, tracer trace.Tracer, prefix string) *SampleWrapper { 190 | return &SampleWrapper{ 191 | Sample: wrapped, 192 | tracer: tracer, 193 | prefix: prefix, 194 | } 195 | } 196 | 197 | // Get ... 198 | func (w *SampleWrapper) Get(ctx context.Context) (a int, err error) { 199 | ctx, span := w.tracer.Start(ctx, w.prefix + "Get") 200 | defer span.End() 201 | 202 | a, err = w.Sample.Get(ctx) 203 | if err != nil { 204 | span.RecordError(err) 205 | span.SetStatus(codes.Error, err.Error()) 206 | } 207 | return a, err 208 | } 209 | 210 | // RepoWrapper wraps OpenTelemetry's span 211 | type RepoWrapper struct { 212 | otelwrap.Repo 213 | tracer trace.Tracer 214 | prefix string 215 | } 216 | 217 | // NewRepoWrapper creates a wrapper 218 | func NewRepoWrapper(wrapped otelwrap.Repo, tracer trace.Tracer, prefix string) *RepoWrapper { 219 | return &RepoWrapper{ 220 | Repo: wrapped, 221 | tracer: tracer, 222 | prefix: prefix, 223 | } 224 | } 225 | 226 | // Update ... 227 | func (w *RepoWrapper) Update(ctx context.Context, id int) (err error) { 228 | ctx, span := w.tracer.Start(ctx, w.prefix + "Update") 229 | defer span.End() 230 | 231 | err = w.Repo.Update(ctx, id) 232 | if err != nil { 233 | span.RecordError(err) 234 | span.SetStatus(codes.Error, err.Error()) 235 | } 236 | return err 237 | } 238 | ` 239 | assert.Equal(t, expected, buf.String()) 240 | } 241 | 242 | func TestCheckInAnother(t *testing.T) { 243 | inAnother := CheckInAnother("hello.go") 244 | assert.Equal(t, false, inAnother) 245 | 246 | inAnother = CheckInAnother("sample/hello.go") 247 | assert.Equal(t, true, inAnother) 248 | 249 | inAnother = CheckInAnother("./hello.go") 250 | assert.Equal(t, false, inAnother) 251 | } 252 | 253 | func TestFindAndGenerate_Alias_Of_Another_Package(t *testing.T) { 254 | var buf bytes.Buffer 255 | err := findAndGenerate(&buf, CommandArgs{ 256 | Dir: ".", 257 | SrcFileName: "command_test.go", 258 | InterfaceNames: []string{"HandlerAlias"}, 259 | }) 260 | assert.Equal(t, nil, err) 261 | expected := ` 262 | package otelwrap 263 | 264 | import ( 265 | "context" 266 | "go.opentelemetry.io/otel/trace" 267 | "go.opentelemetry.io/otel/codes" 268 | ) 269 | 270 | // HandlerAliasWrapper wraps OpenTelemetry's span 271 | type HandlerAliasWrapper struct { 272 | HandlerAlias 273 | tracer trace.Tracer 274 | prefix string 275 | } 276 | 277 | // NewHandlerAliasWrapper creates a wrapper 278 | func NewHandlerAliasWrapper(wrapped HandlerAlias, tracer trace.Tracer, prefix string) *HandlerAliasWrapper { 279 | return &HandlerAliasWrapper{ 280 | HandlerAlias: wrapped, 281 | tracer: tracer, 282 | prefix: prefix, 283 | } 284 | } 285 | 286 | // Process ... 287 | func (w *HandlerAliasWrapper) Process(ctx context.Context, n int) (err error) { 288 | ctx, span := w.tracer.Start(ctx, w.prefix + "Process") 289 | defer span.End() 290 | 291 | err = w.HandlerAlias.Process(ctx, n) 292 | if err != nil { 293 | span.RecordError(err) 294 | span.SetStatus(codes.Error, err.Error()) 295 | } 296 | return err 297 | } 298 | ` 299 | assert.Equal(t, expected, buf.String()) 300 | } 301 | 302 | //go:embed testdata/generic_handler 303 | var genericHandlerData string 304 | 305 | func TestFindAndGenerate_Interface_With_Generic_Types(t *testing.T) { 306 | var buf bytes.Buffer 307 | err := findAndGenerate(&buf, CommandArgs{ 308 | Dir: ".", 309 | SrcFileName: "command_test.go", 310 | InterfaceNames: []string{"hello.GenericHandler"}, 311 | }) 312 | assert.Equal(t, nil, err) 313 | 314 | assert.Equal(t, "\n"+genericHandlerData, buf.String()) 315 | } 316 | -------------------------------------------------------------------------------- /otelwrap/testdata/generic_handler: -------------------------------------------------------------------------------- 1 | package otelwrap 2 | 3 | import ( 4 | "github.com/QuangTung97/otelwrap/internal/generate/hello" 5 | "context" 6 | "github.com/QuangTung97/otelwrap/internal/generate/hello/otel" 7 | "go.opentelemetry.io/otel/trace" 8 | "go.opentelemetry.io/otel/codes" 9 | ) 10 | 11 | // GenericHandlerWrapper wraps OpenTelemetry's span 12 | type GenericHandlerWrapper struct { 13 | hello.GenericHandler 14 | tracer trace.Tracer 15 | prefix string 16 | } 17 | 18 | // NewGenericHandlerWrapper creates a wrapper 19 | func NewGenericHandlerWrapper(wrapped hello.GenericHandler, tracer trace.Tracer, prefix string) *GenericHandlerWrapper { 20 | return &GenericHandlerWrapper{ 21 | GenericHandler: wrapped, 22 | tracer: tracer, 23 | prefix: prefix, 24 | } 25 | } 26 | 27 | // GetNull ... 28 | func (w *GenericHandlerWrapper) GetNull(ctx context.Context, info hello.Null[otelgo.AnotherInfo]) (a hello.Null[otelgo.Person], err error) { 29 | ctx, span := w.tracer.Start(ctx, w.prefix + "GetNull") 30 | defer span.End() 31 | 32 | a, err = w.GenericHandler.GetNull(ctx, info) 33 | if err != nil { 34 | span.RecordError(err) 35 | span.SetStatus(codes.Error, err.Error()) 36 | } 37 | return a, err 38 | } 39 | -------------------------------------------------------------------------------- /revive.toml: -------------------------------------------------------------------------------- 1 | severity = "error" 2 | confidence = 0.8 3 | 4 | # Sets the error code for failures with severity "error" 5 | errorCode = 2 6 | # Sets the error code for failures with severity "warning" 7 | warningCode = 1 8 | 9 | # Enable all available rules 10 | enableAllRules = true 11 | 12 | # Disabled rules 13 | [rule.file-header] 14 | Disabled = true 15 | [rule.package-comments] 16 | Disabled = true 17 | [rule.max-public-structs] 18 | Disabled = true 19 | [rule.function-length] 20 | Disabled = true 21 | [rule.add-constant] 22 | Disabled = true 23 | [rule.banned-characters] 24 | Disabled = true 25 | 26 | # Rule tuning 27 | [rule.argument-limit] 28 | Arguments = [6] 29 | [rule.cyclomatic] 30 | Arguments = [10] 31 | [rule.cognitive-complexity] 32 | Arguments = [14] 33 | [rule.function-result-limit] 34 | Arguments = [4] 35 | [rule.unhandled-error] 36 | Arguments = ["fmt.Printf", "fmt.Println"] 37 | [rule.line-length-limit] 38 | Arguments = [120] 39 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "github.com/mgechev/revive" 8 | ) 9 | --------------------------------------------------------------------------------