From 44f556c3757e6ac2265b6097b587e98efee5c8d5 Mon Sep 17 00:00:00 2001 From: dakimura <34202807+dakimura@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:17:20 +0900 Subject: [PATCH] feat(alpacabkfeeder): API Retry (#621) --- contrib/alpacabkfeeder/README.md | 3 +++ contrib/alpacabkfeeder/api/client.go | 21 ++++++++++++++++---- contrib/alpacabkfeeder/feed/backfill.go | 14 ++++++------- contrib/alpacabkfeeder/feed/backfill_test.go | 20 +++++++++---------- contrib/alpacabkfeeder/feed/paginate_test.go | 11 +++++----- go.mod | 7 +++++-- go.sum | 20 ++++++------------- 7 files changed, 53 insertions(+), 43 deletions(-) diff --git a/contrib/alpacabkfeeder/README.md b/contrib/alpacabkfeeder/README.md index 746cdac6..d94fcb2b 100644 --- a/contrib/alpacabkfeeder/README.md +++ b/contrib/alpacabkfeeder/README.md @@ -65,6 +65,9 @@ bgworkers: # such as "300ms", "5.5s" if you want to change it from the default 10sec timeout. # Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". # + # RetryMax: The number of maximum retries for Alpaca API. + # It can be updated only by "APCA_API_RETRY_MAX" environmental variable. + # # Interval [sec] to call Alpaca Broker API interval: 10 # The data-feeding is executed when 'minute' of the current time matches off_hours_schedule diff --git a/contrib/alpacabkfeeder/api/client.go b/contrib/alpacabkfeeder/api/client.go index 5158bc66..509daf6b 100644 --- a/contrib/alpacabkfeeder/api/client.go +++ b/contrib/alpacabkfeeder/api/client.go @@ -9,10 +9,12 @@ import ( "net/url" "os" "sort" + "strconv" "strings" "time" v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" + "github.com/hashicorp/go-retryablehttp" ) const ( @@ -28,7 +30,9 @@ var ( dataURL = "https://data.alpaca.markets" apiVersion = "v2" clientTimeout = 10 * time.Second - do = defaultDo + // maximum number of retries + retryMax = 1 + do = defaultDo ) func defaultDo(c *Client, req *http.Request) (*http.Response, error) { @@ -44,9 +48,11 @@ func defaultDo(c *Client, req *http.Request) (*http.Response, error) { } } - client := &http.Client{ - Timeout: clientTimeout, - } + retryClient := retryablehttp.NewClient() + retryClient.RetryMax = retryMax + client := retryClient.StandardClient() // *http.Client + client.Timeout = clientTimeout + var resp *http.Response var err error for i := 0; ; i++ { @@ -99,6 +105,13 @@ func init() { } clientTimeout = d } + if s := os.Getenv("APCA_API_RETRY_MAX"); s != "" { + d, err := strconv.Atoi(s) + if err != nil { + log.Fatal("invalid APCA_API_RETRY_MAX: " + err.Error()) + } + retryMax = d + } } // APIError wraps the detailed code and message supplied diff --git a/contrib/alpacabkfeeder/feed/backfill.go b/contrib/alpacabkfeeder/feed/backfill.go index dd0cea54..cd46a0b2 100644 --- a/contrib/alpacabkfeeder/feed/backfill.go +++ b/contrib/alpacabkfeeder/feed/backfill.go @@ -41,7 +41,7 @@ func NewBackfill(symbolManager symbols.Manager, apiClient GetMultiBarsAPIClient, // and store it to "{symbol}/{timeframe}/OHLCV" bucket in marketstore. func (b *Backfill) UpdateSymbols() { allSymbols := b.symbolManager.GetAllSymbols() - y, m, d := time.Now().Date() + y, m, d := time.Now().UTC().Date() until := time.Date(y, m, d, 0, 0, 0, 0, time.UTC) // paginate symbols & paginate bars @@ -133,12 +133,12 @@ type dateRange struct { // datePageIndex returns a channel with paginated date ranges. // datePageIndex assumes that start and end have only year, month, and day information // like time.Date(yyyy, mm, dd, 0,0,0,0, time.UTC) -// e.g. start = 2021-12-01, end = 2021-12-05, pageDays = 2 +// e.g. start = 2021-12-01, end = 2021-12-06, pageDays = 2 // -> chan will return // [ -// {From:2021-12-01, To:2021-12-02}, -// {From:2021-12-03, To:2021-12-04}, -// {From:2021-12-05, To:2021-12-05} +// {From:2021-12-01, To:2021-12-03}, +// {From:2021-12-03, To:2021-12-05}, +// {From:2021-12-05, To:2021-12-06} // ]. func datePageIndex(start, end time.Time, pageDays int) <-chan dateRange { ch := make(chan dateRange) @@ -149,7 +149,7 @@ func datePageIndex(start, end time.Time, pageDays int) <-chan dateRange { i := start for { pageStart := i - pageEnd := i.AddDate(0, 0, pageDays-1) + pageEnd := i.AddDate(0, 0, pageDays) if pageEnd.After(end) { pageEnd = end } @@ -157,7 +157,7 @@ func datePageIndex(start, end time.Time, pageDays int) <-chan dateRange { ch <- page i = i.AddDate(0, 0, pageDays) - if i.After(end) { + if i.Equal(end) || i.After(end) { break } } diff --git a/contrib/alpacabkfeeder/feed/backfill_test.go b/contrib/alpacabkfeeder/feed/backfill_test.go index 3edfc07c..d7830342 100644 --- a/contrib/alpacabkfeeder/feed/backfill_test.go +++ b/contrib/alpacabkfeeder/feed/backfill_test.go @@ -14,11 +14,11 @@ import ( ) var ( - dYear, dMonth, dDay = time.Now().Add(-24 * time.Hour).Date() + dYear, dMonth, dDay = time.Now().UTC().Add(-24 * time.Hour).Date() d = time.Date(dYear, dMonth, dDay, 0, 0, 0, 0, time.UTC) - d2Year, d2Month, d2Day = time.Now().Add(-48 * time.Hour).Date() + d2Year, d2Month, d2Day = time.Now().UTC().Add(-48 * time.Hour).Date() d2 = time.Date(d2Year, d2Month, d2Day, 0, 0, 0, 0, time.UTC) - d3Year, d3Month, d3Day = time.Now().Add(-72 * time.Hour).Date() + d3Year, d3Month, d3Day = time.Now().UTC().Add(-72 * time.Hour).Date() d3 = time.Date(d3Year, d3Month, d3Day, 0, 0, 0, 0, time.UTC) ) @@ -48,7 +48,7 @@ type MockErrorAPIClient struct { } // GetMultiBars returns an error if symbol:"ERROR" is included, but returns data to other symbols. -func (mac *MockErrorAPIClient) GetMultiBars(symbols []string, opts api.GetBarsParams) (map[string][]api.Bar, error) { +func (mac *MockErrorAPIClient) GetMultiBars(symbols []string, req api.GetBarsParams) (map[string][]api.Bar, error) { ret := make(map[string][]api.Bar) for _, symbl := range symbols { if symbl == errorSymbol { @@ -60,10 +60,10 @@ func (mac *MockErrorAPIClient) GetMultiBars(symbols []string, opts api.GetBarsPa // filter by time for _, bar := range bars { barTime := time.Unix(bar.Timestamp.Unix(), 0).UTC().Truncate(24 * time.Hour) // 00:00:00 of the bar time - startDt := opts.Start.UTC().Truncate(24 * time.Hour) - endDt := opts.End.UTC().Truncate(24 * time.Hour) + startDt := req.Start.UTC().Truncate(24 * time.Hour) + endDt := req.End.UTC().Truncate(24 * time.Hour) - if barTime.Equal(startDt) || (barTime.After(startDt) && barTime.Before(startDt)) || barTime.Equal(endDt) { + if barTime.Equal(startDt) || (barTime.After(startDt) && barTime.Before(endDt)) { barPage = append(barPage, bar) } } @@ -104,7 +104,7 @@ func TestBackfill_UpdateSymbols(t *testing.T) { testBars: testBars, maxBarsPerReq: 2, maxSymbolsPerReq: 2, - since: time.Now().Add(-72 * time.Hour), + since: time.Now().UTC().Add(-72 * time.Hour), wantWrittenBarCount: 9, }, { @@ -113,7 +113,7 @@ func TestBackfill_UpdateSymbols(t *testing.T) { testBars: testBars, maxBarsPerReq: 1, maxSymbolsPerReq: 3, - since: time.Now().Add(-72 * time.Hour), + since: time.Now().UTC().Add(-72 * time.Hour), wantWrittenBarCount: 9, }, { @@ -122,7 +122,7 @@ func TestBackfill_UpdateSymbols(t *testing.T) { testBars: testBars, maxBarsPerReq: 2, maxSymbolsPerReq: 2, - since: time.Now().Add(-72 * time.Hour), + since: time.Now().UTC().Add(-72 * time.Hour), // firstPage=[AMZN, AAPL] so all data succeed. // secondPage=[error FB] so all data result in error. wantWrittenBarCount: 6, diff --git a/contrib/alpacabkfeeder/feed/paginate_test.go b/contrib/alpacabkfeeder/feed/paginate_test.go index 178c030b..ed638bb5 100644 --- a/contrib/alpacabkfeeder/feed/paginate_test.go +++ b/contrib/alpacabkfeeder/feed/paginate_test.go @@ -31,9 +31,8 @@ func Test_datePageIndex(t *testing.T) { end: date(5), pageDays: 2, want: []dateRange{ - {From: date(1), To: date(2)}, - {From: date(3), To: date(4)}, - {From: date(5), To: date(5)}, + {From: date(1), To: date(3)}, + {From: date(3), To: date(5)}, }, }, { @@ -51,9 +50,9 @@ func Test_datePageIndex(t *testing.T) { end: date(3), pageDays: 1, want: []dateRange{ - {From: date(1), To: date(1)}, - {From: date(2), To: date(2)}, - {From: date(3), To: date(3)}, + {From: date(1), To: date(2)}, + {From: date(2), To: date(3)}, + {From: date(3), To: date(4)}, }, }, } diff --git a/go.mod b/go.mod index 098c996f..f98590cb 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -require google.golang.org/protobuf v1.28.0 +require ( + github.com/hashicorp/go-retryablehttp v0.7.1 + google.golang.org/protobuf v1.28.0 +) require ( github.com/beorn7/perks v1.0.1 // indirect @@ -52,6 +55,7 @@ require ( github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/gopacket v1.1.16-0.20181023151400-a35e09f9f224 // indirect + github.com/hashicorp/go-cleanhttp v0.5.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect @@ -72,7 +76,6 @@ require ( golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.4.0 // indirect google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/go.sum b/go.sum index 1da9de2c..993e05a5 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,12 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -252,7 +258,6 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -269,7 +274,6 @@ go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2 h1:y102fOLFqhV41b+4GPiJoa0k/x+pJcEi2/HB1Y5T6fU= @@ -280,9 +284,6 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -303,8 +304,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= -golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -314,7 +313,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -336,8 +334,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -359,13 +355,9 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k= -golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=