diff --git a/.github/workflows/vulns.yml b/.github/workflows/vulns.yml index 3921b6c..743458d 100644 --- a/.github/workflows/vulns.yml +++ b/.github/workflows/vulns.yml @@ -13,7 +13,9 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ^1.16 + - name: install depm + run: go install github.com/spiegel-im-spiegel/depm@latest - name: WriteGoList - run: go list -json -m all > go.list + run: depm list --json > go.list - name: Nancy uses: sonatype-nexus-community/nancy-github-action@main diff --git a/README.md b/README.md index acf4405..1a97b98 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ This package is required Go 1.16 or later. ### Import Package -``` +```go import "github.com/spiegel-im-spiegel/aozora-api" ``` ### Search for Aozora-bunko Books Data -``` +```go books, err := aozora.DefaultClient().SearchBooks( aozora.WithBookTitle("/天に積む宝/"), aozora.WithBookAuthor("富田倫生"), @@ -26,13 +26,13 @@ books, err := aozora.DefaultClient().SearchBooks( ### Lookup Aozora-bunko Book Data -``` +```go book, err := aozora.DefaultClient().LookupBook(59489) ``` ### Search for Aozora-bunko Persons Data -``` +```go persons, err := aozora.DefaultClient().SearchPersons( aozora.WithPersonName("富田倫生"), ) @@ -40,13 +40,13 @@ persons, err := aozora.DefaultClient().SearchPersons( ### Lookup Aozora-bunko Person Data -``` +```go person, err := aozora.DefaultClient().LookupPerson(55) ``` ### Search for Aozora-bunko Workers Data -``` +```go workers, err := aozora.DefaultClient().SearchWorkers( aozora.WithWorkerName("雪森"), ) @@ -54,13 +54,13 @@ workers, err := aozora.DefaultClient().SearchWorkers( ### Lookup Aozora-bunko Worker Data -``` +```go worker, err := aozora.DefaultClient().LookupWorker(845) ``` ### Lookup Ranking data of Aozora-bunko -``` +```go tm, err := time.Parse("2006-01", "2019-01") ranking, err := aozora.DefaultClient().Ranking(tm) ``` @@ -69,7 +69,7 @@ ranking, err := aozora.DefaultClient().Ranking(tm) ### Book type -``` +```go //Author is entity class of author and translator info. type Author struct { PersonID int `json:"person_id"` @@ -128,7 +128,7 @@ type Book struct { ### Person type -``` +```go //Person is entity class of person info. type Person struct { PersonID int `json:"person_id"` @@ -148,7 +148,7 @@ type Person struct { ### Worker type -``` +```go //Worker is entity class of worker info. type Worker struct { WorkerID int `json:"id"` @@ -158,7 +158,7 @@ type Worker struct { ### Ranking type -``` +```go //Ranking is entity class of ranking info. type Ranking []struct { BookID int `json:"book_id"` diff --git a/client.go b/client.go index c58d770..f8f4aaf 100644 --- a/client.go +++ b/client.go @@ -3,50 +3,56 @@ package aozora import ( "context" "fmt" - "io" - "net/http" "net/url" "strconv" "time" "github.com/spiegel-im-spiegel/errs" + "github.com/spiegel-im-spiegel/fetch" ) const ( defaultAPIDir = "api/v0.1" ) -//Client is http.Client for Aozora API Server +//Client is http.Client for Aozora API Server. type Client struct { server *Server - client *http.Client - ctx context.Context + client fetch.Client } -//SearchBooksParamsFunc is self-referential function for functional options pattern +//SearchBooksParamsFunc is self-referential function for functional options pattern. type SearchBooksParamsFunc func(url.Values) -//SearchBooksRaw gets list of books (raw data) +//SearchBooksRaw gets list of books. (raw data) func (c *Client) SearchBooksRaw(opts ...SearchBooksParamsFunc) ([]byte, error) { + return c.SearchBooksRawContext(context.Background(), opts...) +} + +//SearchBooks gets list of books. (struct data) +func (c *Client) SearchBooks(opts ...SearchBooksParamsFunc) ([]Book, error) { + return c.SearchBooksContext(context.Background(), opts...) +} + +//SearchBooksRawContext gets list of books with context.Context. (raw data) +func (c *Client) SearchBooksRawContext(ctx context.Context, opts ...SearchBooksParamsFunc) ([]byte, error) { params := url.Values{} for _, opt := range opts { opt(params) } - b, err := c.get(c.makeSearchCommand(TargetBooks, params)) - return b, errs.Wrap(err) + return c.get(ctx, c.makeSearchCommand(TargetBooks, params)) } -//SearchBooks gets list of books (struct data) -func (c *Client) SearchBooks(opts ...SearchBooksParamsFunc) ([]Book, error) { - b, err := c.SearchBooksRaw(opts...) +//SearchBooksContext gets list of books with context.Context. (struct data) +func (c *Client) SearchBooksContext(ctx context.Context, opts ...SearchBooksParamsFunc) ([]Book, error) { + b, err := c.SearchBooksRawContext(ctx, opts...) if err != nil { return nil, errs.Wrap(err) } - books, err := DecodeBooks(b) - return books, errs.Wrap(err) + return DecodeBooks(b) } -//WithBookTitle returns function for setting Marketplace +//WithBookTitle returns function for setting Marketplace. func WithBookTitle(title string) SearchBooksParamsFunc { return func(params url.Values) { if params != nil && len(title) > 0 { @@ -55,7 +61,7 @@ func WithBookTitle(title string) SearchBooksParamsFunc { } } -//WithBookAuthor returns function for setting Marketplace +//WithBookAuthor returns function for setting Marketplace. func WithBookAuthor(author string) SearchBooksParamsFunc { return func(params url.Values) { if params != nil && len(author) > 0 { @@ -64,7 +70,7 @@ func WithBookAuthor(author string) SearchBooksParamsFunc { } } -//WithBookFields returns function for setting Marketplace +//WithBookFields returns function for setting Marketplace. func WithBookFields(fields string) SearchBooksParamsFunc { return func(params url.Values) { if params != nil && len(fields) > 0 { @@ -73,7 +79,7 @@ func WithBookFields(fields string) SearchBooksParamsFunc { } } -//WithBookLimit returns function for setting Marketplace +//WithBookLimit returns function for setting Marketplace. func WithBookLimit(limit int) SearchBooksParamsFunc { return func(params url.Values) { if params != nil && limit > 0 { @@ -82,7 +88,7 @@ func WithBookLimit(limit int) SearchBooksParamsFunc { } } -//WithBookSkip returns function for setting Marketplace +//WithBookSkip returns function for setting Marketplace. func WithBookSkip(skip int) SearchBooksParamsFunc { return func(params url.Values) { if params != nil && skip > 0 { @@ -91,7 +97,7 @@ func WithBookSkip(skip int) SearchBooksParamsFunc { } } -//WithBookAfter returns function for setting Marketplace +//WithBookAfter returns function for setting Marketplace. func WithBookAfter(after time.Time) SearchBooksParamsFunc { return func(params url.Values) { if params != nil && !after.IsZero() { @@ -100,30 +106,38 @@ func WithBookAfter(after time.Time) SearchBooksParamsFunc { } } -//SearchPersonsParamsFunc is self-referential function for functional options pattern +//SearchPersonsParamsFunc is self-referential function for functional options pattern. type SearchPersonsParamsFunc func(url.Values) -//SearchPersonsRaw gets list of persons (raw data) +//SearchPersonsRaw gets list of persons. (raw data) func (c *Client) SearchPersonsRaw(opts ...SearchPersonsParamsFunc) ([]byte, error) { + return c.SearchPersonsRawContext(context.Background(), opts...) +} + +//SearchPersons gets list of persons. (struct data) +func (c *Client) SearchPersons(opts ...SearchPersonsParamsFunc) ([]Person, error) { + return c.SearchPersonsContext(context.Background(), opts...) +} + +//SearchPersonsRawContext gets list of persons with context.Context. (raw data) +func (c *Client) SearchPersonsRawContext(ctx context.Context, opts ...SearchPersonsParamsFunc) ([]byte, error) { params := url.Values{} for _, opt := range opts { opt(params) } - b, err := c.get(c.makeSearchCommand(TargetPersons, params)) - return b, errs.Wrap(err) + return c.get(ctx, c.makeSearchCommand(TargetPersons, params)) } -//SearchPersons gets list of persons (struct data) -func (c *Client) SearchPersons(opts ...SearchPersonsParamsFunc) ([]Person, error) { - b, err := c.SearchPersonsRaw(opts...) +//SearchPersonsContext gets list of persons with context.Context. (struct data) +func (c *Client) SearchPersonsContext(ctx context.Context, opts ...SearchPersonsParamsFunc) ([]Person, error) { + b, err := c.SearchPersonsRawContext(ctx, opts...) if err != nil { return nil, errs.Wrap(err) } - persons, err := DecodePersons(b) - return persons, errs.Wrap(err) + return DecodePersons(b) } -//WithPersonName returns function for setting Marketplace +//WithPersonName returns function for setting Marketplace. func WithPersonName(name string) SearchPersonsParamsFunc { return func(params url.Values) { if params != nil && len(name) > 0 { @@ -132,30 +146,38 @@ func WithPersonName(name string) SearchPersonsParamsFunc { } } -//SearchPersonsParamsFunc is self-referential function for functional options pattern +//SearchPersonsParamsFunc is self-referential function for functional options pattern. type SearchWorkersParamsFunc func(url.Values) //SearchWorkersRaw gets list of workers (raw data) func (c *Client) SearchWorkersRaw(opts ...SearchWorkersParamsFunc) ([]byte, error) { + return c.SearchWorkersRawContext(context.Background(), opts...) +} + +//SearchWorkers gets list of workers (struct data) +func (c *Client) SearchWorkers(opts ...SearchWorkersParamsFunc) ([]Worker, error) { + return c.SearchWorkersContext(context.Background(), opts...) +} + +//SearchWorkersRawContext gets list of workers with context.Context. (raw data) +func (c *Client) SearchWorkersRawContext(ctx context.Context, opts ...SearchWorkersParamsFunc) ([]byte, error) { params := url.Values{} for _, opt := range opts { opt(params) } - b, err := c.get(c.makeSearchCommand(TargetWorkers, params)) - return b, errs.Wrap(err) + return c.get(ctx, c.makeSearchCommand(TargetWorkers, params)) } -//SearchWorkers gets list of workers (struct data) -func (c *Client) SearchWorkers(opts ...SearchWorkersParamsFunc) ([]Worker, error) { - b, err := c.SearchWorkersRaw(opts...) +//SearchWorkersContext gets list of workers with context.Context. (struct data) +func (c *Client) SearchWorkersContext(ctx context.Context, opts ...SearchWorkersParamsFunc) ([]Worker, error) { + b, err := c.SearchWorkersRawContext(ctx, opts...) if err != nil { return nil, errs.Wrap(err) } - workers, err := DecodeWorkers(b) - return workers, errs.Wrap(err) + return DecodeWorkers(b) } -//WithWorkerName returns function for setting Marketplace +//WithWorkerName returns function for setting Marketplace. func WithWorkerName(name string) SearchWorkersParamsFunc { return func(params url.Values) { if params != nil && len(name) > 0 { @@ -164,91 +186,141 @@ func WithWorkerName(name string) SearchWorkersParamsFunc { } } -//LookupBookRaw gets book data (raw data) +//LookupBookRaw gets book data. (raw data) func (c *Client) LookupBookRaw(id int) ([]byte, error) { - b, err := c.get(c.makeLookupCommand(TargetBooks, id)) - return b, errs.Wrap(err) + return c.LookupBookRawContext(context.Background(), id) } -//LookupBook gets books data (struct data) +//LookupBook gets books data. (struct data) func (c *Client) LookupBook(id int) (*Book, error) { - b, err := c.LookupBookRaw(id) - if err != nil { - return nil, errs.Wrap(err) - } - book, err := DecodeBook(b) - return book, errs.Wrap(err) + return c.LookupBookContext(context.Background(), id) } //LookupBookCardRaw gets book card info (HTML page data) func (c *Client) LookupBookCardRaw(id int) ([]byte, error) { - b, err := c.get(c.makeCardCommand(id)) - return b, errs.Wrap(err) + return c.LookupBookCardRawContext(context.Background(), id) } //LookupBookContentRaw gets book content (plain or HTML formatted text data) func (c *Client) LookupBookContentRaw(id int, f Format) ([]byte, error) { - b, err := c.get(c.makeContentCommand(id, f)) - return b, errs.Wrap(err) + return c.LookupBookContentRawContext(context.Background(), id, f) +} + +//LookupBookRawContext gets book data with context.Context. (raw data) +func (c *Client) LookupBookRawContext(ctx context.Context, id int) ([]byte, error) { + return c.get(ctx, c.makeLookupCommand(TargetBooks, id)) } -//LookupPersonRaw gets person data (raw data) +//LookupBookContext gets books data with context.Context. (struct data) +func (c *Client) LookupBookContext(ctx context.Context, id int) (*Book, error) { + b, err := c.LookupBookRawContext(ctx, id) + if err != nil { + return nil, errs.Wrap(err) + } + return DecodeBook(b) +} + +//LookupBookCardRawContext gets book card info with context.Context. (HTML page data) +func (c *Client) LookupBookCardRawContext(ctx context.Context, id int) ([]byte, error) { + return c.get(ctx, c.makeCardCommand(id)) +} + +//LookupBookContentRawContext gets book content with context.Context. (plain or HTML formatted text data) +func (c *Client) LookupBookContentRawContext(ctx context.Context, id int, f Format) ([]byte, error) { + return c.get(ctx, c.makeContentCommand(id, f)) +} + +//LookupPersonRaw gets person data. (raw data) func (c *Client) LookupPersonRaw(id int) ([]byte, error) { - b, err := c.get(c.makeLookupCommand(TargetPersons, id)) - return b, errs.Wrap(err) + return c.LookupPersonRawContext(context.Background(), id) } -//LookupPerson gets person data (struct data) +//LookupPerson gets person data. (struct data) func (c *Client) LookupPerson(id int) (*Person, error) { - b, err := c.LookupPersonRaw(id) + return c.LookupPersonContext(context.Background(), id) +} + +//LookupPersonRawContext gets person data with context.Context. (raw data) +func (c *Client) LookupPersonRawContext(ctx context.Context, id int) ([]byte, error) { + return c.get(ctx, c.makeLookupCommand(TargetPersons, id)) +} + +//LookupPersonContext gets person data with context.Context. (struct data) +func (c *Client) LookupPersonContext(ctx context.Context, id int) (*Person, error) { + b, err := c.LookupPersonRawContext(ctx, id) if err != nil { return nil, errs.Wrap(err) } - person, err := DecodePerson(b) - return person, errs.Wrap(err) + return DecodePerson(b) } -//LookupWorker gets worker data (raw data) +//LookupWorkerRaw gets worker data. (raw data) func (c *Client) LookupWorkerRaw(id int) ([]byte, error) { - b, err := c.get(c.makeLookupCommand(TargetWorkers, id)) - return b, errs.Wrap(err) + return c.LookupWorkerRawContext(context.Background(), id) } -//LookupWorkerRaw gets worker data (struct data) +//LookupWorker gets worker data. (struct data) func (c *Client) LookupWorker(id int) (*Worker, error) { - b, err := c.LookupWorkerRaw(id) + return c.LookupWorkerContext(context.Background(), id) +} + +//LookupWorkerRawContext gets worker data with context.Context. (raw data) +func (c *Client) LookupWorkerRawContext(ctx context.Context, id int) ([]byte, error) { + return c.get(ctx, c.makeLookupCommand(TargetWorkers, id)) +} + +//LookupWorkerContext gets worker data with context.Context. (struct data) +func (c *Client) LookupWorkerContext(ctx context.Context, id int) (*Worker, error) { + b, err := c.LookupWorkerRawContext(ctx, id) if err != nil { return nil, errs.Wrap(err) } - worker, err := DecodeWorker(b) - return worker, errs.Wrap(err) + return DecodeWorker(b) } //RankingRaw gets ranking data (raw data) func (c *Client) RankingRaw(tm time.Time) ([]byte, error) { - b, err := c.get(c.makeRankingCommand(tm)) - return b, errs.Wrap(err) + return c.RankingRawContext(context.Background(), tm) } //Ranking gets ranking data (struct data) func (c *Client) Ranking(tm time.Time) (Ranking, error) { - b, err := c.RankingRaw(tm) + return c.RankingContext(context.Background(), tm) +} + +//RankingRawContext gets ranking data (raw data) +func (c *Client) RankingRawContext(ctx context.Context, tm time.Time) ([]byte, error) { + return c.get(ctx, c.makeRankingCommand(tm)) +} + +//RankingContext gets ranking data (struct data) +func (c *Client) RankingContext(ctx context.Context, tm time.Time) (Ranking, error) { + b, err := c.RankingRawContext(ctx, tm) if err != nil { return nil, errs.Wrap(err) } - ranking, err := DecodeRanking(b) - return ranking, errs.Wrap(err) + return DecodeRanking(b) } func (c *Client) makeSearchCommand(t Target, v url.Values) *url.URL { - u := c.server.URL() + var u *url.URL + if c == nil { + u = New().URL() + } else { + u = c.server.URL() + } u.Path = fmt.Sprintf("/%v/%v", c.apiDir(), t) u.RawQuery = v.Encode() return u } func (c *Client) makeLookupCommand(t Target, id int) *url.URL { - u := c.server.URL() + var u *url.URL + if c == nil { + u = New().URL() + } else { + u = c.server.URL() + } u.Path = fmt.Sprintf("/%v/%v/%v", c.apiDir(), t, strconv.Itoa(id)) return u } @@ -267,7 +339,12 @@ func (c *Client) makeContentCommand(id int, f Format) *url.URL { } func (c *Client) makeRankingCommand(tm time.Time) *url.URL { - u := c.server.URL() + var u *url.URL + if c == nil { + u = New().URL() + } else { + u = c.server.URL() + } u.Path = fmt.Sprintf("/%v/%v/%v/%v", c.apiDir(), TargetRanking, "xhtml", tm.Format("2006/01")) return u } @@ -276,25 +353,15 @@ func (c *Client) apiDir() string { return defaultAPIDir } -func (c *Client) get(u *url.URL) ([]byte, error) { - req, err := http.NewRequestWithContext(c.ctx, "GET", u.String(), nil) - if err != nil { - return nil, errs.Wrap(err, errs.WithContext("url", u.String())) +func (c *Client) get(ctx context.Context, u *url.URL) ([]byte, error) { + if c == nil { + return nil, errs.Wrap(ErrNullPointer) } - resp, err := c.client.Do(req) + resp, err := c.client.Get(u, fetch.WithContext(ctx)) if err != nil { return nil, errs.Wrap(err, errs.WithContext("url", u.String())) } - defer resp.Body.Close() - - if !(resp.StatusCode != 0 && resp.StatusCode < http.StatusBadRequest) { - return nil, errs.Wrap(ErrHTTPStatus, errs.WithContext("url", u.String()), errs.WithContext("status", resp.Status)) - } - body, err := io.ReadAll(resp.Body) - if err != nil { - return body, errs.Wrap(err, errs.WithContext("url", u.String())) - } - return body, nil + return resp.DumpBodyAndClose() } /* Copyright 2019-2021 Spiegel diff --git a/go.mod b/go.mod index 347abc5..aadd416 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.16 require ( github.com/spf13/cobra v1.1.3 github.com/spiegel-im-spiegel/errs v1.0.2 + github.com/spiegel-im-spiegel/fetch v0.2.3 github.com/spiegel-im-spiegel/gocli v0.10.4 ) - -replace github.com/coreos/etcd v3.3.13+incompatible => github.com/coreos/etcd v3.3.25+incompatible diff --git a/go.sum b/go.sum index e9f3211..9bbd1e6 100644 --- a/go.sum +++ b/go.sum @@ -26,7 +26,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -156,6 +156,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spiegel-im-spiegel/errs v1.0.2 h1:v4amEwRDqRWjKHOILQnJSovYhZ4ZttEnBBXNXEzS6Sc= github.com/spiegel-im-spiegel/errs v1.0.2/go.mod h1:UoasJYYujMcdkbT9USv8dfZWoMyaY3btqQxoLJImw0A= +github.com/spiegel-im-spiegel/fetch v0.2.3 h1:Zh5rHvOjfC81rxKvtUD21JT609smds+BRh+H84s8qEw= +github.com/spiegel-im-spiegel/fetch v0.2.3/go.mod h1:ePIXxdC9OvSarXEO6HW1MgQwtBaKQo0qgDLOhKFXkQ0= github.com/spiegel-im-spiegel/gocli v0.10.4 h1:aoAWdiQ4hjNxmEod4EeTZTcjdCJcrNOwgHBs5BQYnEQ= github.com/spiegel-im-spiegel/gocli v0.10.4/go.mod h1:ffI3zoggRyLOZ+IIgaVN8WVMUwfIwfvCEd/0Yl/PZ98= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/server.go b/server.go index ff2cd7e..768eb20 100644 --- a/server.go +++ b/server.go @@ -4,6 +4,8 @@ import ( "context" "net/http" "net/url" + + "github.com/spiegel-im-spiegel/fetch" ) const ( @@ -63,37 +65,26 @@ func (s *Server) CreateClient(opts ...ClientOptFunc) *Client { if s == nil { s = New() } - cli := &Client{ - server: s, - client: nil, - ctx: nil, - } + cli := &Client{server: s} for _, opt := range opts { opt(cli) } if cli.client == nil { - cli.client = http.DefaultClient - } - if cli.ctx == nil { - cli.ctx = context.Background() + cli.client = fetch.New() } return cli } -//WithContext returns function for setting context.Context +//WithContext is dummy function. Because this function is deprecated. func WithContext(ctx context.Context) ClientOptFunc { - return func(c *Client) { - if c != nil { - c.ctx = ctx - } - } + return func(c *Client) {} } //WithHttpClient returns function for setting http.Client func WithHttpClient(client *http.Client) ClientOptFunc { return func(c *Client) { if c != nil { - c.client = client + c.client = fetch.New(fetch.WithHTTPClient(client)) } } } @@ -103,7 +94,7 @@ func DefaultClient() *Client { return New().CreateClient() } -/* Copyright 2019 Spiegel +/* Copyright 2019-2021 Spiegel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/test-all.sh b/test-all.sh index 0d762df..1bd7814 100755 --- a/test-all.sh +++ b/test-all.sh @@ -2,5 +2,5 @@ go mod verify || exit 1 go mod tidy -v || exit 1 depm list --json | docker run --rm -i sonatypecommunity/nancy:latest sleuth -n || exit 1 -docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.37.1 golangci-lint run --enable gosec ./... || exit 1 +docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.38.0 golangci-lint run --enable gosec ./... || exit 1 go test ./...