diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f58c45c..435c5da 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,28 +1,19 @@ -name: Build -on: [push, pull_request] +name: build + +on: + push + +permissions: + contents: read + jobs: + build: - strategy: - matrix: - go-version: [1.18.x] - os: [ubuntu-latest] - runs-on: ${{ matrix.os }} - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v2 - - name: Install Task - uses: arduino/setup-task@v1 - - name: Test Coverage - run: task test - if: matrix.os == 'ubuntu-latest' - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./dist/coverage.txt - verbose: true - if: matrix.os == 'ubuntu-latest' + uses: bzimmer/actions/.github/workflows/build.yaml@main + with: + skipBuild: true + skipCoverage: false + secrets: inherit + + vuln: + uses: bzimmer/actions/.github/workflows/vuln.yaml@main diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml deleted file mode 100644 index f99035a..0000000 --- a/.github/workflows/golangci-lint.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: golangci-lint -on: [push, pull_request] -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: latest - - # Optional: working directory, useful for monorepos - # working-directory: somedir - - # Optional: golangci-lint command line arguments. - args: -v - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true - - # Optional: if set to true then the action will use pre-installed Go. - # skip-go-installation: true - - # Optional: if set to true then the action don't cache or restore ~/go/pkg. - # skip-pkg-cache: true - - # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. - # skip-build-cache: true diff --git a/.github/workflows/mkdocs.yaml b/.github/workflows/mkdocs.yaml new file mode 100644 index 0000000..91e5f38 --- /dev/null +++ b/.github/workflows/mkdocs.yaml @@ -0,0 +1,16 @@ +name: mkdocs + +on: + push: + branches: + - main + - mkdocs + +permissions: + contents: write + +jobs: + + mkdocs: + uses: bzimmer/actions/.github/workflows/mkdocs.yaml@main + secrets: inherit diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3219827..15b3a3f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,4 +1,4 @@ -name: goreleaser +name: release on: push: @@ -9,24 +9,7 @@ permissions: contents: write jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.18 - - name: Install Task - uses: arduino/setup-task@v1 - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release: + uses: bzimmer/actions/.github/workflows/release.yaml@main + secrets: inherit diff --git a/.golangci.yml b/.golangci.yml index 2dae0b2..7f819fe 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,119 +1,323 @@ +# This code is licensed under the terms of the MIT license. + +# Sourced from: +# https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 +# extended the exclude rules block at the bottom of the file + +## Golden config for golangci-lint v1.52.2 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adopt and change it for your needs. + +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml linters-settings: - dupl: - threshold: 100 + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to exclude struct packages and names from check. + # Default: [] + exclude: + # std libs + - "^net/http.Client$" + - "^net/http.Cookie$" + - "^net/http.Request$" + - "^net/http.Response$" + - "^net/http.Server$" + - "^net/http.Transport$" + - "^net/url.URL$" + - "^os/exec.Cmd$" + - "^reflect.StructField$" + # public libs + - "^github.com/Shopify/sarama.Config$" + - "^github.com/Shopify/sarama.ProducerMessage$" + - "^github.com/mitchellh/mapstructure.DecoderConfig$" + - "^github.com/prometheus/client_golang/.+Opts$" + - "^github.com/spf13/cobra.Command$" + - "^github.com/spf13/cobra.CompletionOptions$" + - "^github.com/stretchr/testify/mock.Mock$" + - "^github.com/testcontainers/testcontainers-go.+Request$" + - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" + - "^golang.org/x/tools/go/analysis.Analyzer$" + - "^google.golang.org/protobuf/.+Options$" + - "^gopkg.in/yaml.v3.Node$" + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 statements: 50 - gci: - local-prefixes: github.com/golangci/golangci-lint - goconst: - min-len: 2 - min-occurrences: 2 + + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - disabled-checks: - - dupImport # https://github.com/go-critic/go-critic/issues/845 - - ifElseChain - - octalLiteral - - whyNoLint - - wrapperFunc - gocyclo: - min-complexity: 15 - goimports: - local-prefixes: github.com/golangci/golangci-lint - golint: - min-confidence: 0 - gomnd: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. settings: - mnd: - # don't include the "operation" and "assign" - checks: argument,case,condition,return + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + gomnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - os.Chmod + - os.Mkdir + - os.MkdirAll + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets + - prometheus.ExponentialBucketsRange + - prometheus.LinearBuckets + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/google/uuid + reason: "gofrs' package is not go module" + govet: - check-shadowing: true - lll: - line-length: 140 - maligned: - suggest-new: true - misspell: - locale: US + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + nolintlint: - allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) - allow-unused: false # report any unused nolint directives - require-explanation: false # don't require an explanation for nolint directives - require-specific: false # don't require nolint directives to be specific about which linter is being skipped + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx + + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + linters: - # please, do not use `enable-all`: it's deprecated and will be removed soon. - # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint - disable: - - gomnd + disable-all: true enable: - - bodyclose - - deadcode - - depguard - - dogsled - - dupl - - errcheck - - exhaustive - - exportloopref - - forbidigo - - funlen - - gochecknoglobals - - gochecknoinits - - goconst - # - gocritic - - gocyclo - - gofmt - - goimports - - goprintffuncname - - gosec - - gosimple - - govet - - ineffassign - - lll - - misspell - - nakedret - - noctx - - nolintlint - - revive - - rowserrcheck - - staticcheck - - structcheck - - stylecheck - - typecheck - - unconvert - - unparam - - unused - - varcheck - - whitespace - -# issues: -# # Excluding configuration per-path, per-linter, per-text and per-source -# exclude-rules: -# - path: _test\.go -# linters: -# - gomnd - -# # https://github.com/go-critic/go-critic/issues/926 -# - linters: -# - gocritic -# text: "unnecessaryDefer:" - -# run: -# skip-dirs: -# - test/testdata_etc -# - internal/cache -# - internal/renameio -# - internal/robustio - -# # golangci.com configuration -# # https://github.com/golangci/golangci/wiki/Configuration -# service: -# golangci-lint-version: 1.23.x # use the fixed version to not introduce new linters unexpectedly -# prepare: -# - echo "here I can run custom commands, but no preparation needed for this repo" \ No newline at end of file + ## enabled by default + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - gosimple # specializes in simplifying a code + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # detects when assignments to existing variables are not used + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - typecheck # like the front-end of a Go compiler, parses and type-checks Go code + - unused # checks for unused constants, variables, functions and types + ## disabled by default + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - cyclop # checks function and package cyclomatic complexity + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - execinquery # checks query string in Query function which reads your Go src files and warning it finds + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # forbids identifiers + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + - gochecknoglobals # checks that no global variables exist + - gochecknoinits # checks that no init functions are present in Go code + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + # - godot # checks if comments end in a period + - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt + # - gomnd # detects magic numbers + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - lll # reports long lines + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - stylecheck # is a replacement for golint + - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 + - testableexamples # checks if examples are testable (have an expected output) + - testpackage # makes you use a separate _test package + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + + ## you may want to enable + #- decorder # checks declaration order and count of types, constants, variables and functions + #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + #- gci # controls golang package import order and makes it always deterministic + #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + #- godox # detects FIXME, TODO and other comment keywords + #- goheader # checks is file header matches to pattern + #- interfacebloat # checks the number of methods inside an interface + #- ireturn # accept interfaces, return concrete types + #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # checks that errors returned from external packages are wrapped + + ## disabled + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- goerr113 # [too strict] checks the errors handling expressions + #- gofmt # [replaced by goimports] checks whether code was gofmt-ed + #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines + + ## deprecated + #- deadcode # [deprecated, replaced by unused] finds unused code + #- exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized + #- golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + #- ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible + #- interfacer # [deprecated] suggests narrower interface types + #- maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted + #- nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name + #- scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs + #- structcheck # [deprecated, replaced by unused] finds unused struct fields + #- varcheck # [deprecated, replaced by unused] finds unused global variables and constants + + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + + exclude-rules: + - source: "^//\\s*go:generate\\s" + linters: [ lll ] + - source: "(noinspection|TODO)" + linters: [ godot ] + - source: "//noinspection" + linters: [ gocritic ] + - source: "^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {" + linters: [ errorlint ] + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - gocognit + - goconst + - gosec + - noctx + - wrapcheck + - path: "version.go" + linters: + - gochecknoglobals diff --git a/.goreleaser.yml b/.goreleaser.yml index c5d0775..6e0556f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,15 +1,8 @@ before: hooks: - - task test + - go test ./... builds: - skip: true -archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 checksum: name_template: 'checksums.txt' snapshot: @@ -22,4 +15,4 @@ changelog: - '^test:' - '^Merge pull request' announce: - skip: "true" + skip: true diff --git a/Taskfile-build.yml b/Taskfile-build.yml new file mode 100644 index 0000000..5707ec6 --- /dev/null +++ b/Taskfile-build.yml @@ -0,0 +1,124 @@ +version: '3' + +vars: + + DIST: + "{{.ROOT_DIR}}/dist" + +tasks: + + dist: + desc: Create dist directory + internal: true + cmds: + - mkdir -p {{.DIST}} + + test: + desc: Run unit tests + deps: [dist] + cmds: + - go test {{.CLI_ARGS}} -timeout 20s -race -count=1 -covermode atomic -coverprofile {{.DIST}}/coverage.txt ./... + + test:fuzz: + desc: Run fuzz tests + cmds: + - go test -fuzz=Fuzz{{.CLI_ARGS}} -fuzztime 10s + + test:bench: + desc: Run benchmark tests + cmds: + - go test -v -shuffle=on -run=- -bench=. -benchtime=1x ./... + + test:integration: + desc: Run integration tests + deps: [build] + cmds: + - go test {{.CLI_ARGS}} --tags=integration -race -count=1 -covermode atomic ./... + + cover: + desc: Visualize test coverage + deps: [test] + cmds: + - go tool cover -html={{.DIST}}/coverage.txt + + clean: + desc: Remove build artifacts + cmds: + - rm -rf {{.DIST}} + + build:all: + desc: Build all artifacts + cmds: + - task: generate + - task: build + + build: + desc: Build all binaries + deps: [dist] + vars: + cmds: + "{{ .ROOT_DIR }}/cmd" + binaries: + sh: if [[ -d {{ .cmds }} ]]; then fd -t d --max-depth 1 . {{ .cmds }} -x echo {/}; fi + cmds: + - | + + {{- if .binaries -}} + {{ range ( .binaries | trim | splitLines ) }} + go build -o {{ $.DIST }}/{{ . }} {{ $.cmds }}/{{ . }}/*.go + {{- end -}} + {{- end -}} + + generate: + desc: Run go generate + cmds: + - go generate ./... + + lint: + desc: Runs golint + cmds: + - golangci-lint -v run + - typos + + snapshot: + desc: Build a snapshot + deps: [test, lint] + cmds: + - goreleaser --debug release --snapshot --clean + + scc: + desc: Run scc (https://github.com/boyter/scc) + cmds: + - scc -i go {{.ROOT_DIR}} + + goimports: + desc: Run goimports + vars: + module: + sh: go mod edit -json | jq -r .Module.Path + cmds: + - goimports -w -local "{{.module}}/" . + + docs:build: + desc: Statically generate the documentation + deps: [dist] + cmds: + - "{{.ROOT_DIR}}/env/bin/mkdocs build" + + docs:serve: + desc: Serve the documentation + deps: [docs:generate] + cmds: + - "{{.ROOT_DIR}}/env/bin/mkdocs serve" + + gitleaks: + desc: Run gitleaks + deps: [dist] + cmds: + - gitleaks detect --no-banner --baseline-path {{.ROOT_DIR}}/.gitleaks-baseline.json --report-path {{.DIST}}/gitleaks-report.json + + gitleaks:baseline: + desc: Run gitleaks + deps: [dist] + cmds: + - gitleaks detect --no-banner --report-path {{.ROOT_DIR}}/.gitleaks-baseline.json diff --git a/Taskfile.yml b/Taskfile.yml index 2936f17..2214ba5 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,41 +1,16 @@ -# https://taskfile.dev - version: '3' -vars: - CWD: - sh: git rev-parse --show-toplevel - DIST: - "{{.CWD}}/dist" +includes: + build: ./Taskfile-build.yml + +dotenv: [".clean.env"] tasks: default: cmds: - - task: test + - task -l test: - desc: Run tests - cmds: - - mkdir -p {{.DIST}} - - go test {{.CLI_ARGS}} -count=1 -race -covermode atomic -coverprofile {{.DIST}}/coverage.txt ./... - - cover: - desc: Visualize test coverage - deps: [test] - cmds: - - go tool cover -html={{.DIST}}/coverage.txt - - clean: - desc: Remove artifacts - cmds: - - rm -rf {{.DIST}} - - snapshot: - desc: Build a snapshot - cmds: - - goreleaser release --snapshot --rm-dist - - lint: - desc: Runs lint + desc: Run all tests cmds: - - golangci-lint -v run + - task: build:test diff --git a/album.go b/album.go index b248fb7..3fd1715 100644 --- a/album.go +++ b/album.go @@ -26,7 +26,9 @@ func (s *AlbumService) album(req *http.Request) (*Album, error) { func (s *AlbumService) expand(album *Album, expansions map[string]*json.RawMessage) (*Album, error) { if album.URIs.User != nil { if val, ok := expansions[album.URIs.User.URI]; ok { - res := struct{ User *User }{} + res := struct { + User *User `json:"User"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -35,7 +37,9 @@ func (s *AlbumService) expand(album *Album, expansions map[string]*json.RawMessa } if album.URIs.HighlightImage != nil { if val, ok := expansions[album.URIs.HighlightImage.URI]; ok { - res := struct{ Image *Image }{} + res := struct { + Image *Image `json:"Image"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -44,7 +48,9 @@ func (s *AlbumService) expand(album *Album, expansions map[string]*json.RawMessa } if album.URIs.Node != nil { if val, ok := expansions[album.URIs.Node.URI]; ok { - res := struct{ Node *Node }{} + res := struct { + Node *Node `json:"Node"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -71,7 +77,7 @@ func (s *AlbumService) albums(req *http.Request) ([]*Album, *Pages, error) { return nil, nil, err } for i := range res.Response.Album { - if _, err := s.expand(res.Response.Album[i], res.Expansions); err != nil { + if _, err = s.expand(res.Response.Album[i], res.Expansions); err != nil { return nil, nil, err } } @@ -112,7 +118,8 @@ func (s *AlbumService) SearchIter(ctx context.Context, iter AlbumIterFunc, optio } // Patch updates the metadata for `albumKey` -func (s *AlbumService) Patch(ctx context.Context, albumKey string, data map[string]interface{}, options ...APIOption) (*Album, error) { +func (s *AlbumService) Patch( + ctx context.Context, albumKey string, data map[string]interface{}, options ...APIOption) (*Album, error) { uri := fmt.Sprintf("album/%s", albumKey) body, err := json.Marshal(data) if err != nil { diff --git a/album_test.go b/album_test.go index 5fa15e6..53567b2 100644 --- a/album_test.go +++ b/album_test.go @@ -69,6 +69,7 @@ func TestAlbum(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { + t.Parallel() mux := http.NewServeMux() mux.HandleFunc("/album/WJvpCp", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, tt.filename) @@ -177,7 +178,7 @@ func TestAlbumSearchIter(t *testing.T) { }, // album iteration with error func(mg *smugmug.Client) error { - err := mg.Album.AlbumsIter(context.TODO(), "foobar", func(album *smugmug.Album) (bool, error) { + err := mg.Album.AlbumsIter(context.TODO(), "foobar", func(_ *smugmug.Album) (bool, error) { return false, errors.New("dummy error") }, smugmug.WithSearch("", "Marmot")) a.Error(err) @@ -186,7 +187,7 @@ func TestAlbumSearchIter(t *testing.T) { // album iteration with no error but no continue func(mg *smugmug.Client) error { var n int - err := mg.Album.AlbumsIter(context.TODO(), "foobar", func(album *smugmug.Album) (bool, error) { + err := mg.Album.AlbumsIter(context.TODO(), "foobar", func(_ *smugmug.Album) (bool, error) { n++ return false, nil }, smugmug.WithSearch("", "Marmot")) @@ -200,6 +201,7 @@ func TestAlbumSearchIter(t *testing.T) { f := tests[i] t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { var j int + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var fn string switch j { diff --git a/go.mod b/go.mod index 23bc7bd..60715eb 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,21 @@ module github.com/bzimmer/smugmug -go 1.18 +go 1.22.3 require ( - github.com/bzimmer/httpwares v0.1.0 + github.com/bzimmer/httpwares v0.1.3 github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 - github.com/spf13/afero v1.8.2 + github.com/spf13/afero v1.11.0 github.com/stretchr/testify v1.7.0 - golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 - golang.org/x/text v0.3.7 + golang.org/x/sync v0.7.0 + golang.org/x/text v0.15.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect + golang.org/x/time v0.5.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e // indirect ) diff --git a/go.sum b/go.sum index 6dab281..a803e5f 100644 --- a/go.sum +++ b/go.sum @@ -1,455 +1,34 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/bzimmer/httpwares v0.1.0 h1:ZaMJ51JXKNqJRsx1vtgQGVa7Qz3yZJOzvyhGrQYEPTk= -github.com/bzimmer/httpwares v0.1.0/go.mod h1:d7haVrcuMW9I+4Ha/V54JtyqGbu0raahd0NqqQSr2p0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/bzimmer/httpwares v0.1.3 h1:Haw1fGBRW51iv7O2NIkIZyuUt3XLZZG+ePd6NEwbD5Q= +github.com/bzimmer/httpwares v0.1.3/go.mod h1:8pi184rxXR7Pbn7cNL8uMPeqYA8+DbQSl4oOQE5q4Vk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM= github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -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-20190605123033-f99c8df09eb5/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/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= -golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w= -golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e h1:3i3ny04XV6HbZ2N1oIBw1UBYATHAOpo4tfTF83JM3Z0= +gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/image.go b/image.go index 319385e..29c407f 100644 --- a/image.go +++ b/image.go @@ -4,12 +4,15 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/http" ) // @todo(bzimmer) add image search +var errNoImage = errors.New("no image") + // ImageService is the API for image endpoints type ImageService service @@ -32,8 +35,10 @@ func (s *ImageService) images(req *http.Request) ([]*Image, *Pages, error) { return nil, nil, err } for i := range res.Response.Images { - if _, err := s.expand(res.Response.Images[i], res.Expansions); err != nil { - return nil, nil, err + if _, err = s.expand(res.Response.Images[i], res.Expansions); err != nil { + if !errors.Is(err, errNoImage) { + return nil, nil, err + } } } return res.Response.Images, res.Response.Pages, nil @@ -42,11 +47,13 @@ func (s *ImageService) images(req *http.Request) ([]*Image, *Pages, error) { func (s *ImageService) expand(image *Image, expansions map[string]*json.RawMessage) (*Image, error) { // a delete request will result in no image in the response if image == nil { - return nil, nil + return nil, errNoImage } if image.URIs.ImageSizeDetails != nil { if val, ok := expansions[image.URIs.ImageSizeDetails.URI]; ok { - res := struct{ ImageSizeDetails *ImageSizeDetails }{} + res := struct { + ImageSizeDetails *ImageSizeDetails `json:"ImageSizeDetails"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -56,7 +63,9 @@ func (s *ImageService) expand(image *Image, expansions map[string]*json.RawMessa // Album exists when expanding an image by the album key (eg HighlightImage) if image.URIs.Album != nil { if val, ok := expansions[image.URIs.Album.URI]; ok { - res := struct{ Album *Album }{} + res := struct { + Album *Album `json:"Album"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -67,7 +76,9 @@ func (s *ImageService) expand(image *Image, expansions map[string]*json.RawMessa // ImageAlbum exists when querying an image directly if image.URIs.ImageAlbum != nil { if val, ok := expansions[image.URIs.ImageAlbum.URI]; ok { - res := struct{ Album *Album }{} + res := struct { + Album *Album `json:"Album"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -90,7 +101,8 @@ func (s *ImageService) Image(ctx context.Context, imageKey string, options ...AP // Patch updates the metadata for `imageKey` // The image is not updated; to update the image use the `Upload` service -func (s *ImageService) Patch(ctx context.Context, imageKey string, data map[string]interface{}, options ...APIOption) (*Image, error) { +func (s *ImageService) Patch( + ctx context.Context, imageKey string, data map[string]interface{}, options ...APIOption) (*Image, error) { uri := fmt.Sprintf("image/%s", imageKey) body, err := json.Marshal(data) if err != nil { @@ -111,11 +123,15 @@ func (s *ImageService) Delete(ctx context.Context, albumKey, imageKey string, op return false, err } _, err = s.image(req) - return err == nil, err + if err == nil || errors.Is(err, errNoImage) { + return true, nil + } + return false, err } // Images returns a single page of image results for the album -func (s *ImageService) Images(ctx context.Context, albumKey string, options ...APIOption) ([]*Image, *Pages, error) { +func (s *ImageService) Images( + ctx context.Context, albumKey string, options ...APIOption) ([]*Image, *Pages, error) { uri := fmt.Sprintf("album/%s!images", albumKey) req, err := s.client.newRequest(ctx, uri, options) if err != nil { @@ -125,7 +141,8 @@ func (s *ImageService) Images(ctx context.Context, albumKey string, options ...A } // ImagesIter iterates all images in the album -func (s *ImageService) ImagesIter(ctx context.Context, albumKey string, iter ImageIterFunc, options ...APIOption) error { +func (s *ImageService) ImagesIter( + ctx context.Context, albumKey string, iter ImageIterFunc, options ...APIOption) error { return iterate(ctx, func(ctx context.Context, options ...APIOption) ([]*Image, *Pages, error) { return s.Images(ctx, albumKey, options...) }, iter, options...) diff --git a/image_test.go b/image_test.go index e34cfb0..6f74bf4 100644 --- a/image_test.go +++ b/image_test.go @@ -13,7 +13,7 @@ import ( "github.com/bzimmer/smugmug" ) -func TestImage(t *testing.T) { //nolint +func TestImage(t *testing.T) { t.Parallel() a := assert.New(t) @@ -33,10 +33,11 @@ func TestImage(t *testing.T) { //nolint f: func(image *smugmug.Image, err error) { a.Error(err) a.Nil(image) - q, ok := err.(*smugmug.Fault) + var fault *smugmug.Fault + ok := errors.As(err, &fault) a.True(ok) - a.Equal(http.StatusNotFound, q.Code) - a.Equal(http.StatusText(http.StatusNotFound), q.Message) + a.Equal(http.StatusNotFound, fault.Code) + a.Equal(http.StatusText(http.StatusNotFound), fault.Message) }, }, { @@ -94,6 +95,7 @@ func TestImage(t *testing.T) { //nolint for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tt.filename == "" { w.WriteHeader(http.StatusNotFound) @@ -110,12 +112,12 @@ func TestImage(t *testing.T) { //nolint opts = append(opts, tt.options...) } - ctx := context.TODO() + var image *smugmug.Image if tt.patch != nil { - image, err := mg.Image.Patch(ctx, tt.imageKey, tt.patch, opts...) + image, err = mg.Image.Patch(context.TODO(), tt.imageKey, tt.patch, opts...) tt.f(image, err) } else { - image, err := mg.Image.Image(ctx, tt.imageKey, opts...) + image, err = mg.Image.Image(context.TODO(), tt.imageKey, opts...) tt.f(image, err) } }) @@ -178,6 +180,7 @@ func TestImages(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, tt.filename) })) @@ -216,9 +219,10 @@ func TestImagesIter(t *testing.T) { defer svr.Close() var n int - mg, err := smugmug.NewClient(smugmug.WithBaseURL(svr.URL), smugmug.WithHTTPTracing(true)) + mg, err := smugmug.NewClient(smugmug.WithBaseURL(svr.URL)) a.NoError(err) - err = mg.Image.ImagesIter(context.TODO(), "HZMsPf", func(img *smugmug.Image) (bool, error) { + a.NotNil(mg) + err = mg.Image.ImagesIter(context.TODO(), "HZMsPf", func(_ *smugmug.Image) (bool, error) { n++ return true, nil }, smugmug.WithSearch("", "Marmot")) @@ -261,6 +265,7 @@ func TestDeleteImage(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tt.filename == "" { w.WriteHeader(http.StatusNotFound) diff --git a/model.go b/model.go index 24a2a3a..abd7010 100644 --- a/model.go +++ b/model.go @@ -7,7 +7,7 @@ import ( "time" ) -type Fault struct { +type Fault struct { //nolint:errname // smugmug naming convention Code int `json:"code"` Message string `json:"message"` } diff --git a/model_test.go b/model_test.go index 659c0c4..c8ba93b 100644 --- a/model_test.go +++ b/model_test.go @@ -4,8 +4,9 @@ import ( "encoding/json" "testing" - "github.com/bzimmer/smugmug" "github.com/stretchr/testify/assert" + + "github.com/bzimmer/smugmug" ) func TestFault(t *testing.T) { @@ -22,7 +23,7 @@ func TestCoordinate(t *testing.T) { a := assert.New(t) type q struct { - C smugmug.Coordinate + C smugmug.Coordinate `json:"C"` } tests := []struct { @@ -49,14 +50,14 @@ func TestCoordinate(t *testing.T) { { name: "invalid", value: `{"C": }`, - f: func(c smugmug.Coordinate, err error) { + f: func(_ smugmug.Coordinate, err error) { a.Error(err) }, }, { name: "not a float", value: `{"C": "abc"}`, - f: func(c smugmug.Coordinate, err error) { + f: func(_ smugmug.Coordinate, err error) { a.Error(err) }, }, @@ -66,6 +67,7 @@ func TestCoordinate(t *testing.T) { test := tests[i] t.Run(test.name, func(t *testing.T) { var qq q + t.Parallel() err := json.Unmarshal([]byte(test.value), &qq) test.f(qq.C, err) }) diff --git a/node.go b/node.go index aecae2c..0380804 100644 --- a/node.go +++ b/node.go @@ -30,17 +30,19 @@ func (s *NodeService) nodes(req *http.Request) ([]*Node, *Pages, error) { return nil, nil, err } for i := range res.Response.Node { - if _, err := s.expand(res.Response.Node[i], res.Expansions); err != nil { + if _, err = s.expand(res.Response.Node[i], res.Expansions); err != nil { return nil, nil, err } } return res.Response.Node, res.Response.Pages, nil } -func (s *NodeService) expand(node *Node, expansions map[string]*json.RawMessage) (*Node, error) { +func (s *NodeService) expand(node *Node, expansions map[string]*json.RawMessage) (*Node, error) { //nolint:gocognit if node.URIs.User != nil { if val, ok := expansions[node.URIs.User.URI]; ok { - res := struct{ User *User }{} + res := struct { + User *User `json:"User"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -49,7 +51,9 @@ func (s *NodeService) expand(node *Node, expansions map[string]*json.RawMessage) } if node.URIs.HighlightImage != nil { if val, ok := expansions[node.URIs.HighlightImage.URI]; ok { - res := struct{ Image *Image }{} + res := struct { + Image *Image `json:"Image"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -58,7 +62,9 @@ func (s *NodeService) expand(node *Node, expansions map[string]*json.RawMessage) } if node.URIs.Parent != nil { if val, ok := expansions[node.URIs.Parent.URI]; ok { - res := struct{ Node *Node }{} + res := struct { + Node *Node `json:"Node"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -71,7 +77,9 @@ func (s *NodeService) expand(node *Node, expansions map[string]*json.RawMessage) case TypeAlbum: if node.URIs.Album != nil { if val, ok := expansions[node.URIs.Album.URI]; ok { - res := struct{ Album *Album }{} + res := struct { + Album *Album `json:"Album"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } @@ -107,7 +115,8 @@ func (s *NodeService) Create(ctx context.Context, parentID string, nodelet *Node } // Children returns a single page of direct children of the node (does not traverse) -func (s *NodeService) Children(ctx context.Context, nodeID string, options ...APIOption) ([]*Node, *Pages, error) { +func (s *NodeService) Children( + ctx context.Context, nodeID string, options ...APIOption) ([]*Node, *Pages, error) { uri := fmt.Sprintf("node/%s!children", nodeID) req, err := s.client.newRequest(ctx, uri, options) if err != nil { @@ -117,14 +126,16 @@ func (s *NodeService) Children(ctx context.Context, nodeID string, options ...AP } // ChildrenIter iterates all direct children of the node -func (s *NodeService) ChildrenIter(ctx context.Context, nodeID string, iter NodeIterFunc, options ...APIOption) error { +func (s *NodeService) ChildrenIter( + ctx context.Context, nodeID string, iter NodeIterFunc, options ...APIOption) error { return iterate(ctx, func(ctx context.Context, options ...APIOption) ([]*Node, *Pages, error) { return s.Children(ctx, nodeID, options...) }, iter, options...) } // Search returns a single page of search results (does not traverse) -func (s *NodeService) Search(ctx context.Context, options ...APIOption) ([]*Node, *Pages, error) { +func (s *NodeService) Search( + ctx context.Context, options ...APIOption) ([]*Node, *Pages, error) { req, err := s.client.newRequest(ctx, "node!search", options) if err != nil { return nil, nil, err @@ -133,12 +144,14 @@ func (s *NodeService) Search(ctx context.Context, options ...APIOption) ([]*Node } // SearchIter iterates all search results -func (s *NodeService) SearchIter(ctx context.Context, iter NodeIterFunc, options ...APIOption) error { +func (s *NodeService) SearchIter( + ctx context.Context, iter NodeIterFunc, options ...APIOption) error { return iterate(ctx, s.Search, iter, options...) } // Parent returns the parent node -func (s *NodeService) Parent(ctx context.Context, nodeID string, options ...APIOption) (*Node, error) { +func (s *NodeService) Parent( + ctx context.Context, nodeID string, options ...APIOption) (*Node, error) { uri := fmt.Sprintf("node/%s!parent", nodeID) req, err := s.client.newRequest(ctx, uri, options) if err != nil { @@ -148,7 +161,8 @@ func (s *NodeService) Parent(ctx context.Context, nodeID string, options ...APIO } // Parents returns a single page of parent nodes (does not traverse) -func (s *NodeService) Parents(ctx context.Context, nodeID string, options ...APIOption) ([]*Node, *Pages, error) { +func (s *NodeService) Parents( + ctx context.Context, nodeID string, options ...APIOption) ([]*Node, *Pages, error) { uri := fmt.Sprintf("node/%s!parents", nodeID) req, err := s.client.newRequest(ctx, uri, options) if err != nil { @@ -158,35 +172,38 @@ func (s *NodeService) Parents(ctx context.Context, nodeID string, options ...API } // ParentsIter iterates all parental ancestors -func (s *NodeService) ParentsIter(ctx context.Context, nodeID string, iter NodeIterFunc, options ...APIOption) error { +func (s *NodeService) ParentsIter( + ctx context.Context, nodeID string, iter NodeIterFunc, options ...APIOption) error { return iterate(ctx, func(ctx context.Context, options ...APIOption) ([]*Node, *Pages, error) { return s.Parents(ctx, nodeID, options...) }, iter, options...) } // Walk traverses all children of the node rooted at `nodeID` -func (s *NodeService) Walk(ctx context.Context, nodeID string, fn NodeIterFunc, options ...APIOption) error { +func (s *NodeService) Walk( + ctx context.Context, nodeID string, fn NodeIterFunc, options ...APIOption) error { return s.WalkN(ctx, nodeID, fn, -1, options...) } // WalkN traverses all children of the node rooted at `nodeID` to the specified depth -func (s *NodeService) WalkN(ctx context.Context, nodeID string, fn NodeIterFunc, depth int, options ...APIOption) error { +func (s *NodeService) WalkN( + ctx context.Context, nodeID string, fn NodeIterFunc, depth int, options ...APIOption) error { k := &stack{} k.push(nodeID, nil, 0) for { + var err error nid, ok := k.pop() if !ok { return nil } node := nid.node if node == nil { - var err error node, err = s.Node(ctx, nid.id, options...) if err != nil { return err } } - if ok, err := fn(node); err != nil { + if ok, err = fn(node); err != nil { return err } else if !ok { return nil @@ -196,7 +213,7 @@ func (s *NodeService) WalkN(ctx context.Context, nodeID string, fn NodeIterFunc, // ignore, no children case "Folder": if nid.depth != depth { - if err := s.ChildrenIter(ctx, nid.id, func(node *Node) (bool, error) { + if err = s.ChildrenIter(ctx, nid.id, func(node *Node) (bool, error) { k.push(node.NodeID, node, nid.depth+1) return true, nil }, options...); err != nil { diff --git a/node_test.go b/node_test.go index 1f76a8b..c5e6c01 100644 --- a/node_test.go +++ b/node_test.go @@ -12,7 +12,7 @@ import ( "github.com/bzimmer/smugmug" ) -func TestNode(t *testing.T) { //nolint +func TestNode(t *testing.T) { t.Parallel() a := assert.New(t) @@ -100,6 +100,7 @@ func TestNode(t *testing.T) { //nolint tt.name = tt.filename } t.Run(tt.name, func(t *testing.T) { + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tt.filename == "" { w.WriteHeader(http.StatusForbidden) @@ -120,7 +121,7 @@ func TestNode(t *testing.T) { //nolint } } -func TestNodes(t *testing.T) { //nolint +func TestNodes(t *testing.T) { t.Parallel() a := assert.New(t) @@ -134,7 +135,7 @@ func TestNodes(t *testing.T) { //nolint name: "search iteration for search results", f: func(mg *smugmug.Client) { var n int - err := mg.Node.SearchIter(context.TODO(), func(node *smugmug.Node) (bool, error) { + err := mg.Node.SearchIter(context.TODO(), func(_ *smugmug.Node) (bool, error) { n++ return true, nil }, smugmug.WithSearch("", "Marmot"), smugmug.WithExpansions("HighlightImage")) @@ -150,7 +151,7 @@ func TestNodes(t *testing.T) { //nolint name: "search iteration for search results fail", f: func(mg *smugmug.Client) { var n int - err := mg.Node.SearchIter(context.TODO(), func(node *smugmug.Node) (bool, error) { + err := mg.Node.SearchIter(context.TODO(), func(_ *smugmug.Node) (bool, error) { n++ return true, nil }, withError()) @@ -166,7 +167,7 @@ func TestNodes(t *testing.T) { //nolint name: "node iteration of children", f: func(mg *smugmug.Client) { var n int - err := mg.Node.ChildrenIter(context.TODO(), "zx4Fx", func(node *smugmug.Node) (bool, error) { + err := mg.Node.ChildrenIter(context.TODO(), "zx4Fx", func(_ *smugmug.Node) (bool, error) { n++ return true, nil }, smugmug.WithExpansions("HighlightImage")) @@ -182,7 +183,7 @@ func TestNodes(t *testing.T) { //nolint name: "node iteration of children fail", f: func(mg *smugmug.Client) { var n int - err := mg.Node.ChildrenIter(context.TODO(), "zx4Fx", func(node *smugmug.Node) (bool, error) { + err := mg.Node.ChildrenIter(context.TODO(), "zx4Fx", func(_ *smugmug.Node) (bool, error) { n++ return true, nil }, withError()) @@ -198,7 +199,7 @@ func TestNodes(t *testing.T) { //nolint name: "node walk iteration", f: func(mg *smugmug.Client) { var n int - err := mg.Node.Walk(context.TODO(), "zx4Fx", func(node *smugmug.Node) (bool, error) { + err := mg.Node.Walk(context.TODO(), "zx4Fx", func(_ *smugmug.Node) (bool, error) { n++ return true, nil }, smugmug.WithExpansions("HighlightImage")) @@ -233,7 +234,7 @@ func TestNodes(t *testing.T) { //nolint { name: "node walk with type `unknown`", f: func(mg *smugmug.Client) { - err := mg.Node.Walk(context.TODO(), "zx4Fx", func(node *smugmug.Node) (bool, error) { + err := mg.Node.Walk(context.TODO(), "zx4Fx", func(_ *smugmug.Node) (bool, error) { return true, nil }) a.Error(err) @@ -307,6 +308,7 @@ func TestNodes(t *testing.T) { //nolint test := tests[i] t.Run(test.name, func(t *testing.T) { var j int + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if test.status != 0 { w.WriteHeader(test.status) diff --git a/smugmug.go b/smugmug.go index 36bc2a2..dab936d 100644 --- a/smugmug.go +++ b/smugmug.go @@ -33,18 +33,6 @@ const ( var ( // albumNameRE allowable characters albumNameRE = regexp.MustCompile(`[\p{L}\d]+`) - // searchReplaceRE special characters to replace - searchReplaceRE = func() map[*regexp.Regexp]string { //nolint - re := make(map[*regexp.Regexp]string, 0) - for search, replace := range map[string]string{ - `-`: " ", - "[" + "`" + `'"` + "]": "", - } { - c := regexp.MustCompile(search) - re[c] = replace - } - return re - }() ) // provider specifies OAuth 1.0 URLs for SmugMug @@ -184,6 +172,19 @@ func WithSearch(scope, text string) APIOption { } } +// searchReplaceREs special characters to replace +func searchReplaceREs() map[*regexp.Regexp]string { + re := make(map[*regexp.Regexp]string, 0) + for search, replace := range map[string]string{ + `-`: " ", + "[" + "`" + `'"` + "]": "", + } { + c := regexp.MustCompile(search) + re[c] = replace + } + return re +} + // URLName returns `name` as a suitable URL name for a folder or album func URLName(name string, tags ...language.Tag) string { s := name @@ -192,7 +193,7 @@ func URLName(name string, tags ...language.Tag) string { tag = tags[0] } upper := cases.Upper(tag) - for search, replace := range searchReplaceRE { + for search, replace := range searchReplaceREs() { s = search.ReplaceAllString(s, replace) } t := albumNameRE.FindAllString(s, -1) @@ -219,7 +220,8 @@ func (c *Client) newRequest(ctx context.Context, uri string, options []APIOption } // newRequest constructs an http.Request for the uri applying all provided `APIOption`s -func (c *Client) newRequestWithBody(ctx context.Context, method, uri string, body io.Reader, options []APIOption) (*http.Request, error) { +func (c *Client) newRequestWithBody( + ctx context.Context, method, uri string, body io.Reader, options []APIOption) (*http.Request, error) { uri = fmt.Sprintf("%s/%s", c.baseURL, uri) switch method { case http.MethodGet, http.MethodPost, http.MethodPatch, http.MethodDelete: @@ -255,7 +257,8 @@ func iterate[T any](ctx context.Context, } n += pages.Count for _, node := range nodes { - if ok, err := f(node); err != nil { + var ok bool + if ok, err = f(node); err != nil { return err } else if !ok { return nil diff --git a/smugmug_test.go b/smugmug_test.go index 29a6bdf..658d4cd 100644 --- a/smugmug_test.go +++ b/smugmug_test.go @@ -9,15 +9,16 @@ import ( "testing" "time" - "github.com/bzimmer/smugmug" "github.com/stretchr/testify/assert" "golang.org/x/text/language" + + "github.com/bzimmer/smugmug" ) var errFail = errors.New("fail") func withError() smugmug.APIOption { - return func(v url.Values) error { + return func(_ url.Values) error { return errFail } } @@ -94,7 +95,7 @@ func TestDo(t *testing.T) { defer svr.Close() sleeper := func(dur time.Duration) smugmug.APIOption { - return func(v url.Values) error { + return func(_ url.Values) error { time.Sleep(dur) return nil } @@ -115,7 +116,10 @@ func TestDo(t *testing.T) { user, err = client.User.AuthUser(ctx) a.Nil(user) a.Error(err) - a.Equal("boom", err.(*url.Error).Unwrap().Error()) + var q *url.Error + ok := errors.As(err, &q) + a.True(ok) + a.Equal("boom", q.Unwrap().Error()) ctx, cancel := context.WithTimeout(ctx, time.Millisecond*5) defer cancel() @@ -135,17 +139,41 @@ func TestOAuthClient(t *testing.T) { func TestURLName(t *testing.T) { t.Parallel() - a := assert.New(t) - a.Equal("", smugmug.URLName("")) - a.Equal("Foo-Bar", smugmug.URLName("foo bar")) - a.Equal("Foo-Bar", smugmug.URLName("foo bar ")) - a.Equal("Foo-Bar", smugmug.URLName("Foo bar")) - a.Equal("Foo-1-Bar", smugmug.URLName("foo & 1 bar")) - a.Equal("Someones-Something", smugmug.URLName("Someone's something")) - a.Equal("Someones-Something", smugmug.URLName(`Someone"s something`)) - a.Equal("2022-03-04-Zürich", smugmug.URLName("2022-03-04 Zürich")) - a.Equal("2021-01-01-Foo-1-Bar", smugmug.URLName("2021-01-01 foo & 1 bar")) - a.Equal("Foo-1-Bar", smugmug.URLName("foo & 1 bar", language.English)) - a.Equal("2009-10-11-BIFD-Pancake-Breakfast", smugmug.URLName("2009-10-11 BIFD Pancake Breakfast", language.English)) - a.Equal("2009-10-11-BIFD-Pancake-Breakfast", smugmug.URLName("2009-10-11 BIFD `Pancake Breakfast`", language.English)) + + tests := []struct { + url string + album string + lang language.Tag + }{ + {url: "", album: ""}, + {url: "Foo-Bar", album: "foo bar"}, + {url: "Foo-Bar", album: "foo bar "}, + {url: "Foo-Bar", album: "Foo bar"}, + {url: "Foo-1-Bar", album: "foo & 1 bar"}, + {url: "Someones-Something", album: "Someone's something"}, + {url: "Someones-Something", album: `Someone"s something`}, + {url: "2022-03-04-Zürich", album: "2022-03-04 Zürich"}, + {url: "2022-03-04-Zürich", album: "2022-03-04 Zürich", lang: language.German}, + {url: "2022-03-04-Zürich", album: "2022-03-04-Zürich-", lang: language.German}, + {url: "2022-03-04-Zürich", album: "2022-03-04 Zürich & ___", lang: language.German}, + {url: "2022-03-04-Zürich", album: "2022-03-04 Zürich & ___"}, + {url: "2021-01-01-Foo-1-Bar", album: "2021-01-01 foo & 1 bar"}, + {url: "Foo-1-Bar", album: "foo & 1 bar", lang: language.English}, + {url: "2009-10-11-BIFD-Pancake-Breakfast", album: "2009-10-11 BIFD Pancake Breakfast", lang: language.English}, + {url: "2009-10-11-BIFD-Pancake-Breakfast", album: "2009-10-11 BIFD `Pancake Breakfast`", lang: language.English}, + } + + for i := range tests { + test := tests[i] + t.Run(test.url+test.album, func(t *testing.T) { + t.Parallel() + a := assert.New(t) + switch test.lang.IsRoot() { + case true: + a.Equal(test.url, smugmug.URLName(test.album)) + case false: + a.Equal(test.url, smugmug.URLName(test.album, test.lang)) + } + }) + } } diff --git a/upload.go b/upload.go index 2cbe77e..869c545 100644 --- a/upload.go +++ b/upload.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strconv" "time" @@ -26,7 +27,9 @@ func (s *UploadService) Upload(ctx context.Context, up *Uploadable) (*Upload, er return nil, errors.New("missing albumKey") } - uri := fmt.Sprintf("%s/%s", s.client.uploadURL, up.Name) + // https://api.smugmug.com/services/api/?method=upload + + uri := fmt.Sprintf("%s/photo.jpg", s.client.uploadURL) req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, up.Reader) if err != nil { return nil, err @@ -40,6 +43,7 @@ func (s *UploadService) Upload(ctx context.Context, up *Uploadable) (*Upload, er "X-Smug-Version": "v2", "X-Smug-AlbumUri": "/api/v2/album/" + up.AlbumKey, "X-Smug-ResponseType": "JSON", + "X-Smug-FileName": url.PathEscape(up.Name), } if up.Replaces != "" { @@ -54,13 +58,13 @@ func (s *UploadService) Upload(ctx context.Context, up *Uploadable) (*Upload, er ur := &uploadResponse{} err = s.client.do(req, ur) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to upload file `%s` with error %w", up.Name, err) } return ur.Upload(up, time.Since(t)), nil } // Uploads consumes Uploadables from uploadables, uploads them to SmugMug returning status in Upload instances -func (s *UploadService) Uploads(ctx context.Context, uploadables Uploadables) (uploads <-chan *Upload, errs <-chan error) { +func (s *UploadService) Uploads(ctx context.Context, uploadables Uploadables) (<-chan *Upload, <-chan error) { updc := make(chan *Upload) errc := make(chan error, 1) grp, ctx := errgroup.WithContext(ctx) @@ -89,7 +93,8 @@ func (s *UploadService) Uploads(ctx context.Context, uploadables Uploadables) (u return updc, errc } -func (s *UploadService) uploads(ctx context.Context, uploadablesc <-chan *Uploadable, updc chan<- *Upload) func() error { +func (s *UploadService) uploads( + ctx context.Context, uploadablesc <-chan *Uploadable, updc chan<- *Upload) func() error { return func() error { for { select { diff --git a/upload_test.go b/upload_test.go index 4aab7eb..eac6de4 100644 --- a/upload_test.go +++ b/upload_test.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "net/http/httptest" + "net/url" "testing" "time" @@ -17,23 +18,57 @@ func TestUpload(t *testing.T) { t.Parallel() a := assert.New(t) - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "testdata/upload_CVvj69L.json") - })) - defer svr.Close() - - mg, err := smugmug.NewClient(smugmug.WithBaseURL(svr.URL), smugmug.WithUploadURL(svr.URL)) - a.NoError(err) - - up := &smugmug.Uploadable{Name: "DSC33556.jpg"} - upload, err := mg.Upload.Upload(context.TODO(), up) - a.Error(err) - a.Nil(upload) + tests := []struct { + name string + album string + filename string + err string + }{ + { + name: "no album", + filename: "DSC33556.jpg", + err: "missing albumKey", + }, + { + name: "with album", + album: "7dFHSm", + filename: "DSC33556.jpg", + }, + { + name: "filename with spaces", + album: "7dFHSm", + filename: "This is a name with spaces.jpg", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + mux := http.NewServeMux() + mux.HandleFunc("/photo.jpg", func(w http.ResponseWriter, r *http.Request) { + a.Equal(http.MethodPut, r.Method) + a.Equal(url.PathEscape(tt.filename), r.Header.Get("X-Smug-FileName")) + http.ServeFile(w, r, "testdata/upload_CVvj69L.json") + }) + svr := httptest.NewServer(mux) + defer svr.Close() + mg, err := smugmug.NewClient(smugmug.WithBaseURL(svr.URL), smugmug.WithUploadURL(svr.URL)) + a.NoError(err) - up.AlbumKey = "7dFHSm" - upload, err = mg.Upload.Upload(context.TODO(), up) - a.NoError(err) - a.NotNil(upload) + up := &smugmug.Uploadable{ + Name: tt.filename, + AlbumKey: tt.album, + } + upload, err := mg.Upload.Upload(context.TODO(), up) + if tt.err != "" { + a.Error(err) + a.Contains(err.Error(), tt.err) + } else { + a.NoError(err) + a.NotNil(upload) + } + }) + } } type testUploadables struct { @@ -41,7 +76,7 @@ type testUploadables struct { sleep time.Duration } -func (t *testUploadables) Uploadables(ctx context.Context) (uploadables <-chan *smugmug.Uploadable, errs <-chan error) { +func (t *testUploadables) Uploadables(_ context.Context) (<-chan *smugmug.Uploadable, <-chan error) { errc := make(chan error) uploadablesc := make(chan *smugmug.Uploadable, 1) @@ -101,6 +136,7 @@ func TestUploads(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, tt.filename) })) diff --git a/uploadable/filesystem/uploadable.go b/uploadable/filesystem/uploadable.go index 4443789..66574de 100644 --- a/uploadable/filesystem/uploadable.go +++ b/uploadable/filesystem/uploadable.go @@ -2,7 +2,7 @@ package filesystem import ( "bytes" - "crypto/md5" //nolint:gosec + "crypto/md5" //nolint:gosec // used to match md5 at smugmug "errors" "fmt" "io" @@ -18,7 +18,7 @@ import ( type PreFunc func(fs afero.Fs, filename string) (bool, error) // UseFunc is called after the file is opened but before being uploaded -type UseFunc func(up *smugmug.Uploadable) (*smugmug.Uploadable, error) +type UseFunc func(up *smugmug.Uploadable) error // FsUploadable creates Uploadable instances type FsUploadable interface { @@ -30,6 +30,9 @@ type FsUploadable interface { Use(...UseFunc) } +// ErrSkip is used to skip an Uploadable +var ErrSkip = errors.New("skip") + // Extensions represents the valid list of extensions to upload func Extensions(extension ...string) PreFunc { return func(_ afero.Fs, filename string) (bool, error) { @@ -46,33 +49,33 @@ func Extensions(extension ...string) PreFunc { // Skip checks if the Uploadable is already uploaded by comparing MD5s // If `force` is true the Uploadable will be always be uploaded func Skip(force bool, images map[string]*smugmug.Image) UseFunc { - return func(up *smugmug.Uploadable) (*smugmug.Uploadable, error) { + return func(up *smugmug.Uploadable) error { if force { - return up, nil + return nil } img, ok := images[up.Name] if !ok { - return up, nil + return nil } if up.MD5 == img.ArchivedMD5 { - return nil, nil + return ErrSkip } - return up, nil + return nil } } // Replace will update the Uploadable's URI if the image was already uploaded // If `update` is false the URI will not be updated func Replace(update bool, images map[string]*smugmug.Image) UseFunc { - return func(up *smugmug.Uploadable) (*smugmug.Uploadable, error) { + return func(up *smugmug.Uploadable) error { if !update { - return up, nil + return nil } img, ok := images[up.Name] if ok { up.Replaces = img.URIs.Image.URI } - return up, nil + return nil } } @@ -105,7 +108,7 @@ func (p *fsUploadable) Uploadable(fs afero.Fs, filename string) (*smugmug.Upload return nil, err } if !ok { - return nil, nil + return nil, ErrSkip } } @@ -116,13 +119,10 @@ func (p *fsUploadable) Uploadable(fs afero.Fs, filename string) (*smugmug.Upload up.AlbumKey = p.albumKey for i := range p.use { - up, err = p.use[i](up) + err = p.use[i](up) if err != nil { return nil, err } - if up == nil { - return nil, nil - } } return up, nil @@ -144,7 +144,7 @@ func (p *fsUploadable) open(fs afero.Fs, path string) (*smugmug.Uploadable, erro return &smugmug.Uploadable{ Name: filepath.Base(path), Size: size, - MD5: fmt.Sprintf("%x", md5.Sum(buf.Bytes())), //nolint:gosec + MD5: fmt.Sprintf("%x", md5.Sum(buf.Bytes())), //nolint:gosec // required for smugmug Reader: &buf, }, nil } diff --git a/uploadable/filesystem/uploadable_test.go b/uploadable/filesystem/uploadable_test.go index d6a9e57..ba3b901 100644 --- a/uploadable/filesystem/uploadable_test.go +++ b/uploadable/filesystem/uploadable_test.go @@ -1,6 +1,7 @@ package filesystem_test import ( + "errors" "testing" "github.com/spf13/afero" @@ -26,16 +27,23 @@ func TestUploadable(t *testing.T) { tests := []struct { filename string replace string - empty bool + skip bool pre filesystem.PreFunc use filesystem.UseFunc uri string }{ - {filename: ".DS_Info", empty: true, pre: filesystem.Extensions(".jpg")}, - {filename: "DSC1234.jpg", empty: false}, + { + filename: ".DS_Info", + skip: true, + pre: filesystem.Extensions(".jpg"), + }, + { + filename: "DSC1234.jpg", + skip: false, + }, { filename: "DSC12345.jpg", - empty: true, + skip: true, use: filesystem.Skip(false, map[string]*smugmug.Image{ "DSC12345.jpg": { ArchivedMD5: "54b0c58c7ce9f2a8b551351102ee0938", @@ -44,7 +52,7 @@ func TestUploadable(t *testing.T) { }, { filename: "DSC12345.jpg", - empty: false, + skip: false, uri: "foo", use: filesystem.Replace(true, map[string]*smugmug.Image{ "DSC12345.jpg": { @@ -58,7 +66,7 @@ func TestUploadable(t *testing.T) { }, { filename: "DSC12345.jpg", - empty: false, + skip: false, uri: "", use: filesystem.Replace(false, map[string]*smugmug.Image{ "DSC12345.jpg": { @@ -76,6 +84,7 @@ func TestUploadable(t *testing.T) { for i := range tests { test := tests[i] t.Run(test.filename, func(t *testing.T) { + t.Parallel() fs := new(afero.MemMapFs) a.NoError(afero.WriteFile(fs, test.filename, []byte("this is a test"), 0644)) fsup, err := filesystem.NewFsUploadable(albumKey) @@ -88,9 +97,10 @@ func TestUploadable(t *testing.T) { } up, err := fsup.Uploadable(fs, test.filename) - a.NoError(err) - switch test.empty { + switch test.skip { case true: + a.Error(err) + a.True(errors.Is(err, filesystem.ErrSkip)) a.Nil(up) case false: a.NotNil(up) diff --git a/uploadable/filesystem/uploadables.go b/uploadable/filesystem/uploadables.go index 3207e4f..0adf735 100644 --- a/uploadable/filesystem/uploadables.go +++ b/uploadable/filesystem/uploadables.go @@ -2,6 +2,7 @@ package filesystem import ( "context" + "errors" "io/fs" "github.com/spf13/afero" @@ -17,12 +18,12 @@ type fsUploadables struct { } // NewFsUploadables returns a new instance of an Uploadables which creates Uploadable instances -// from files on the filesystem +// from files on the filesystem func NewFsUploadables(afs afero.Fs, filenames []string, uploadable FsUploadable) smugmug.Uploadables { return &fsUploadables{fs: afs, filenames: filenames, uploadable: uploadable} } -func (p *fsUploadables) Uploadables(ctx context.Context) (uploadables <-chan *smugmug.Uploadable, errs <-chan error) { +func (p *fsUploadables) Uploadables(ctx context.Context) (<-chan *smugmug.Uploadable, <-chan error) { //nolint:gocognit grp, ctx := errgroup.WithContext(ctx) errc := make(chan error, 1) @@ -48,11 +49,11 @@ func (p *fsUploadables) Uploadables(ctx context.Context) (uploadables <-chan *sm } up, err := p.uploadable.Uploadable(p.fs, filename) if err != nil { + if errors.Is(err, ErrSkip) { + continue + } return err } - if up == nil { - continue - } select { case <-ctx.Done(): return ctx.Err() @@ -73,7 +74,7 @@ func (p *fsUploadables) Uploadables(ctx context.Context) (uploadables <-chan *sm return uploadablesc, errc } -func (p *fsUploadables) walk(ctx context.Context) (names <-chan string, errs <-chan error) { +func (p *fsUploadables) walk(ctx context.Context) (<-chan string, <-chan error) { errc := make(chan error, 1) filenamesc := make(chan string) go func() { diff --git a/uploadable/filesystem/uploadables_test.go b/uploadable/filesystem/uploadables_test.go index 0442657..d0c9c78 100644 --- a/uploadable/filesystem/uploadables_test.go +++ b/uploadable/filesystem/uploadables_test.go @@ -14,14 +14,14 @@ import ( type testFsUploadable struct{} -func (t *testFsUploadable) Uploadable(fsys afero.Fs, filename string) (*smugmug.Uploadable, error) { +func (t *testFsUploadable) Uploadable(_ afero.Fs, filename string) (*smugmug.Uploadable, error) { switch filename { case "DSC4321.jpg": return &smugmug.Uploadable{ Name: "DSC4321.jpg", }, nil case "Readme.md": - return nil, nil + return nil, filesystem.ErrSkip default: return nil, errors.New("missing") } diff --git a/user.go b/user.go index 81c1b10..4f595b4 100644 --- a/user.go +++ b/user.go @@ -10,7 +10,9 @@ type UserService service func (s *UserService) expand(user *User, expansions map[string]*json.RawMessage) (*User, error) { if val, ok := expansions[user.URIs.Node.URI]; ok { - res := struct{ Node *Node }{} + res := struct { + Node *Node `json:"Node"` + }{} if err := json.Unmarshal(*val, &res); err != nil { return nil, err } diff --git a/user_test.go b/user_test.go index 85adbe3..3d3fe81 100644 --- a/user_test.go +++ b/user_test.go @@ -52,6 +52,7 @@ func TestAuthUser(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { + t.Parallel() svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { a.Equal("/!authuser", r.URL.Path) http.ServeFile(w, r, tt.filename)