From 311603d0c0b04d451e9fb8e5e8335dca8425e2c4 Mon Sep 17 00:00:00 2001 From: mo khan Date: Thu, 31 Jul 2025 16:09:12 -0600 Subject: Connect to postgresql --- .env | 2 + .gitignore | 4 + Dockerfile | 7 +- Makefile | 39 +- Procfile.template | 5 + app/app.go | 2 + app/controllers/health/controller.go | 43 + app/controllers/sparkles/controller.go | 17 - app/db/connection.go | 54 + app/db/url.go | 19 + app/init.go | 11 + bin/postgres | 8 + bin/tool | 9 + etc/postgres/postgresql.conf | 25 + go.mod | 9 +- go.sum | 14 +- mise.toml | 1 + .../github.com/deckarep/golang-set/v2/.gitignore | 23 - vendor/github.com/deckarep/golang-set/v2/LICENSE | 22 - vendor/github.com/deckarep/golang-set/v2/README.md | 195 - .../github.com/deckarep/golang-set/v2/iterator.go | 58 - .../deckarep/golang-set/v2/new_improved.jpeg | Bin 120935 -> 0 bytes vendor/github.com/deckarep/golang-set/v2/set.go | 269 -- vendor/github.com/deckarep/golang-set/v2/sorted.go | 42 - .../deckarep/golang-set/v2/threadsafe.go | 312 -- .../deckarep/golang-set/v2/threadunsafe.go | 352 -- vendor/github.com/go-stack/stack/LICENSE.md | 21 - vendor/github.com/go-stack/stack/README.md | 38 - vendor/github.com/go-stack/stack/stack.go | 400 -- vendor/github.com/lib/pq/.gitignore | 6 + vendor/github.com/lib/pq/LICENSE.md | 8 + vendor/github.com/lib/pq/README.md | 36 + vendor/github.com/lib/pq/TESTS.md | 33 + vendor/github.com/lib/pq/array.go | 895 ++++ vendor/github.com/lib/pq/buf.go | 91 + vendor/github.com/lib/pq/conn.go | 2112 +++++++++ vendor/github.com/lib/pq/conn_go115.go | 8 + vendor/github.com/lib/pq/conn_go18.go | 247 ++ vendor/github.com/lib/pq/connector.go | 120 + vendor/github.com/lib/pq/copy.go | 348 ++ vendor/github.com/lib/pq/doc.go | 268 ++ vendor/github.com/lib/pq/encode.go | 632 +++ vendor/github.com/lib/pq/error.go | 523 +++ vendor/github.com/lib/pq/krb.go | 27 + vendor/github.com/lib/pq/notice.go | 72 + vendor/github.com/lib/pq/notify.go | 858 ++++ vendor/github.com/lib/pq/oid/doc.go | 6 + vendor/github.com/lib/pq/oid/types.go | 343 ++ vendor/github.com/lib/pq/rows.go | 93 + vendor/github.com/lib/pq/scram/scram.go | 264 ++ vendor/github.com/lib/pq/ssl.go | 204 + vendor/github.com/lib/pq/ssl_permissions.go | 93 + vendor/github.com/lib/pq/ssl_windows.go | 10 + vendor/github.com/lib/pq/url.go | 76 + vendor/github.com/lib/pq/user_other.go | 10 + vendor/github.com/lib/pq/user_posix.go | 25 + vendor/github.com/lib/pq/user_windows.go | 27 + vendor/github.com/lib/pq/uuid.go | 23 + .../playwright-go/.gitattributes | 3 - .../playwright-community/playwright-go/.gitignore | 34 - .../playwright-community/playwright-go/.gitmodules | 3 - .../playwright-go/.golangci.yaml | 6 - .../playwright-community/playwright-go/.nojekyll | 0 .../playwright-community/playwright-go/404.html | 25 - .../playwright-go/CONTRIBUTING.md | 39 - .../playwright-go/Dockerfile.example | 25 - .../playwright-community/playwright-go/LICENSE | 21 - .../playwright-community/playwright-go/README.md | 148 - .../playwright-community/playwright-go/_config.yml | 21 - .../playwright-go/apiresponse_assertions.go | 75 - .../playwright-community/playwright-go/artifact.go | 70 - .../playwright-go/assertions.go | 146 - .../playwright-go/binding_call.go | 87 - .../playwright-community/playwright-go/browser.go | 274 -- .../playwright-go/browser_context.go | 914 ---- .../playwright-go/browser_type.go | 181 - .../playwright-go/cdp_session.go | 38 - .../playwright-community/playwright-go/channel.go | 92 - .../playwright-go/channel_owner.go | 122 - .../playwright-community/playwright-go/clock.go | 111 - .../playwright-go/cmd/playwright/main.go | 25 - .../playwright-go/connection.go | 401 -- .../playwright-go/console_message.go | 47 - .../playwright-community/playwright-go/dialog.go | 48 - .../playwright-community/playwright-go/download.go | 56 - .../playwright-go/element_handle.go | 403 -- .../playwright-community/playwright-go/errors.go | 58 - .../playwright-go/event_emitter.go | 163 - .../playwright-community/playwright-go/fetch.go | 451 -- .../playwright-go/file_chooser.go | 44 - .../playwright-community/playwright-go/frame.go | 792 ---- .../playwright-go/frame_locator.go | 130 - .../playwright-go/generated-enums.go | 404 -- .../playwright-go/generated-interfaces.go | 4658 -------------------- .../playwright-go/generated-structs.go | 4364 ------------------ .../playwright-community/playwright-go/glob.go | 170 - .../playwright-go/har_router.go | 110 - .../playwright-community/playwright-go/helpers.go | 628 --- .../playwright-community/playwright-go/input.go | 117 - .../playwright-go/input_files_helper.go | 202 - .../playwright-go/internal/safe/map.go | 90 - .../playwright-go/js_handle.go | 421 -- .../playwright-community/playwright-go/jsonPipe.go | 64 - .../playwright-go/local_utils.go | 165 - .../playwright-community/playwright-go/locator.go | 914 ---- .../playwright-go/locator_assertions.go | 568 --- .../playwright-go/locator_helpers.go | 133 - .../playwright-community/playwright-go/network.go | 62 - .../playwright-go/objectFactory.go | 74 - .../playwright-community/playwright-go/page.go | 1384 ------ .../playwright-go/page_assertions.go | 70 - .../playwright-go/playwright.go | 64 - .../playwright-community/playwright-go/request.go | 274 -- .../playwright-community/playwright-go/response.go | 162 - .../playwright-community/playwright-go/route.go | 270 -- .../playwright-community/playwright-go/run.go | 409 -- .../playwright-community/playwright-go/run_unix.go | 10 - .../playwright-community/playwright-go/run_win.go | 10 - .../playwright-go/selectors.go | 88 - .../playwright-community/playwright-go/stream.go | 68 - .../playwright-community/playwright-go/tracing.go | 164 - .../playwright-go/transport.go | 141 - .../playwright-go/type_helpers.go | 72 - .../playwright-community/playwright-go/video.go | 97 - .../playwright-community/playwright-go/waiter.go | 181 - .../playwright-go/web_error.go | 21 - .../playwright-go/websocket.go | 134 - .../playwright-go/websocket_route.go | 220 - .../playwright-community/playwright-go/worker.go | 78 - .../playwright-go/writable_stream.go | 44 - vendor/github.com/xlgmokha/minit/.gitignore | 1 + vendor/github.com/xlgmokha/minit/Makefile | 16 + vendor/github.com/xlgmokha/minit/main.go | 122 +- .../xlgmokha/minit/pkg/procfile/parse.go | 44 + .../github.com/xlgmokha/minit/pkg/procfile/proc.go | 27 + vendor/modules.txt | 19 +- 136 files changed, 7850 insertions(+), 23996 deletions(-) create mode 100644 Procfile.template create mode 100644 app/controllers/health/controller.go create mode 100644 app/db/connection.go create mode 100644 app/db/url.go create mode 100755 bin/postgres create mode 100755 bin/tool create mode 100644 etc/postgres/postgresql.conf delete mode 100644 vendor/github.com/deckarep/golang-set/v2/.gitignore delete mode 100644 vendor/github.com/deckarep/golang-set/v2/LICENSE delete mode 100644 vendor/github.com/deckarep/golang-set/v2/README.md delete mode 100644 vendor/github.com/deckarep/golang-set/v2/iterator.go delete mode 100644 vendor/github.com/deckarep/golang-set/v2/new_improved.jpeg delete mode 100644 vendor/github.com/deckarep/golang-set/v2/set.go delete mode 100644 vendor/github.com/deckarep/golang-set/v2/sorted.go delete mode 100644 vendor/github.com/deckarep/golang-set/v2/threadsafe.go delete mode 100644 vendor/github.com/deckarep/golang-set/v2/threadunsafe.go delete mode 100644 vendor/github.com/go-stack/stack/LICENSE.md delete mode 100644 vendor/github.com/go-stack/stack/README.md delete mode 100644 vendor/github.com/go-stack/stack/stack.go create mode 100644 vendor/github.com/lib/pq/.gitignore create mode 100644 vendor/github.com/lib/pq/LICENSE.md create mode 100644 vendor/github.com/lib/pq/README.md create mode 100644 vendor/github.com/lib/pq/TESTS.md create mode 100644 vendor/github.com/lib/pq/array.go create mode 100644 vendor/github.com/lib/pq/buf.go create mode 100644 vendor/github.com/lib/pq/conn.go create mode 100644 vendor/github.com/lib/pq/conn_go115.go create mode 100644 vendor/github.com/lib/pq/conn_go18.go create mode 100644 vendor/github.com/lib/pq/connector.go create mode 100644 vendor/github.com/lib/pq/copy.go create mode 100644 vendor/github.com/lib/pq/doc.go create mode 100644 vendor/github.com/lib/pq/encode.go create mode 100644 vendor/github.com/lib/pq/error.go create mode 100644 vendor/github.com/lib/pq/krb.go create mode 100644 vendor/github.com/lib/pq/notice.go create mode 100644 vendor/github.com/lib/pq/notify.go create mode 100644 vendor/github.com/lib/pq/oid/doc.go create mode 100644 vendor/github.com/lib/pq/oid/types.go create mode 100644 vendor/github.com/lib/pq/rows.go create mode 100644 vendor/github.com/lib/pq/scram/scram.go create mode 100644 vendor/github.com/lib/pq/ssl.go create mode 100644 vendor/github.com/lib/pq/ssl_permissions.go create mode 100644 vendor/github.com/lib/pq/ssl_windows.go create mode 100644 vendor/github.com/lib/pq/url.go create mode 100644 vendor/github.com/lib/pq/user_other.go create mode 100644 vendor/github.com/lib/pq/user_posix.go create mode 100644 vendor/github.com/lib/pq/user_windows.go create mode 100644 vendor/github.com/lib/pq/uuid.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/.gitattributes delete mode 100644 vendor/github.com/playwright-community/playwright-go/.gitignore delete mode 100644 vendor/github.com/playwright-community/playwright-go/.gitmodules delete mode 100644 vendor/github.com/playwright-community/playwright-go/.golangci.yaml delete mode 100644 vendor/github.com/playwright-community/playwright-go/.nojekyll delete mode 100644 vendor/github.com/playwright-community/playwright-go/404.html delete mode 100644 vendor/github.com/playwright-community/playwright-go/CONTRIBUTING.md delete mode 100644 vendor/github.com/playwright-community/playwright-go/Dockerfile.example delete mode 100644 vendor/github.com/playwright-community/playwright-go/LICENSE delete mode 100644 vendor/github.com/playwright-community/playwright-go/README.md delete mode 100644 vendor/github.com/playwright-community/playwright-go/_config.yml delete mode 100644 vendor/github.com/playwright-community/playwright-go/apiresponse_assertions.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/artifact.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/assertions.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/binding_call.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/browser.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/browser_context.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/browser_type.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/cdp_session.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/channel.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/channel_owner.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/clock.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/cmd/playwright/main.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/connection.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/console_message.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/dialog.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/download.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/element_handle.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/errors.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/event_emitter.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/fetch.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/file_chooser.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/frame.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/frame_locator.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/generated-enums.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/generated-interfaces.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/generated-structs.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/glob.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/har_router.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/helpers.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/input.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/input_files_helper.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/internal/safe/map.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/js_handle.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/jsonPipe.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/local_utils.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/locator.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/locator_assertions.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/locator_helpers.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/network.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/objectFactory.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/page.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/page_assertions.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/playwright.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/request.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/response.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/route.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/run.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/run_unix.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/run_win.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/selectors.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/stream.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/tracing.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/transport.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/type_helpers.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/video.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/waiter.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/web_error.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/websocket.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/websocket_route.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/worker.go delete mode 100644 vendor/github.com/playwright-community/playwright-go/writable_stream.go create mode 100644 vendor/github.com/xlgmokha/minit/.gitignore create mode 100644 vendor/github.com/xlgmokha/minit/Makefile create mode 100644 vendor/github.com/xlgmokha/minit/pkg/procfile/parse.go create mode 100644 vendor/github.com/xlgmokha/minit/pkg/procfile/proc.go diff --git a/.env b/.env index e4c8f51..b92b6ba 100644 --- a/.env +++ b/.env @@ -1,9 +1,11 @@ APP_ENV=development # AUTHZD_HOST=0.0.0.0:20000 +DATABASE_URL=postgresql:///sparkle_development?host=/tmp HMAC_SESSION_SECRET=session_secret LOG_LEVEL=warn OAUTH_CLIENT_ID=client_id OAUTH_CLIENT_SECRET=client_secret OIDC_ISSUER=https://gitlab.com +RUNWAY_PG_USER_POSTGRES_PASSWORD_SPARKLE= ZED_ENDPOINT=localhost:50051 ZED_TOKEN=secret diff --git a/.gitignore b/.gitignore index 3c35ead..65ba73b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ /tmp /log .claude/ + +# Generated Procfiles (use Procfile.template as source) +Procfile +Procfile.dev diff --git a/Dockerfile b/Dockerfile index 1b2f792..290513e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,11 +14,12 @@ FROM authzed/spicedb:latest AS spicedb-binary # Build stage for sparkle FROM golang:1.24-alpine AS build ENV CGO_ENABLED=0 -RUN go install github.com/xlgmokha/minit@latest WORKDIR /app COPY . ./ RUN go build -o /bin/sparkled ./cmd/sparkled/main.go RUN go build -o /bin/authzd ./cmd/authzd/main.go +WORKDIR /app/vendor/github.com/xlgmokha/minit +RUN go build -o /bin/minit main.go # Final stage FROM gcr.io/distroless/base-debian12:debug-nonroot @@ -36,8 +37,8 @@ COPY --from=build /app/etc/envoy /etc/envoy COPY --from=build /app/etc/authzd /etc/authzd COPY --from=build /app/public /public COPY --from=build /bin/authzd /bin/authzd +COPY --from=build /bin/minit /bin/minit COPY --from=build /bin/sparkled /bin/sparkled -COPY --from=build /go/bin/minit /bin/minit ENTRYPOINT ["/bin/dumb-init", "--"] -CMD ["/bin/minit"] +CMD ["/bin/minit", "-f", "/Procfile"] diff --git a/Makefile b/Makefile index ee81a5e..f50bc8d 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,10 @@ GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | sed 's/\//_/g') IMAGE_TAG := $(PROJECT_NAME):$(GIT_BRANCH) AUTHZD_BIN := bin/authzd SPARKLED_BIN := bin/sparkled +TOOL := bin/tool ZED := bin/zed -.PHONY: clean setup build test run db-setup +.PHONY: clean setup build test run db-setup db-create db-stop db-reset $(AUTHZD_BIN): $(shell find . -name "*.go" 2>/dev/null) go.sum @go build -o $(AUTHZD_BIN) ./cmd/authzd/main.go @@ -13,13 +14,15 @@ $(AUTHZD_BIN): $(shell find . -name "*.go" 2>/dev/null) go.sum $(SPARKLED_BIN): $(shell find . -name "*.go" 2>/dev/null) go.sum @go build -o $(SPARKLED_BIN) ./cmd/sparkled/main.go -clean: +db-clean: + @rm -rf tmp/postgres tmp/postgres.log + +clean: db-clean @rm -f $(AUTHZD_BIN) $(SPARKLED_BIN) @go clean -testcache setup: @mise install - @mise exec go -- go install github.com/xlgmokha/minit@latest @mise exec go -- go mod tidy @mise exec go -- go mod vendor @mise exec go -- go tool @@ -28,6 +31,12 @@ setup: fi @mise exec python -- pip install dumb-init +Procfile: Procfile.template + @grep -v "# DEV_ONLY:" Procfile.template > Procfile + +Procfile.dev: Procfile.template + @sed 's/# DEV_ONLY: //' Procfile.template > Procfile.dev + build: $(AUTHZD_BIN) $(SPARKLED_BIN) test-schema: @@ -44,24 +53,25 @@ test-integration: build-image test: clean test-unit test-schema test-integration -build-image: +build-image: Procfile @docker build --network=host --no-cache --tag $(IMAGE_TAG) . -run: clean build - @go tool godotenv -f .env.local,.env minit +run: clean build Procfile.dev db-init + @$(TOOL) godotenv -f .env.local,.env $(TOOL) minit -f Procfile.dev run-image: clean build-image - @docker run --rm --network host --env-file .env.local -p 10000:10000 -p 9901:9901 -p 8080:8080 -it $(IMAGE_TAG) + @docker run --rm --network host --env-file .env.local -p 10000:10000 -it $(IMAGE_TAG) lint: - @go tool yamlfmt -lint -exclude vendor . + @$(TOOL) yamlfmt -lint -exclude vendor . @$(ZED) validate etc/authzd/* tidy: @go get -u ./... + @go tool | grep github | awk '{print $1}' | xargs -I {} go get -tool {}@latest @go mod tidy @go mod vendor - @go tool yamlfmt -exclude vendor . + @$(TOOL) yamlfmt -exclude vendor . db-schema-load: @$(ZED) schema write etc/authzd/schema.zed @@ -69,4 +79,15 @@ db-schema-load: db-seed: @$(ZED) import etc/authzd/relationships.yaml +db-stop: + @mise exec postgres -- pg_ctl -D tmp/postgres stop || true + +db-reset: db-stop db-clean db-create + +db-init: + @[ ! -d tmp/postgres ] && mise exec postgres -- initdb -D tmp/postgres || true + +db-create: db-init + @mise exec postgres -- createdb -h /tmp sparkle_development || true + db-setup: db-schema-load db-seed diff --git a/Procfile.template b/Procfile.template new file mode 100644 index 0000000..3f20a32 --- /dev/null +++ b/Procfile.template @@ -0,0 +1,5 @@ +# DEV_ONLY: postgres: ./bin/postgres -D tmp/postgres -c config_file=etc/postgres/postgresql.conf +spicedb: ./bin/spicedb serve --grpc-preshared-key $ZED_TOKEN --http-addr :8081 --grpc-addr :50051 --datastore-engine memory --log-level $LOG_LEVEL --log-format json --telemetry-endpoint "" --skip-release-check +authzd: ./bin/authzd +envoy: ./bin/envoy-shim +sparkled: ./bin/sparkled diff --git a/app/app.go b/app/app.go index 9ccdaba..ad92028 100644 --- a/app/app.go +++ b/app/app.go @@ -9,6 +9,7 @@ import ( "github.com/xlgmokha/x/pkg/log" "github.com/xlgmokha/x/pkg/x" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/dashboard" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/health" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/sparkles" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/middleware" ) @@ -22,6 +23,7 @@ func New(rootDir string) http.Handler { mountable := []Mountable{ ioc.MustResolve[*dashboard.Controller](ioc.Default), + ioc.MustResolve[*health.Controller](ioc.Default), ioc.MustResolve[*sparkles.Controller](ioc.Default), } for _, m := range mountable { diff --git a/app/controllers/health/controller.go b/app/controllers/health/controller.go new file mode 100644 index 0000000..99ff4cd --- /dev/null +++ b/app/controllers/health/controller.go @@ -0,0 +1,43 @@ +package health + +import ( + "context" + "net/http" + "time" + + "github.com/xlgmokha/x/pkg/serde" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/db" +) + +type Controller struct { + dbConnection *db.Connection +} + +func New(dbConnection *db.Connection) *Controller { + return &Controller{ + dbConnection: dbConnection, + } +} + +func (c *Controller) MountTo(mux *http.ServeMux) { + mux.Handle("GET /-/health", http.HandlerFunc(c.Health)) + mux.Handle("GET /-/health/database", http.HandlerFunc(c.Database)) +} + +func (c *Controller) Health(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + serde.ToHTTP(w, r, map[string]string{"status": "ok"}) +} + +func (c *Controller) Database(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + defer cancel() + + if c.dbConnection.IsHealthy(ctx) { + w.WriteHeader(http.StatusOK) + serde.ToHTTP(w, r, map[string]string{"database": "ok"}) + } else { + w.WriteHeader(http.StatusServiceUnavailable) + serde.ToHTTP(w, r, map[string]string{"database": "unavailable"}) + } +} diff --git a/app/controllers/sparkles/controller.go b/app/controllers/sparkles/controller.go index 90767b2..86610e1 100644 --- a/app/controllers/sparkles/controller.go +++ b/app/controllers/sparkles/controller.go @@ -3,7 +3,6 @@ package sparkles import ( "net/http" - "github.com/xlgmokha/x/pkg/log" "github.com/xlgmokha/x/pkg/mapper" "github.com/xlgmokha/x/pkg/serde" "github.com/xlgmokha/x/pkg/x" @@ -32,9 +31,6 @@ func (c *Controller) MountTo(mux *http.ServeMux) { middleware.RequireUser(), // middleware.RequirePermission("create", c.check), )) - - // This is a temporary endpoint to restore a backup - mux.HandleFunc("POST /sparkles/restore", c.Restore) } func (c *Controller) Index(w http.ResponseWriter, r *http.Request) { @@ -64,16 +60,3 @@ func (c *Controller) Create(w http.ResponseWriter, r *http.Request) { return } } - -// This is a temporary endpoint to restore a backup -// of sparkles and can be deleted once we have an actual database -func (c *Controller) Restore(w http.ResponseWriter, r *http.Request) { - sparkles, _ := serde.FromHTTP[[]*domain.Sparkle](r) - log.WithFields(r.Context(), log.Fields{"sparkles": sparkles}) - - x.Each(sparkles, func(sparkle *domain.Sparkle) { - if err := c.db.Save(r.Context(), sparkle); err != nil { - pls.LogError(r.Context(), err) - } - }) -} diff --git a/app/db/connection.go b/app/db/connection.go new file mode 100644 index 0000000..d494f6c --- /dev/null +++ b/app/db/connection.go @@ -0,0 +1,54 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + + _ "github.com/lib/pq" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" +) + +type Connection struct { + db *sql.DB +} + +func NewConnection(databaseURL string) (*Connection, error) { + db, err := sql.Open("postgres", databaseURL) + if err != nil { + return nil, fmt.Errorf("failed to open database connection: %w", err) + } + + return &Connection{ + db: db, + }, nil +} + +func (c *Connection) Ping(ctx context.Context) error { + if c.db == nil { + return fmt.Errorf("database connection not available") + } + + return c.db.PingContext(ctx) +} + +func (c *Connection) IsHealthy(ctx context.Context) bool { + if c.db == nil { + return false + } + + err := c.Ping(ctx) + if err != nil { + pls.LogError(ctx, err) + return false + } + + return true +} + +func (c *Connection) Close() error { + if c.db == nil { + return nil + } + return c.db.Close() +} diff --git a/app/db/url.go b/app/db/url.go new file mode 100644 index 0000000..b17c651 --- /dev/null +++ b/app/db/url.go @@ -0,0 +1,19 @@ +package db + +import ( + "fmt" + + "github.com/xlgmokha/x/pkg/env" +) + +func URL() string { + if url := env.Fetch("DATABASE_URL", ""); url != "" { + return url + } + + return fmt.Sprintf( + "postgresql://postgres:%s@localhost:5000/%s?sslmode=disable", + env.Fetch("RUNWAY_PG_USER_POSTGRES_PASSWORD_SPARKLE", ""), + env.Fetch("DATABASE_NAME", "sparkle"), + ) +} diff --git a/app/init.go b/app/init.go index 8e5e0e5..ab1d6f8 100644 --- a/app/init.go +++ b/app/init.go @@ -14,6 +14,7 @@ import ( "github.com/xlgmokha/x/pkg/mapper" "github.com/xlgmokha/x/pkg/x" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/dashboard" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/health" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/controllers/sparkles" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/db" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" @@ -29,6 +30,13 @@ func init() { ioc.RegisterSingleton[*zerolog.Logger](c, func() *zerolog.Logger { return log.New(os.Stdout, log.Fields{"app": "sparkled"}) }) + ioc.RegisterSingleton[*db.Connection](c, func() *db.Connection { + conn, err := db.NewConnection(db.URL()) + if err != nil { + pls.LogErrorNow(context.Background(), err) + } + return conn + }) ioc.RegisterSingleton[*authzed.Client](c, func() *authzed.Client { return authz.NewSpiceDBClient( context.Background(), @@ -62,6 +70,9 @@ func init() { ioc.Register[*dashboard.Controller](c, func() *dashboard.Controller { return dashboard.New() }) + ioc.Register[*health.Controller](c, func() *health.Controller { + return health.New(ioc.MustResolve[*db.Connection](c)) + }) ioc.Register[*sparkles.Controller](c, func() *sparkles.Controller { return sparkles.New( ioc.MustResolve[domain.Repository[*domain.Sparkle]](c), diff --git a/bin/postgres b/bin/postgres new file mode 100755 index 0000000..66c0ab0 --- /dev/null +++ b/bin/postgres @@ -0,0 +1,8 @@ +#!/bin/sh + +if ! command -v postgres >/dev/null 2>&1; then + echo "Install postgres via mise: mise install postgres" + exit 1 +fi + +exec postgres $@ diff --git a/bin/tool b/bin/tool new file mode 100755 index 0000000..7b46bce --- /dev/null +++ b/bin/tool @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +tool_bin=$(go tool -n "$1") + +shift + +exec "$tool_bin" "$@" diff --git a/etc/postgres/postgresql.conf b/etc/postgres/postgresql.conf new file mode 100644 index 0000000..4d7f703 --- /dev/null +++ b/etc/postgres/postgresql.conf @@ -0,0 +1,25 @@ +# Connection settings +listen_addresses = '' +unix_socket_directories = '/tmp' + +# Logging settings +logging_collector = off +log_destination = 'stderr' +log_statement = 'ddl' +log_line_prefix = '' +log_min_duration_statement = 1000 +log_checkpoints = on + +# Performance and Development +shared_preload_libraries = '' # No additional libraries needed +max_connections = 100 # Default connection limit +shared_buffers = 128MB # Default buffer size + +# Development-friendly settings +fsync = off # Faster writes (safe for development only) +synchronous_commit = off # Faster commits (safe for development only) +full_page_writes = off # Reduce I/O (safe for development only) + +# Timezone +timezone = 'UTC' # Use UTC for consistency +log_timezone = 'UTC' # Log timestamps in UTC diff --git a/go.mod b/go.mod index a926312..d4623be 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/coreos/go-oidc/v3 v3.15.0 github.com/docker/docker v28.3.3+incompatible github.com/envoyproxy/go-control-plane/envoy v1.32.4 + github.com/golobby/container/v3 v3.3.2 + github.com/lib/pq v1.10.9 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 github.com/oklog/ulid v1.3.1 github.com/rs/zerolog v1.34.0 @@ -77,7 +79,6 @@ require ( github.com/dalzilio/rudd v1.1.1-0.20230806153452-9e08a6ea8170 // indirect github.com/danieljoos/wincred v1.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckarep/golang-set/v2 v2.8.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -98,13 +99,11 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zerologr v1.2.3 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-stack/stack v1.8.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/golobby/container/v3 v3.3.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-github/v43 v43.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -159,7 +158,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 // indirect - github.com/playwright-community/playwright-go v0.5200.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.21.1 // indirect @@ -189,7 +187,7 @@ require ( github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect - github.com/xlgmokha/minit v0.0.0-20250725204255-8e0834741617 // indirect + github.com/xlgmokha/minit v0.0.0-20250731211218-d06d0bc4c199 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/errs v1.4.0 // indirect @@ -223,6 +221,5 @@ tool ( github.com/fullstorydev/grpcurl/cmd/grpcurl github.com/google/yamlfmt/cmd/yamlfmt github.com/joho/godotenv/cmd/godotenv - github.com/playwright-community/playwright-go/cmd/playwright github.com/xlgmokha/minit ) diff --git a/go.sum b/go.sum index bcaeba8..75fb96c 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= -github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlmiddlecote/sqlstats v1.0.2 h1:gSU11YN23D/iY50A2zVYwgXgy072khatTsIW6UPjUtI= @@ -258,8 +256,6 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -390,6 +386,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lthibault/jitterbug v2.0.0+incompatible h1:qouq51IKzlMx25+15jbxhC/d79YyTj0q6XFoptNqaUw= @@ -418,8 +416,6 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -482,8 +478,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 h1:S1hI5JiKP7883xBzZAr1ydcxrKNSVNm7+3+JwjxZEsg= github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25/go.mod h1:ZQntvDG8TkPgljxtA0R9frDoND4QORU1VXz015N5Ks4= -github.com/playwright-community/playwright-go v0.5200.0 h1:z/5LGuX2tBrg3ug1HupMXLjIG93f1d2MWdDsNhkMQ9c= -github.com/playwright-community/playwright-go v0.5200.0/go.mod h1:UnnyQZaqUOO5ywAZu60+N4EiWReUqX1MQBBA3Oofvf8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -587,8 +581,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xlgmokha/minit v0.0.0-20250725204255-8e0834741617 h1:MM4yskiyvXBz8ufTTp3PWgpAZMY4BEGIIbuS3arc0Ws= -github.com/xlgmokha/minit v0.0.0-20250725204255-8e0834741617/go.mod h1:2Kojbsk+Nuj/Rcf+kHkJOKnf+VstT/r1ZRS4pPqtVXY= +github.com/xlgmokha/minit v0.0.0-20250731211218-d06d0bc4c199 h1:uCej+MMWWjvQwCdo6NQm4aDGVs3CsC8mLvwAoVJZy0A= +github.com/xlgmokha/minit v0.0.0-20250731211218-d06d0bc4c199/go.mod h1:2Kojbsk+Nuj/Rcf+kHkJOKnf+VstT/r1ZRS4pPqtVXY= github.com/xlgmokha/x v0.0.0-20250730165105-1a2af5f242cf h1:XXOAL/L880uxlbwOMAr6WMKCQzYyZfaYg4ovehJniY0= github.com/xlgmokha/x v0.0.0-20250730165105-1a2af5f242cf/go.mod h1:zhU3cB9VYGt4IlTIaLVw3wsXIS5UzXiYmCB+q+DjfHs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= diff --git a/mise.toml b/mise.toml index 76c2bd0..919d7c6 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,4 @@ [tools] go = "1.24.5" +postgres = "17" python = "3.13.4" diff --git a/vendor/github.com/deckarep/golang-set/v2/.gitignore b/vendor/github.com/deckarep/golang-set/v2/.gitignore deleted file mode 100644 index 4eb156d..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -.idea \ No newline at end of file diff --git a/vendor/github.com/deckarep/golang-set/v2/LICENSE b/vendor/github.com/deckarep/golang-set/v2/LICENSE deleted file mode 100644 index efd4827..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/deckarep/golang-set/v2/README.md b/vendor/github.com/deckarep/golang-set/v2/README.md deleted file mode 100644 index e471368..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/README.md +++ /dev/null @@ -1,195 +0,0 @@ -![example workflow](https://github.com/deckarep/golang-set/actions/workflows/ci.yml/badge.svg) -[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set/v2)](https://goreportcard.com/report/github.com/deckarep/golang-set/v2) -[![GoDoc](https://godoc.org/github.com/deckarep/golang-set/v2?status.svg)](http://godoc.org/github.com/deckarep/golang-set/v2) - -# golang-set - -The missing `generic` set collection for the Go language. Until Go has sets built-in...use this. - -## Psst -* Hi there, 👋! Do you use or have interest in the [Zig programming language](https://ziglang.org/) created by Andrew Kelley? If so, the golang-set project has a new sibling project: [ziglang-set](https://github.com/deckarep/ziglang-set)! Come check it out! - -## Update 3/14/2025 -* Packaged version: `2.8.0` introduces support for true iterators for Go 1.23+. Please see [issue #141](https://github.com/deckarep/golang-set/issues/141) -for further details on the implications of how iterations work between older Go versions vs newer Go versions. Additionally, this -release has a minor unit-test spelling fix. - -## Update 12/3/2024 -* Packaged version: `2.7.0` fixes a long-standing bug with *JSON Unmarshaling*. A large refactor in the interest of performance -introduced this bug and there was no way around it but to revert the code back to how it was previously. The performance -difference was likely negligible to begin with. JSON Marshaling and Unmarshaling is now properly supported again without -needing to do workarounds. - -## Update 3/5/2023 -* Packaged version: `2.2.0` release includes a refactor to minimize pointer indirection, better method documentation standards and a few constructor convenience methods to increase ergonomics when appending items `Append` or creating a new set from an exist `Map`. -* supports `new generic` syntax -* Go `1.18.0` or higher -* Workflow tested on Go `1.20` - -![With Generics](new_improved.jpeg) - -Coming from Python one of the things I miss is the superbly wonderful set collection. This is my attempt to mimic the primary features of the set collection from Python. -You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library. To those I say simply ignore this repository and carry-on and to the rest that find this useful please contribute in helping me make it better by contributing with suggestions or PRs. - -## Install - -Use `go get` to install this package. - -```shell -go get github.com/deckarep/golang-set/v2 -``` - -## Features - -* *NEW* [Generics](https://go.dev/doc/tutorial/generics) based implementation (requires [Go 1.18](https://go.dev/blog/go1.18beta1) or higher) -* One common *interface* to both implementations - * a **non threadsafe** implementation favoring *performance* - * a **threadsafe** implementation favoring *concurrent* use -* Feature complete set implementation modeled after [Python's set implementation](https://docs.python.org/3/library/stdtypes.html#set). -* Exhaustive unit-test and benchmark suite - -## Trusted by - -This package is trusted by many companies and thousands of open-source packages. Here are just a few sample users of this package. - -* Notable projects/companies using this package - * Ethereum - * Docker - * 1Password - * Hashicorp - -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=deckarep/golang-set&type=Date)](https://star-history.com/#deckarep/golang-set&Date) - - -## Usage - -The code below demonstrates how a Set collection can better manage data and actually minimize boilerplate and needless loops in code. This package now fully supports *generic* syntax so you are now able to instantiate a collection for any [comparable](https://flaviocopes.com/golang-comparing-values/) type object. - -What is considered comparable in Go? -* `Booleans`, `integers`, `strings`, `floats` or basically primitive types. -* `Pointers` -* `Arrays` -* `Structs` if *all of their fields* are also comparable independently - -Using this library is as simple as creating either a threadsafe or non-threadsafe set and providing a `comparable` type for instantiation of the collection. - -```go -// Syntax example, doesn't compile. -mySet := mapset.NewSet[T]() // where T is some concrete comparable type. - -// Therefore this code creates an int set -mySet := mapset.NewSet[int]() - -// Or perhaps you want a string set -mySet := mapset.NewSet[string]() - -type myStruct struct { - name string - age uint8 -} - -// Alternatively a set of structs -mySet := mapset.NewSet[myStruct]() - -// Lastly a set that can hold anything using the any or empty interface keyword: interface{}. This is effectively removes type safety. -mySet := mapset.NewSet[any]() -``` - -## Comprehensive Example - -```go -package main - -import ( - "fmt" - mapset "github.com/deckarep/golang-set/v2" -) - -func main() { - // Create a string-based set of required classes. - required := mapset.NewSet[string]() - required.Add("cooking") - required.Add("english") - required.Add("math") - required.Add("biology") - - // Create a string-based set of science classes. - sciences := mapset.NewSet[string]() - sciences.Add("biology") - sciences.Add("chemistry") - - // Create a string-based set of electives. - electives := mapset.NewSet[string]() - electives.Add("welding") - electives.Add("music") - electives.Add("automotive") - - // Create a string-based set of bonus programming classes. - bonus := mapset.NewSet[string]() - bonus.Add("beginner go") - bonus.Add("python for dummies") -} -``` - -Create a set of all unique classes. -Sets will *automatically* deduplicate the same data. - -```go - all := required - .Union(sciences) - .Union(electives) - .Union(bonus) - - fmt.Println(all) -``` - -Output: -```sh -Set{cooking, english, math, chemistry, welding, biology, music, automotive, beginner go, python for dummies} -``` - -Is cooking considered a science class? -```go -result := sciences.Contains("cooking") -fmt.Println(result) -``` - -Output: -```false -false -``` - -Show me all classes that are not science classes, since I don't enjoy science. -```go -notScience := all.Difference(sciences) -fmt.Println(notScience) -``` - -```sh -Set{ music, automotive, beginner go, python for dummies, cooking, english, math, welding } -``` - -Which science classes are also required classes? -```go -reqScience := sciences.Intersect(required) -``` - -Output: -```sh -Set{biology} -``` - -How many bonus classes do you offer? -```go -fmt.Println(bonus.Cardinality()) -``` -Output: -```sh -2 -``` - -Thanks for visiting! - --deckarep diff --git a/vendor/github.com/deckarep/golang-set/v2/iterator.go b/vendor/github.com/deckarep/golang-set/v2/iterator.go deleted file mode 100644 index fc14e70..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/iterator.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package mapset - -// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's -// elements. -type Iterator[T comparable] struct { - C <-chan T - stop chan struct{} -} - -// Stop stops the Iterator, no further elements will be received on C, C will be closed. -func (i *Iterator[T]) Stop() { - // Allows for Stop() to be called multiple times - // (close() panics when called on already closed channel) - defer func() { - recover() - }() - - close(i.stop) - - // Exhaust any remaining elements. - for range i.C { - } -} - -// newIterator returns a new Iterator instance together with its item and stop channels. -func newIterator[T comparable]() (*Iterator[T], chan<- T, <-chan struct{}) { - itemChan := make(chan T) - stopChan := make(chan struct{}) - return &Iterator[T]{ - C: itemChan, - stop: stopChan, - }, itemChan, stopChan -} diff --git a/vendor/github.com/deckarep/golang-set/v2/new_improved.jpeg b/vendor/github.com/deckarep/golang-set/v2/new_improved.jpeg deleted file mode 100644 index 429752a..0000000 Binary files a/vendor/github.com/deckarep/golang-set/v2/new_improved.jpeg and /dev/null differ diff --git a/vendor/github.com/deckarep/golang-set/v2/set.go b/vendor/github.com/deckarep/golang-set/v2/set.go deleted file mode 100644 index e9409aa..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/set.go +++ /dev/null @@ -1,269 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -// Package mapset implements a simple and set collection. -// Items stored within it are unordered and unique. It supports -// typical set operations: membership testing, intersection, union, -// difference, symmetric difference and cloning. -// -// Package mapset provides two implementations of the Set -// interface. The default implementation is safe for concurrent -// access, but a non-thread-safe implementation is also provided for -// programs that can benefit from the slight speed improvement and -// that can enforce mutual exclusion through other means. -package mapset - -// Set is the primary interface provided by the mapset package. It -// represents an unordered set of data and a large number of -// operations that can be applied to that set. -type Set[T comparable] interface { - // Add adds an element to the set. Returns whether - // the item was added. - Add(val T) bool - - // Append multiple elements to the set. Returns - // the number of elements added. - Append(val ...T) int - - // Cardinality returns the number of elements in the set. - Cardinality() int - - // Clear removes all elements from the set, leaving - // the empty set. - Clear() - - // Clone returns a clone of the set using the same - // implementation, duplicating all keys. - Clone() Set[T] - - // Contains returns whether the given items - // are all in the set. - Contains(val ...T) bool - - // ContainsOne returns whether the given item - // is in the set. - // - // Contains may cause the argument to escape to the heap. - // See: https://github.com/deckarep/golang-set/issues/118 - ContainsOne(val T) bool - - // ContainsAny returns whether at least one of the - // given items are in the set. - ContainsAny(val ...T) bool - - // ContainsAnyElement returns whether at least one of the - // given element are in the set. - ContainsAnyElement(other Set[T]) bool - - // Difference returns the difference between this set - // and other. The returned set will contain - // all elements of this set that are not also - // elements of other. - // - // Note that the argument to Difference - // must be of the same type as the receiver - // of the method. Otherwise, Difference will - // panic. - Difference(other Set[T]) Set[T] - - // Equal determines if two sets are equal to each - // other. If they have the same cardinality - // and contain the same elements, they are - // considered equal. The order in which - // the elements were added is irrelevant. - // - // Note that the argument to Equal must be - // of the same type as the receiver of the - // method. Otherwise, Equal will panic. - Equal(other Set[T]) bool - - // Intersect returns a new set containing only the elements - // that exist only in both sets. - // - // Note that the argument to Intersect - // must be of the same type as the receiver - // of the method. Otherwise, Intersect will - // panic. - Intersect(other Set[T]) Set[T] - - // IsEmpty determines if there are elements in the set. - IsEmpty() bool - - // IsProperSubset determines if every element in this set is in - // the other set but the two sets are not equal. - // - // Note that the argument to IsProperSubset - // must be of the same type as the receiver - // of the method. Otherwise, IsProperSubset - // will panic. - IsProperSubset(other Set[T]) bool - - // IsProperSuperset determines if every element in the other set - // is in this set but the two sets are not - // equal. - // - // Note that the argument to IsSuperset - // must be of the same type as the receiver - // of the method. Otherwise, IsSuperset will - // panic. - IsProperSuperset(other Set[T]) bool - - // IsSubset determines if every element in this set is in - // the other set. - // - // Note that the argument to IsSubset - // must be of the same type as the receiver - // of the method. Otherwise, IsSubset will - // panic. - IsSubset(other Set[T]) bool - - // IsSuperset determines if every element in the other set - // is in this set. - // - // Note that the argument to IsSuperset - // must be of the same type as the receiver - // of the method. Otherwise, IsSuperset will - // panic. - IsSuperset(other Set[T]) bool - - // Each iterates over elements and executes the passed func against each element. - // If passed func returns true, stop iteration at the time. - Each(func(T) bool) - - // Iter returns a channel of elements that you can - // range over. - Iter() <-chan T - - // Iterator returns an Iterator object that you can - // use to range over the set. - Iterator() *Iterator[T] - - // Remove removes a single element from the set. - Remove(i T) - - // RemoveAll removes multiple elements from the set. - RemoveAll(i ...T) - - // String provides a convenient string representation - // of the current state of the set. - String() string - - // SymmetricDifference returns a new set with all elements which are - // in either this set or the other set but not in both. - // - // Note that the argument to SymmetricDifference - // must be of the same type as the receiver - // of the method. Otherwise, SymmetricDifference - // will panic. - SymmetricDifference(other Set[T]) Set[T] - - // Union returns a new set with all elements in both sets. - // - // Note that the argument to Union must be of the - // same type as the receiver of the method. - // Otherwise, Union will panic. - Union(other Set[T]) Set[T] - - // Pop removes and returns an arbitrary item from the set. - Pop() (T, bool) - - // ToSlice returns the members of the set as a slice. - ToSlice() []T - - // MarshalJSON will marshal the set into a JSON-based representation. - MarshalJSON() ([]byte, error) - - // UnmarshalJSON will unmarshal a JSON-based byte slice into a full Set datastructure. - // For this to work, set subtypes must implemented the Marshal/Unmarshal interface. - UnmarshalJSON(b []byte) error -} - -// NewSet creates and returns a new set with the given elements. -// Operations on the resulting set are thread-safe. -func NewSet[T comparable](vals ...T) Set[T] { - s := newThreadSafeSetWithSize[T](len(vals)) - for _, item := range vals { - s.Add(item) - } - return s -} - -// NewSetWithSize creates and returns a reference to an empty set with a specified -// capacity. Operations on the resulting set are thread-safe. -func NewSetWithSize[T comparable](cardinality int) Set[T] { - s := newThreadSafeSetWithSize[T](cardinality) - return s -} - -// NewThreadUnsafeSet creates and returns a new set with the given elements. -// Operations on the resulting set are not thread-safe. -func NewThreadUnsafeSet[T comparable](vals ...T) Set[T] { - s := newThreadUnsafeSetWithSize[T](len(vals)) - for _, item := range vals { - s.Add(item) - } - return s -} - -// NewThreadUnsafeSetWithSize creates and returns a reference to an empty set with -// a specified capacity. Operations on the resulting set are not thread-safe. -func NewThreadUnsafeSetWithSize[T comparable](cardinality int) Set[T] { - s := newThreadUnsafeSetWithSize[T](cardinality) - return s -} - -// NewSetFromMapKeys creates and returns a new set with the given keys of the map. -// Operations on the resulting set are thread-safe. -func NewSetFromMapKeys[T comparable, V any](val map[T]V) Set[T] { - s := NewSetWithSize[T](len(val)) - - for k := range val { - s.Add(k) - } - - return s -} - -// NewThreadUnsafeSetFromMapKeys creates and returns a new set with the given keys of the map. -// Operations on the resulting set are not thread-safe. -func NewThreadUnsafeSetFromMapKeys[T comparable, V any](val map[T]V) Set[T] { - s := NewThreadUnsafeSetWithSize[T](len(val)) - - for k := range val { - s.Add(k) - } - - return s -} - -// Elements returns an iterator that yields the elements of the set. Starting -// with Go 1.23, users can use a for loop to iterate over it. -func Elements[T comparable](s Set[T]) func(func(element T) bool) { - return func(yield func(element T) bool) { - s.Each(func(t T) bool { - return !yield(t) - }) - } -} diff --git a/vendor/github.com/deckarep/golang-set/v2/sorted.go b/vendor/github.com/deckarep/golang-set/v2/sorted.go deleted file mode 100644 index 8ee2e70..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/sorted.go +++ /dev/null @@ -1,42 +0,0 @@ -//go:build go1.21 -// +build go1.21 - -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 - 2023 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package mapset - -import ( - "cmp" - "slices" -) - -// Sorted returns a sorted slice of a set of any ordered type in ascending order. -// When sorting floating-point numbers, NaNs are ordered before other values. -func Sorted[E cmp.Ordered](set Set[E]) []E { - s := set.ToSlice() - slices.Sort(s) - return s -} diff --git a/vendor/github.com/deckarep/golang-set/v2/threadsafe.go b/vendor/github.com/deckarep/golang-set/v2/threadsafe.go deleted file mode 100644 index 664fc61..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/threadsafe.go +++ /dev/null @@ -1,312 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package mapset - -import "sync" - -type threadSafeSet[T comparable] struct { - sync.RWMutex - uss *threadUnsafeSet[T] -} - -func newThreadSafeSet[T comparable]() *threadSafeSet[T] { - return &threadSafeSet[T]{ - uss: newThreadUnsafeSet[T](), - } -} - -func newThreadSafeSetWithSize[T comparable](cardinality int) *threadSafeSet[T] { - return &threadSafeSet[T]{ - uss: newThreadUnsafeSetWithSize[T](cardinality), - } -} - -func (t *threadSafeSet[T]) Add(v T) bool { - t.Lock() - ret := t.uss.Add(v) - t.Unlock() - return ret -} - -func (t *threadSafeSet[T]) Append(v ...T) int { - t.Lock() - ret := t.uss.Append(v...) - t.Unlock() - return ret -} - -func (t *threadSafeSet[T]) Contains(v ...T) bool { - t.RLock() - ret := t.uss.Contains(v...) - t.RUnlock() - - return ret -} - -func (t *threadSafeSet[T]) ContainsOne(v T) bool { - t.RLock() - ret := t.uss.ContainsOne(v) - t.RUnlock() - - return ret -} - -func (t *threadSafeSet[T]) ContainsAny(v ...T) bool { - t.RLock() - ret := t.uss.ContainsAny(v...) - t.RUnlock() - - return ret -} - -func (t *threadSafeSet[T]) ContainsAnyElement(other Set[T]) bool { - o := other.(*threadSafeSet[T]) - - t.RLock() - o.RLock() - - ret := t.uss.ContainsAnyElement(o.uss) - - t.RUnlock() - o.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) IsEmpty() bool { - return t.Cardinality() == 0 -} - -func (t *threadSafeSet[T]) IsSubset(other Set[T]) bool { - o := other.(*threadSafeSet[T]) - - t.RLock() - o.RLock() - - ret := t.uss.IsSubset(o.uss) - t.RUnlock() - o.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) IsProperSubset(other Set[T]) bool { - o := other.(*threadSafeSet[T]) - - t.RLock() - defer t.RUnlock() - o.RLock() - defer o.RUnlock() - - return t.uss.IsProperSubset(o.uss) -} - -func (t *threadSafeSet[T]) IsSuperset(other Set[T]) bool { - return other.IsSubset(t) -} - -func (t *threadSafeSet[T]) IsProperSuperset(other Set[T]) bool { - return other.IsProperSubset(t) -} - -func (t *threadSafeSet[T]) Union(other Set[T]) Set[T] { - o := other.(*threadSafeSet[T]) - - t.RLock() - o.RLock() - - unsafeUnion := t.uss.Union(o.uss).(*threadUnsafeSet[T]) - ret := &threadSafeSet[T]{uss: unsafeUnion} - t.RUnlock() - o.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) Intersect(other Set[T]) Set[T] { - o := other.(*threadSafeSet[T]) - - t.RLock() - o.RLock() - - unsafeIntersection := t.uss.Intersect(o.uss).(*threadUnsafeSet[T]) - ret := &threadSafeSet[T]{uss: unsafeIntersection} - t.RUnlock() - o.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) Difference(other Set[T]) Set[T] { - o := other.(*threadSafeSet[T]) - - t.RLock() - o.RLock() - - unsafeDifference := t.uss.Difference(o.uss).(*threadUnsafeSet[T]) - ret := &threadSafeSet[T]{uss: unsafeDifference} - t.RUnlock() - o.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) SymmetricDifference(other Set[T]) Set[T] { - o := other.(*threadSafeSet[T]) - - t.RLock() - o.RLock() - - unsafeDifference := t.uss.SymmetricDifference(o.uss).(*threadUnsafeSet[T]) - ret := &threadSafeSet[T]{uss: unsafeDifference} - t.RUnlock() - o.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) Clear() { - t.Lock() - t.uss.Clear() - t.Unlock() -} - -func (t *threadSafeSet[T]) Remove(v T) { - t.Lock() - delete(*t.uss, v) - t.Unlock() -} - -func (t *threadSafeSet[T]) RemoveAll(i ...T) { - t.Lock() - t.uss.RemoveAll(i...) - t.Unlock() -} - -func (t *threadSafeSet[T]) Cardinality() int { - t.RLock() - defer t.RUnlock() - return len(*t.uss) -} - -func (t *threadSafeSet[T]) Each(cb func(T) bool) { - t.RLock() - for elem := range *t.uss { - if cb(elem) { - break - } - } - t.RUnlock() -} - -func (t *threadSafeSet[T]) Iter() <-chan T { - ch := make(chan T) - go func() { - t.RLock() - - for elem := range *t.uss { - ch <- elem - } - close(ch) - t.RUnlock() - }() - - return ch -} - -func (t *threadSafeSet[T]) Iterator() *Iterator[T] { - iterator, ch, stopCh := newIterator[T]() - - go func() { - t.RLock() - L: - for elem := range *t.uss { - select { - case <-stopCh: - break L - case ch <- elem: - } - } - close(ch) - t.RUnlock() - }() - - return iterator -} - -func (t *threadSafeSet[T]) Equal(other Set[T]) bool { - o := other.(*threadSafeSet[T]) - - t.RLock() - o.RLock() - - ret := t.uss.Equal(o.uss) - t.RUnlock() - o.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) Clone() Set[T] { - t.RLock() - - unsafeClone := t.uss.Clone().(*threadUnsafeSet[T]) - ret := &threadSafeSet[T]{uss: unsafeClone} - t.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) String() string { - t.RLock() - ret := t.uss.String() - t.RUnlock() - return ret -} - -func (t *threadSafeSet[T]) Pop() (T, bool) { - t.Lock() - defer t.Unlock() - return t.uss.Pop() -} - -func (t *threadSafeSet[T]) ToSlice() []T { - keys := make([]T, 0, t.Cardinality()) - t.RLock() - for elem := range *t.uss { - keys = append(keys, elem) - } - t.RUnlock() - return keys -} - -func (t *threadSafeSet[T]) MarshalJSON() ([]byte, error) { - t.RLock() - b, err := t.uss.MarshalJSON() - t.RUnlock() - - return b, err -} - -func (t *threadSafeSet[T]) UnmarshalJSON(p []byte) error { - t.RLock() - err := t.uss.UnmarshalJSON(p) - t.RUnlock() - - return err -} diff --git a/vendor/github.com/deckarep/golang-set/v2/threadunsafe.go b/vendor/github.com/deckarep/golang-set/v2/threadunsafe.go deleted file mode 100644 index c95d32b..0000000 --- a/vendor/github.com/deckarep/golang-set/v2/threadunsafe.go +++ /dev/null @@ -1,352 +0,0 @@ -/* -Open Source Initiative OSI - The MIT License (MIT):Licensing - -The MIT License (MIT) -Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -package mapset - -import ( - "encoding/json" - "fmt" - "strings" -) - -type threadUnsafeSet[T comparable] map[T]struct{} - -// Assert concrete type:threadUnsafeSet adheres to Set interface. -var _ Set[string] = (*threadUnsafeSet[string])(nil) - -func newThreadUnsafeSet[T comparable]() *threadUnsafeSet[T] { - t := make(threadUnsafeSet[T]) - return &t -} - -func newThreadUnsafeSetWithSize[T comparable](cardinality int) *threadUnsafeSet[T] { - t := make(threadUnsafeSet[T], cardinality) - return &t -} - -func (s threadUnsafeSet[T]) Add(v T) bool { - prevLen := len(s) - s[v] = struct{}{} - return prevLen != len(s) -} - -func (s *threadUnsafeSet[T]) Append(v ...T) int { - prevLen := len(*s) - for _, val := range v { - (*s)[val] = struct{}{} - } - return len(*s) - prevLen -} - -// private version of Add which doesn't return a value -func (s *threadUnsafeSet[T]) add(v T) { - (*s)[v] = struct{}{} -} - -func (s *threadUnsafeSet[T]) Cardinality() int { - return len(*s) -} - -func (s *threadUnsafeSet[T]) Clear() { - // Constructions like this are optimised by compiler, and replaced by - // mapclear() function, defined in - // https://github.com/golang/go/blob/29bbca5c2c1ad41b2a9747890d183b6dd3a4ace4/src/runtime/map.go#L993) - for key := range *s { - delete(*s, key) - } -} - -func (s *threadUnsafeSet[T]) Clone() Set[T] { - clonedSet := newThreadUnsafeSetWithSize[T](s.Cardinality()) - for elem := range *s { - clonedSet.add(elem) - } - return clonedSet -} - -func (s *threadUnsafeSet[T]) Contains(v ...T) bool { - for _, val := range v { - if _, ok := (*s)[val]; !ok { - return false - } - } - return true -} - -func (s *threadUnsafeSet[T]) ContainsOne(v T) bool { - _, ok := (*s)[v] - return ok -} - -func (s *threadUnsafeSet[T]) ContainsAny(v ...T) bool { - for _, val := range v { - if _, ok := (*s)[val]; ok { - return true - } - } - return false -} - -func (s *threadUnsafeSet[T]) ContainsAnyElement(other Set[T]) bool { - o := other.(*threadUnsafeSet[T]) - - // loop over smaller set - if s.Cardinality() < other.Cardinality() { - for elem := range *s { - if o.contains(elem) { - return true - } - } - } else { - for elem := range *o { - if s.contains(elem) { - return true - } - } - } - return false -} - -// private version of Contains for a single element v -func (s *threadUnsafeSet[T]) contains(v T) (ok bool) { - _, ok = (*s)[v] - return ok -} - -func (s *threadUnsafeSet[T]) Difference(other Set[T]) Set[T] { - o := other.(*threadUnsafeSet[T]) - - diff := newThreadUnsafeSet[T]() - for elem := range *s { - if !o.contains(elem) { - diff.add(elem) - } - } - return diff -} - -func (s *threadUnsafeSet[T]) Each(cb func(T) bool) { - for elem := range *s { - if cb(elem) { - break - } - } -} - -func (s *threadUnsafeSet[T]) Equal(other Set[T]) bool { - o := other.(*threadUnsafeSet[T]) - - if s.Cardinality() != other.Cardinality() { - return false - } - for elem := range *s { - if !o.contains(elem) { - return false - } - } - return true -} - -func (s *threadUnsafeSet[T]) Intersect(other Set[T]) Set[T] { - o := other.(*threadUnsafeSet[T]) - - intersection := newThreadUnsafeSet[T]() - // loop over smaller set - if s.Cardinality() < other.Cardinality() { - for elem := range *s { - if o.contains(elem) { - intersection.add(elem) - } - } - } else { - for elem := range *o { - if s.contains(elem) { - intersection.add(elem) - } - } - } - return intersection -} - -func (s *threadUnsafeSet[T]) IsEmpty() bool { - return s.Cardinality() == 0 -} - -func (s *threadUnsafeSet[T]) IsProperSubset(other Set[T]) bool { - return s.Cardinality() < other.Cardinality() && s.IsSubset(other) -} - -func (s *threadUnsafeSet[T]) IsProperSuperset(other Set[T]) bool { - return s.Cardinality() > other.Cardinality() && s.IsSuperset(other) -} - -func (s *threadUnsafeSet[T]) IsSubset(other Set[T]) bool { - o := other.(*threadUnsafeSet[T]) - if s.Cardinality() > other.Cardinality() { - return false - } - for elem := range *s { - if !o.contains(elem) { - return false - } - } - return true -} - -func (s *threadUnsafeSet[T]) IsSuperset(other Set[T]) bool { - return other.IsSubset(s) -} - -func (s *threadUnsafeSet[T]) Iter() <-chan T { - ch := make(chan T) - go func() { - for elem := range *s { - ch <- elem - } - close(ch) - }() - - return ch -} - -func (s *threadUnsafeSet[T]) Iterator() *Iterator[T] { - iterator, ch, stopCh := newIterator[T]() - - go func() { - L: - for elem := range *s { - select { - case <-stopCh: - break L - case ch <- elem: - } - } - close(ch) - }() - - return iterator -} - -// Pop returns a popped item in case set is not empty, or nil-value of T -// if set is already empty -func (s *threadUnsafeSet[T]) Pop() (v T, ok bool) { - for item := range *s { - delete(*s, item) - return item, true - } - return v, false -} - -func (s threadUnsafeSet[T]) Remove(v T) { - delete(s, v) -} - -func (s threadUnsafeSet[T]) RemoveAll(i ...T) { - for _, elem := range i { - delete(s, elem) - } -} - -func (s threadUnsafeSet[T]) String() string { - items := make([]string, 0, len(s)) - - for elem := range s { - items = append(items, fmt.Sprintf("%v", elem)) - } - return fmt.Sprintf("Set{%s}", strings.Join(items, ", ")) -} - -func (s *threadUnsafeSet[T]) SymmetricDifference(other Set[T]) Set[T] { - o := other.(*threadUnsafeSet[T]) - - sd := newThreadUnsafeSet[T]() - for elem := range *s { - if !o.contains(elem) { - sd.add(elem) - } - } - for elem := range *o { - if !s.contains(elem) { - sd.add(elem) - } - } - return sd -} - -func (s threadUnsafeSet[T]) ToSlice() []T { - keys := make([]T, 0, s.Cardinality()) - for elem := range s { - keys = append(keys, elem) - } - - return keys -} - -func (s threadUnsafeSet[T]) Union(other Set[T]) Set[T] { - o := other.(*threadUnsafeSet[T]) - - n := s.Cardinality() - if o.Cardinality() > n { - n = o.Cardinality() - } - unionedSet := make(threadUnsafeSet[T], n) - - for elem := range s { - unionedSet.add(elem) - } - for elem := range *o { - unionedSet.add(elem) - } - return &unionedSet -} - -// MarshalJSON creates a JSON array from the set, it marshals all elements -func (s threadUnsafeSet[T]) MarshalJSON() ([]byte, error) { - items := make([]string, 0, s.Cardinality()) - - for elem := range s { - b, err := json.Marshal(elem) - if err != nil { - return nil, err - } - - items = append(items, string(b)) - } - - return []byte(fmt.Sprintf("[%s]", strings.Join(items, ","))), nil -} - -// UnmarshalJSON recreates a set from a JSON array, it only decodes -// primitive types. Numbers are decoded as json.Number. -func (s *threadUnsafeSet[T]) UnmarshalJSON(b []byte) error { - var i []T - err := json.Unmarshal(b, &i) - if err != nil { - return err - } - s.Append(i...) - - return nil -} diff --git a/vendor/github.com/go-stack/stack/LICENSE.md b/vendor/github.com/go-stack/stack/LICENSE.md deleted file mode 100644 index 2abf98e..0000000 --- a/vendor/github.com/go-stack/stack/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Chris Hines - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/go-stack/stack/README.md b/vendor/github.com/go-stack/stack/README.md deleted file mode 100644 index f11cccc..0000000 --- a/vendor/github.com/go-stack/stack/README.md +++ /dev/null @@ -1,38 +0,0 @@ -[![GoDoc](https://godoc.org/github.com/go-stack/stack?status.svg)](https://godoc.org/github.com/go-stack/stack) -[![Go Report Card](https://goreportcard.com/badge/go-stack/stack)](https://goreportcard.com/report/go-stack/stack) -[![TravisCI](https://travis-ci.org/go-stack/stack.svg?branch=master)](https://travis-ci.org/go-stack/stack) -[![Coverage Status](https://coveralls.io/repos/github/go-stack/stack/badge.svg?branch=master)](https://coveralls.io/github/go-stack/stack?branch=master) - -# stack - -Package stack implements utilities to capture, manipulate, and format call -stacks. It provides a simpler API than package runtime. - -The implementation takes care of the minutia and special cases of interpreting -the program counter (pc) values returned by runtime.Callers. - -## Versioning - -Package stack publishes releases via [semver](http://semver.org/) compatible Git -tags prefixed with a single 'v'. The master branch always contains the latest -release. The develop branch contains unreleased commits. - -## Formatting - -Package stack's types implement fmt.Formatter, which provides a simple and -flexible way to declaratively configure formatting when used with logging or -error tracking packages. - -```go -func DoTheThing() { - c := stack.Caller(0) - log.Print(c) // "source.go:10" - log.Printf("%+v", c) // "pkg/path/source.go:10" - log.Printf("%n", c) // "DoTheThing" - - s := stack.Trace().TrimRuntime() - log.Print(s) // "[source.go:15 caller.go:42 main.go:14]" -} -``` - -See the docs for all of the supported formatting options. diff --git a/vendor/github.com/go-stack/stack/stack.go b/vendor/github.com/go-stack/stack/stack.go deleted file mode 100644 index ac3b93b..0000000 --- a/vendor/github.com/go-stack/stack/stack.go +++ /dev/null @@ -1,400 +0,0 @@ -// +build go1.7 - -// Package stack implements utilities to capture, manipulate, and format call -// stacks. It provides a simpler API than package runtime. -// -// The implementation takes care of the minutia and special cases of -// interpreting the program counter (pc) values returned by runtime.Callers. -// -// Package stack's types implement fmt.Formatter, which provides a simple and -// flexible way to declaratively configure formatting when used with logging -// or error tracking packages. -package stack - -import ( - "bytes" - "errors" - "fmt" - "io" - "runtime" - "strconv" - "strings" -) - -// Call records a single function invocation from a goroutine stack. -type Call struct { - frame runtime.Frame -} - -// Caller returns a Call from the stack of the current goroutine. The argument -// skip is the number of stack frames to ascend, with 0 identifying the -// calling function. -func Caller(skip int) Call { - // As of Go 1.9 we need room for up to three PC entries. - // - // 0. An entry for the stack frame prior to the target to check for - // special handling needed if that prior entry is runtime.sigpanic. - // 1. A possible second entry to hold metadata about skipped inlined - // functions. If inline functions were not skipped the target frame - // PC will be here. - // 2. A third entry for the target frame PC when the second entry - // is used for skipped inline functions. - var pcs [3]uintptr - n := runtime.Callers(skip+1, pcs[:]) - frames := runtime.CallersFrames(pcs[:n]) - frame, _ := frames.Next() - frame, _ = frames.Next() - - return Call{ - frame: frame, - } -} - -// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c). -func (c Call) String() string { - return fmt.Sprint(c) -} - -// MarshalText implements encoding.TextMarshaler. It formats the Call the same -// as fmt.Sprintf("%v", c). -func (c Call) MarshalText() ([]byte, error) { - if c.frame == (runtime.Frame{}) { - return nil, ErrNoFunc - } - - buf := bytes.Buffer{} - fmt.Fprint(&buf, c) - return buf.Bytes(), nil -} - -// ErrNoFunc means that the Call has a nil *runtime.Func. The most likely -// cause is a Call with the zero value. -var ErrNoFunc = errors.New("no call stack information") - -// Format implements fmt.Formatter with support for the following verbs. -// -// %s source file -// %d line number -// %n function name -// %k last segment of the package path -// %v equivalent to %s:%d -// -// It accepts the '+' and '#' flags for most of the verbs as follows. -// -// %+s path of source file relative to the compile time GOPATH, -// or the module path joined to the path of source file relative -// to module root -// %#s full path of source file -// %+n import path qualified function name -// %+k full package path -// %+v equivalent to %+s:%d -// %#v equivalent to %#s:%d -func (c Call) Format(s fmt.State, verb rune) { - if c.frame == (runtime.Frame{}) { - fmt.Fprintf(s, "%%!%c(NOFUNC)", verb) - return - } - - switch verb { - case 's', 'v': - file := c.frame.File - switch { - case s.Flag('#'): - // done - case s.Flag('+'): - file = pkgFilePath(&c.frame) - default: - const sep = "/" - if i := strings.LastIndex(file, sep); i != -1 { - file = file[i+len(sep):] - } - } - io.WriteString(s, file) - if verb == 'v' { - buf := [7]byte{':'} - s.Write(strconv.AppendInt(buf[:1], int64(c.frame.Line), 10)) - } - - case 'd': - buf := [6]byte{} - s.Write(strconv.AppendInt(buf[:0], int64(c.frame.Line), 10)) - - case 'k': - name := c.frame.Function - const pathSep = "/" - start, end := 0, len(name) - if i := strings.LastIndex(name, pathSep); i != -1 { - start = i + len(pathSep) - } - const pkgSep = "." - if i := strings.Index(name[start:], pkgSep); i != -1 { - end = start + i - } - if s.Flag('+') { - start = 0 - } - io.WriteString(s, name[start:end]) - - case 'n': - name := c.frame.Function - if !s.Flag('+') { - const pathSep = "/" - if i := strings.LastIndex(name, pathSep); i != -1 { - name = name[i+len(pathSep):] - } - const pkgSep = "." - if i := strings.Index(name, pkgSep); i != -1 { - name = name[i+len(pkgSep):] - } - } - io.WriteString(s, name) - } -} - -// Frame returns the call frame infomation for the Call. -func (c Call) Frame() runtime.Frame { - return c.frame -} - -// PC returns the program counter for this call frame; multiple frames may -// have the same PC value. -// -// Deprecated: Use Call.Frame instead. -func (c Call) PC() uintptr { - return c.frame.PC -} - -// CallStack records a sequence of function invocations from a goroutine -// stack. -type CallStack []Call - -// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs). -func (cs CallStack) String() string { - return fmt.Sprint(cs) -} - -var ( - openBracketBytes = []byte("[") - closeBracketBytes = []byte("]") - spaceBytes = []byte(" ") -) - -// MarshalText implements encoding.TextMarshaler. It formats the CallStack the -// same as fmt.Sprintf("%v", cs). -func (cs CallStack) MarshalText() ([]byte, error) { - buf := bytes.Buffer{} - buf.Write(openBracketBytes) - for i, pc := range cs { - if i > 0 { - buf.Write(spaceBytes) - } - fmt.Fprint(&buf, pc) - } - buf.Write(closeBracketBytes) - return buf.Bytes(), nil -} - -// Format implements fmt.Formatter by printing the CallStack as square brackets -// ([, ]) surrounding a space separated list of Calls each formatted with the -// supplied verb and options. -func (cs CallStack) Format(s fmt.State, verb rune) { - s.Write(openBracketBytes) - for i, pc := range cs { - if i > 0 { - s.Write(spaceBytes) - } - pc.Format(s, verb) - } - s.Write(closeBracketBytes) -} - -// Trace returns a CallStack for the current goroutine with element 0 -// identifying the calling function. -func Trace() CallStack { - var pcs [512]uintptr - n := runtime.Callers(1, pcs[:]) - - frames := runtime.CallersFrames(pcs[:n]) - cs := make(CallStack, 0, n) - - // Skip extra frame retrieved just to make sure the runtime.sigpanic - // special case is handled. - frame, more := frames.Next() - - for more { - frame, more = frames.Next() - cs = append(cs, Call{frame: frame}) - } - - return cs -} - -// TrimBelow returns a slice of the CallStack with all entries below c -// removed. -func (cs CallStack) TrimBelow(c Call) CallStack { - for len(cs) > 0 && cs[0] != c { - cs = cs[1:] - } - return cs -} - -// TrimAbove returns a slice of the CallStack with all entries above c -// removed. -func (cs CallStack) TrimAbove(c Call) CallStack { - for len(cs) > 0 && cs[len(cs)-1] != c { - cs = cs[:len(cs)-1] - } - return cs -} - -// pkgIndex returns the index that results in file[index:] being the path of -// file relative to the compile time GOPATH, and file[:index] being the -// $GOPATH/src/ portion of file. funcName must be the name of a function in -// file as returned by runtime.Func.Name. -func pkgIndex(file, funcName string) int { - // As of Go 1.6.2 there is no direct way to know the compile time GOPATH - // at runtime, but we can infer the number of path segments in the GOPATH. - // We note that runtime.Func.Name() returns the function name qualified by - // the import path, which does not include the GOPATH. Thus we can trim - // segments from the beginning of the file path until the number of path - // separators remaining is one more than the number of path separators in - // the function name. For example, given: - // - // GOPATH /home/user - // file /home/user/src/pkg/sub/file.go - // fn.Name() pkg/sub.Type.Method - // - // We want to produce: - // - // file[:idx] == /home/user/src/ - // file[idx:] == pkg/sub/file.go - // - // From this we can easily see that fn.Name() has one less path separator - // than our desired result for file[idx:]. We count separators from the - // end of the file path until it finds two more than in the function name - // and then move one character forward to preserve the initial path - // segment without a leading separator. - const sep = "/" - i := len(file) - for n := strings.Count(funcName, sep) + 2; n > 0; n-- { - i = strings.LastIndex(file[:i], sep) - if i == -1 { - i = -len(sep) - break - } - } - // get back to 0 or trim the leading separator - return i + len(sep) -} - -// pkgFilePath returns the frame's filepath relative to the compile-time GOPATH, -// or its module path joined to its path relative to the module root. -// -// As of Go 1.11 there is no direct way to know the compile time GOPATH or -// module paths at runtime, but we can piece together the desired information -// from available information. We note that runtime.Frame.Function contains the -// function name qualified by the package path, which includes the module path -// but not the GOPATH. We can extract the package path from that and append the -// last segments of the file path to arrive at the desired package qualified -// file path. For example, given: -// -// GOPATH /home/user -// import path pkg/sub -// frame.File /home/user/src/pkg/sub/file.go -// frame.Function pkg/sub.Type.Method -// Desired return pkg/sub/file.go -// -// It appears that we simply need to trim ".Type.Method" from frame.Function and -// append "/" + path.Base(file). -// -// But there are other wrinkles. Although it is idiomatic to do so, the internal -// name of a package is not required to match the last segment of its import -// path. In addition, the introduction of modules in Go 1.11 allows working -// without a GOPATH. So we also must make these work right: -// -// GOPATH /home/user -// import path pkg/go-sub -// package name sub -// frame.File /home/user/src/pkg/go-sub/file.go -// frame.Function pkg/sub.Type.Method -// Desired return pkg/go-sub/file.go -// -// Module path pkg/v2 -// import path pkg/v2/go-sub -// package name sub -// frame.File /home/user/cloned-pkg/go-sub/file.go -// frame.Function pkg/v2/sub.Type.Method -// Desired return pkg/v2/go-sub/file.go -// -// We can handle all of these situations by using the package path extracted -// from frame.Function up to, but not including, the last segment as the prefix -// and the last two segments of frame.File as the suffix of the returned path. -// This preserves the existing behavior when working in a GOPATH without modules -// and a semantically equivalent behavior when used in module aware project. -func pkgFilePath(frame *runtime.Frame) string { - pre := pkgPrefix(frame.Function) - post := pathSuffix(frame.File) - if pre == "" { - return post - } - return pre + "/" + post -} - -// pkgPrefix returns the import path of the function's package with the final -// segment removed. -func pkgPrefix(funcName string) string { - const pathSep = "/" - end := strings.LastIndex(funcName, pathSep) - if end == -1 { - return "" - } - return funcName[:end] -} - -// pathSuffix returns the last two segments of path. -func pathSuffix(path string) string { - const pathSep = "/" - lastSep := strings.LastIndex(path, pathSep) - if lastSep == -1 { - return path - } - return path[strings.LastIndex(path[:lastSep], pathSep)+1:] -} - -var runtimePath string - -func init() { - var pcs [3]uintptr - runtime.Callers(0, pcs[:]) - frames := runtime.CallersFrames(pcs[:]) - frame, _ := frames.Next() - file := frame.File - - idx := pkgIndex(frame.File, frame.Function) - - runtimePath = file[:idx] - if runtime.GOOS == "windows" { - runtimePath = strings.ToLower(runtimePath) - } -} - -func inGoroot(c Call) bool { - file := c.frame.File - if len(file) == 0 || file[0] == '?' { - return true - } - if runtime.GOOS == "windows" { - file = strings.ToLower(file) - } - return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go") -} - -// TrimRuntime returns a slice of the CallStack with the topmost entries from -// the go runtime removed. It considers any calls originating from unknown -// files, files under GOROOT, or _testmain.go as part of the runtime. -func (cs CallStack) TrimRuntime() CallStack { - for len(cs) > 0 && inGoroot(cs[len(cs)-1]) { - cs = cs[:len(cs)-1] - } - return cs -} diff --git a/vendor/github.com/lib/pq/.gitignore b/vendor/github.com/lib/pq/.gitignore new file mode 100644 index 0000000..3243952 --- /dev/null +++ b/vendor/github.com/lib/pq/.gitignore @@ -0,0 +1,6 @@ +.db +*.test +*~ +*.swp +.idea +.vscode \ No newline at end of file diff --git a/vendor/github.com/lib/pq/LICENSE.md b/vendor/github.com/lib/pq/LICENSE.md new file mode 100644 index 0000000..5773904 --- /dev/null +++ b/vendor/github.com/lib/pq/LICENSE.md @@ -0,0 +1,8 @@ +Copyright (c) 2011-2013, 'pq' Contributors +Portions Copyright (C) 2011 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/lib/pq/README.md b/vendor/github.com/lib/pq/README.md new file mode 100644 index 0000000..126ee5d --- /dev/null +++ b/vendor/github.com/lib/pq/README.md @@ -0,0 +1,36 @@ +# pq - A pure Go postgres driver for Go's database/sql package + +[![GoDoc](https://godoc.org/github.com/lib/pq?status.svg)](https://pkg.go.dev/github.com/lib/pq?tab=doc) + +## Install + + go get github.com/lib/pq + +## Features + +* SSL +* Handles bad connections for `database/sql` +* Scan `time.Time` correctly (i.e. `timestamp[tz]`, `time[tz]`, `date`) +* Scan binary blobs correctly (i.e. `bytea`) +* Package for `hstore` support +* COPY FROM support +* pq.ParseURL for converting urls to connection strings for sql.Open. +* Many libpq compatible environment variables +* Unix socket support +* Notifications: `LISTEN`/`NOTIFY` +* pgpass support +* GSS (Kerberos) auth + +## Tests + +`go test` is used for testing. See [TESTS.md](TESTS.md) for more details. + +## Status + +This package is currently in maintenance mode, which means: +1. It generally does not accept new features. +2. It does accept bug fixes and version compatability changes provided by the community. +3. Maintainers usually do not resolve reported issues. +4. Community members are encouraged to help each other with reported issues. + +For users that require new features or reliable resolution of reported bugs, we recommend using [pgx](https://github.com/jackc/pgx) which is under active development. diff --git a/vendor/github.com/lib/pq/TESTS.md b/vendor/github.com/lib/pq/TESTS.md new file mode 100644 index 0000000..f050211 --- /dev/null +++ b/vendor/github.com/lib/pq/TESTS.md @@ -0,0 +1,33 @@ +# Tests + +## Running Tests + +`go test` is used for testing. A running PostgreSQL +server is required, with the ability to log in. The +database to connect to test with is "pqgotest," on +"localhost" but these can be overridden using [environment +variables](https://www.postgresql.org/docs/9.3/static/libpq-envars.html). + +Example: + + PGHOST=/run/postgresql go test + +## Benchmarks + +A benchmark suite can be run as part of the tests: + + go test -bench . + +## Example setup (Docker) + +Run a postgres container: + +``` +docker run --expose 5432:5432 postgres +``` + +Run tests: + +``` +PGHOST=localhost PGPORT=5432 PGUSER=postgres PGSSLMODE=disable PGDATABASE=postgres go test +``` diff --git a/vendor/github.com/lib/pq/array.go b/vendor/github.com/lib/pq/array.go new file mode 100644 index 0000000..39c8f7e --- /dev/null +++ b/vendor/github.com/lib/pq/array.go @@ -0,0 +1,895 @@ +package pq + +import ( + "bytes" + "database/sql" + "database/sql/driver" + "encoding/hex" + "fmt" + "reflect" + "strconv" + "strings" +) + +var typeByteSlice = reflect.TypeOf([]byte{}) +var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem() +var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem() + +// Array returns the optimal driver.Valuer and sql.Scanner for an array or +// slice of any dimension. +// +// For example: +// db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401})) +// +// var x []sql.NullInt64 +// db.QueryRow(`SELECT ARRAY[235, 401]`).Scan(pq.Array(&x)) +// +// Scanning multi-dimensional arrays is not supported. Arrays where the lower +// bound is not one (such as `[0:0]={1}') are not supported. +func Array(a interface{}) interface { + driver.Valuer + sql.Scanner +} { + switch a := a.(type) { + case []bool: + return (*BoolArray)(&a) + case []float64: + return (*Float64Array)(&a) + case []float32: + return (*Float32Array)(&a) + case []int64: + return (*Int64Array)(&a) + case []int32: + return (*Int32Array)(&a) + case []string: + return (*StringArray)(&a) + case [][]byte: + return (*ByteaArray)(&a) + + case *[]bool: + return (*BoolArray)(a) + case *[]float64: + return (*Float64Array)(a) + case *[]float32: + return (*Float32Array)(a) + case *[]int64: + return (*Int64Array)(a) + case *[]int32: + return (*Int32Array)(a) + case *[]string: + return (*StringArray)(a) + case *[][]byte: + return (*ByteaArray)(a) + } + + return GenericArray{a} +} + +// ArrayDelimiter may be optionally implemented by driver.Valuer or sql.Scanner +// to override the array delimiter used by GenericArray. +type ArrayDelimiter interface { + // ArrayDelimiter returns the delimiter character(s) for this element's type. + ArrayDelimiter() string +} + +// BoolArray represents a one-dimensional array of the PostgreSQL boolean type. +type BoolArray []bool + +// Scan implements the sql.Scanner interface. +func (a *BoolArray) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to BoolArray", src) +} + +func (a *BoolArray) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "BoolArray") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(BoolArray, len(elems)) + for i, v := range elems { + if len(v) != 1 { + return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v) + } + switch v[0] { + case 't': + b[i] = true + case 'f': + b[i] = false + default: + return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v) + } + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a BoolArray) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be exactly two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1+2*n) + + for i := 0; i < n; i++ { + b[2*i] = ',' + if a[i] { + b[1+2*i] = 't' + } else { + b[1+2*i] = 'f' + } + } + + b[0] = '{' + b[2*n] = '}' + + return string(b), nil + } + + return "{}", nil +} + +// ByteaArray represents a one-dimensional array of the PostgreSQL bytea type. +type ByteaArray [][]byte + +// Scan implements the sql.Scanner interface. +func (a *ByteaArray) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to ByteaArray", src) +} + +func (a *ByteaArray) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "ByteaArray") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(ByteaArray, len(elems)) + for i, v := range elems { + b[i], err = parseBytea(v) + if err != nil { + return fmt.Errorf("could not parse bytea array index %d: %s", i, err.Error()) + } + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. It uses the "hex" format which +// is only supported on PostgreSQL 9.0 or newer. +func (a ByteaArray) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, 2*N bytes of quotes, + // 3*N bytes of hex formatting, and N-1 bytes of delimiters. + size := 1 + 6*n + for _, x := range a { + size += hex.EncodedLen(len(x)) + } + + b := make([]byte, size) + + for i, s := 0, b; i < n; i++ { + o := copy(s, `,"\\x`) + o += hex.Encode(s[o:], a[i]) + s[o] = '"' + s = s[o+1:] + } + + b[0] = '{' + b[size-1] = '}' + + return string(b), nil + } + + return "{}", nil +} + +// Float64Array represents a one-dimensional array of the PostgreSQL double +// precision type. +type Float64Array []float64 + +// Scan implements the sql.Scanner interface. +func (a *Float64Array) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to Float64Array", src) +} + +func (a *Float64Array) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "Float64Array") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(Float64Array, len(elems)) + for i, v := range elems { + if b[i], err = strconv.ParseFloat(string(v), 64); err != nil { + return fmt.Errorf("pq: parsing array element index %d: %v", i, err) + } + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a Float64Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+2*n) + b[0] = '{' + + b = strconv.AppendFloat(b, a[0], 'f', -1, 64) + for i := 1; i < n; i++ { + b = append(b, ',') + b = strconv.AppendFloat(b, a[i], 'f', -1, 64) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + +// Float32Array represents a one-dimensional array of the PostgreSQL double +// precision type. +type Float32Array []float32 + +// Scan implements the sql.Scanner interface. +func (a *Float32Array) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to Float32Array", src) +} + +func (a *Float32Array) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "Float32Array") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(Float32Array, len(elems)) + for i, v := range elems { + var x float64 + if x, err = strconv.ParseFloat(string(v), 32); err != nil { + return fmt.Errorf("pq: parsing array element index %d: %v", i, err) + } + b[i] = float32(x) + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a Float32Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+2*n) + b[0] = '{' + + b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 32) + for i := 1; i < n; i++ { + b = append(b, ',') + b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 32) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + +// GenericArray implements the driver.Valuer and sql.Scanner interfaces for +// an array or slice of any dimension. +type GenericArray struct{ A interface{} } + +func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) { + var assign func([]byte, reflect.Value) error + var del = "," + + // TODO calculate the assign function for other types + // TODO repeat this section on the element type of arrays or slices (multidimensional) + { + if reflect.PtrTo(rt).Implements(typeSQLScanner) { + // dest is always addressable because it is an element of a slice. + assign = func(src []byte, dest reflect.Value) (err error) { + ss := dest.Addr().Interface().(sql.Scanner) + if src == nil { + err = ss.Scan(nil) + } else { + err = ss.Scan(src) + } + return + } + goto FoundType + } + + assign = func([]byte, reflect.Value) error { + return fmt.Errorf("pq: scanning to %s is not implemented; only sql.Scanner", rt) + } + } + +FoundType: + + if ad, ok := reflect.Zero(rt).Interface().(ArrayDelimiter); ok { + del = ad.ArrayDelimiter() + } + + return rt, assign, del +} + +// Scan implements the sql.Scanner interface. +func (a GenericArray) Scan(src interface{}) error { + dpv := reflect.ValueOf(a.A) + switch { + case dpv.Kind() != reflect.Ptr: + return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A) + case dpv.IsNil(): + return fmt.Errorf("pq: destination %T is nil", a.A) + } + + dv := dpv.Elem() + switch dv.Kind() { + case reflect.Slice: + case reflect.Array: + default: + return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A) + } + + switch src := src.(type) { + case []byte: + return a.scanBytes(src, dv) + case string: + return a.scanBytes([]byte(src), dv) + case nil: + if dv.Kind() == reflect.Slice { + dv.Set(reflect.Zero(dv.Type())) + return nil + } + } + + return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type()) +} + +func (a GenericArray) scanBytes(src []byte, dv reflect.Value) error { + dtype, assign, del := a.evaluateDestination(dv.Type().Elem()) + dims, elems, err := parseArray(src, []byte(del)) + if err != nil { + return err + } + + // TODO allow multidimensional + + if len(dims) > 1 { + return fmt.Errorf("pq: scanning from multidimensional ARRAY%s is not implemented", + strings.Replace(fmt.Sprint(dims), " ", "][", -1)) + } + + // Treat a zero-dimensional array like an array with a single dimension of zero. + if len(dims) == 0 { + dims = append(dims, 0) + } + + for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() { + switch rt.Kind() { + case reflect.Slice: + case reflect.Array: + if rt.Len() != dims[i] { + return fmt.Errorf("pq: cannot convert ARRAY%s to %s", + strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type()) + } + default: + // TODO handle multidimensional + } + } + + values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems)) + for i, e := range elems { + if err := assign(e, values.Index(i)); err != nil { + return fmt.Errorf("pq: parsing array element index %d: %v", i, err) + } + } + + // TODO handle multidimensional + + switch dv.Kind() { + case reflect.Slice: + dv.Set(values.Slice(0, dims[0])) + case reflect.Array: + for i := 0; i < dims[0]; i++ { + dv.Index(i).Set(values.Index(i)) + } + } + + return nil +} + +// Value implements the driver.Valuer interface. +func (a GenericArray) Value() (driver.Value, error) { + if a.A == nil { + return nil, nil + } + + rv := reflect.ValueOf(a.A) + + switch rv.Kind() { + case reflect.Slice: + if rv.IsNil() { + return nil, nil + } + case reflect.Array: + default: + return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A) + } + + if n := rv.Len(); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 0, 1+2*n) + + b, _, err := appendArray(b, rv, n) + return string(b), err + } + + return "{}", nil +} + +// Int64Array represents a one-dimensional array of the PostgreSQL integer types. +type Int64Array []int64 + +// Scan implements the sql.Scanner interface. +func (a *Int64Array) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to Int64Array", src) +} + +func (a *Int64Array) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "Int64Array") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(Int64Array, len(elems)) + for i, v := range elems { + if b[i], err = strconv.ParseInt(string(v), 10, 64); err != nil { + return fmt.Errorf("pq: parsing array element index %d: %v", i, err) + } + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a Int64Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+2*n) + b[0] = '{' + + b = strconv.AppendInt(b, a[0], 10) + for i := 1; i < n; i++ { + b = append(b, ',') + b = strconv.AppendInt(b, a[i], 10) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + +// Int32Array represents a one-dimensional array of the PostgreSQL integer types. +type Int32Array []int32 + +// Scan implements the sql.Scanner interface. +func (a *Int32Array) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to Int32Array", src) +} + +func (a *Int32Array) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "Int32Array") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(Int32Array, len(elems)) + for i, v := range elems { + x, err := strconv.ParseInt(string(v), 10, 32) + if err != nil { + return fmt.Errorf("pq: parsing array element index %d: %v", i, err) + } + b[i] = int32(x) + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a Int32Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+2*n) + b[0] = '{' + + b = strconv.AppendInt(b, int64(a[0]), 10) + for i := 1; i < n; i++ { + b = append(b, ',') + b = strconv.AppendInt(b, int64(a[i]), 10) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + +// StringArray represents a one-dimensional array of the PostgreSQL character types. +type StringArray []string + +// Scan implements the sql.Scanner interface. +func (a *StringArray) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to StringArray", src) +} + +func (a *StringArray) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "StringArray") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(StringArray, len(elems)) + for i, v := range elems { + if b[i] = string(v); v == nil { + return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i) + } + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a StringArray) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, 2*N bytes of quotes, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+3*n) + b[0] = '{' + + b = appendArrayQuotedBytes(b, []byte(a[0])) + for i := 1; i < n; i++ { + b = append(b, ',') + b = appendArrayQuotedBytes(b, []byte(a[i])) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + +// appendArray appends rv to the buffer, returning the extended buffer and +// the delimiter used between elements. +// +// It panics when n <= 0 or rv's Kind is not reflect.Array nor reflect.Slice. +func appendArray(b []byte, rv reflect.Value, n int) ([]byte, string, error) { + var del string + var err error + + b = append(b, '{') + + if b, del, err = appendArrayElement(b, rv.Index(0)); err != nil { + return b, del, err + } + + for i := 1; i < n; i++ { + b = append(b, del...) + if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil { + return b, del, err + } + } + + return append(b, '}'), del, nil +} + +// appendArrayElement appends rv to the buffer, returning the extended buffer +// and the delimiter to use before the next element. +// +// When rv's Kind is neither reflect.Array nor reflect.Slice, it is converted +// using driver.DefaultParameterConverter and the resulting []byte or string +// is double-quoted. +// +// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO +func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) { + if k := rv.Kind(); k == reflect.Array || k == reflect.Slice { + if t := rv.Type(); t != typeByteSlice && !t.Implements(typeDriverValuer) { + if n := rv.Len(); n > 0 { + return appendArray(b, rv, n) + } + + return b, "", nil + } + } + + var del = "," + var err error + var iv interface{} = rv.Interface() + + if ad, ok := iv.(ArrayDelimiter); ok { + del = ad.ArrayDelimiter() + } + + if iv, err = driver.DefaultParameterConverter.ConvertValue(iv); err != nil { + return b, del, err + } + + switch v := iv.(type) { + case nil: + return append(b, "NULL"...), del, nil + case []byte: + return appendArrayQuotedBytes(b, v), del, nil + case string: + return appendArrayQuotedBytes(b, []byte(v)), del, nil + } + + b, err = appendValue(b, iv) + return b, del, err +} + +func appendArrayQuotedBytes(b, v []byte) []byte { + b = append(b, '"') + for { + i := bytes.IndexAny(v, `"\`) + if i < 0 { + b = append(b, v...) + break + } + if i > 0 { + b = append(b, v[:i]...) + } + b = append(b, '\\', v[i]) + v = v[i+1:] + } + return append(b, '"') +} + +func appendValue(b []byte, v driver.Value) ([]byte, error) { + return append(b, encode(nil, v, 0)...), nil +} + +// parseArray extracts the dimensions and elements of an array represented in +// text format. Only representations emitted by the backend are supported. +// Notably, whitespace around brackets and delimiters is significant, and NULL +// is case-sensitive. +// +// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO +func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) { + var depth, i int + + if len(src) < 1 || src[0] != '{' { + return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0) + } + +Open: + for i < len(src) { + switch src[i] { + case '{': + depth++ + i++ + case '}': + elems = make([][]byte, 0) + goto Close + default: + break Open + } + } + dims = make([]int, i) + +Element: + for i < len(src) { + switch src[i] { + case '{': + if depth == len(dims) { + break Element + } + depth++ + dims[depth-1] = 0 + i++ + case '"': + var elem = []byte{} + var escape bool + for i++; i < len(src); i++ { + if escape { + elem = append(elem, src[i]) + escape = false + } else { + switch src[i] { + default: + elem = append(elem, src[i]) + case '\\': + escape = true + case '"': + elems = append(elems, elem) + i++ + break Element + } + } + } + default: + for start := i; i < len(src); i++ { + if bytes.HasPrefix(src[i:], del) || src[i] == '}' { + elem := src[start:i] + if len(elem) == 0 { + return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i) + } + if bytes.Equal(elem, []byte("NULL")) { + elem = nil + } + elems = append(elems, elem) + break Element + } + } + } + } + + for i < len(src) { + if bytes.HasPrefix(src[i:], del) && depth > 0 { + dims[depth-1]++ + i += len(del) + goto Element + } else if src[i] == '}' && depth > 0 { + dims[depth-1]++ + depth-- + i++ + } else { + return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i) + } + } + +Close: + for i < len(src) { + if src[i] == '}' && depth > 0 { + depth-- + i++ + } else { + return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i) + } + } + if depth > 0 { + err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i) + } + if err == nil { + for _, d := range dims { + if (len(elems) % d) != 0 { + err = fmt.Errorf("pq: multidimensional arrays must have elements with matching dimensions") + } + } + } + return +} + +func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) { + dims, elems, err := parseArray(src, del) + if err != nil { + return nil, err + } + if len(dims) > 1 { + return nil, fmt.Errorf("pq: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ) + } + return elems, err +} diff --git a/vendor/github.com/lib/pq/buf.go b/vendor/github.com/lib/pq/buf.go new file mode 100644 index 0000000..4b0a0a8 --- /dev/null +++ b/vendor/github.com/lib/pq/buf.go @@ -0,0 +1,91 @@ +package pq + +import ( + "bytes" + "encoding/binary" + + "github.com/lib/pq/oid" +) + +type readBuf []byte + +func (b *readBuf) int32() (n int) { + n = int(int32(binary.BigEndian.Uint32(*b))) + *b = (*b)[4:] + return +} + +func (b *readBuf) oid() (n oid.Oid) { + n = oid.Oid(binary.BigEndian.Uint32(*b)) + *b = (*b)[4:] + return +} + +// N.B: this is actually an unsigned 16-bit integer, unlike int32 +func (b *readBuf) int16() (n int) { + n = int(binary.BigEndian.Uint16(*b)) + *b = (*b)[2:] + return +} + +func (b *readBuf) string() string { + i := bytes.IndexByte(*b, 0) + if i < 0 { + errorf("invalid message format; expected string terminator") + } + s := (*b)[:i] + *b = (*b)[i+1:] + return string(s) +} + +func (b *readBuf) next(n int) (v []byte) { + v = (*b)[:n] + *b = (*b)[n:] + return +} + +func (b *readBuf) byte() byte { + return b.next(1)[0] +} + +type writeBuf struct { + buf []byte + pos int +} + +func (b *writeBuf) int32(n int) { + x := make([]byte, 4) + binary.BigEndian.PutUint32(x, uint32(n)) + b.buf = append(b.buf, x...) +} + +func (b *writeBuf) int16(n int) { + x := make([]byte, 2) + binary.BigEndian.PutUint16(x, uint16(n)) + b.buf = append(b.buf, x...) +} + +func (b *writeBuf) string(s string) { + b.buf = append(append(b.buf, s...), '\000') +} + +func (b *writeBuf) byte(c byte) { + b.buf = append(b.buf, c) +} + +func (b *writeBuf) bytes(v []byte) { + b.buf = append(b.buf, v...) +} + +func (b *writeBuf) wrap() []byte { + p := b.buf[b.pos:] + binary.BigEndian.PutUint32(p, uint32(len(p))) + return b.buf +} + +func (b *writeBuf) next(c byte) { + p := b.buf[b.pos:] + binary.BigEndian.PutUint32(p, uint32(len(p))) + b.pos = len(b.buf) + 1 + b.buf = append(b.buf, c, 0, 0, 0, 0) +} diff --git a/vendor/github.com/lib/pq/conn.go b/vendor/github.com/lib/pq/conn.go new file mode 100644 index 0000000..da4ff9d --- /dev/null +++ b/vendor/github.com/lib/pq/conn.go @@ -0,0 +1,2112 @@ +package pq + +import ( + "bufio" + "bytes" + "context" + "crypto/md5" + "crypto/sha256" + "database/sql" + "database/sql/driver" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "os" + "os/user" + "path" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + "unicode" + + "github.com/lib/pq/oid" + "github.com/lib/pq/scram" +) + +// Common error types +var ( + ErrNotSupported = errors.New("pq: Unsupported command") + ErrInFailedTransaction = errors.New("pq: Could not complete operation in a failed transaction") + ErrSSLNotSupported = errors.New("pq: SSL is not enabled on the server") + ErrSSLKeyUnknownOwnership = errors.New("pq: Could not get owner information for private key, may not be properly protected") + ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key has world access. Permissions should be u=rw,g=r (0640) if owned by root, or u=rw (0600), or less") + + ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly") + + errUnexpectedReady = errors.New("unexpected ReadyForQuery") + errNoRowsAffected = errors.New("no RowsAffected available after the empty statement") + errNoLastInsertID = errors.New("no LastInsertId available after the empty statement") +) + +// Compile time validation that our types implement the expected interfaces +var ( + _ driver.Driver = Driver{} +) + +// Driver is the Postgres database driver. +type Driver struct{} + +// Open opens a new connection to the database. name is a connection string. +// Most users should only use it through database/sql package from the standard +// library. +func (d Driver) Open(name string) (driver.Conn, error) { + return Open(name) +} + +func init() { + sql.Register("postgres", &Driver{}) +} + +type parameterStatus struct { + // server version in the same format as server_version_num, or 0 if + // unavailable + serverVersion int + + // the current location based on the TimeZone value of the session, if + // available + currentLocation *time.Location +} + +type transactionStatus byte + +const ( + txnStatusIdle transactionStatus = 'I' + txnStatusIdleInTransaction transactionStatus = 'T' + txnStatusInFailedTransaction transactionStatus = 'E' +) + +func (s transactionStatus) String() string { + switch s { + case txnStatusIdle: + return "idle" + case txnStatusIdleInTransaction: + return "idle in transaction" + case txnStatusInFailedTransaction: + return "in a failed transaction" + default: + errorf("unknown transactionStatus %d", s) + } + + panic("not reached") +} + +// Dialer is the dialer interface. It can be used to obtain more control over +// how pq creates network connections. +type Dialer interface { + Dial(network, address string) (net.Conn, error) + DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) +} + +// DialerContext is the context-aware dialer interface. +type DialerContext interface { + DialContext(ctx context.Context, network, address string) (net.Conn, error) +} + +type defaultDialer struct { + d net.Dialer +} + +func (d defaultDialer) Dial(network, address string) (net.Conn, error) { + return d.d.Dial(network, address) +} +func (d defaultDialer) DialTimeout( + network, address string, timeout time.Duration, +) (net.Conn, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return d.DialContext(ctx, network, address) +} +func (d defaultDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return d.d.DialContext(ctx, network, address) +} + +type conn struct { + c net.Conn + buf *bufio.Reader + namei int + scratch [512]byte + txnStatus transactionStatus + txnFinish func() + + // Save connection arguments to use during CancelRequest. + dialer Dialer + opts values + + // Cancellation key data for use with CancelRequest messages. + processID int + secretKey int + + parameterStatus parameterStatus + + saveMessageType byte + saveMessageBuffer []byte + + // If an error is set, this connection is bad and all public-facing + // functions should return the appropriate error by calling get() + // (ErrBadConn) or getForNext(). + err syncErr + + // If set, this connection should never use the binary format when + // receiving query results from prepared statements. Only provided for + // debugging. + disablePreparedBinaryResult bool + + // Whether to always send []byte parameters over as binary. Enables single + // round-trip mode for non-prepared Query calls. + binaryParameters bool + + // If true this connection is in the middle of a COPY + inCopy bool + + // If not nil, notices will be synchronously sent here + noticeHandler func(*Error) + + // If not nil, notifications will be synchronously sent here + notificationHandler func(*Notification) + + // GSSAPI context + gss GSS +} + +type syncErr struct { + err error + sync.Mutex +} + +// Return ErrBadConn if connection is bad. +func (e *syncErr) get() error { + e.Lock() + defer e.Unlock() + if e.err != nil { + return driver.ErrBadConn + } + return nil +} + +// Return the error set on the connection. Currently only used by rows.Next. +func (e *syncErr) getForNext() error { + e.Lock() + defer e.Unlock() + return e.err +} + +// Set error, only if it isn't set yet. +func (e *syncErr) set(err error) { + if err == nil { + panic("attempt to set nil err") + } + e.Lock() + defer e.Unlock() + if e.err == nil { + e.err = err + } +} + +// Handle driver-side settings in parsed connection string. +func (cn *conn) handleDriverSettings(o values) (err error) { + boolSetting := func(key string, val *bool) error { + if value, ok := o[key]; ok { + if value == "yes" { + *val = true + } else if value == "no" { + *val = false + } else { + return fmt.Errorf("unrecognized value %q for %s", value, key) + } + } + return nil + } + + err = boolSetting("disable_prepared_binary_result", &cn.disablePreparedBinaryResult) + if err != nil { + return err + } + return boolSetting("binary_parameters", &cn.binaryParameters) +} + +func (cn *conn) handlePgpass(o values) { + // if a password was supplied, do not process .pgpass + if _, ok := o["password"]; ok { + return + } + filename := os.Getenv("PGPASSFILE") + if filename == "" { + // XXX this code doesn't work on Windows where the default filename is + // XXX %APPDATA%\postgresql\pgpass.conf + // Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470 + userHome := os.Getenv("HOME") + if userHome == "" { + user, err := user.Current() + if err != nil { + return + } + userHome = user.HomeDir + } + filename = filepath.Join(userHome, ".pgpass") + } + fileinfo, err := os.Stat(filename) + if err != nil { + return + } + mode := fileinfo.Mode() + if mode&(0x77) != 0 { + // XXX should warn about incorrect .pgpass permissions as psql does + return + } + file, err := os.Open(filename) + if err != nil { + return + } + defer file.Close() + scanner := bufio.NewScanner(io.Reader(file)) + // From: https://github.com/tg/pgpass/blob/master/reader.go + for scanner.Scan() { + if scanText(scanner.Text(), o) { + break + } + } +} + +// GetFields is a helper function for scanText. +func getFields(s string) []string { + fs := make([]string, 0, 5) + f := make([]rune, 0, len(s)) + + var esc bool + for _, c := range s { + switch { + case esc: + f = append(f, c) + esc = false + case c == '\\': + esc = true + case c == ':': + fs = append(fs, string(f)) + f = f[:0] + default: + f = append(f, c) + } + } + return append(fs, string(f)) +} + +// ScanText assists HandlePgpass in it's objective. +func scanText(line string, o values) bool { + hostname := o["host"] + ntw, _ := network(o) + port := o["port"] + db := o["dbname"] + username := o["user"] + if len(line) == 0 || line[0] == '#' { + return false + } + split := getFields(line) + if len(split) != 5 { + return false + } + if (split[0] == "*" || split[0] == hostname || (split[0] == "localhost" && (hostname == "" || ntw == "unix"))) && (split[1] == "*" || split[1] == port) && (split[2] == "*" || split[2] == db) && (split[3] == "*" || split[3] == username) { + o["password"] = split[4] + return true + } + return false +} + +func (cn *conn) writeBuf(b byte) *writeBuf { + cn.scratch[0] = b + return &writeBuf{ + buf: cn.scratch[:5], + pos: 1, + } +} + +// Open opens a new connection to the database. dsn is a connection string. +// Most users should only use it through database/sql package from the standard +// library. +func Open(dsn string) (_ driver.Conn, err error) { + return DialOpen(defaultDialer{}, dsn) +} + +// DialOpen opens a new connection to the database using a dialer. +func DialOpen(d Dialer, dsn string) (_ driver.Conn, err error) { + c, err := NewConnector(dsn) + if err != nil { + return nil, err + } + c.Dialer(d) + return c.open(context.Background()) +} + +func (c *Connector) open(ctx context.Context) (cn *conn, err error) { + // Handle any panics during connection initialization. Note that we + // specifically do *not* want to use errRecover(), as that would turn any + // connection errors into ErrBadConns, hiding the real error message from + // the user. + defer errRecoverNoErrBadConn(&err) + + // Create a new values map (copy). This makes it so maps in different + // connections do not reference the same underlying data structure, so it + // is safe for multiple connections to concurrently write to their opts. + o := make(values) + for k, v := range c.opts { + o[k] = v + } + + cn = &conn{ + opts: o, + dialer: c.dialer, + } + err = cn.handleDriverSettings(o) + if err != nil { + return nil, err + } + cn.handlePgpass(o) + + cn.c, err = dial(ctx, c.dialer, o) + if err != nil { + return nil, err + } + + err = cn.ssl(o) + if err != nil { + if cn.c != nil { + cn.c.Close() + } + return nil, err + } + + // cn.startup panics on error. Make sure we don't leak cn.c. + panicking := true + defer func() { + if panicking { + cn.c.Close() + } + }() + + cn.buf = bufio.NewReader(cn.c) + cn.startup(o) + + // reset the deadline, in case one was set (see dial) + if timeout, ok := o["connect_timeout"]; ok && timeout != "0" { + err = cn.c.SetDeadline(time.Time{}) + } + panicking = false + return cn, err +} + +func dial(ctx context.Context, d Dialer, o values) (net.Conn, error) { + network, address := network(o) + + // Zero or not specified means wait indefinitely. + if timeout, ok := o["connect_timeout"]; ok && timeout != "0" { + seconds, err := strconv.ParseInt(timeout, 10, 0) + if err != nil { + return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err) + } + duration := time.Duration(seconds) * time.Second + + // connect_timeout should apply to the entire connection establishment + // procedure, so we both use a timeout for the TCP connection + // establishment and set a deadline for doing the initial handshake. + // The deadline is then reset after startup() is done. + deadline := time.Now().Add(duration) + var conn net.Conn + if dctx, ok := d.(DialerContext); ok { + ctx, cancel := context.WithTimeout(ctx, duration) + defer cancel() + conn, err = dctx.DialContext(ctx, network, address) + } else { + conn, err = d.DialTimeout(network, address, duration) + } + if err != nil { + return nil, err + } + err = conn.SetDeadline(deadline) + return conn, err + } + if dctx, ok := d.(DialerContext); ok { + return dctx.DialContext(ctx, network, address) + } + return d.Dial(network, address) +} + +func network(o values) (string, string) { + host := o["host"] + + if strings.HasPrefix(host, "/") { + sockPath := path.Join(host, ".s.PGSQL."+o["port"]) + return "unix", sockPath + } + + return "tcp", net.JoinHostPort(host, o["port"]) +} + +type values map[string]string + +// scanner implements a tokenizer for libpq-style option strings. +type scanner struct { + s []rune + i int +} + +// newScanner returns a new scanner initialized with the option string s. +func newScanner(s string) *scanner { + return &scanner{[]rune(s), 0} +} + +// Next returns the next rune. +// It returns 0, false if the end of the text has been reached. +func (s *scanner) Next() (rune, bool) { + if s.i >= len(s.s) { + return 0, false + } + r := s.s[s.i] + s.i++ + return r, true +} + +// SkipSpaces returns the next non-whitespace rune. +// It returns 0, false if the end of the text has been reached. +func (s *scanner) SkipSpaces() (rune, bool) { + r, ok := s.Next() + for unicode.IsSpace(r) && ok { + r, ok = s.Next() + } + return r, ok +} + +// parseOpts parses the options from name and adds them to the values. +// +// The parsing code is based on conninfo_parse from libpq's fe-connect.c +func parseOpts(name string, o values) error { + s := newScanner(name) + + for { + var ( + keyRunes, valRunes []rune + r rune + ok bool + ) + + if r, ok = s.SkipSpaces(); !ok { + break + } + + // Scan the key + for !unicode.IsSpace(r) && r != '=' { + keyRunes = append(keyRunes, r) + if r, ok = s.Next(); !ok { + break + } + } + + // Skip any whitespace if we're not at the = yet + if r != '=' { + r, ok = s.SkipSpaces() + } + + // The current character should be = + if r != '=' || !ok { + return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes)) + } + + // Skip any whitespace after the = + if r, ok = s.SkipSpaces(); !ok { + // If we reach the end here, the last value is just an empty string as per libpq. + o[string(keyRunes)] = "" + break + } + + if r != '\'' { + for !unicode.IsSpace(r) { + if r == '\\' { + if r, ok = s.Next(); !ok { + return fmt.Errorf(`missing character after backslash`) + } + } + valRunes = append(valRunes, r) + + if r, ok = s.Next(); !ok { + break + } + } + } else { + quote: + for { + if r, ok = s.Next(); !ok { + return fmt.Errorf(`unterminated quoted string literal in connection string`) + } + switch r { + case '\'': + break quote + case '\\': + r, _ = s.Next() + fallthrough + default: + valRunes = append(valRunes, r) + } + } + } + + o[string(keyRunes)] = string(valRunes) + } + + return nil +} + +func (cn *conn) isInTransaction() bool { + return cn.txnStatus == txnStatusIdleInTransaction || + cn.txnStatus == txnStatusInFailedTransaction +} + +func (cn *conn) checkIsInTransaction(intxn bool) { + if cn.isInTransaction() != intxn { + cn.err.set(driver.ErrBadConn) + errorf("unexpected transaction status %v", cn.txnStatus) + } +} + +func (cn *conn) Begin() (_ driver.Tx, err error) { + return cn.begin("") +} + +func (cn *conn) begin(mode string) (_ driver.Tx, err error) { + if err := cn.err.get(); err != nil { + return nil, err + } + defer cn.errRecover(&err) + + cn.checkIsInTransaction(false) + _, commandTag, err := cn.simpleExec("BEGIN" + mode) + if err != nil { + return nil, err + } + if commandTag != "BEGIN" { + cn.err.set(driver.ErrBadConn) + return nil, fmt.Errorf("unexpected command tag %s", commandTag) + } + if cn.txnStatus != txnStatusIdleInTransaction { + cn.err.set(driver.ErrBadConn) + return nil, fmt.Errorf("unexpected transaction status %v", cn.txnStatus) + } + return cn, nil +} + +func (cn *conn) closeTxn() { + if finish := cn.txnFinish; finish != nil { + finish() + } +} + +func (cn *conn) Commit() (err error) { + defer cn.closeTxn() + if err := cn.err.get(); err != nil { + return err + } + defer cn.errRecover(&err) + + cn.checkIsInTransaction(true) + // We don't want the client to think that everything is okay if it tries + // to commit a failed transaction. However, no matter what we return, + // database/sql will release this connection back into the free connection + // pool so we have to abort the current transaction here. Note that you + // would get the same behaviour if you issued a COMMIT in a failed + // transaction, so it's also the least surprising thing to do here. + if cn.txnStatus == txnStatusInFailedTransaction { + if err := cn.rollback(); err != nil { + return err + } + return ErrInFailedTransaction + } + + _, commandTag, err := cn.simpleExec("COMMIT") + if err != nil { + if cn.isInTransaction() { + cn.err.set(driver.ErrBadConn) + } + return err + } + if commandTag != "COMMIT" { + cn.err.set(driver.ErrBadConn) + return fmt.Errorf("unexpected command tag %s", commandTag) + } + cn.checkIsInTransaction(false) + return nil +} + +func (cn *conn) Rollback() (err error) { + defer cn.closeTxn() + if err := cn.err.get(); err != nil { + return err + } + defer cn.errRecover(&err) + return cn.rollback() +} + +func (cn *conn) rollback() (err error) { + cn.checkIsInTransaction(true) + _, commandTag, err := cn.simpleExec("ROLLBACK") + if err != nil { + if cn.isInTransaction() { + cn.err.set(driver.ErrBadConn) + } + return err + } + if commandTag != "ROLLBACK" { + return fmt.Errorf("unexpected command tag %s", commandTag) + } + cn.checkIsInTransaction(false) + return nil +} + +func (cn *conn) gname() string { + cn.namei++ + return strconv.FormatInt(int64(cn.namei), 10) +} + +func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err error) { + b := cn.writeBuf('Q') + b.string(q) + cn.send(b) + + for { + t, r := cn.recv1() + switch t { + case 'C': + res, commandTag = cn.parseComplete(r.string()) + case 'Z': + cn.processReadyForQuery(r) + if res == nil && err == nil { + err = errUnexpectedReady + } + // done + return + case 'E': + err = parseError(r) + case 'I': + res = emptyRows + case 'T', 'D': + // ignore any results + default: + cn.err.set(driver.ErrBadConn) + errorf("unknown response for simple query: %q", t) + } + } +} + +func (cn *conn) simpleQuery(q string) (res *rows, err error) { + defer cn.errRecover(&err) + + b := cn.writeBuf('Q') + b.string(q) + cn.send(b) + + for { + t, r := cn.recv1() + switch t { + case 'C', 'I': + // We allow queries which don't return any results through Query as + // well as Exec. We still have to give database/sql a rows object + // the user can close, though, to avoid connections from being + // leaked. A "rows" with done=true works fine for that purpose. + if err != nil { + cn.err.set(driver.ErrBadConn) + errorf("unexpected message %q in simple query execution", t) + } + if res == nil { + res = &rows{ + cn: cn, + } + } + // Set the result and tag to the last command complete if there wasn't a + // query already run. Although queries usually return from here and cede + // control to Next, a query with zero results does not. + if t == 'C' { + res.result, res.tag = cn.parseComplete(r.string()) + if res.colNames != nil { + return + } + } + res.done = true + case 'Z': + cn.processReadyForQuery(r) + // done + return + case 'E': + res = nil + err = parseError(r) + case 'D': + if res == nil { + cn.err.set(driver.ErrBadConn) + errorf("unexpected DataRow in simple query execution") + } + // the query didn't fail; kick off to Next + cn.saveMessage(t, r) + return + case 'T': + // res might be non-nil here if we received a previous + // CommandComplete, but that's fine; just overwrite it + res = &rows{cn: cn} + res.rowsHeader = parsePortalRowDescribe(r) + + // To work around a bug in QueryRow in Go 1.2 and earlier, wait + // until the first DataRow has been received. + default: + cn.err.set(driver.ErrBadConn) + errorf("unknown response for simple query: %q", t) + } + } +} + +type noRows struct{} + +var emptyRows noRows + +var _ driver.Result = noRows{} + +func (noRows) LastInsertId() (int64, error) { + return 0, errNoLastInsertID +} + +func (noRows) RowsAffected() (int64, error) { + return 0, errNoRowsAffected +} + +// Decides which column formats to use for a prepared statement. The input is +// an array of type oids, one element per result column. +func decideColumnFormats( + colTyps []fieldDesc, forceText bool, +) (colFmts []format, colFmtData []byte) { + if len(colTyps) == 0 { + return nil, colFmtDataAllText + } + + colFmts = make([]format, len(colTyps)) + if forceText { + return colFmts, colFmtDataAllText + } + + allBinary := true + allText := true + for i, t := range colTyps { + switch t.OID { + // This is the list of types to use binary mode for when receiving them + // through a prepared statement. If a type appears in this list, it + // must also be implemented in binaryDecode in encode.go. + case oid.T_bytea: + fallthrough + case oid.T_int8: + fallthrough + case oid.T_int4: + fallthrough + case oid.T_int2: + fallthrough + case oid.T_uuid: + colFmts[i] = formatBinary + allText = false + + default: + allBinary = false + } + } + + if allBinary { + return colFmts, colFmtDataAllBinary + } else if allText { + return colFmts, colFmtDataAllText + } else { + colFmtData = make([]byte, 2+len(colFmts)*2) + binary.BigEndian.PutUint16(colFmtData, uint16(len(colFmts))) + for i, v := range colFmts { + binary.BigEndian.PutUint16(colFmtData[2+i*2:], uint16(v)) + } + return colFmts, colFmtData + } +} + +func (cn *conn) prepareTo(q, stmtName string) *stmt { + st := &stmt{cn: cn, name: stmtName} + + b := cn.writeBuf('P') + b.string(st.name) + b.string(q) + b.int16(0) + + b.next('D') + b.byte('S') + b.string(st.name) + + b.next('S') + cn.send(b) + + cn.readParseResponse() + st.paramTyps, st.colNames, st.colTyps = cn.readStatementDescribeResponse() + st.colFmts, st.colFmtData = decideColumnFormats(st.colTyps, cn.disablePreparedBinaryResult) + cn.readReadyForQuery() + return st +} + +func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) { + if err := cn.err.get(); err != nil { + return nil, err + } + defer cn.errRecover(&err) + + if len(q) >= 4 && strings.EqualFold(q[:4], "COPY") { + s, err := cn.prepareCopyIn(q) + if err == nil { + cn.inCopy = true + } + return s, err + } + return cn.prepareTo(q, cn.gname()), nil +} + +func (cn *conn) Close() (err error) { + // Skip cn.bad return here because we always want to close a connection. + defer cn.errRecover(&err) + + // Ensure that cn.c.Close is always run. Since error handling is done with + // panics and cn.errRecover, the Close must be in a defer. + defer func() { + cerr := cn.c.Close() + if err == nil { + err = cerr + } + }() + + // Don't go through send(); ListenerConn relies on us not scribbling on the + // scratch buffer of this connection. + return cn.sendSimpleMessage('X') +} + +// Implement the "Queryer" interface +func (cn *conn) Query(query string, args []driver.Value) (driver.Rows, error) { + return cn.query(query, args) +} + +func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) { + if err := cn.err.get(); err != nil { + return nil, err + } + if cn.inCopy { + return nil, errCopyInProgress + } + defer cn.errRecover(&err) + + // Check to see if we can use the "simpleQuery" interface, which is + // *much* faster than going through prepare/exec + if len(args) == 0 { + return cn.simpleQuery(query) + } + + if cn.binaryParameters { + cn.sendBinaryModeQuery(query, args) + + cn.readParseResponse() + cn.readBindResponse() + rows := &rows{cn: cn} + rows.rowsHeader = cn.readPortalDescribeResponse() + cn.postExecuteWorkaround() + return rows, nil + } + st := cn.prepareTo(query, "") + st.exec(args) + return &rows{ + cn: cn, + rowsHeader: st.rowsHeader, + }, nil +} + +// Implement the optional "Execer" interface for one-shot queries +func (cn *conn) Exec(query string, args []driver.Value) (res driver.Result, err error) { + if err := cn.err.get(); err != nil { + return nil, err + } + defer cn.errRecover(&err) + + // Check to see if we can use the "simpleExec" interface, which is + // *much* faster than going through prepare/exec + if len(args) == 0 { + // ignore commandTag, our caller doesn't care + r, _, err := cn.simpleExec(query) + return r, err + } + + if cn.binaryParameters { + cn.sendBinaryModeQuery(query, args) + + cn.readParseResponse() + cn.readBindResponse() + cn.readPortalDescribeResponse() + cn.postExecuteWorkaround() + res, _, err = cn.readExecuteResponse("Execute") + return res, err + } + // Use the unnamed statement to defer planning until bind + // time, or else value-based selectivity estimates cannot be + // used. + st := cn.prepareTo(query, "") + r, err := st.Exec(args) + if err != nil { + panic(err) + } + return r, err +} + +type safeRetryError struct { + Err error +} + +func (se *safeRetryError) Error() string { + return se.Err.Error() +} + +func (cn *conn) send(m *writeBuf) { + n, err := cn.c.Write(m.wrap()) + if err != nil { + if n == 0 { + err = &safeRetryError{Err: err} + } + panic(err) + } +} + +func (cn *conn) sendStartupPacket(m *writeBuf) error { + _, err := cn.c.Write((m.wrap())[1:]) + return err +} + +// Send a message of type typ to the server on the other end of cn. The +// message should have no payload. This method does not use the scratch +// buffer. +func (cn *conn) sendSimpleMessage(typ byte) (err error) { + _, err = cn.c.Write([]byte{typ, '\x00', '\x00', '\x00', '\x04'}) + return err +} + +// saveMessage memorizes a message and its buffer in the conn struct. +// recvMessage will then return these values on the next call to it. This +// method is useful in cases where you have to see what the next message is +// going to be (e.g. to see whether it's an error or not) but you can't handle +// the message yourself. +func (cn *conn) saveMessage(typ byte, buf *readBuf) { + if cn.saveMessageType != 0 { + cn.err.set(driver.ErrBadConn) + errorf("unexpected saveMessageType %d", cn.saveMessageType) + } + cn.saveMessageType = typ + cn.saveMessageBuffer = *buf +} + +// recvMessage receives any message from the backend, or returns an error if +// a problem occurred while reading the message. +func (cn *conn) recvMessage(r *readBuf) (byte, error) { + // workaround for a QueryRow bug, see exec + if cn.saveMessageType != 0 { + t := cn.saveMessageType + *r = cn.saveMessageBuffer + cn.saveMessageType = 0 + cn.saveMessageBuffer = nil + return t, nil + } + + x := cn.scratch[:5] + _, err := io.ReadFull(cn.buf, x) + if err != nil { + return 0, err + } + + // read the type and length of the message that follows + t := x[0] + n := int(binary.BigEndian.Uint32(x[1:])) - 4 + var y []byte + if n <= len(cn.scratch) { + y = cn.scratch[:n] + } else { + y = make([]byte, n) + } + _, err = io.ReadFull(cn.buf, y) + if err != nil { + return 0, err + } + *r = y + return t, nil +} + +// recv receives a message from the backend, but if an error happened while +// reading the message or the received message was an ErrorResponse, it panics. +// NoticeResponses are ignored. This function should generally be used only +// during the startup sequence. +func (cn *conn) recv() (t byte, r *readBuf) { + for { + var err error + r = &readBuf{} + t, err = cn.recvMessage(r) + if err != nil { + panic(err) + } + switch t { + case 'E': + panic(parseError(r)) + case 'N': + if n := cn.noticeHandler; n != nil { + n(parseError(r)) + } + case 'A': + if n := cn.notificationHandler; n != nil { + n(recvNotification(r)) + } + default: + return + } + } +} + +// recv1Buf is exactly equivalent to recv1, except it uses a buffer supplied by +// the caller to avoid an allocation. +func (cn *conn) recv1Buf(r *readBuf) byte { + for { + t, err := cn.recvMessage(r) + if err != nil { + panic(err) + } + + switch t { + case 'A': + if n := cn.notificationHandler; n != nil { + n(recvNotification(r)) + } + case 'N': + if n := cn.noticeHandler; n != nil { + n(parseError(r)) + } + case 'S': + cn.processParameterStatus(r) + default: + return t + } + } +} + +// recv1 receives a message from the backend, panicking if an error occurs +// while attempting to read it. All asynchronous messages are ignored, with +// the exception of ErrorResponse. +func (cn *conn) recv1() (t byte, r *readBuf) { + r = &readBuf{} + t = cn.recv1Buf(r) + return t, r +} + +func (cn *conn) ssl(o values) error { + upgrade, err := ssl(o) + if err != nil { + return err + } + + if upgrade == nil { + // Nothing to do + return nil + } + + w := cn.writeBuf(0) + w.int32(80877103) + if err = cn.sendStartupPacket(w); err != nil { + return err + } + + b := cn.scratch[:1] + _, err = io.ReadFull(cn.c, b) + if err != nil { + return err + } + + if b[0] != 'S' { + return ErrSSLNotSupported + } + + cn.c, err = upgrade(cn.c) + return err +} + +// isDriverSetting returns true iff a setting is purely for configuring the +// driver's options and should not be sent to the server in the connection +// startup packet. +func isDriverSetting(key string) bool { + switch key { + case "host", "port": + return true + case "password": + return true + case "sslmode", "sslcert", "sslkey", "sslrootcert", "sslinline", "sslsni": + return true + case "fallback_application_name": + return true + case "connect_timeout": + return true + case "disable_prepared_binary_result": + return true + case "binary_parameters": + return true + case "krbsrvname": + return true + case "krbspn": + return true + default: + return false + } +} + +func (cn *conn) startup(o values) { + w := cn.writeBuf(0) + w.int32(196608) + // Send the backend the name of the database we want to connect to, and the + // user we want to connect as. Additionally, we send over any run-time + // parameters potentially included in the connection string. If the server + // doesn't recognize any of them, it will reply with an error. + for k, v := range o { + if isDriverSetting(k) { + // skip options which can't be run-time parameters + continue + } + // The protocol requires us to supply the database name as "database" + // instead of "dbname". + if k == "dbname" { + k = "database" + } + w.string(k) + w.string(v) + } + w.string("") + if err := cn.sendStartupPacket(w); err != nil { + panic(err) + } + + for { + t, r := cn.recv() + switch t { + case 'K': + cn.processBackendKeyData(r) + case 'S': + cn.processParameterStatus(r) + case 'R': + cn.auth(r, o) + case 'Z': + cn.processReadyForQuery(r) + return + default: + errorf("unknown response for startup: %q", t) + } + } +} + +func (cn *conn) auth(r *readBuf, o values) { + switch code := r.int32(); code { + case 0: + // OK + case 3: + w := cn.writeBuf('p') + w.string(o["password"]) + cn.send(w) + + t, r := cn.recv() + if t != 'R' { + errorf("unexpected password response: %q", t) + } + + if r.int32() != 0 { + errorf("unexpected authentication response: %q", t) + } + case 5: + s := string(r.next(4)) + w := cn.writeBuf('p') + w.string("md5" + md5s(md5s(o["password"]+o["user"])+s)) + cn.send(w) + + t, r := cn.recv() + if t != 'R' { + errorf("unexpected password response: %q", t) + } + + if r.int32() != 0 { + errorf("unexpected authentication response: %q", t) + } + case 7: // GSSAPI, startup + if newGss == nil { + errorf("kerberos error: no GSSAPI provider registered (import github.com/lib/pq/auth/kerberos if you need Kerberos support)") + } + cli, err := newGss() + if err != nil { + errorf("kerberos error: %s", err.Error()) + } + + var token []byte + + if spn, ok := o["krbspn"]; ok { + // Use the supplied SPN if provided.. + token, err = cli.GetInitTokenFromSpn(spn) + } else { + // Allow the kerberos service name to be overridden + service := "postgres" + if val, ok := o["krbsrvname"]; ok { + service = val + } + + token, err = cli.GetInitToken(o["host"], service) + } + + if err != nil { + errorf("failed to get Kerberos ticket: %q", err) + } + + w := cn.writeBuf('p') + w.bytes(token) + cn.send(w) + + // Store for GSSAPI continue message + cn.gss = cli + + case 8: // GSSAPI continue + + if cn.gss == nil { + errorf("GSSAPI protocol error") + } + + b := []byte(*r) + + done, tokOut, err := cn.gss.Continue(b) + if err == nil && !done { + w := cn.writeBuf('p') + w.bytes(tokOut) + cn.send(w) + } + + // Errors fall through and read the more detailed message + // from the server.. + + case 10: + sc := scram.NewClient(sha256.New, o["user"], o["password"]) + sc.Step(nil) + if sc.Err() != nil { + errorf("SCRAM-SHA-256 error: %s", sc.Err().Error()) + } + scOut := sc.Out() + + w := cn.writeBuf('p') + w.string("SCRAM-SHA-256") + w.int32(len(scOut)) + w.bytes(scOut) + cn.send(w) + + t, r := cn.recv() + if t != 'R' { + errorf("unexpected password response: %q", t) + } + + if r.int32() != 11 { + errorf("unexpected authentication response: %q", t) + } + + nextStep := r.next(len(*r)) + sc.Step(nextStep) + if sc.Err() != nil { + errorf("SCRAM-SHA-256 error: %s", sc.Err().Error()) + } + + scOut = sc.Out() + w = cn.writeBuf('p') + w.bytes(scOut) + cn.send(w) + + t, r = cn.recv() + if t != 'R' { + errorf("unexpected password response: %q", t) + } + + if r.int32() != 12 { + errorf("unexpected authentication response: %q", t) + } + + nextStep = r.next(len(*r)) + sc.Step(nextStep) + if sc.Err() != nil { + errorf("SCRAM-SHA-256 error: %s", sc.Err().Error()) + } + + default: + errorf("unknown authentication response: %d", code) + } +} + +type format int + +const formatText format = 0 +const formatBinary format = 1 + +// One result-column format code with the value 1 (i.e. all binary). +var colFmtDataAllBinary = []byte{0, 1, 0, 1} + +// No result-column format codes (i.e. all text). +var colFmtDataAllText = []byte{0, 0} + +type stmt struct { + cn *conn + name string + rowsHeader + colFmtData []byte + paramTyps []oid.Oid + closed bool +} + +func (st *stmt) Close() (err error) { + if st.closed { + return nil + } + if err := st.cn.err.get(); err != nil { + return err + } + defer st.cn.errRecover(&err) + + w := st.cn.writeBuf('C') + w.byte('S') + w.string(st.name) + st.cn.send(w) + + st.cn.send(st.cn.writeBuf('S')) + + t, _ := st.cn.recv1() + if t != '3' { + st.cn.err.set(driver.ErrBadConn) + errorf("unexpected close response: %q", t) + } + st.closed = true + + t, r := st.cn.recv1() + if t != 'Z' { + st.cn.err.set(driver.ErrBadConn) + errorf("expected ready for query, but got: %q", t) + } + st.cn.processReadyForQuery(r) + + return nil +} + +func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) { + return st.query(v) +} + +func (st *stmt) query(v []driver.Value) (r *rows, err error) { + if err := st.cn.err.get(); err != nil { + return nil, err + } + defer st.cn.errRecover(&err) + + st.exec(v) + return &rows{ + cn: st.cn, + rowsHeader: st.rowsHeader, + }, nil +} + +func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) { + if err := st.cn.err.get(); err != nil { + return nil, err + } + defer st.cn.errRecover(&err) + + st.exec(v) + res, _, err = st.cn.readExecuteResponse("simple query") + return res, err +} + +func (st *stmt) exec(v []driver.Value) { + if len(v) >= 65536 { + errorf("got %d parameters but PostgreSQL only supports 65535 parameters", len(v)) + } + if len(v) != len(st.paramTyps) { + errorf("got %d parameters but the statement requires %d", len(v), len(st.paramTyps)) + } + + cn := st.cn + w := cn.writeBuf('B') + w.byte(0) // unnamed portal + w.string(st.name) + + if cn.binaryParameters { + cn.sendBinaryParameters(w, v) + } else { + w.int16(0) + w.int16(len(v)) + for i, x := range v { + if x == nil { + w.int32(-1) + } else { + b := encode(&cn.parameterStatus, x, st.paramTyps[i]) + w.int32(len(b)) + w.bytes(b) + } + } + } + w.bytes(st.colFmtData) + + w.next('E') + w.byte(0) + w.int32(0) + + w.next('S') + cn.send(w) + + cn.readBindResponse() + cn.postExecuteWorkaround() + +} + +func (st *stmt) NumInput() int { + return len(st.paramTyps) +} + +// parseComplete parses the "command tag" from a CommandComplete message, and +// returns the number of rows affected (if applicable) and a string +// identifying only the command that was executed, e.g. "ALTER TABLE". If the +// command tag could not be parsed, parseComplete panics. +func (cn *conn) parseComplete(commandTag string) (driver.Result, string) { + commandsWithAffectedRows := []string{ + "SELECT ", + // INSERT is handled below + "UPDATE ", + "DELETE ", + "FETCH ", + "MOVE ", + "COPY ", + } + + var affectedRows *string + for _, tag := range commandsWithAffectedRows { + if strings.HasPrefix(commandTag, tag) { + t := commandTag[len(tag):] + affectedRows = &t + commandTag = tag[:len(tag)-1] + break + } + } + // INSERT also includes the oid of the inserted row in its command tag. + // Oids in user tables are deprecated, and the oid is only returned when + // exactly one row is inserted, so it's unlikely to be of value to any + // real-world application and we can ignore it. + if affectedRows == nil && strings.HasPrefix(commandTag, "INSERT ") { + parts := strings.Split(commandTag, " ") + if len(parts) != 3 { + cn.err.set(driver.ErrBadConn) + errorf("unexpected INSERT command tag %s", commandTag) + } + affectedRows = &parts[len(parts)-1] + commandTag = "INSERT" + } + // There should be no affected rows attached to the tag, just return it + if affectedRows == nil { + return driver.RowsAffected(0), commandTag + } + n, err := strconv.ParseInt(*affectedRows, 10, 64) + if err != nil { + cn.err.set(driver.ErrBadConn) + errorf("could not parse commandTag: %s", err) + } + return driver.RowsAffected(n), commandTag +} + +type rowsHeader struct { + colNames []string + colTyps []fieldDesc + colFmts []format +} + +type rows struct { + cn *conn + finish func() + rowsHeader + done bool + rb readBuf + result driver.Result + tag string + + next *rowsHeader +} + +func (rs *rows) Close() error { + if finish := rs.finish; finish != nil { + defer finish() + } + // no need to look at cn.bad as Next() will + for { + err := rs.Next(nil) + switch err { + case nil: + case io.EOF: + // rs.Next can return io.EOF on both 'Z' (ready for query) and 'T' (row + // description, used with HasNextResultSet). We need to fetch messages until + // we hit a 'Z', which is done by waiting for done to be set. + if rs.done { + return nil + } + default: + return err + } + } +} + +func (rs *rows) Columns() []string { + return rs.colNames +} + +func (rs *rows) Result() driver.Result { + if rs.result == nil { + return emptyRows + } + return rs.result +} + +func (rs *rows) Tag() string { + return rs.tag +} + +func (rs *rows) Next(dest []driver.Value) (err error) { + if rs.done { + return io.EOF + } + + conn := rs.cn + if err := conn.err.getForNext(); err != nil { + return err + } + defer conn.errRecover(&err) + + for { + t := conn.recv1Buf(&rs.rb) + switch t { + case 'E': + err = parseError(&rs.rb) + case 'C', 'I': + if t == 'C' { + rs.result, rs.tag = conn.parseComplete(rs.rb.string()) + } + continue + case 'Z': + conn.processReadyForQuery(&rs.rb) + rs.done = true + if err != nil { + return err + } + return io.EOF + case 'D': + n := rs.rb.int16() + if err != nil { + conn.err.set(driver.ErrBadConn) + errorf("unexpected DataRow after error %s", err) + } + if n < len(dest) { + dest = dest[:n] + } + for i := range dest { + l := rs.rb.int32() + if l == -1 { + dest[i] = nil + continue + } + dest[i] = decode(&conn.parameterStatus, rs.rb.next(l), rs.colTyps[i].OID, rs.colFmts[i]) + } + return + case 'T': + next := parsePortalRowDescribe(&rs.rb) + rs.next = &next + return io.EOF + default: + errorf("unexpected message after execute: %q", t) + } + } +} + +func (rs *rows) HasNextResultSet() bool { + hasNext := rs.next != nil && !rs.done + return hasNext +} + +func (rs *rows) NextResultSet() error { + if rs.next == nil { + return io.EOF + } + rs.rowsHeader = *rs.next + rs.next = nil + return nil +} + +// QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be +// used as part of an SQL statement. For example: +// +// tblname := "my_table" +// data := "my_data" +// quoted := pq.QuoteIdentifier(tblname) +// err := db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ($1)", quoted), data) +// +// Any double quotes in name will be escaped. The quoted identifier will be +// case sensitive when used in a query. If the input string contains a zero +// byte, the result will be truncated immediately before it. +func QuoteIdentifier(name string) string { + end := strings.IndexRune(name, 0) + if end > -1 { + name = name[:end] + } + return `"` + strings.Replace(name, `"`, `""`, -1) + `"` +} + +// BufferQuoteIdentifier satisfies the same purpose as QuoteIdentifier, but backed by a +// byte buffer. +func BufferQuoteIdentifier(name string, buffer *bytes.Buffer) { + end := strings.IndexRune(name, 0) + if end > -1 { + name = name[:end] + } + buffer.WriteRune('"') + buffer.WriteString(strings.Replace(name, `"`, `""`, -1)) + buffer.WriteRune('"') +} + +// QuoteLiteral quotes a 'literal' (e.g. a parameter, often used to pass literal +// to DDL and other statements that do not accept parameters) to be used as part +// of an SQL statement. For example: +// +// exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z") +// err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date)) +// +// Any single quotes in name will be escaped. Any backslashes (i.e. "\") will be +// replaced by two backslashes (i.e. "\\") and the C-style escape identifier +// that PostgreSQL provides ('E') will be prepended to the string. +func QuoteLiteral(literal string) string { + // This follows the PostgreSQL internal algorithm for handling quoted literals + // from libpq, which can be found in the "PQEscapeStringInternal" function, + // which is found in the libpq/fe-exec.c source file: + // https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/interfaces/libpq/fe-exec.c + // + // substitute any single-quotes (') with two single-quotes ('') + literal = strings.Replace(literal, `'`, `''`, -1) + // determine if the string has any backslashes (\) in it. + // if it does, replace any backslashes (\) with two backslashes (\\) + // then, we need to wrap the entire string with a PostgreSQL + // C-style escape. Per how "PQEscapeStringInternal" handles this case, we + // also add a space before the "E" + if strings.Contains(literal, `\`) { + literal = strings.Replace(literal, `\`, `\\`, -1) + literal = ` E'` + literal + `'` + } else { + // otherwise, we can just wrap the literal with a pair of single quotes + literal = `'` + literal + `'` + } + return literal +} + +func md5s(s string) string { + h := md5.New() + h.Write([]byte(s)) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func (cn *conn) sendBinaryParameters(b *writeBuf, args []driver.Value) { + // Do one pass over the parameters to see if we're going to send any of + // them over in binary. If we are, create a paramFormats array at the + // same time. + var paramFormats []int + for i, x := range args { + _, ok := x.([]byte) + if ok { + if paramFormats == nil { + paramFormats = make([]int, len(args)) + } + paramFormats[i] = 1 + } + } + if paramFormats == nil { + b.int16(0) + } else { + b.int16(len(paramFormats)) + for _, x := range paramFormats { + b.int16(x) + } + } + + b.int16(len(args)) + for _, x := range args { + if x == nil { + b.int32(-1) + } else { + datum := binaryEncode(&cn.parameterStatus, x) + b.int32(len(datum)) + b.bytes(datum) + } + } +} + +func (cn *conn) sendBinaryModeQuery(query string, args []driver.Value) { + if len(args) >= 65536 { + errorf("got %d parameters but PostgreSQL only supports 65535 parameters", len(args)) + } + + b := cn.writeBuf('P') + b.byte(0) // unnamed statement + b.string(query) + b.int16(0) + + b.next('B') + b.int16(0) // unnamed portal and statement + cn.sendBinaryParameters(b, args) + b.bytes(colFmtDataAllText) + + b.next('D') + b.byte('P') + b.byte(0) // unnamed portal + + b.next('E') + b.byte(0) + b.int32(0) + + b.next('S') + cn.send(b) +} + +func (cn *conn) processParameterStatus(r *readBuf) { + var err error + + param := r.string() + switch param { + case "server_version": + var major1 int + var major2 int + _, err = fmt.Sscanf(r.string(), "%d.%d", &major1, &major2) + if err == nil { + cn.parameterStatus.serverVersion = major1*10000 + major2*100 + } + + case "TimeZone": + cn.parameterStatus.currentLocation, err = time.LoadLocation(r.string()) + if err != nil { + cn.parameterStatus.currentLocation = nil + } + + default: + // ignore + } +} + +func (cn *conn) processReadyForQuery(r *readBuf) { + cn.txnStatus = transactionStatus(r.byte()) +} + +func (cn *conn) readReadyForQuery() { + t, r := cn.recv1() + switch t { + case 'Z': + cn.processReadyForQuery(r) + return + default: + cn.err.set(driver.ErrBadConn) + errorf("unexpected message %q; expected ReadyForQuery", t) + } +} + +func (cn *conn) processBackendKeyData(r *readBuf) { + cn.processID = r.int32() + cn.secretKey = r.int32() +} + +func (cn *conn) readParseResponse() { + t, r := cn.recv1() + switch t { + case '1': + return + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.err.set(driver.ErrBadConn) + errorf("unexpected Parse response %q", t) + } +} + +func (cn *conn) readStatementDescribeResponse() ( + paramTyps []oid.Oid, + colNames []string, + colTyps []fieldDesc, +) { + for { + t, r := cn.recv1() + switch t { + case 't': + nparams := r.int16() + paramTyps = make([]oid.Oid, nparams) + for i := range paramTyps { + paramTyps[i] = r.oid() + } + case 'n': + return paramTyps, nil, nil + case 'T': + colNames, colTyps = parseStatementRowDescribe(r) + return paramTyps, colNames, colTyps + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.err.set(driver.ErrBadConn) + errorf("unexpected Describe statement response %q", t) + } + } +} + +func (cn *conn) readPortalDescribeResponse() rowsHeader { + t, r := cn.recv1() + switch t { + case 'T': + return parsePortalRowDescribe(r) + case 'n': + return rowsHeader{} + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.err.set(driver.ErrBadConn) + errorf("unexpected Describe response %q", t) + } + panic("not reached") +} + +func (cn *conn) readBindResponse() { + t, r := cn.recv1() + switch t { + case '2': + return + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.err.set(driver.ErrBadConn) + errorf("unexpected Bind response %q", t) + } +} + +func (cn *conn) postExecuteWorkaround() { + // Work around a bug in sql.DB.QueryRow: in Go 1.2 and earlier it ignores + // any errors from rows.Next, which masks errors that happened during the + // execution of the query. To avoid the problem in common cases, we wait + // here for one more message from the database. If it's not an error the + // query will likely succeed (or perhaps has already, if it's a + // CommandComplete), so we push the message into the conn struct; recv1 + // will return it as the next message for rows.Next or rows.Close. + // However, if it's an error, we wait until ReadyForQuery and then return + // the error to our caller. + for { + t, r := cn.recv1() + switch t { + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + case 'C', 'D', 'I': + // the query didn't fail, but we can't process this message + cn.saveMessage(t, r) + return + default: + cn.err.set(driver.ErrBadConn) + errorf("unexpected message during extended query execution: %q", t) + } + } +} + +// Only for Exec(), since we ignore the returned data +func (cn *conn) readExecuteResponse( + protocolState string, +) (res driver.Result, commandTag string, err error) { + for { + t, r := cn.recv1() + switch t { + case 'C': + if err != nil { + cn.err.set(driver.ErrBadConn) + errorf("unexpected CommandComplete after error %s", err) + } + res, commandTag = cn.parseComplete(r.string()) + case 'Z': + cn.processReadyForQuery(r) + if res == nil && err == nil { + err = errUnexpectedReady + } + return res, commandTag, err + case 'E': + err = parseError(r) + case 'T', 'D', 'I': + if err != nil { + cn.err.set(driver.ErrBadConn) + errorf("unexpected %q after error %s", t, err) + } + if t == 'I' { + res = emptyRows + } + // ignore any results + default: + cn.err.set(driver.ErrBadConn) + errorf("unknown %s response: %q", protocolState, t) + } + } +} + +func parseStatementRowDescribe(r *readBuf) (colNames []string, colTyps []fieldDesc) { + n := r.int16() + colNames = make([]string, n) + colTyps = make([]fieldDesc, n) + for i := range colNames { + colNames[i] = r.string() + r.next(6) + colTyps[i].OID = r.oid() + colTyps[i].Len = r.int16() + colTyps[i].Mod = r.int32() + // format code not known when describing a statement; always 0 + r.next(2) + } + return +} + +func parsePortalRowDescribe(r *readBuf) rowsHeader { + n := r.int16() + colNames := make([]string, n) + colFmts := make([]format, n) + colTyps := make([]fieldDesc, n) + for i := range colNames { + colNames[i] = r.string() + r.next(6) + colTyps[i].OID = r.oid() + colTyps[i].Len = r.int16() + colTyps[i].Mod = r.int32() + colFmts[i] = format(r.int16()) + } + return rowsHeader{ + colNames: colNames, + colFmts: colFmts, + colTyps: colTyps, + } +} + +// parseEnviron tries to mimic some of libpq's environment handling +// +// To ease testing, it does not directly reference os.Environ, but is +// designed to accept its output. +// +// Environment-set connection information is intended to have a higher +// precedence than a library default but lower than any explicitly +// passed information (such as in the URL or connection string). +func parseEnviron(env []string) (out map[string]string) { + out = make(map[string]string) + + for _, v := range env { + parts := strings.SplitN(v, "=", 2) + + accrue := func(keyname string) { + out[keyname] = parts[1] + } + unsupported := func() { + panic(fmt.Sprintf("setting %v not supported", parts[0])) + } + + // The order of these is the same as is seen in the + // PostgreSQL 9.1 manual. Unsupported but well-defined + // keys cause a panic; these should be unset prior to + // execution. Options which pq expects to be set to a + // certain value are allowed, but must be set to that + // value if present (they can, of course, be absent). + switch parts[0] { + case "PGHOST": + accrue("host") + case "PGHOSTADDR": + unsupported() + case "PGPORT": + accrue("port") + case "PGDATABASE": + accrue("dbname") + case "PGUSER": + accrue("user") + case "PGPASSWORD": + accrue("password") + case "PGSERVICE", "PGSERVICEFILE", "PGREALM": + unsupported() + case "PGOPTIONS": + accrue("options") + case "PGAPPNAME": + accrue("application_name") + case "PGSSLMODE": + accrue("sslmode") + case "PGSSLCERT": + accrue("sslcert") + case "PGSSLKEY": + accrue("sslkey") + case "PGSSLROOTCERT": + accrue("sslrootcert") + case "PGSSLSNI": + accrue("sslsni") + case "PGREQUIRESSL", "PGSSLCRL": + unsupported() + case "PGREQUIREPEER": + unsupported() + case "PGKRBSRVNAME", "PGGSSLIB": + unsupported() + case "PGCONNECT_TIMEOUT": + accrue("connect_timeout") + case "PGCLIENTENCODING": + accrue("client_encoding") + case "PGDATESTYLE": + accrue("datestyle") + case "PGTZ": + accrue("timezone") + case "PGGEQO": + accrue("geqo") + case "PGSYSCONFDIR", "PGLOCALEDIR": + unsupported() + } + } + + return out +} + +// isUTF8 returns whether name is a fuzzy variation of the string "UTF-8". +func isUTF8(name string) bool { + // Recognize all sorts of silly things as "UTF-8", like Postgres does + s := strings.Map(alnumLowerASCII, name) + return s == "utf8" || s == "unicode" +} + +func alnumLowerASCII(ch rune) rune { + if 'A' <= ch && ch <= 'Z' { + return ch + ('a' - 'A') + } + if 'a' <= ch && ch <= 'z' || '0' <= ch && ch <= '9' { + return ch + } + return -1 // discard +} + +// The database/sql/driver package says: +// All Conn implementations should implement the following interfaces: Pinger, SessionResetter, and Validator. +var _ driver.Pinger = &conn{} +var _ driver.SessionResetter = &conn{} + +func (cn *conn) ResetSession(ctx context.Context) error { + // Ensure bad connections are reported: From database/sql/driver: + // If a connection is never returned to the connection pool but immediately reused, then + // ResetSession is called prior to reuse but IsValid is not called. + return cn.err.get() +} + +func (cn *conn) IsValid() bool { + return cn.err.get() == nil +} diff --git a/vendor/github.com/lib/pq/conn_go115.go b/vendor/github.com/lib/pq/conn_go115.go new file mode 100644 index 0000000..f4ef030 --- /dev/null +++ b/vendor/github.com/lib/pq/conn_go115.go @@ -0,0 +1,8 @@ +//go:build go1.15 +// +build go1.15 + +package pq + +import "database/sql/driver" + +var _ driver.Validator = &conn{} diff --git a/vendor/github.com/lib/pq/conn_go18.go b/vendor/github.com/lib/pq/conn_go18.go new file mode 100644 index 0000000..63d4ca6 --- /dev/null +++ b/vendor/github.com/lib/pq/conn_go18.go @@ -0,0 +1,247 @@ +package pq + +import ( + "context" + "database/sql" + "database/sql/driver" + "fmt" + "io" + "io/ioutil" + "time" +) + +const ( + watchCancelDialContextTimeout = time.Second * 10 +) + +// Implement the "QueryerContext" interface +func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + list := make([]driver.Value, len(args)) + for i, nv := range args { + list[i] = nv.Value + } + finish := cn.watchCancel(ctx) + r, err := cn.query(query, list) + if err != nil { + if finish != nil { + finish() + } + return nil, err + } + r.finish = finish + return r, nil +} + +// Implement the "ExecerContext" interface +func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + list := make([]driver.Value, len(args)) + for i, nv := range args { + list[i] = nv.Value + } + + if finish := cn.watchCancel(ctx); finish != nil { + defer finish() + } + + return cn.Exec(query, list) +} + +// Implement the "ConnPrepareContext" interface +func (cn *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + if finish := cn.watchCancel(ctx); finish != nil { + defer finish() + } + return cn.Prepare(query) +} + +// Implement the "ConnBeginTx" interface +func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + var mode string + + switch sql.IsolationLevel(opts.Isolation) { + case sql.LevelDefault: + // Don't touch mode: use the server's default + case sql.LevelReadUncommitted: + mode = " ISOLATION LEVEL READ UNCOMMITTED" + case sql.LevelReadCommitted: + mode = " ISOLATION LEVEL READ COMMITTED" + case sql.LevelRepeatableRead: + mode = " ISOLATION LEVEL REPEATABLE READ" + case sql.LevelSerializable: + mode = " ISOLATION LEVEL SERIALIZABLE" + default: + return nil, fmt.Errorf("pq: isolation level not supported: %d", opts.Isolation) + } + + if opts.ReadOnly { + mode += " READ ONLY" + } else { + mode += " READ WRITE" + } + + tx, err := cn.begin(mode) + if err != nil { + return nil, err + } + cn.txnFinish = cn.watchCancel(ctx) + return tx, nil +} + +func (cn *conn) Ping(ctx context.Context) error { + if finish := cn.watchCancel(ctx); finish != nil { + defer finish() + } + rows, err := cn.simpleQuery(";") + if err != nil { + return driver.ErrBadConn // https://golang.org/pkg/database/sql/driver/#Pinger + } + rows.Close() + return nil +} + +func (cn *conn) watchCancel(ctx context.Context) func() { + if done := ctx.Done(); done != nil { + finished := make(chan struct{}, 1) + go func() { + select { + case <-done: + select { + case finished <- struct{}{}: + default: + // We raced with the finish func, let the next query handle this with the + // context. + return + } + + // Set the connection state to bad so it does not get reused. + cn.err.set(ctx.Err()) + + // At this point the function level context is canceled, + // so it must not be used for the additional network + // request to cancel the query. + // Create a new context to pass into the dial. + ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout) + defer cancel() + + _ = cn.cancel(ctxCancel) + case <-finished: + } + }() + return func() { + select { + case <-finished: + cn.err.set(ctx.Err()) + cn.Close() + case finished <- struct{}{}: + } + } + } + return nil +} + +func (cn *conn) cancel(ctx context.Context) error { + // Create a new values map (copy). This makes sure the connection created + // in this method cannot write to the same underlying data, which could + // cause a concurrent map write panic. This is necessary because cancel + // is called from a goroutine in watchCancel. + o := make(values) + for k, v := range cn.opts { + o[k] = v + } + + c, err := dial(ctx, cn.dialer, o) + if err != nil { + return err + } + defer c.Close() + + { + can := conn{ + c: c, + } + err = can.ssl(o) + if err != nil { + return err + } + + w := can.writeBuf(0) + w.int32(80877102) // cancel request code + w.int32(cn.processID) + w.int32(cn.secretKey) + + if err := can.sendStartupPacket(w); err != nil { + return err + } + } + + // Read until EOF to ensure that the server received the cancel. + { + _, err := io.Copy(ioutil.Discard, c) + return err + } +} + +// Implement the "StmtQueryContext" interface +func (st *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + list := make([]driver.Value, len(args)) + for i, nv := range args { + list[i] = nv.Value + } + finish := st.watchCancel(ctx) + r, err := st.query(list) + if err != nil { + if finish != nil { + finish() + } + return nil, err + } + r.finish = finish + return r, nil +} + +// Implement the "StmtExecContext" interface +func (st *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + list := make([]driver.Value, len(args)) + for i, nv := range args { + list[i] = nv.Value + } + + if finish := st.watchCancel(ctx); finish != nil { + defer finish() + } + + return st.Exec(list) +} + +// watchCancel is implemented on stmt in order to not mark the parent conn as bad +func (st *stmt) watchCancel(ctx context.Context) func() { + if done := ctx.Done(); done != nil { + finished := make(chan struct{}) + go func() { + select { + case <-done: + // At this point the function level context is canceled, + // so it must not be used for the additional network + // request to cancel the query. + // Create a new context to pass into the dial. + ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout) + defer cancel() + + _ = st.cancel(ctxCancel) + finished <- struct{}{} + case <-finished: + } + }() + return func() { + select { + case <-finished: + case finished <- struct{}{}: + } + } + } + return nil +} + +func (st *stmt) cancel(ctx context.Context) error { + return st.cn.cancel(ctx) +} diff --git a/vendor/github.com/lib/pq/connector.go b/vendor/github.com/lib/pq/connector.go new file mode 100644 index 0000000..1145e12 --- /dev/null +++ b/vendor/github.com/lib/pq/connector.go @@ -0,0 +1,120 @@ +package pq + +import ( + "context" + "database/sql/driver" + "errors" + "fmt" + "os" + "strings" +) + +// Connector represents a fixed configuration for the pq driver with a given +// name. Connector satisfies the database/sql/driver Connector interface and +// can be used to create any number of DB Conn's via the database/sql OpenDB +// function. +// +// See https://golang.org/pkg/database/sql/driver/#Connector. +// See https://golang.org/pkg/database/sql/#OpenDB. +type Connector struct { + opts values + dialer Dialer +} + +// Connect returns a connection to the database using the fixed configuration +// of this Connector. Context is not used. +func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { + return c.open(ctx) +} + +// Dialer allows change the dialer used to open connections. +func (c *Connector) Dialer(dialer Dialer) { + c.dialer = dialer +} + +// Driver returns the underlying driver of this Connector. +func (c *Connector) Driver() driver.Driver { + return &Driver{} +} + +// NewConnector returns a connector for the pq driver in a fixed configuration +// with the given dsn. The returned connector can be used to create any number +// of equivalent Conn's. The returned connector is intended to be used with +// database/sql.OpenDB. +// +// See https://golang.org/pkg/database/sql/driver/#Connector. +// See https://golang.org/pkg/database/sql/#OpenDB. +func NewConnector(dsn string) (*Connector, error) { + var err error + o := make(values) + + // A number of defaults are applied here, in this order: + // + // * Very low precedence defaults applied in every situation + // * Environment variables + // * Explicitly passed connection information + o["host"] = "localhost" + o["port"] = "5432" + // N.B.: Extra float digits should be set to 3, but that breaks + // Postgres 8.4 and older, where the max is 2. + o["extra_float_digits"] = "2" + for k, v := range parseEnviron(os.Environ()) { + o[k] = v + } + + if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { + dsn, err = ParseURL(dsn) + if err != nil { + return nil, err + } + } + + if err := parseOpts(dsn, o); err != nil { + return nil, err + } + + // Use the "fallback" application name if necessary + if fallback, ok := o["fallback_application_name"]; ok { + if _, ok := o["application_name"]; !ok { + o["application_name"] = fallback + } + } + + // We can't work with any client_encoding other than UTF-8 currently. + // However, we have historically allowed the user to set it to UTF-8 + // explicitly, and there's no reason to break such programs, so allow that. + // Note that the "options" setting could also set client_encoding, but + // parsing its value is not worth it. Instead, we always explicitly send + // client_encoding as a separate run-time parameter, which should override + // anything set in options. + if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) { + return nil, errors.New("client_encoding must be absent or 'UTF8'") + } + o["client_encoding"] = "UTF8" + // DateStyle needs a similar treatment. + if datestyle, ok := o["datestyle"]; ok { + if datestyle != "ISO, MDY" { + return nil, fmt.Errorf("setting datestyle must be absent or %v; got %v", "ISO, MDY", datestyle) + } + } else { + o["datestyle"] = "ISO, MDY" + } + + // If a user is not provided by any other means, the last + // resort is to use the current operating system provided user + // name. + if _, ok := o["user"]; !ok { + u, err := userCurrent() + if err != nil { + return nil, err + } + o["user"] = u + } + + // SSL is not necessary or supported over UNIX domain sockets + if network, _ := network(o); network == "unix" { + o["sslmode"] = "disable" + } + + return &Connector{opts: o, dialer: defaultDialer{}}, nil +} diff --git a/vendor/github.com/lib/pq/copy.go b/vendor/github.com/lib/pq/copy.go new file mode 100644 index 0000000..a8f16b2 --- /dev/null +++ b/vendor/github.com/lib/pq/copy.go @@ -0,0 +1,348 @@ +package pq + +import ( + "bytes" + "context" + "database/sql/driver" + "encoding/binary" + "errors" + "fmt" + "sync" +) + +var ( + errCopyInClosed = errors.New("pq: copyin statement has already been closed") + errBinaryCopyNotSupported = errors.New("pq: only text format supported for COPY") + errCopyToNotSupported = errors.New("pq: COPY TO is not supported") + errCopyNotSupportedOutsideTxn = errors.New("pq: COPY is only allowed inside a transaction") + errCopyInProgress = errors.New("pq: COPY in progress") +) + +// CopyIn creates a COPY FROM statement which can be prepared with +// Tx.Prepare(). The target table should be visible in search_path. +func CopyIn(table string, columns ...string) string { + buffer := bytes.NewBufferString("COPY ") + BufferQuoteIdentifier(table, buffer) + buffer.WriteString(" (") + makeStmt(buffer, columns...) + return buffer.String() +} + +// MakeStmt makes the stmt string for CopyIn and CopyInSchema. +func makeStmt(buffer *bytes.Buffer, columns ...string) { + //s := bytes.NewBufferString() + for i, col := range columns { + if i != 0 { + buffer.WriteString(", ") + } + BufferQuoteIdentifier(col, buffer) + } + buffer.WriteString(") FROM STDIN") +} + +// CopyInSchema creates a COPY FROM statement which can be prepared with +// Tx.Prepare(). +func CopyInSchema(schema, table string, columns ...string) string { + buffer := bytes.NewBufferString("COPY ") + BufferQuoteIdentifier(schema, buffer) + buffer.WriteRune('.') + BufferQuoteIdentifier(table, buffer) + buffer.WriteString(" (") + makeStmt(buffer, columns...) + return buffer.String() +} + +type copyin struct { + cn *conn + buffer []byte + rowData chan []byte + done chan bool + + closed bool + + mu struct { + sync.Mutex + err error + driver.Result + } +} + +const ciBufferSize = 64 * 1024 + +// flush buffer before the buffer is filled up and needs reallocation +const ciBufferFlushSize = 63 * 1024 + +func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) { + if !cn.isInTransaction() { + return nil, errCopyNotSupportedOutsideTxn + } + + ci := ©in{ + cn: cn, + buffer: make([]byte, 0, ciBufferSize), + rowData: make(chan []byte), + done: make(chan bool, 1), + } + // add CopyData identifier + 4 bytes for message length + ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0) + + b := cn.writeBuf('Q') + b.string(q) + cn.send(b) + +awaitCopyInResponse: + for { + t, r := cn.recv1() + switch t { + case 'G': + if r.byte() != 0 { + err = errBinaryCopyNotSupported + break awaitCopyInResponse + } + go ci.resploop() + return ci, nil + case 'H': + err = errCopyToNotSupported + break awaitCopyInResponse + case 'E': + err = parseError(r) + case 'Z': + if err == nil { + ci.setBad(driver.ErrBadConn) + errorf("unexpected ReadyForQuery in response to COPY") + } + cn.processReadyForQuery(r) + return nil, err + default: + ci.setBad(driver.ErrBadConn) + errorf("unknown response for copy query: %q", t) + } + } + + // something went wrong, abort COPY before we return + b = cn.writeBuf('f') + b.string(err.Error()) + cn.send(b) + + for { + t, r := cn.recv1() + switch t { + case 'c', 'C', 'E': + case 'Z': + // correctly aborted, we're done + cn.processReadyForQuery(r) + return nil, err + default: + ci.setBad(driver.ErrBadConn) + errorf("unknown response for CopyFail: %q", t) + } + } +} + +func (ci *copyin) flush(buf []byte) { + // set message length (without message identifier) + binary.BigEndian.PutUint32(buf[1:], uint32(len(buf)-1)) + + _, err := ci.cn.c.Write(buf) + if err != nil { + panic(err) + } +} + +func (ci *copyin) resploop() { + for { + var r readBuf + t, err := ci.cn.recvMessage(&r) + if err != nil { + ci.setBad(driver.ErrBadConn) + ci.setError(err) + ci.done <- true + return + } + switch t { + case 'C': + // complete + res, _ := ci.cn.parseComplete(r.string()) + ci.setResult(res) + case 'N': + if n := ci.cn.noticeHandler; n != nil { + n(parseError(&r)) + } + case 'Z': + ci.cn.processReadyForQuery(&r) + ci.done <- true + return + case 'E': + err := parseError(&r) + ci.setError(err) + default: + ci.setBad(driver.ErrBadConn) + ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t)) + ci.done <- true + return + } + } +} + +func (ci *copyin) setBad(err error) { + ci.cn.err.set(err) +} + +func (ci *copyin) getBad() error { + return ci.cn.err.get() +} + +func (ci *copyin) err() error { + ci.mu.Lock() + err := ci.mu.err + ci.mu.Unlock() + return err +} + +// setError() sets ci.err if one has not been set already. Caller must not be +// holding ci.Mutex. +func (ci *copyin) setError(err error) { + ci.mu.Lock() + if ci.mu.err == nil { + ci.mu.err = err + } + ci.mu.Unlock() +} + +func (ci *copyin) setResult(result driver.Result) { + ci.mu.Lock() + ci.mu.Result = result + ci.mu.Unlock() +} + +func (ci *copyin) getResult() driver.Result { + ci.mu.Lock() + result := ci.mu.Result + ci.mu.Unlock() + if result == nil { + return driver.RowsAffected(0) + } + return result +} + +func (ci *copyin) NumInput() int { + return -1 +} + +func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) { + return nil, ErrNotSupported +} + +// Exec inserts values into the COPY stream. The insert is asynchronous +// and Exec can return errors from previous Exec calls to the same +// COPY stmt. +// +// You need to call Exec(nil) to sync the COPY stream and to get any +// errors from pending data, since Stmt.Close() doesn't return errors +// to the user. +func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { + if ci.closed { + return nil, errCopyInClosed + } + + if err := ci.getBad(); err != nil { + return nil, err + } + defer ci.cn.errRecover(&err) + + if err := ci.err(); err != nil { + return nil, err + } + + if len(v) == 0 { + if err := ci.Close(); err != nil { + return driver.RowsAffected(0), err + } + + return ci.getResult(), nil + } + + numValues := len(v) + for i, value := range v { + ci.buffer = appendEncodedText(&ci.cn.parameterStatus, ci.buffer, value) + if i < numValues-1 { + ci.buffer = append(ci.buffer, '\t') + } + } + + ci.buffer = append(ci.buffer, '\n') + + if len(ci.buffer) > ciBufferFlushSize { + ci.flush(ci.buffer) + // reset buffer, keep bytes for message identifier and length + ci.buffer = ci.buffer[:5] + } + + return driver.RowsAffected(0), nil +} + +// CopyData inserts a raw string into the COPY stream. The insert is +// asynchronous and CopyData can return errors from previous CopyData calls to +// the same COPY stmt. +// +// You need to call Exec(nil) to sync the COPY stream and to get any +// errors from pending data, since Stmt.Close() doesn't return errors +// to the user. +func (ci *copyin) CopyData(ctx context.Context, line string) (r driver.Result, err error) { + if ci.closed { + return nil, errCopyInClosed + } + + if finish := ci.cn.watchCancel(ctx); finish != nil { + defer finish() + } + + if err := ci.getBad(); err != nil { + return nil, err + } + defer ci.cn.errRecover(&err) + + if err := ci.err(); err != nil { + return nil, err + } + + ci.buffer = append(ci.buffer, []byte(line)...) + ci.buffer = append(ci.buffer, '\n') + + if len(ci.buffer) > ciBufferFlushSize { + ci.flush(ci.buffer) + // reset buffer, keep bytes for message identifier and length + ci.buffer = ci.buffer[:5] + } + + return driver.RowsAffected(0), nil +} + +func (ci *copyin) Close() (err error) { + if ci.closed { // Don't do anything, we're already closed + return nil + } + ci.closed = true + + if err := ci.getBad(); err != nil { + return err + } + defer ci.cn.errRecover(&err) + + if len(ci.buffer) > 0 { + ci.flush(ci.buffer) + } + // Avoid touching the scratch buffer as resploop could be using it. + err = ci.cn.sendSimpleMessage('c') + if err != nil { + return err + } + + <-ci.done + ci.cn.inCopy = false + + if err := ci.err(); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/lib/pq/doc.go b/vendor/github.com/lib/pq/doc.go new file mode 100644 index 0000000..b571848 --- /dev/null +++ b/vendor/github.com/lib/pq/doc.go @@ -0,0 +1,268 @@ +/* +Package pq is a pure Go Postgres driver for the database/sql package. + +In most cases clients will use the database/sql package instead of +using this package directly. For example: + + import ( + "database/sql" + + _ "github.com/lib/pq" + ) + + func main() { + connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full" + db, err := sql.Open("postgres", connStr) + if err != nil { + log.Fatal(err) + } + + age := 21 + rows, err := db.Query("SELECT name FROM users WHERE age = $1", age) + … + } + +You can also connect to a database using a URL. For example: + + connStr := "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full" + db, err := sql.Open("postgres", connStr) + + +Connection String Parameters + + +Similarly to libpq, when establishing a connection using pq you are expected to +supply a connection string containing zero or more parameters. +A subset of the connection parameters supported by libpq are also supported by pq. +Additionally, pq also lets you specify run-time parameters (such as search_path or work_mem) +directly in the connection string. This is different from libpq, which does not allow +run-time parameters in the connection string, instead requiring you to supply +them in the options parameter. + +For compatibility with libpq, the following special connection parameters are +supported: + + * dbname - The name of the database to connect to + * user - The user to sign in as + * password - The user's password + * host - The host to connect to. Values that start with / are for unix + domain sockets. (default is localhost) + * port - The port to bind to. (default is 5432) + * sslmode - Whether or not to use SSL (default is require, this is not + the default for libpq) + * fallback_application_name - An application_name to fall back to if one isn't provided. + * connect_timeout - Maximum wait for connection, in seconds. Zero or + not specified means wait indefinitely. + * sslcert - Cert file location. The file must contain PEM encoded data. + * sslkey - Key file location. The file must contain PEM encoded data. + * sslrootcert - The location of the root certificate file. The file + must contain PEM encoded data. + +Valid values for sslmode are: + + * disable - No SSL + * require - Always SSL (skip verification) + * verify-ca - Always SSL (verify that the certificate presented by the + server was signed by a trusted CA) + * verify-full - Always SSL (verify that the certification presented by + the server was signed by a trusted CA and the server host name + matches the one in the certificate) + +See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING +for more information about connection string parameters. + +Use single quotes for values that contain whitespace: + + "user=pqgotest password='with spaces'" + +A backslash will escape the next character in values: + + "user=space\ man password='it\'s valid'" + +Note that the connection parameter client_encoding (which sets the +text encoding for the connection) may be set but must be "UTF8", +matching with the same rules as Postgres. It is an error to provide +any other value. + +In addition to the parameters listed above, any run-time parameter that can be +set at backend start time can be set in the connection string. For more +information, see +http://www.postgresql.org/docs/current/static/runtime-config.html. + +Most environment variables as specified at http://www.postgresql.org/docs/current/static/libpq-envars.html +supported by libpq are also supported by pq. If any of the environment +variables not supported by pq are set, pq will panic during connection +establishment. Environment variables have a lower precedence than explicitly +provided connection parameters. + +The pgpass mechanism as described in http://www.postgresql.org/docs/current/static/libpq-pgpass.html +is supported, but on Windows PGPASSFILE must be specified explicitly. + + +Queries + + +database/sql does not dictate any specific format for parameter +markers in query strings, and pq uses the Postgres-native ordinal markers, +as shown above. The same marker can be reused for the same parameter: + + rows, err := db.Query(`SELECT name FROM users WHERE favorite_fruit = $1 + OR age BETWEEN $2 AND $2 + 3`, "orange", 64) + +pq does not support the LastInsertId() method of the Result type in database/sql. +To return the identifier of an INSERT (or UPDATE or DELETE), use the Postgres +RETURNING clause with a standard Query or QueryRow call: + + var userid int + err := db.QueryRow(`INSERT INTO users(name, favorite_fruit, age) + VALUES('beatrice', 'starfruit', 93) RETURNING id`).Scan(&userid) + +For more details on RETURNING, see the Postgres documentation: + + http://www.postgresql.org/docs/current/static/sql-insert.html + http://www.postgresql.org/docs/current/static/sql-update.html + http://www.postgresql.org/docs/current/static/sql-delete.html + +For additional instructions on querying see the documentation for the database/sql package. + + +Data Types + + +Parameters pass through driver.DefaultParameterConverter before they are handled +by this package. When the binary_parameters connection option is enabled, +[]byte values are sent directly to the backend as data in binary format. + +This package returns the following types for values from the PostgreSQL backend: + + - integer types smallint, integer, and bigint are returned as int64 + - floating-point types real and double precision are returned as float64 + - character types char, varchar, and text are returned as string + - temporal types date, time, timetz, timestamp, and timestamptz are + returned as time.Time + - the boolean type is returned as bool + - the bytea type is returned as []byte + +All other types are returned directly from the backend as []byte values in text format. + + +Errors + + +pq may return errors of type *pq.Error which can be interrogated for error details: + + if err, ok := err.(*pq.Error); ok { + fmt.Println("pq error:", err.Code.Name()) + } + +See the pq.Error type for details. + + +Bulk imports + +You can perform bulk imports by preparing a statement returned by pq.CopyIn (or +pq.CopyInSchema) in an explicit transaction (sql.Tx). The returned statement +handle can then be repeatedly "executed" to copy data into the target table. +After all data has been processed you should call Exec() once with no arguments +to flush all buffered data. Any call to Exec() might return an error which +should be handled appropriately, but because of the internal buffering an error +returned by Exec() might not be related to the data passed in the call that +failed. + +CopyIn uses COPY FROM internally. It is not possible to COPY outside of an +explicit transaction in pq. + +Usage example: + + txn, err := db.Begin() + if err != nil { + log.Fatal(err) + } + + stmt, err := txn.Prepare(pq.CopyIn("users", "name", "age")) + if err != nil { + log.Fatal(err) + } + + for _, user := range users { + _, err = stmt.Exec(user.Name, int64(user.Age)) + if err != nil { + log.Fatal(err) + } + } + + _, err = stmt.Exec() + if err != nil { + log.Fatal(err) + } + + err = stmt.Close() + if err != nil { + log.Fatal(err) + } + + err = txn.Commit() + if err != nil { + log.Fatal(err) + } + + +Notifications + + +PostgreSQL supports a simple publish/subscribe model over database +connections. See http://www.postgresql.org/docs/current/static/sql-notify.html +for more information about the general mechanism. + +To start listening for notifications, you first have to open a new connection +to the database by calling NewListener. This connection can not be used for +anything other than LISTEN / NOTIFY. Calling Listen will open a "notification +channel"; once a notification channel is open, a notification generated on that +channel will effect a send on the Listener.Notify channel. A notification +channel will remain open until Unlisten is called, though connection loss might +result in some notifications being lost. To solve this problem, Listener sends +a nil pointer over the Notify channel any time the connection is re-established +following a connection loss. The application can get information about the +state of the underlying connection by setting an event callback in the call to +NewListener. + +A single Listener can safely be used from concurrent goroutines, which means +that there is often no need to create more than one Listener in your +application. However, a Listener is always connected to a single database, so +you will need to create a new Listener instance for every database you want to +receive notifications in. + +The channel name in both Listen and Unlisten is case sensitive, and can contain +any characters legal in an identifier (see +http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS +for more information). Note that the channel name will be truncated to 63 +bytes by the PostgreSQL server. + +You can find a complete, working example of Listener usage at +https://godoc.org/github.com/lib/pq/example/listen. + + +Kerberos Support + + +If you need support for Kerberos authentication, add the following to your main +package: + + import "github.com/lib/pq/auth/kerberos" + + func init() { + pq.RegisterGSSProvider(func() (pq.Gss, error) { return kerberos.NewGSS() }) + } + +This package is in a separate module so that users who don't need Kerberos +don't have to download unnecessary dependencies. + +When imported, additional connection string parameters are supported: + + * krbsrvname - GSS (Kerberos) service name when constructing the + SPN (default is `postgres`). This will be combined with the host + to form the full SPN: `krbsrvname/host`. + * krbspn - GSS (Kerberos) SPN. This takes priority over + `krbsrvname` if present. +*/ +package pq diff --git a/vendor/github.com/lib/pq/encode.go b/vendor/github.com/lib/pq/encode.go new file mode 100644 index 0000000..bffe609 --- /dev/null +++ b/vendor/github.com/lib/pq/encode.go @@ -0,0 +1,632 @@ +package pq + +import ( + "bytes" + "database/sql/driver" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "math" + "regexp" + "strconv" + "strings" + "sync" + "time" + + "github.com/lib/pq/oid" +) + +var time2400Regex = regexp.MustCompile(`^(24:00(?::00(?:\.0+)?)?)(?:[Z+-].*)?$`) + +func binaryEncode(parameterStatus *parameterStatus, x interface{}) []byte { + switch v := x.(type) { + case []byte: + return v + default: + return encode(parameterStatus, x, oid.T_unknown) + } +} + +func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) []byte { + switch v := x.(type) { + case int64: + return strconv.AppendInt(nil, v, 10) + case float64: + return strconv.AppendFloat(nil, v, 'f', -1, 64) + case []byte: + if pgtypOid == oid.T_bytea { + return encodeBytea(parameterStatus.serverVersion, v) + } + + return v + case string: + if pgtypOid == oid.T_bytea { + return encodeBytea(parameterStatus.serverVersion, []byte(v)) + } + + return []byte(v) + case bool: + return strconv.AppendBool(nil, v) + case time.Time: + return formatTs(v) + + default: + errorf("encode: unknown type for %T", v) + } + + panic("not reached") +} + +func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid, f format) interface{} { + switch f { + case formatBinary: + return binaryDecode(parameterStatus, s, typ) + case formatText: + return textDecode(parameterStatus, s, typ) + default: + panic("not reached") + } +} + +func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} { + switch typ { + case oid.T_bytea: + return s + case oid.T_int8: + return int64(binary.BigEndian.Uint64(s)) + case oid.T_int4: + return int64(int32(binary.BigEndian.Uint32(s))) + case oid.T_int2: + return int64(int16(binary.BigEndian.Uint16(s))) + case oid.T_uuid: + b, err := decodeUUIDBinary(s) + if err != nil { + panic(err) + } + return b + + default: + errorf("don't know how to decode binary parameter of type %d", uint32(typ)) + } + + panic("not reached") +} + +func textDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} { + switch typ { + case oid.T_char, oid.T_varchar, oid.T_text: + return string(s) + case oid.T_bytea: + b, err := parseBytea(s) + if err != nil { + errorf("%s", err) + } + return b + case oid.T_timestamptz: + return parseTs(parameterStatus.currentLocation, string(s)) + case oid.T_timestamp, oid.T_date: + return parseTs(nil, string(s)) + case oid.T_time: + return mustParse("15:04:05", typ, s) + case oid.T_timetz: + return mustParse("15:04:05-07", typ, s) + case oid.T_bool: + return s[0] == 't' + case oid.T_int8, oid.T_int4, oid.T_int2: + i, err := strconv.ParseInt(string(s), 10, 64) + if err != nil { + errorf("%s", err) + } + return i + case oid.T_float4, oid.T_float8: + // We always use 64 bit parsing, regardless of whether the input text is for + // a float4 or float8, because clients expect float64s for all float datatypes + // and returning a 32-bit parsed float64 produces lossy results. + f, err := strconv.ParseFloat(string(s), 64) + if err != nil { + errorf("%s", err) + } + return f + } + + return s +} + +// appendEncodedText encodes item in text format as required by COPY +// and appends to buf +func appendEncodedText(parameterStatus *parameterStatus, buf []byte, x interface{}) []byte { + switch v := x.(type) { + case int64: + return strconv.AppendInt(buf, v, 10) + case float64: + return strconv.AppendFloat(buf, v, 'f', -1, 64) + case []byte: + encodedBytea := encodeBytea(parameterStatus.serverVersion, v) + return appendEscapedText(buf, string(encodedBytea)) + case string: + return appendEscapedText(buf, v) + case bool: + return strconv.AppendBool(buf, v) + case time.Time: + return append(buf, formatTs(v)...) + case nil: + return append(buf, "\\N"...) + default: + errorf("encode: unknown type for %T", v) + } + + panic("not reached") +} + +func appendEscapedText(buf []byte, text string) []byte { + escapeNeeded := false + startPos := 0 + var c byte + + // check if we need to escape + for i := 0; i < len(text); i++ { + c = text[i] + if c == '\\' || c == '\n' || c == '\r' || c == '\t' { + escapeNeeded = true + startPos = i + break + } + } + if !escapeNeeded { + return append(buf, text...) + } + + // copy till first char to escape, iterate the rest + result := append(buf, text[:startPos]...) + for i := startPos; i < len(text); i++ { + c = text[i] + switch c { + case '\\': + result = append(result, '\\', '\\') + case '\n': + result = append(result, '\\', 'n') + case '\r': + result = append(result, '\\', 'r') + case '\t': + result = append(result, '\\', 't') + default: + result = append(result, c) + } + } + return result +} + +func mustParse(f string, typ oid.Oid, s []byte) time.Time { + str := string(s) + + // Check for a minute and second offset in the timezone. + if typ == oid.T_timestamptz || typ == oid.T_timetz { + for i := 3; i <= 6; i += 3 { + if str[len(str)-i] == ':' { + f += ":00" + continue + } + break + } + } + + // Special case for 24:00 time. + // Unfortunately, golang does not parse 24:00 as a proper time. + // In this case, we want to try "round to the next day", to differentiate. + // As such, we find if the 24:00 time matches at the beginning; if so, + // we default it back to 00:00 but add a day later. + var is2400Time bool + switch typ { + case oid.T_timetz, oid.T_time: + if matches := time2400Regex.FindStringSubmatch(str); matches != nil { + // Concatenate timezone information at the back. + str = "00:00:00" + str[len(matches[1]):] + is2400Time = true + } + } + t, err := time.Parse(f, str) + if err != nil { + errorf("decode: %s", err) + } + if is2400Time { + t = t.Add(24 * time.Hour) + } + return t +} + +var errInvalidTimestamp = errors.New("invalid timestamp") + +type timestampParser struct { + err error +} + +func (p *timestampParser) expect(str string, char byte, pos int) { + if p.err != nil { + return + } + if pos+1 > len(str) { + p.err = errInvalidTimestamp + return + } + if c := str[pos]; c != char && p.err == nil { + p.err = fmt.Errorf("expected '%v' at position %v; got '%v'", char, pos, c) + } +} + +func (p *timestampParser) mustAtoi(str string, begin int, end int) int { + if p.err != nil { + return 0 + } + if begin < 0 || end < 0 || begin > end || end > len(str) { + p.err = errInvalidTimestamp + return 0 + } + result, err := strconv.Atoi(str[begin:end]) + if err != nil { + if p.err == nil { + p.err = fmt.Errorf("expected number; got '%v'", str) + } + return 0 + } + return result +} + +// The location cache caches the time zones typically used by the client. +type locationCache struct { + cache map[int]*time.Location + lock sync.Mutex +} + +// All connections share the same list of timezones. Benchmarking shows that +// about 5% speed could be gained by putting the cache in the connection and +// losing the mutex, at the cost of a small amount of memory and a somewhat +// significant increase in code complexity. +var globalLocationCache = newLocationCache() + +func newLocationCache() *locationCache { + return &locationCache{cache: make(map[int]*time.Location)} +} + +// Returns the cached timezone for the specified offset, creating and caching +// it if necessary. +func (c *locationCache) getLocation(offset int) *time.Location { + c.lock.Lock() + defer c.lock.Unlock() + + location, ok := c.cache[offset] + if !ok { + location = time.FixedZone("", offset) + c.cache[offset] = location + } + + return location +} + +var infinityTsEnabled = false +var infinityTsNegative time.Time +var infinityTsPositive time.Time + +const ( + infinityTsEnabledAlready = "pq: infinity timestamp enabled already" + infinityTsNegativeMustBeSmaller = "pq: infinity timestamp: negative value must be smaller (before) than positive" +) + +// EnableInfinityTs controls the handling of Postgres' "-infinity" and +// "infinity" "timestamp"s. +// +// If EnableInfinityTs is not called, "-infinity" and "infinity" will return +// []byte("-infinity") and []byte("infinity") respectively, and potentially +// cause error "sql: Scan error on column index 0: unsupported driver -> Scan +// pair: []uint8 -> *time.Time", when scanning into a time.Time value. +// +// Once EnableInfinityTs has been called, all connections created using this +// driver will decode Postgres' "-infinity" and "infinity" for "timestamp", +// "timestamp with time zone" and "date" types to the predefined minimum and +// maximum times, respectively. When encoding time.Time values, any time which +// equals or precedes the predefined minimum time will be encoded to +// "-infinity". Any values at or past the maximum time will similarly be +// encoded to "infinity". +// +// If EnableInfinityTs is called with negative >= positive, it will panic. +// Calling EnableInfinityTs after a connection has been established results in +// undefined behavior. If EnableInfinityTs is called more than once, it will +// panic. +func EnableInfinityTs(negative time.Time, positive time.Time) { + if infinityTsEnabled { + panic(infinityTsEnabledAlready) + } + if !negative.Before(positive) { + panic(infinityTsNegativeMustBeSmaller) + } + infinityTsEnabled = true + infinityTsNegative = negative + infinityTsPositive = positive +} + +/* + * Testing might want to toggle infinityTsEnabled + */ +func disableInfinityTs() { + infinityTsEnabled = false +} + +// This is a time function specific to the Postgres default DateStyle +// setting ("ISO, MDY"), the only one we currently support. This +// accounts for the discrepancies between the parsing available with +// time.Parse and the Postgres date formatting quirks. +func parseTs(currentLocation *time.Location, str string) interface{} { + switch str { + case "-infinity": + if infinityTsEnabled { + return infinityTsNegative + } + return []byte(str) + case "infinity": + if infinityTsEnabled { + return infinityTsPositive + } + return []byte(str) + } + t, err := ParseTimestamp(currentLocation, str) + if err != nil { + panic(err) + } + return t +} + +// ParseTimestamp parses Postgres' text format. It returns a time.Time in +// currentLocation iff that time's offset agrees with the offset sent from the +// Postgres server. Otherwise, ParseTimestamp returns a time.Time with the +// fixed offset offset provided by the Postgres server. +func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, error) { + p := timestampParser{} + + monSep := strings.IndexRune(str, '-') + // this is Gregorian year, not ISO Year + // In Gregorian system, the year 1 BC is followed by AD 1 + year := p.mustAtoi(str, 0, monSep) + daySep := monSep + 3 + month := p.mustAtoi(str, monSep+1, daySep) + p.expect(str, '-', daySep) + timeSep := daySep + 3 + day := p.mustAtoi(str, daySep+1, timeSep) + + minLen := monSep + len("01-01") + 1 + + isBC := strings.HasSuffix(str, " BC") + if isBC { + minLen += 3 + } + + var hour, minute, second int + if len(str) > minLen { + p.expect(str, ' ', timeSep) + minSep := timeSep + 3 + p.expect(str, ':', minSep) + hour = p.mustAtoi(str, timeSep+1, minSep) + secSep := minSep + 3 + p.expect(str, ':', secSep) + minute = p.mustAtoi(str, minSep+1, secSep) + secEnd := secSep + 3 + second = p.mustAtoi(str, secSep+1, secEnd) + } + remainderIdx := monSep + len("01-01 00:00:00") + 1 + // Three optional (but ordered) sections follow: the + // fractional seconds, the time zone offset, and the BC + // designation. We set them up here and adjust the other + // offsets if the preceding sections exist. + + nanoSec := 0 + tzOff := 0 + + if remainderIdx < len(str) && str[remainderIdx] == '.' { + fracStart := remainderIdx + 1 + fracOff := strings.IndexAny(str[fracStart:], "-+Z ") + if fracOff < 0 { + fracOff = len(str) - fracStart + } + fracSec := p.mustAtoi(str, fracStart, fracStart+fracOff) + nanoSec = fracSec * (1000000000 / int(math.Pow(10, float64(fracOff)))) + + remainderIdx += fracOff + 1 + } + if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') { + // time zone separator is always '-' or '+' or 'Z' (UTC is +00) + var tzSign int + switch c := str[tzStart]; c { + case '-': + tzSign = -1 + case '+': + tzSign = +1 + default: + return time.Time{}, fmt.Errorf("expected '-' or '+' at position %v; got %v", tzStart, c) + } + tzHours := p.mustAtoi(str, tzStart+1, tzStart+3) + remainderIdx += 3 + var tzMin, tzSec int + if remainderIdx < len(str) && str[remainderIdx] == ':' { + tzMin = p.mustAtoi(str, remainderIdx+1, remainderIdx+3) + remainderIdx += 3 + } + if remainderIdx < len(str) && str[remainderIdx] == ':' { + tzSec = p.mustAtoi(str, remainderIdx+1, remainderIdx+3) + remainderIdx += 3 + } + tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec) + } else if tzStart < len(str) && str[tzStart] == 'Z' { + // time zone Z separator indicates UTC is +00 + remainderIdx += 1 + } + + var isoYear int + + if isBC { + isoYear = 1 - year + remainderIdx += 3 + } else { + isoYear = year + } + if remainderIdx < len(str) { + return time.Time{}, fmt.Errorf("expected end of input, got %v", str[remainderIdx:]) + } + t := time.Date(isoYear, time.Month(month), day, + hour, minute, second, nanoSec, + globalLocationCache.getLocation(tzOff)) + + if currentLocation != nil { + // Set the location of the returned Time based on the session's + // TimeZone value, but only if the local time zone database agrees with + // the remote database on the offset. + lt := t.In(currentLocation) + _, newOff := lt.Zone() + if newOff == tzOff { + t = lt + } + } + + return t, p.err +} + +// formatTs formats t into a format postgres understands. +func formatTs(t time.Time) []byte { + if infinityTsEnabled { + // t <= -infinity : ! (t > -infinity) + if !t.After(infinityTsNegative) { + return []byte("-infinity") + } + // t >= infinity : ! (!t < infinity) + if !t.Before(infinityTsPositive) { + return []byte("infinity") + } + } + return FormatTimestamp(t) +} + +// FormatTimestamp formats t into Postgres' text format for timestamps. +func FormatTimestamp(t time.Time) []byte { + // Need to send dates before 0001 A.D. with " BC" suffix, instead of the + // minus sign preferred by Go. + // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on + bc := false + if t.Year() <= 0 { + // flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11" + t = t.AddDate((-t.Year())*2+1, 0, 0) + bc = true + } + b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00")) + + _, offset := t.Zone() + offset %= 60 + if offset != 0 { + // RFC3339Nano already printed the minus sign + if offset < 0 { + offset = -offset + } + + b = append(b, ':') + if offset < 10 { + b = append(b, '0') + } + b = strconv.AppendInt(b, int64(offset), 10) + } + + if bc { + b = append(b, " BC"...) + } + return b +} + +// Parse a bytea value received from the server. Both "hex" and the legacy +// "escape" format are supported. +func parseBytea(s []byte) (result []byte, err error) { + if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) { + // bytea_output = hex + s = s[2:] // trim off leading "\\x" + result = make([]byte, hex.DecodedLen(len(s))) + _, err := hex.Decode(result, s) + if err != nil { + return nil, err + } + } else { + // bytea_output = escape + for len(s) > 0 { + if s[0] == '\\' { + // escaped '\\' + if len(s) >= 2 && s[1] == '\\' { + result = append(result, '\\') + s = s[2:] + continue + } + + // '\\' followed by an octal number + if len(s) < 4 { + return nil, fmt.Errorf("invalid bytea sequence %v", s) + } + r, err := strconv.ParseUint(string(s[1:4]), 8, 8) + if err != nil { + return nil, fmt.Errorf("could not parse bytea value: %s", err.Error()) + } + result = append(result, byte(r)) + s = s[4:] + } else { + // We hit an unescaped, raw byte. Try to read in as many as + // possible in one go. + i := bytes.IndexByte(s, '\\') + if i == -1 { + result = append(result, s...) + break + } + result = append(result, s[:i]...) + s = s[i:] + } + } + } + + return result, nil +} + +func encodeBytea(serverVersion int, v []byte) (result []byte) { + if serverVersion >= 90000 { + // Use the hex format if we know that the server supports it + result = make([]byte, 2+hex.EncodedLen(len(v))) + result[0] = '\\' + result[1] = 'x' + hex.Encode(result[2:], v) + } else { + // .. or resort to "escape" + for _, b := range v { + if b == '\\' { + result = append(result, '\\', '\\') + } else if b < 0x20 || b > 0x7e { + result = append(result, []byte(fmt.Sprintf("\\%03o", b))...) + } else { + result = append(result, b) + } + } + } + + return result +} + +// NullTime represents a time.Time that may be null. NullTime implements the +// sql.Scanner interface so it can be used as a scan destination, similar to +// sql.NullString. +type NullTime struct { + Time time.Time + Valid bool // Valid is true if Time is not NULL +} + +// Scan implements the Scanner interface. +func (nt *NullTime) Scan(value interface{}) error { + nt.Time, nt.Valid = value.(time.Time) + return nil +} + +// Value implements the driver Valuer interface. +func (nt NullTime) Value() (driver.Value, error) { + if !nt.Valid { + return nil, nil + } + return nt.Time, nil +} diff --git a/vendor/github.com/lib/pq/error.go b/vendor/github.com/lib/pq/error.go new file mode 100644 index 0000000..f67c5a5 --- /dev/null +++ b/vendor/github.com/lib/pq/error.go @@ -0,0 +1,523 @@ +package pq + +import ( + "database/sql/driver" + "fmt" + "io" + "net" + "runtime" +) + +// Error severities +const ( + Efatal = "FATAL" + Epanic = "PANIC" + Ewarning = "WARNING" + Enotice = "NOTICE" + Edebug = "DEBUG" + Einfo = "INFO" + Elog = "LOG" +) + +// Error represents an error communicating with the server. +// +// See http://www.postgresql.org/docs/current/static/protocol-error-fields.html for details of the fields +type Error struct { + Severity string + Code ErrorCode + Message string + Detail string + Hint string + Position string + InternalPosition string + InternalQuery string + Where string + Schema string + Table string + Column string + DataTypeName string + Constraint string + File string + Line string + Routine string +} + +// ErrorCode is a five-character error code. +type ErrorCode string + +// Name returns a more human friendly rendering of the error code, namely the +// "condition name". +// +// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for +// details. +func (ec ErrorCode) Name() string { + return errorCodeNames[ec] +} + +// ErrorClass is only the class part of an error code. +type ErrorClass string + +// Name returns the condition name of an error class. It is equivalent to the +// condition name of the "standard" error code (i.e. the one having the last +// three characters "000"). +func (ec ErrorClass) Name() string { + return errorCodeNames[ErrorCode(ec+"000")] +} + +// Class returns the error class, e.g. "28". +// +// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for +// details. +func (ec ErrorCode) Class() ErrorClass { + return ErrorClass(ec[0:2]) +} + +// errorCodeNames is a mapping between the five-character error codes and the +// human readable "condition names". It is derived from the list at +// http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html +var errorCodeNames = map[ErrorCode]string{ + // Class 00 - Successful Completion + "00000": "successful_completion", + // Class 01 - Warning + "01000": "warning", + "0100C": "dynamic_result_sets_returned", + "01008": "implicit_zero_bit_padding", + "01003": "null_value_eliminated_in_set_function", + "01007": "privilege_not_granted", + "01006": "privilege_not_revoked", + "01004": "string_data_right_truncation", + "01P01": "deprecated_feature", + // Class 02 - No Data (this is also a warning class per the SQL standard) + "02000": "no_data", + "02001": "no_additional_dynamic_result_sets_returned", + // Class 03 - SQL Statement Not Yet Complete + "03000": "sql_statement_not_yet_complete", + // Class 08 - Connection Exception + "08000": "connection_exception", + "08003": "connection_does_not_exist", + "08006": "connection_failure", + "08001": "sqlclient_unable_to_establish_sqlconnection", + "08004": "sqlserver_rejected_establishment_of_sqlconnection", + "08007": "transaction_resolution_unknown", + "08P01": "protocol_violation", + // Class 09 - Triggered Action Exception + "09000": "triggered_action_exception", + // Class 0A - Feature Not Supported + "0A000": "feature_not_supported", + // Class 0B - Invalid Transaction Initiation + "0B000": "invalid_transaction_initiation", + // Class 0F - Locator Exception + "0F000": "locator_exception", + "0F001": "invalid_locator_specification", + // Class 0L - Invalid Grantor + "0L000": "invalid_grantor", + "0LP01": "invalid_grant_operation", + // Class 0P - Invalid Role Specification + "0P000": "invalid_role_specification", + // Class 0Z - Diagnostics Exception + "0Z000": "diagnostics_exception", + "0Z002": "stacked_diagnostics_accessed_without_active_handler", + // Class 20 - Case Not Found + "20000": "case_not_found", + // Class 21 - Cardinality Violation + "21000": "cardinality_violation", + // Class 22 - Data Exception + "22000": "data_exception", + "2202E": "array_subscript_error", + "22021": "character_not_in_repertoire", + "22008": "datetime_field_overflow", + "22012": "division_by_zero", + "22005": "error_in_assignment", + "2200B": "escape_character_conflict", + "22022": "indicator_overflow", + "22015": "interval_field_overflow", + "2201E": "invalid_argument_for_logarithm", + "22014": "invalid_argument_for_ntile_function", + "22016": "invalid_argument_for_nth_value_function", + "2201F": "invalid_argument_for_power_function", + "2201G": "invalid_argument_for_width_bucket_function", + "22018": "invalid_character_value_for_cast", + "22007": "invalid_datetime_format", + "22019": "invalid_escape_character", + "2200D": "invalid_escape_octet", + "22025": "invalid_escape_sequence", + "22P06": "nonstandard_use_of_escape_character", + "22010": "invalid_indicator_parameter_value", + "22023": "invalid_parameter_value", + "2201B": "invalid_regular_expression", + "2201W": "invalid_row_count_in_limit_clause", + "2201X": "invalid_row_count_in_result_offset_clause", + "22009": "invalid_time_zone_displacement_value", + "2200C": "invalid_use_of_escape_character", + "2200G": "most_specific_type_mismatch", + "22004": "null_value_not_allowed", + "22002": "null_value_no_indicator_parameter", + "22003": "numeric_value_out_of_range", + "2200H": "sequence_generator_limit_exceeded", + "22026": "string_data_length_mismatch", + "22001": "string_data_right_truncation", + "22011": "substring_error", + "22027": "trim_error", + "22024": "unterminated_c_string", + "2200F": "zero_length_character_string", + "22P01": "floating_point_exception", + "22P02": "invalid_text_representation", + "22P03": "invalid_binary_representation", + "22P04": "bad_copy_file_format", + "22P05": "untranslatable_character", + "2200L": "not_an_xml_document", + "2200M": "invalid_xml_document", + "2200N": "invalid_xml_content", + "2200S": "invalid_xml_comment", + "2200T": "invalid_xml_processing_instruction", + // Class 23 - Integrity Constraint Violation + "23000": "integrity_constraint_violation", + "23001": "restrict_violation", + "23502": "not_null_violation", + "23503": "foreign_key_violation", + "23505": "unique_violation", + "23514": "check_violation", + "23P01": "exclusion_violation", + // Class 24 - Invalid Cursor State + "24000": "invalid_cursor_state", + // Class 25 - Invalid Transaction State + "25000": "invalid_transaction_state", + "25001": "active_sql_transaction", + "25002": "branch_transaction_already_active", + "25008": "held_cursor_requires_same_isolation_level", + "25003": "inappropriate_access_mode_for_branch_transaction", + "25004": "inappropriate_isolation_level_for_branch_transaction", + "25005": "no_active_sql_transaction_for_branch_transaction", + "25006": "read_only_sql_transaction", + "25007": "schema_and_data_statement_mixing_not_supported", + "25P01": "no_active_sql_transaction", + "25P02": "in_failed_sql_transaction", + // Class 26 - Invalid SQL Statement Name + "26000": "invalid_sql_statement_name", + // Class 27 - Triggered Data Change Violation + "27000": "triggered_data_change_violation", + // Class 28 - Invalid Authorization Specification + "28000": "invalid_authorization_specification", + "28P01": "invalid_password", + // Class 2B - Dependent Privilege Descriptors Still Exist + "2B000": "dependent_privilege_descriptors_still_exist", + "2BP01": "dependent_objects_still_exist", + // Class 2D - Invalid Transaction Termination + "2D000": "invalid_transaction_termination", + // Class 2F - SQL Routine Exception + "2F000": "sql_routine_exception", + "2F005": "function_executed_no_return_statement", + "2F002": "modifying_sql_data_not_permitted", + "2F003": "prohibited_sql_statement_attempted", + "2F004": "reading_sql_data_not_permitted", + // Class 34 - Invalid Cursor Name + "34000": "invalid_cursor_name", + // Class 38 - External Routine Exception + "38000": "external_routine_exception", + "38001": "containing_sql_not_permitted", + "38002": "modifying_sql_data_not_permitted", + "38003": "prohibited_sql_statement_attempted", + "38004": "reading_sql_data_not_permitted", + // Class 39 - External Routine Invocation Exception + "39000": "external_routine_invocation_exception", + "39001": "invalid_sqlstate_returned", + "39004": "null_value_not_allowed", + "39P01": "trigger_protocol_violated", + "39P02": "srf_protocol_violated", + // Class 3B - Savepoint Exception + "3B000": "savepoint_exception", + "3B001": "invalid_savepoint_specification", + // Class 3D - Invalid Catalog Name + "3D000": "invalid_catalog_name", + // Class 3F - Invalid Schema Name + "3F000": "invalid_schema_name", + // Class 40 - Transaction Rollback + "40000": "transaction_rollback", + "40002": "transaction_integrity_constraint_violation", + "40001": "serialization_failure", + "40003": "statement_completion_unknown", + "40P01": "deadlock_detected", + // Class 42 - Syntax Error or Access Rule Violation + "42000": "syntax_error_or_access_rule_violation", + "42601": "syntax_error", + "42501": "insufficient_privilege", + "42846": "cannot_coerce", + "42803": "grouping_error", + "42P20": "windowing_error", + "42P19": "invalid_recursion", + "42830": "invalid_foreign_key", + "42602": "invalid_name", + "42622": "name_too_long", + "42939": "reserved_name", + "42804": "datatype_mismatch", + "42P18": "indeterminate_datatype", + "42P21": "collation_mismatch", + "42P22": "indeterminate_collation", + "42809": "wrong_object_type", + "42703": "undefined_column", + "42883": "undefined_function", + "42P01": "undefined_table", + "42P02": "undefined_parameter", + "42704": "undefined_object", + "42701": "duplicate_column", + "42P03": "duplicate_cursor", + "42P04": "duplicate_database", + "42723": "duplicate_function", + "42P05": "duplicate_prepared_statement", + "42P06": "duplicate_schema", + "42P07": "duplicate_table", + "42712": "duplicate_alias", + "42710": "duplicate_object", + "42702": "ambiguous_column", + "42725": "ambiguous_function", + "42P08": "ambiguous_parameter", + "42P09": "ambiguous_alias", + "42P10": "invalid_column_reference", + "42611": "invalid_column_definition", + "42P11": "invalid_cursor_definition", + "42P12": "invalid_database_definition", + "42P13": "invalid_function_definition", + "42P14": "invalid_prepared_statement_definition", + "42P15": "invalid_schema_definition", + "42P16": "invalid_table_definition", + "42P17": "invalid_object_definition", + // Class 44 - WITH CHECK OPTION Violation + "44000": "with_check_option_violation", + // Class 53 - Insufficient Resources + "53000": "insufficient_resources", + "53100": "disk_full", + "53200": "out_of_memory", + "53300": "too_many_connections", + "53400": "configuration_limit_exceeded", + // Class 54 - Program Limit Exceeded + "54000": "program_limit_exceeded", + "54001": "statement_too_complex", + "54011": "too_many_columns", + "54023": "too_many_arguments", + // Class 55 - Object Not In Prerequisite State + "55000": "object_not_in_prerequisite_state", + "55006": "object_in_use", + "55P02": "cant_change_runtime_param", + "55P03": "lock_not_available", + // Class 57 - Operator Intervention + "57000": "operator_intervention", + "57014": "query_canceled", + "57P01": "admin_shutdown", + "57P02": "crash_shutdown", + "57P03": "cannot_connect_now", + "57P04": "database_dropped", + // Class 58 - System Error (errors external to PostgreSQL itself) + "58000": "system_error", + "58030": "io_error", + "58P01": "undefined_file", + "58P02": "duplicate_file", + // Class F0 - Configuration File Error + "F0000": "config_file_error", + "F0001": "lock_file_exists", + // Class HV - Foreign Data Wrapper Error (SQL/MED) + "HV000": "fdw_error", + "HV005": "fdw_column_name_not_found", + "HV002": "fdw_dynamic_parameter_value_needed", + "HV010": "fdw_function_sequence_error", + "HV021": "fdw_inconsistent_descriptor_information", + "HV024": "fdw_invalid_attribute_value", + "HV007": "fdw_invalid_column_name", + "HV008": "fdw_invalid_column_number", + "HV004": "fdw_invalid_data_type", + "HV006": "fdw_invalid_data_type_descriptors", + "HV091": "fdw_invalid_descriptor_field_identifier", + "HV00B": "fdw_invalid_handle", + "HV00C": "fdw_invalid_option_index", + "HV00D": "fdw_invalid_option_name", + "HV090": "fdw_invalid_string_length_or_buffer_length", + "HV00A": "fdw_invalid_string_format", + "HV009": "fdw_invalid_use_of_null_pointer", + "HV014": "fdw_too_many_handles", + "HV001": "fdw_out_of_memory", + "HV00P": "fdw_no_schemas", + "HV00J": "fdw_option_name_not_found", + "HV00K": "fdw_reply_handle", + "HV00Q": "fdw_schema_not_found", + "HV00R": "fdw_table_not_found", + "HV00L": "fdw_unable_to_create_execution", + "HV00M": "fdw_unable_to_create_reply", + "HV00N": "fdw_unable_to_establish_connection", + // Class P0 - PL/pgSQL Error + "P0000": "plpgsql_error", + "P0001": "raise_exception", + "P0002": "no_data_found", + "P0003": "too_many_rows", + // Class XX - Internal Error + "XX000": "internal_error", + "XX001": "data_corrupted", + "XX002": "index_corrupted", +} + +func parseError(r *readBuf) *Error { + err := new(Error) + for t := r.byte(); t != 0; t = r.byte() { + msg := r.string() + switch t { + case 'S': + err.Severity = msg + case 'C': + err.Code = ErrorCode(msg) + case 'M': + err.Message = msg + case 'D': + err.Detail = msg + case 'H': + err.Hint = msg + case 'P': + err.Position = msg + case 'p': + err.InternalPosition = msg + case 'q': + err.InternalQuery = msg + case 'W': + err.Where = msg + case 's': + err.Schema = msg + case 't': + err.Table = msg + case 'c': + err.Column = msg + case 'd': + err.DataTypeName = msg + case 'n': + err.Constraint = msg + case 'F': + err.File = msg + case 'L': + err.Line = msg + case 'R': + err.Routine = msg + } + } + return err +} + +// Fatal returns true if the Error Severity is fatal. +func (err *Error) Fatal() bool { + return err.Severity == Efatal +} + +// SQLState returns the SQLState of the error. +func (err *Error) SQLState() string { + return string(err.Code) +} + +// Get implements the legacy PGError interface. New code should use the fields +// of the Error struct directly. +func (err *Error) Get(k byte) (v string) { + switch k { + case 'S': + return err.Severity + case 'C': + return string(err.Code) + case 'M': + return err.Message + case 'D': + return err.Detail + case 'H': + return err.Hint + case 'P': + return err.Position + case 'p': + return err.InternalPosition + case 'q': + return err.InternalQuery + case 'W': + return err.Where + case 's': + return err.Schema + case 't': + return err.Table + case 'c': + return err.Column + case 'd': + return err.DataTypeName + case 'n': + return err.Constraint + case 'F': + return err.File + case 'L': + return err.Line + case 'R': + return err.Routine + } + return "" +} + +func (err *Error) Error() string { + return "pq: " + err.Message +} + +// PGError is an interface used by previous versions of pq. It is provided +// only to support legacy code. New code should use the Error type. +type PGError interface { + Error() string + Fatal() bool + Get(k byte) (v string) +} + +func errorf(s string, args ...interface{}) { + panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))) +} + +// TODO(ainar-g) Rename to errorf after removing panics. +func fmterrorf(s string, args ...interface{}) error { + return fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)) +} + +func errRecoverNoErrBadConn(err *error) { + e := recover() + if e == nil { + // Do nothing + return + } + var ok bool + *err, ok = e.(error) + if !ok { + *err = fmt.Errorf("pq: unexpected error: %#v", e) + } +} + +func (cn *conn) errRecover(err *error) { + e := recover() + switch v := e.(type) { + case nil: + // Do nothing + case runtime.Error: + cn.err.set(driver.ErrBadConn) + panic(v) + case *Error: + if v.Fatal() { + *err = driver.ErrBadConn + } else { + *err = v + } + case *net.OpError: + cn.err.set(driver.ErrBadConn) + *err = v + case *safeRetryError: + cn.err.set(driver.ErrBadConn) + *err = driver.ErrBadConn + case error: + if v == io.EOF || v.Error() == "remote error: handshake failure" { + *err = driver.ErrBadConn + } else { + *err = v + } + + default: + cn.err.set(driver.ErrBadConn) + panic(fmt.Sprintf("unknown error: %#v", e)) + } + + // Any time we return ErrBadConn, we need to remember it since *Tx doesn't + // mark the connection bad in database/sql. + if *err == driver.ErrBadConn { + cn.err.set(driver.ErrBadConn) + } +} diff --git a/vendor/github.com/lib/pq/krb.go b/vendor/github.com/lib/pq/krb.go new file mode 100644 index 0000000..408ec01 --- /dev/null +++ b/vendor/github.com/lib/pq/krb.go @@ -0,0 +1,27 @@ +package pq + +// NewGSSFunc creates a GSS authentication provider, for use with +// RegisterGSSProvider. +type NewGSSFunc func() (GSS, error) + +var newGss NewGSSFunc + +// RegisterGSSProvider registers a GSS authentication provider. For example, if +// you need to use Kerberos to authenticate with your server, add this to your +// main package: +// +// import "github.com/lib/pq/auth/kerberos" +// +// func init() { +// pq.RegisterGSSProvider(func() (pq.GSS, error) { return kerberos.NewGSS() }) +// } +func RegisterGSSProvider(newGssArg NewGSSFunc) { + newGss = newGssArg +} + +// GSS provides GSSAPI authentication (e.g., Kerberos). +type GSS interface { + GetInitToken(host string, service string) ([]byte, error) + GetInitTokenFromSpn(spn string) ([]byte, error) + Continue(inToken []byte) (done bool, outToken []byte, err error) +} diff --git a/vendor/github.com/lib/pq/notice.go b/vendor/github.com/lib/pq/notice.go new file mode 100644 index 0000000..70ad122 --- /dev/null +++ b/vendor/github.com/lib/pq/notice.go @@ -0,0 +1,72 @@ +//go:build go1.10 +// +build go1.10 + +package pq + +import ( + "context" + "database/sql/driver" +) + +// NoticeHandler returns the notice handler on the given connection, if any. A +// runtime panic occurs if c is not a pq connection. This is rarely used +// directly, use ConnectorNoticeHandler and ConnectorWithNoticeHandler instead. +func NoticeHandler(c driver.Conn) func(*Error) { + return c.(*conn).noticeHandler +} + +// SetNoticeHandler sets the given notice handler on the given connection. A +// runtime panic occurs if c is not a pq connection. A nil handler may be used +// to unset it. This is rarely used directly, use ConnectorNoticeHandler and +// ConnectorWithNoticeHandler instead. +// +// Note: Notice handlers are executed synchronously by pq meaning commands +// won't continue to be processed until the handler returns. +func SetNoticeHandler(c driver.Conn, handler func(*Error)) { + c.(*conn).noticeHandler = handler +} + +// NoticeHandlerConnector wraps a regular connector and sets a notice handler +// on it. +type NoticeHandlerConnector struct { + driver.Connector + noticeHandler func(*Error) +} + +// Connect calls the underlying connector's connect method and then sets the +// notice handler. +func (n *NoticeHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) { + c, err := n.Connector.Connect(ctx) + if err == nil { + SetNoticeHandler(c, n.noticeHandler) + } + return c, err +} + +// ConnectorNoticeHandler returns the currently set notice handler, if any. If +// the given connector is not a result of ConnectorWithNoticeHandler, nil is +// returned. +func ConnectorNoticeHandler(c driver.Connector) func(*Error) { + if c, ok := c.(*NoticeHandlerConnector); ok { + return c.noticeHandler + } + return nil +} + +// ConnectorWithNoticeHandler creates or sets the given handler for the given +// connector. If the given connector is a result of calling this function +// previously, it is simply set on the given connector and returned. Otherwise, +// this returns a new connector wrapping the given one and setting the notice +// handler. A nil notice handler may be used to unset it. +// +// The returned connector is intended to be used with database/sql.OpenDB. +// +// Note: Notice handlers are executed synchronously by pq meaning commands +// won't continue to be processed until the handler returns. +func ConnectorWithNoticeHandler(c driver.Connector, handler func(*Error)) *NoticeHandlerConnector { + if c, ok := c.(*NoticeHandlerConnector); ok { + c.noticeHandler = handler + return c + } + return &NoticeHandlerConnector{Connector: c, noticeHandler: handler} +} diff --git a/vendor/github.com/lib/pq/notify.go b/vendor/github.com/lib/pq/notify.go new file mode 100644 index 0000000..5c421fd --- /dev/null +++ b/vendor/github.com/lib/pq/notify.go @@ -0,0 +1,858 @@ +package pq + +// Package pq is a pure Go Postgres driver for the database/sql package. +// This module contains support for Postgres LISTEN/NOTIFY. + +import ( + "context" + "database/sql/driver" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" +) + +// Notification represents a single notification from the database. +type Notification struct { + // Process ID (PID) of the notifying postgres backend. + BePid int + // Name of the channel the notification was sent on. + Channel string + // Payload, or the empty string if unspecified. + Extra string +} + +func recvNotification(r *readBuf) *Notification { + bePid := r.int32() + channel := r.string() + extra := r.string() + + return &Notification{bePid, channel, extra} +} + +// SetNotificationHandler sets the given notification handler on the given +// connection. A runtime panic occurs if c is not a pq connection. A nil handler +// may be used to unset it. +// +// Note: Notification handlers are executed synchronously by pq meaning commands +// won't continue to be processed until the handler returns. +func SetNotificationHandler(c driver.Conn, handler func(*Notification)) { + c.(*conn).notificationHandler = handler +} + +// NotificationHandlerConnector wraps a regular connector and sets a notification handler +// on it. +type NotificationHandlerConnector struct { + driver.Connector + notificationHandler func(*Notification) +} + +// Connect calls the underlying connector's connect method and then sets the +// notification handler. +func (n *NotificationHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) { + c, err := n.Connector.Connect(ctx) + if err == nil { + SetNotificationHandler(c, n.notificationHandler) + } + return c, err +} + +// ConnectorNotificationHandler returns the currently set notification handler, if any. If +// the given connector is not a result of ConnectorWithNotificationHandler, nil is +// returned. +func ConnectorNotificationHandler(c driver.Connector) func(*Notification) { + if c, ok := c.(*NotificationHandlerConnector); ok { + return c.notificationHandler + } + return nil +} + +// ConnectorWithNotificationHandler creates or sets the given handler for the given +// connector. If the given connector is a result of calling this function +// previously, it is simply set on the given connector and returned. Otherwise, +// this returns a new connector wrapping the given one and setting the notification +// handler. A nil notification handler may be used to unset it. +// +// The returned connector is intended to be used with database/sql.OpenDB. +// +// Note: Notification handlers are executed synchronously by pq meaning commands +// won't continue to be processed until the handler returns. +func ConnectorWithNotificationHandler(c driver.Connector, handler func(*Notification)) *NotificationHandlerConnector { + if c, ok := c.(*NotificationHandlerConnector); ok { + c.notificationHandler = handler + return c + } + return &NotificationHandlerConnector{Connector: c, notificationHandler: handler} +} + +const ( + connStateIdle int32 = iota + connStateExpectResponse + connStateExpectReadyForQuery +) + +type message struct { + typ byte + err error +} + +var errListenerConnClosed = errors.New("pq: ListenerConn has been closed") + +// ListenerConn is a low-level interface for waiting for notifications. You +// should use Listener instead. +type ListenerConn struct { + // guards cn and err + connectionLock sync.Mutex + cn *conn + err error + + connState int32 + + // the sending goroutine will be holding this lock + senderLock sync.Mutex + + notificationChan chan<- *Notification + + replyChan chan message +} + +// NewListenerConn creates a new ListenerConn. Use NewListener instead. +func NewListenerConn(name string, notificationChan chan<- *Notification) (*ListenerConn, error) { + return newDialListenerConn(defaultDialer{}, name, notificationChan) +} + +func newDialListenerConn(d Dialer, name string, c chan<- *Notification) (*ListenerConn, error) { + cn, err := DialOpen(d, name) + if err != nil { + return nil, err + } + + l := &ListenerConn{ + cn: cn.(*conn), + notificationChan: c, + connState: connStateIdle, + replyChan: make(chan message, 2), + } + + go l.listenerConnMain() + + return l, nil +} + +// We can only allow one goroutine at a time to be running a query on the +// connection for various reasons, so the goroutine sending on the connection +// must be holding senderLock. +// +// Returns an error if an unrecoverable error has occurred and the ListenerConn +// should be abandoned. +func (l *ListenerConn) acquireSenderLock() error { + // we must acquire senderLock first to avoid deadlocks; see ExecSimpleQuery + l.senderLock.Lock() + + l.connectionLock.Lock() + err := l.err + l.connectionLock.Unlock() + if err != nil { + l.senderLock.Unlock() + return err + } + return nil +} + +func (l *ListenerConn) releaseSenderLock() { + l.senderLock.Unlock() +} + +// setState advances the protocol state to newState. Returns false if moving +// to that state from the current state is not allowed. +func (l *ListenerConn) setState(newState int32) bool { + var expectedState int32 + + switch newState { + case connStateIdle: + expectedState = connStateExpectReadyForQuery + case connStateExpectResponse: + expectedState = connStateIdle + case connStateExpectReadyForQuery: + expectedState = connStateExpectResponse + default: + panic(fmt.Sprintf("unexpected listenerConnState %d", newState)) + } + + return atomic.CompareAndSwapInt32(&l.connState, expectedState, newState) +} + +// Main logic is here: receive messages from the postgres backend, forward +// notifications and query replies and keep the internal state in sync with the +// protocol state. Returns when the connection has been lost, is about to go +// away or should be discarded because we couldn't agree on the state with the +// server backend. +func (l *ListenerConn) listenerConnLoop() (err error) { + defer errRecoverNoErrBadConn(&err) + + r := &readBuf{} + for { + t, err := l.cn.recvMessage(r) + if err != nil { + return err + } + + switch t { + case 'A': + // recvNotification copies all the data so we don't need to worry + // about the scratch buffer being overwritten. + l.notificationChan <- recvNotification(r) + + case 'T', 'D': + // only used by tests; ignore + + case 'E': + // We might receive an ErrorResponse even when not in a query; it + // is expected that the server will close the connection after + // that, but we should make sure that the error we display is the + // one from the stray ErrorResponse, not io.ErrUnexpectedEOF. + if !l.setState(connStateExpectReadyForQuery) { + return parseError(r) + } + l.replyChan <- message{t, parseError(r)} + + case 'C', 'I': + if !l.setState(connStateExpectReadyForQuery) { + // protocol out of sync + return fmt.Errorf("unexpected CommandComplete") + } + // ExecSimpleQuery doesn't need to know about this message + + case 'Z': + if !l.setState(connStateIdle) { + // protocol out of sync + return fmt.Errorf("unexpected ReadyForQuery") + } + l.replyChan <- message{t, nil} + + case 'S': + // ignore + case 'N': + if n := l.cn.noticeHandler; n != nil { + n(parseError(r)) + } + default: + return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t) + } + } +} + +// This is the main routine for the goroutine receiving on the database +// connection. Most of the main logic is in listenerConnLoop. +func (l *ListenerConn) listenerConnMain() { + err := l.listenerConnLoop() + + // listenerConnLoop terminated; we're done, but we still have to clean up. + // Make sure nobody tries to start any new queries by making sure the err + // pointer is set. It is important that we do not overwrite its value; a + // connection could be closed by either this goroutine or one sending on + // the connection -- whoever closes the connection is assumed to have the + // more meaningful error message (as the other one will probably get + // net.errClosed), so that goroutine sets the error we expose while the + // other error is discarded. If the connection is lost while two + // goroutines are operating on the socket, it probably doesn't matter which + // error we expose so we don't try to do anything more complex. + l.connectionLock.Lock() + if l.err == nil { + l.err = err + } + l.cn.Close() + l.connectionLock.Unlock() + + // There might be a query in-flight; make sure nobody's waiting for a + // response to it, since there's not going to be one. + close(l.replyChan) + + // let the listener know we're done + close(l.notificationChan) + + // this ListenerConn is done +} + +// Listen sends a LISTEN query to the server. See ExecSimpleQuery. +func (l *ListenerConn) Listen(channel string) (bool, error) { + return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel)) +} + +// Unlisten sends an UNLISTEN query to the server. See ExecSimpleQuery. +func (l *ListenerConn) Unlisten(channel string) (bool, error) { + return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel)) +} + +// UnlistenAll sends an `UNLISTEN *` query to the server. See ExecSimpleQuery. +func (l *ListenerConn) UnlistenAll() (bool, error) { + return l.ExecSimpleQuery("UNLISTEN *") +} + +// Ping the remote server to make sure it's alive. Non-nil error means the +// connection has failed and should be abandoned. +func (l *ListenerConn) Ping() error { + sent, err := l.ExecSimpleQuery("") + if !sent { + return err + } + if err != nil { + // shouldn't happen + panic(err) + } + return nil +} + +// Attempt to send a query on the connection. Returns an error if sending the +// query failed, and the caller should initiate closure of this connection. +// The caller must be holding senderLock (see acquireSenderLock and +// releaseSenderLock). +func (l *ListenerConn) sendSimpleQuery(q string) (err error) { + defer errRecoverNoErrBadConn(&err) + + // must set connection state before sending the query + if !l.setState(connStateExpectResponse) { + panic("two queries running at the same time") + } + + // Can't use l.cn.writeBuf here because it uses the scratch buffer which + // might get overwritten by listenerConnLoop. + b := &writeBuf{ + buf: []byte("Q\x00\x00\x00\x00"), + pos: 1, + } + b.string(q) + l.cn.send(b) + + return nil +} + +// ExecSimpleQuery executes a "simple query" (i.e. one with no bindable +// parameters) on the connection. The possible return values are: +// 1) "executed" is true; the query was executed to completion on the +// database server. If the query failed, err will be set to the error +// returned by the database, otherwise err will be nil. +// 2) If "executed" is false, the query could not be executed on the remote +// server. err will be non-nil. +// +// After a call to ExecSimpleQuery has returned an executed=false value, the +// connection has either been closed or will be closed shortly thereafter, and +// all subsequently executed queries will return an error. +func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) { + if err = l.acquireSenderLock(); err != nil { + return false, err + } + defer l.releaseSenderLock() + + err = l.sendSimpleQuery(q) + if err != nil { + // We can't know what state the protocol is in, so we need to abandon + // this connection. + l.connectionLock.Lock() + // Set the error pointer if it hasn't been set already; see + // listenerConnMain. + if l.err == nil { + l.err = err + } + l.connectionLock.Unlock() + l.cn.c.Close() + return false, err + } + + // now we just wait for a reply.. + for { + m, ok := <-l.replyChan + if !ok { + // We lost the connection to server, don't bother waiting for a + // a response. err should have been set already. + l.connectionLock.Lock() + err := l.err + l.connectionLock.Unlock() + return false, err + } + switch m.typ { + case 'Z': + // sanity check + if m.err != nil { + panic("m.err != nil") + } + // done; err might or might not be set + return true, err + + case 'E': + // sanity check + if m.err == nil { + panic("m.err == nil") + } + // server responded with an error; ReadyForQuery to follow + err = m.err + + default: + return false, fmt.Errorf("unknown response for simple query: %q", m.typ) + } + } +} + +// Close closes the connection. +func (l *ListenerConn) Close() error { + l.connectionLock.Lock() + if l.err != nil { + l.connectionLock.Unlock() + return errListenerConnClosed + } + l.err = errListenerConnClosed + l.connectionLock.Unlock() + // We can't send anything on the connection without holding senderLock. + // Simply close the net.Conn to wake up everyone operating on it. + return l.cn.c.Close() +} + +// Err returns the reason the connection was closed. It is not safe to call +// this function until l.Notify has been closed. +func (l *ListenerConn) Err() error { + return l.err +} + +var errListenerClosed = errors.New("pq: Listener has been closed") + +// ErrChannelAlreadyOpen is returned from Listen when a channel is already +// open. +var ErrChannelAlreadyOpen = errors.New("pq: channel is already open") + +// ErrChannelNotOpen is returned from Unlisten when a channel is not open. +var ErrChannelNotOpen = errors.New("pq: channel is not open") + +// ListenerEventType is an enumeration of listener event types. +type ListenerEventType int + +const ( + // ListenerEventConnected is emitted only when the database connection + // has been initially initialized. The err argument of the callback + // will always be nil. + ListenerEventConnected ListenerEventType = iota + + // ListenerEventDisconnected is emitted after a database connection has + // been lost, either because of an error or because Close has been + // called. The err argument will be set to the reason the database + // connection was lost. + ListenerEventDisconnected + + // ListenerEventReconnected is emitted after a database connection has + // been re-established after connection loss. The err argument of the + // callback will always be nil. After this event has been emitted, a + // nil pq.Notification is sent on the Listener.Notify channel. + ListenerEventReconnected + + // ListenerEventConnectionAttemptFailed is emitted after a connection + // to the database was attempted, but failed. The err argument will be + // set to an error describing why the connection attempt did not + // succeed. + ListenerEventConnectionAttemptFailed +) + +// EventCallbackType is the event callback type. See also ListenerEventType +// constants' documentation. +type EventCallbackType func(event ListenerEventType, err error) + +// Listener provides an interface for listening to notifications from a +// PostgreSQL database. For general usage information, see section +// "Notifications". +// +// Listener can safely be used from concurrently running goroutines. +type Listener struct { + // Channel for receiving notifications from the database. In some cases a + // nil value will be sent. See section "Notifications" above. + Notify chan *Notification + + name string + minReconnectInterval time.Duration + maxReconnectInterval time.Duration + dialer Dialer + eventCallback EventCallbackType + + lock sync.Mutex + isClosed bool + reconnectCond *sync.Cond + cn *ListenerConn + connNotificationChan <-chan *Notification + channels map[string]struct{} +} + +// NewListener creates a new database connection dedicated to LISTEN / NOTIFY. +// +// name should be set to a connection string to be used to establish the +// database connection (see section "Connection String Parameters" above). +// +// minReconnectInterval controls the duration to wait before trying to +// re-establish the database connection after connection loss. After each +// consecutive failure this interval is doubled, until maxReconnectInterval is +// reached. Successfully completing the connection establishment procedure +// resets the interval back to minReconnectInterval. +// +// The last parameter eventCallback can be set to a function which will be +// called by the Listener when the state of the underlying database connection +// changes. This callback will be called by the goroutine which dispatches the +// notifications over the Notify channel, so you should try to avoid doing +// potentially time-consuming operations from the callback. +func NewListener(name string, + minReconnectInterval time.Duration, + maxReconnectInterval time.Duration, + eventCallback EventCallbackType) *Listener { + return NewDialListener(defaultDialer{}, name, minReconnectInterval, maxReconnectInterval, eventCallback) +} + +// NewDialListener is like NewListener but it takes a Dialer. +func NewDialListener(d Dialer, + name string, + minReconnectInterval time.Duration, + maxReconnectInterval time.Duration, + eventCallback EventCallbackType) *Listener { + + l := &Listener{ + name: name, + minReconnectInterval: minReconnectInterval, + maxReconnectInterval: maxReconnectInterval, + dialer: d, + eventCallback: eventCallback, + + channels: make(map[string]struct{}), + + Notify: make(chan *Notification, 32), + } + l.reconnectCond = sync.NewCond(&l.lock) + + go l.listenerMain() + + return l +} + +// NotificationChannel returns the notification channel for this listener. +// This is the same channel as Notify, and will not be recreated during the +// life time of the Listener. +func (l *Listener) NotificationChannel() <-chan *Notification { + return l.Notify +} + +// Listen starts listening for notifications on a channel. Calls to this +// function will block until an acknowledgement has been received from the +// server. Note that Listener automatically re-establishes the connection +// after connection loss, so this function may block indefinitely if the +// connection can not be re-established. +// +// Listen will only fail in three conditions: +// 1) The channel is already open. The returned error will be +// ErrChannelAlreadyOpen. +// 2) The query was executed on the remote server, but PostgreSQL returned an +// error message in response to the query. The returned error will be a +// pq.Error containing the information the server supplied. +// 3) Close is called on the Listener before the request could be completed. +// +// The channel name is case-sensitive. +func (l *Listener) Listen(channel string) error { + l.lock.Lock() + defer l.lock.Unlock() + + if l.isClosed { + return errListenerClosed + } + + // The server allows you to issue a LISTEN on a channel which is already + // open, but it seems useful to be able to detect this case to spot for + // mistakes in application logic. If the application genuinely does't + // care, it can check the exported error and ignore it. + _, exists := l.channels[channel] + if exists { + return ErrChannelAlreadyOpen + } + + if l.cn != nil { + // If gotResponse is true but error is set, the query was executed on + // the remote server, but resulted in an error. This should be + // relatively rare, so it's fine if we just pass the error to our + // caller. However, if gotResponse is false, we could not complete the + // query on the remote server and our underlying connection is about + // to go away, so we only add relname to l.channels, and wait for + // resync() to take care of the rest. + gotResponse, err := l.cn.Listen(channel) + if gotResponse && err != nil { + return err + } + } + + l.channels[channel] = struct{}{} + for l.cn == nil { + l.reconnectCond.Wait() + // we let go of the mutex for a while + if l.isClosed { + return errListenerClosed + } + } + + return nil +} + +// Unlisten removes a channel from the Listener's channel list. Returns +// ErrChannelNotOpen if the Listener is not listening on the specified channel. +// Returns immediately with no error if there is no connection. Note that you +// might still get notifications for this channel even after Unlisten has +// returned. +// +// The channel name is case-sensitive. +func (l *Listener) Unlisten(channel string) error { + l.lock.Lock() + defer l.lock.Unlock() + + if l.isClosed { + return errListenerClosed + } + + // Similarly to LISTEN, this is not an error in Postgres, but it seems + // useful to distinguish from the normal conditions. + _, exists := l.channels[channel] + if !exists { + return ErrChannelNotOpen + } + + if l.cn != nil { + // Similarly to Listen (see comment in that function), the caller + // should only be bothered with an error if it came from the backend as + // a response to our query. + gotResponse, err := l.cn.Unlisten(channel) + if gotResponse && err != nil { + return err + } + } + + // Don't bother waiting for resync if there's no connection. + delete(l.channels, channel) + return nil +} + +// UnlistenAll removes all channels from the Listener's channel list. Returns +// immediately with no error if there is no connection. Note that you might +// still get notifications for any of the deleted channels even after +// UnlistenAll has returned. +func (l *Listener) UnlistenAll() error { + l.lock.Lock() + defer l.lock.Unlock() + + if l.isClosed { + return errListenerClosed + } + + if l.cn != nil { + // Similarly to Listen (see comment in that function), the caller + // should only be bothered with an error if it came from the backend as + // a response to our query. + gotResponse, err := l.cn.UnlistenAll() + if gotResponse && err != nil { + return err + } + } + + // Don't bother waiting for resync if there's no connection. + l.channels = make(map[string]struct{}) + return nil +} + +// Ping the remote server to make sure it's alive. Non-nil return value means +// that there is no active connection. +func (l *Listener) Ping() error { + l.lock.Lock() + defer l.lock.Unlock() + + if l.isClosed { + return errListenerClosed + } + if l.cn == nil { + return errors.New("no connection") + } + + return l.cn.Ping() +} + +// Clean up after losing the server connection. Returns l.cn.Err(), which +// should have the reason the connection was lost. +func (l *Listener) disconnectCleanup() error { + l.lock.Lock() + defer l.lock.Unlock() + + // sanity check; can't look at Err() until the channel has been closed + select { + case _, ok := <-l.connNotificationChan: + if ok { + panic("connNotificationChan not closed") + } + default: + panic("connNotificationChan not closed") + } + + err := l.cn.Err() + l.cn.Close() + l.cn = nil + return err +} + +// Synchronize the list of channels we want to be listening on with the server +// after the connection has been established. +func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notification) error { + doneChan := make(chan error) + go func(notificationChan <-chan *Notification) { + for channel := range l.channels { + // If we got a response, return that error to our caller as it's + // going to be more descriptive than cn.Err(). + gotResponse, err := cn.Listen(channel) + if gotResponse && err != nil { + doneChan <- err + return + } + + // If we couldn't reach the server, wait for notificationChan to + // close and then return the error message from the connection, as + // per ListenerConn's interface. + if err != nil { + for range notificationChan { + } + doneChan <- cn.Err() + return + } + } + doneChan <- nil + }(notificationChan) + + // Ignore notifications while synchronization is going on to avoid + // deadlocks. We have to send a nil notification over Notify anyway as + // we can't possibly know which notifications (if any) were lost while + // the connection was down, so there's no reason to try and process + // these messages at all. + for { + select { + case _, ok := <-notificationChan: + if !ok { + notificationChan = nil + } + + case err := <-doneChan: + return err + } + } +} + +// caller should NOT be holding l.lock +func (l *Listener) closed() bool { + l.lock.Lock() + defer l.lock.Unlock() + + return l.isClosed +} + +func (l *Listener) connect() error { + notificationChan := make(chan *Notification, 32) + cn, err := newDialListenerConn(l.dialer, l.name, notificationChan) + if err != nil { + return err + } + + l.lock.Lock() + defer l.lock.Unlock() + + err = l.resync(cn, notificationChan) + if err != nil { + cn.Close() + return err + } + + l.cn = cn + l.connNotificationChan = notificationChan + l.reconnectCond.Broadcast() + + return nil +} + +// Close disconnects the Listener from the database and shuts it down. +// Subsequent calls to its methods will return an error. Close returns an +// error if the connection has already been closed. +func (l *Listener) Close() error { + l.lock.Lock() + defer l.lock.Unlock() + + if l.isClosed { + return errListenerClosed + } + + if l.cn != nil { + l.cn.Close() + } + l.isClosed = true + + // Unblock calls to Listen() + l.reconnectCond.Broadcast() + + return nil +} + +func (l *Listener) emitEvent(event ListenerEventType, err error) { + if l.eventCallback != nil { + l.eventCallback(event, err) + } +} + +// Main logic here: maintain a connection to the server when possible, wait +// for notifications and emit events. +func (l *Listener) listenerConnLoop() { + var nextReconnect time.Time + + reconnectInterval := l.minReconnectInterval + for { + for { + err := l.connect() + if err == nil { + break + } + + if l.closed() { + return + } + l.emitEvent(ListenerEventConnectionAttemptFailed, err) + + time.Sleep(reconnectInterval) + reconnectInterval *= 2 + if reconnectInterval > l.maxReconnectInterval { + reconnectInterval = l.maxReconnectInterval + } + } + + if nextReconnect.IsZero() { + l.emitEvent(ListenerEventConnected, nil) + } else { + l.emitEvent(ListenerEventReconnected, nil) + l.Notify <- nil + } + + reconnectInterval = l.minReconnectInterval + nextReconnect = time.Now().Add(reconnectInterval) + + for { + notification, ok := <-l.connNotificationChan + if !ok { + // lost connection, loop again + break + } + l.Notify <- notification + } + + err := l.disconnectCleanup() + if l.closed() { + return + } + l.emitEvent(ListenerEventDisconnected, err) + + time.Sleep(time.Until(nextReconnect)) + } +} + +func (l *Listener) listenerMain() { + l.listenerConnLoop() + close(l.Notify) +} diff --git a/vendor/github.com/lib/pq/oid/doc.go b/vendor/github.com/lib/pq/oid/doc.go new file mode 100644 index 0000000..caaede2 --- /dev/null +++ b/vendor/github.com/lib/pq/oid/doc.go @@ -0,0 +1,6 @@ +// Package oid contains OID constants +// as defined by the Postgres server. +package oid + +// Oid is a Postgres Object ID. +type Oid uint32 diff --git a/vendor/github.com/lib/pq/oid/types.go b/vendor/github.com/lib/pq/oid/types.go new file mode 100644 index 0000000..ecc84c2 --- /dev/null +++ b/vendor/github.com/lib/pq/oid/types.go @@ -0,0 +1,343 @@ +// Code generated by gen.go. DO NOT EDIT. + +package oid + +const ( + T_bool Oid = 16 + T_bytea Oid = 17 + T_char Oid = 18 + T_name Oid = 19 + T_int8 Oid = 20 + T_int2 Oid = 21 + T_int2vector Oid = 22 + T_int4 Oid = 23 + T_regproc Oid = 24 + T_text Oid = 25 + T_oid Oid = 26 + T_tid Oid = 27 + T_xid Oid = 28 + T_cid Oid = 29 + T_oidvector Oid = 30 + T_pg_ddl_command Oid = 32 + T_pg_type Oid = 71 + T_pg_attribute Oid = 75 + T_pg_proc Oid = 81 + T_pg_class Oid = 83 + T_json Oid = 114 + T_xml Oid = 142 + T__xml Oid = 143 + T_pg_node_tree Oid = 194 + T__json Oid = 199 + T_smgr Oid = 210 + T_index_am_handler Oid = 325 + T_point Oid = 600 + T_lseg Oid = 601 + T_path Oid = 602 + T_box Oid = 603 + T_polygon Oid = 604 + T_line Oid = 628 + T__line Oid = 629 + T_cidr Oid = 650 + T__cidr Oid = 651 + T_float4 Oid = 700 + T_float8 Oid = 701 + T_abstime Oid = 702 + T_reltime Oid = 703 + T_tinterval Oid = 704 + T_unknown Oid = 705 + T_circle Oid = 718 + T__circle Oid = 719 + T_money Oid = 790 + T__money Oid = 791 + T_macaddr Oid = 829 + T_inet Oid = 869 + T__bool Oid = 1000 + T__bytea Oid = 1001 + T__char Oid = 1002 + T__name Oid = 1003 + T__int2 Oid = 1005 + T__int2vector Oid = 1006 + T__int4 Oid = 1007 + T__regproc Oid = 1008 + T__text Oid = 1009 + T__tid Oid = 1010 + T__xid Oid = 1011 + T__cid Oid = 1012 + T__oidvector Oid = 1013 + T__bpchar Oid = 1014 + T__varchar Oid = 1015 + T__int8 Oid = 1016 + T__point Oid = 1017 + T__lseg Oid = 1018 + T__path Oid = 1019 + T__box Oid = 1020 + T__float4 Oid = 1021 + T__float8 Oid = 1022 + T__abstime Oid = 1023 + T__reltime Oid = 1024 + T__tinterval Oid = 1025 + T__polygon Oid = 1027 + T__oid Oid = 1028 + T_aclitem Oid = 1033 + T__aclitem Oid = 1034 + T__macaddr Oid = 1040 + T__inet Oid = 1041 + T_bpchar Oid = 1042 + T_varchar Oid = 1043 + T_date Oid = 1082 + T_time Oid = 1083 + T_timestamp Oid = 1114 + T__timestamp Oid = 1115 + T__date Oid = 1182 + T__time Oid = 1183 + T_timestamptz Oid = 1184 + T__timestamptz Oid = 1185 + T_interval Oid = 1186 + T__interval Oid = 1187 + T__numeric Oid = 1231 + T_pg_database Oid = 1248 + T__cstring Oid = 1263 + T_timetz Oid = 1266 + T__timetz Oid = 1270 + T_bit Oid = 1560 + T__bit Oid = 1561 + T_varbit Oid = 1562 + T__varbit Oid = 1563 + T_numeric Oid = 1700 + T_refcursor Oid = 1790 + T__refcursor Oid = 2201 + T_regprocedure Oid = 2202 + T_regoper Oid = 2203 + T_regoperator Oid = 2204 + T_regclass Oid = 2205 + T_regtype Oid = 2206 + T__regprocedure Oid = 2207 + T__regoper Oid = 2208 + T__regoperator Oid = 2209 + T__regclass Oid = 2210 + T__regtype Oid = 2211 + T_record Oid = 2249 + T_cstring Oid = 2275 + T_any Oid = 2276 + T_anyarray Oid = 2277 + T_void Oid = 2278 + T_trigger Oid = 2279 + T_language_handler Oid = 2280 + T_internal Oid = 2281 + T_opaque Oid = 2282 + T_anyelement Oid = 2283 + T__record Oid = 2287 + T_anynonarray Oid = 2776 + T_pg_authid Oid = 2842 + T_pg_auth_members Oid = 2843 + T__txid_snapshot Oid = 2949 + T_uuid Oid = 2950 + T__uuid Oid = 2951 + T_txid_snapshot Oid = 2970 + T_fdw_handler Oid = 3115 + T_pg_lsn Oid = 3220 + T__pg_lsn Oid = 3221 + T_tsm_handler Oid = 3310 + T_anyenum Oid = 3500 + T_tsvector Oid = 3614 + T_tsquery Oid = 3615 + T_gtsvector Oid = 3642 + T__tsvector Oid = 3643 + T__gtsvector Oid = 3644 + T__tsquery Oid = 3645 + T_regconfig Oid = 3734 + T__regconfig Oid = 3735 + T_regdictionary Oid = 3769 + T__regdictionary Oid = 3770 + T_jsonb Oid = 3802 + T__jsonb Oid = 3807 + T_anyrange Oid = 3831 + T_event_trigger Oid = 3838 + T_int4range Oid = 3904 + T__int4range Oid = 3905 + T_numrange Oid = 3906 + T__numrange Oid = 3907 + T_tsrange Oid = 3908 + T__tsrange Oid = 3909 + T_tstzrange Oid = 3910 + T__tstzrange Oid = 3911 + T_daterange Oid = 3912 + T__daterange Oid = 3913 + T_int8range Oid = 3926 + T__int8range Oid = 3927 + T_pg_shseclabel Oid = 4066 + T_regnamespace Oid = 4089 + T__regnamespace Oid = 4090 + T_regrole Oid = 4096 + T__regrole Oid = 4097 +) + +var TypeName = map[Oid]string{ + T_bool: "BOOL", + T_bytea: "BYTEA", + T_char: "CHAR", + T_name: "NAME", + T_int8: "INT8", + T_int2: "INT2", + T_int2vector: "INT2VECTOR", + T_int4: "INT4", + T_regproc: "REGPROC", + T_text: "TEXT", + T_oid: "OID", + T_tid: "TID", + T_xid: "XID", + T_cid: "CID", + T_oidvector: "OIDVECTOR", + T_pg_ddl_command: "PG_DDL_COMMAND", + T_pg_type: "PG_TYPE", + T_pg_attribute: "PG_ATTRIBUTE", + T_pg_proc: "PG_PROC", + T_pg_class: "PG_CLASS", + T_json: "JSON", + T_xml: "XML", + T__xml: "_XML", + T_pg_node_tree: "PG_NODE_TREE", + T__json: "_JSON", + T_smgr: "SMGR", + T_index_am_handler: "INDEX_AM_HANDLER", + T_point: "POINT", + T_lseg: "LSEG", + T_path: "PATH", + T_box: "BOX", + T_polygon: "POLYGON", + T_line: "LINE", + T__line: "_LINE", + T_cidr: "CIDR", + T__cidr: "_CIDR", + T_float4: "FLOAT4", + T_float8: "FLOAT8", + T_abstime: "ABSTIME", + T_reltime: "RELTIME", + T_tinterval: "TINTERVAL", + T_unknown: "UNKNOWN", + T_circle: "CIRCLE", + T__circle: "_CIRCLE", + T_money: "MONEY", + T__money: "_MONEY", + T_macaddr: "MACADDR", + T_inet: "INET", + T__bool: "_BOOL", + T__bytea: "_BYTEA", + T__char: "_CHAR", + T__name: "_NAME", + T__int2: "_INT2", + T__int2vector: "_INT2VECTOR", + T__int4: "_INT4", + T__regproc: "_REGPROC", + T__text: "_TEXT", + T__tid: "_TID", + T__xid: "_XID", + T__cid: "_CID", + T__oidvector: "_OIDVECTOR", + T__bpchar: "_BPCHAR", + T__varchar: "_VARCHAR", + T__int8: "_INT8", + T__point: "_POINT", + T__lseg: "_LSEG", + T__path: "_PATH", + T__box: "_BOX", + T__float4: "_FLOAT4", + T__float8: "_FLOAT8", + T__abstime: "_ABSTIME", + T__reltime: "_RELTIME", + T__tinterval: "_TINTERVAL", + T__polygon: "_POLYGON", + T__oid: "_OID", + T_aclitem: "ACLITEM", + T__aclitem: "_ACLITEM", + T__macaddr: "_MACADDR", + T__inet: "_INET", + T_bpchar: "BPCHAR", + T_varchar: "VARCHAR", + T_date: "DATE", + T_time: "TIME", + T_timestamp: "TIMESTAMP", + T__timestamp: "_TIMESTAMP", + T__date: "_DATE", + T__time: "_TIME", + T_timestamptz: "TIMESTAMPTZ", + T__timestamptz: "_TIMESTAMPTZ", + T_interval: "INTERVAL", + T__interval: "_INTERVAL", + T__numeric: "_NUMERIC", + T_pg_database: "PG_DATABASE", + T__cstring: "_CSTRING", + T_timetz: "TIMETZ", + T__timetz: "_TIMETZ", + T_bit: "BIT", + T__bit: "_BIT", + T_varbit: "VARBIT", + T__varbit: "_VARBIT", + T_numeric: "NUMERIC", + T_refcursor: "REFCURSOR", + T__refcursor: "_REFCURSOR", + T_regprocedure: "REGPROCEDURE", + T_regoper: "REGOPER", + T_regoperator: "REGOPERATOR", + T_regclass: "REGCLASS", + T_regtype: "REGTYPE", + T__regprocedure: "_REGPROCEDURE", + T__regoper: "_REGOPER", + T__regoperator: "_REGOPERATOR", + T__regclass: "_REGCLASS", + T__regtype: "_REGTYPE", + T_record: "RECORD", + T_cstring: "CSTRING", + T_any: "ANY", + T_anyarray: "ANYARRAY", + T_void: "VOID", + T_trigger: "TRIGGER", + T_language_handler: "LANGUAGE_HANDLER", + T_internal: "INTERNAL", + T_opaque: "OPAQUE", + T_anyelement: "ANYELEMENT", + T__record: "_RECORD", + T_anynonarray: "ANYNONARRAY", + T_pg_authid: "PG_AUTHID", + T_pg_auth_members: "PG_AUTH_MEMBERS", + T__txid_snapshot: "_TXID_SNAPSHOT", + T_uuid: "UUID", + T__uuid: "_UUID", + T_txid_snapshot: "TXID_SNAPSHOT", + T_fdw_handler: "FDW_HANDLER", + T_pg_lsn: "PG_LSN", + T__pg_lsn: "_PG_LSN", + T_tsm_handler: "TSM_HANDLER", + T_anyenum: "ANYENUM", + T_tsvector: "TSVECTOR", + T_tsquery: "TSQUERY", + T_gtsvector: "GTSVECTOR", + T__tsvector: "_TSVECTOR", + T__gtsvector: "_GTSVECTOR", + T__tsquery: "_TSQUERY", + T_regconfig: "REGCONFIG", + T__regconfig: "_REGCONFIG", + T_regdictionary: "REGDICTIONARY", + T__regdictionary: "_REGDICTIONARY", + T_jsonb: "JSONB", + T__jsonb: "_JSONB", + T_anyrange: "ANYRANGE", + T_event_trigger: "EVENT_TRIGGER", + T_int4range: "INT4RANGE", + T__int4range: "_INT4RANGE", + T_numrange: "NUMRANGE", + T__numrange: "_NUMRANGE", + T_tsrange: "TSRANGE", + T__tsrange: "_TSRANGE", + T_tstzrange: "TSTZRANGE", + T__tstzrange: "_TSTZRANGE", + T_daterange: "DATERANGE", + T__daterange: "_DATERANGE", + T_int8range: "INT8RANGE", + T__int8range: "_INT8RANGE", + T_pg_shseclabel: "PG_SHSECLABEL", + T_regnamespace: "REGNAMESPACE", + T__regnamespace: "_REGNAMESPACE", + T_regrole: "REGROLE", + T__regrole: "_REGROLE", +} diff --git a/vendor/github.com/lib/pq/rows.go b/vendor/github.com/lib/pq/rows.go new file mode 100644 index 0000000..c6aa5b9 --- /dev/null +++ b/vendor/github.com/lib/pq/rows.go @@ -0,0 +1,93 @@ +package pq + +import ( + "math" + "reflect" + "time" + + "github.com/lib/pq/oid" +) + +const headerSize = 4 + +type fieldDesc struct { + // The object ID of the data type. + OID oid.Oid + // The data type size (see pg_type.typlen). + // Note that negative values denote variable-width types. + Len int + // The type modifier (see pg_attribute.atttypmod). + // The meaning of the modifier is type-specific. + Mod int +} + +func (fd fieldDesc) Type() reflect.Type { + switch fd.OID { + case oid.T_int8: + return reflect.TypeOf(int64(0)) + case oid.T_int4: + return reflect.TypeOf(int32(0)) + case oid.T_int2: + return reflect.TypeOf(int16(0)) + case oid.T_varchar, oid.T_text: + return reflect.TypeOf("") + case oid.T_bool: + return reflect.TypeOf(false) + case oid.T_date, oid.T_time, oid.T_timetz, oid.T_timestamp, oid.T_timestamptz: + return reflect.TypeOf(time.Time{}) + case oid.T_bytea: + return reflect.TypeOf([]byte(nil)) + default: + return reflect.TypeOf(new(interface{})).Elem() + } +} + +func (fd fieldDesc) Name() string { + return oid.TypeName[fd.OID] +} + +func (fd fieldDesc) Length() (length int64, ok bool) { + switch fd.OID { + case oid.T_text, oid.T_bytea: + return math.MaxInt64, true + case oid.T_varchar, oid.T_bpchar: + return int64(fd.Mod - headerSize), true + default: + return 0, false + } +} + +func (fd fieldDesc) PrecisionScale() (precision, scale int64, ok bool) { + switch fd.OID { + case oid.T_numeric, oid.T__numeric: + mod := fd.Mod - headerSize + precision = int64((mod >> 16) & 0xffff) + scale = int64(mod & 0xffff) + return precision, scale, true + default: + return 0, 0, false + } +} + +// ColumnTypeScanType returns the value type that can be used to scan types into. +func (rs *rows) ColumnTypeScanType(index int) reflect.Type { + return rs.colTyps[index].Type() +} + +// ColumnTypeDatabaseTypeName return the database system type name. +func (rs *rows) ColumnTypeDatabaseTypeName(index int) string { + return rs.colTyps[index].Name() +} + +// ColumnTypeLength returns the length of the column type if the column is a +// variable length type. If the column is not a variable length type ok +// should return false. +func (rs *rows) ColumnTypeLength(index int) (length int64, ok bool) { + return rs.colTyps[index].Length() +} + +// ColumnTypePrecisionScale should return the precision and scale for decimal +// types. If not applicable, ok should be false. +func (rs *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) { + return rs.colTyps[index].PrecisionScale() +} diff --git a/vendor/github.com/lib/pq/scram/scram.go b/vendor/github.com/lib/pq/scram/scram.go new file mode 100644 index 0000000..477216b --- /dev/null +++ b/vendor/github.com/lib/pq/scram/scram.go @@ -0,0 +1,264 @@ +// Copyright (c) 2014 - Gustavo Niemeyer +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Package scram implements a SCRAM-{SHA-1,etc} client per RFC5802. +// +// http://tools.ietf.org/html/rfc5802 +// +package scram + +import ( + "bytes" + "crypto/hmac" + "crypto/rand" + "encoding/base64" + "fmt" + "hash" + "strconv" + "strings" +) + +// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc). +// +// A Client may be used within a SASL conversation with logic resembling: +// +// var in []byte +// var client = scram.NewClient(sha1.New, user, pass) +// for client.Step(in) { +// out := client.Out() +// // send out to server +// in := serverOut +// } +// if client.Err() != nil { +// // auth failed +// } +// +type Client struct { + newHash func() hash.Hash + + user string + pass string + step int + out bytes.Buffer + err error + + clientNonce []byte + serverNonce []byte + saltedPass []byte + authMsg bytes.Buffer +} + +// NewClient returns a new SCRAM-* client with the provided hash algorithm. +// +// For SCRAM-SHA-256, for example, use: +// +// client := scram.NewClient(sha256.New, user, pass) +// +func NewClient(newHash func() hash.Hash, user, pass string) *Client { + c := &Client{ + newHash: newHash, + user: user, + pass: pass, + } + c.out.Grow(256) + c.authMsg.Grow(256) + return c +} + +// Out returns the data to be sent to the server in the current step. +func (c *Client) Out() []byte { + if c.out.Len() == 0 { + return nil + } + return c.out.Bytes() +} + +// Err returns the error that occurred, or nil if there were no errors. +func (c *Client) Err() error { + return c.err +} + +// SetNonce sets the client nonce to the provided value. +// If not set, the nonce is generated automatically out of crypto/rand on the first step. +func (c *Client) SetNonce(nonce []byte) { + c.clientNonce = nonce +} + +var escaper = strings.NewReplacer("=", "=3D", ",", "=2C") + +// Step processes the incoming data from the server and makes the +// next round of data for the server available via Client.Out. +// Step returns false if there are no errors and more data is +// still expected. +func (c *Client) Step(in []byte) bool { + c.out.Reset() + if c.step > 2 || c.err != nil { + return false + } + c.step++ + switch c.step { + case 1: + c.err = c.step1(in) + case 2: + c.err = c.step2(in) + case 3: + c.err = c.step3(in) + } + return c.step > 2 || c.err != nil +} + +func (c *Client) step1(in []byte) error { + if len(c.clientNonce) == 0 { + const nonceLen = 16 + buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen)) + if _, err := rand.Read(buf[:nonceLen]); err != nil { + return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %v", err) + } + c.clientNonce = buf[nonceLen:] + b64.Encode(c.clientNonce, buf[:nonceLen]) + } + c.authMsg.WriteString("n=") + escaper.WriteString(&c.authMsg, c.user) + c.authMsg.WriteString(",r=") + c.authMsg.Write(c.clientNonce) + + c.out.WriteString("n,,") + c.out.Write(c.authMsg.Bytes()) + return nil +} + +var b64 = base64.StdEncoding + +func (c *Client) step2(in []byte) error { + c.authMsg.WriteByte(',') + c.authMsg.Write(in) + + fields := bytes.Split(in, []byte(",")) + if len(fields) != 3 { + return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in) + } + if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 { + return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0]) + } + if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 { + return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1]) + } + if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 { + return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2]) + } + + c.serverNonce = fields[0][2:] + if !bytes.HasPrefix(c.serverNonce, c.clientNonce) { + return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce) + } + + salt := make([]byte, b64.DecodedLen(len(fields[1][2:]))) + n, err := b64.Decode(salt, fields[1][2:]) + if err != nil { + return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1]) + } + salt = salt[:n] + iterCount, err := strconv.Atoi(string(fields[2][2:])) + if err != nil { + return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2]) + } + c.saltPassword(salt, iterCount) + + c.authMsg.WriteString(",c=biws,r=") + c.authMsg.Write(c.serverNonce) + + c.out.WriteString("c=biws,r=") + c.out.Write(c.serverNonce) + c.out.WriteString(",p=") + c.out.Write(c.clientProof()) + return nil +} + +func (c *Client) step3(in []byte) error { + var isv, ise bool + var fields = bytes.Split(in, []byte(",")) + if len(fields) == 1 { + isv = bytes.HasPrefix(fields[0], []byte("v=")) + ise = bytes.HasPrefix(fields[0], []byte("e=")) + } + if ise { + return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:]) + } else if !isv { + return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in) + } + if !bytes.Equal(c.serverSignature(), fields[0][2:]) { + return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:]) + } + return nil +} + +func (c *Client) saltPassword(salt []byte, iterCount int) { + mac := hmac.New(c.newHash, []byte(c.pass)) + mac.Write(salt) + mac.Write([]byte{0, 0, 0, 1}) + ui := mac.Sum(nil) + hi := make([]byte, len(ui)) + copy(hi, ui) + for i := 1; i < iterCount; i++ { + mac.Reset() + mac.Write(ui) + mac.Sum(ui[:0]) + for j, b := range ui { + hi[j] ^= b + } + } + c.saltedPass = hi +} + +func (c *Client) clientProof() []byte { + mac := hmac.New(c.newHash, c.saltedPass) + mac.Write([]byte("Client Key")) + clientKey := mac.Sum(nil) + hash := c.newHash() + hash.Write(clientKey) + storedKey := hash.Sum(nil) + mac = hmac.New(c.newHash, storedKey) + mac.Write(c.authMsg.Bytes()) + clientProof := mac.Sum(nil) + for i, b := range clientKey { + clientProof[i] ^= b + } + clientProof64 := make([]byte, b64.EncodedLen(len(clientProof))) + b64.Encode(clientProof64, clientProof) + return clientProof64 +} + +func (c *Client) serverSignature() []byte { + mac := hmac.New(c.newHash, c.saltedPass) + mac.Write([]byte("Server Key")) + serverKey := mac.Sum(nil) + + mac = hmac.New(c.newHash, serverKey) + mac.Write(c.authMsg.Bytes()) + serverSignature := mac.Sum(nil) + + encoded := make([]byte, b64.EncodedLen(len(serverSignature))) + b64.Encode(encoded, serverSignature) + return encoded +} diff --git a/vendor/github.com/lib/pq/ssl.go b/vendor/github.com/lib/pq/ssl.go new file mode 100644 index 0000000..36b61ba --- /dev/null +++ b/vendor/github.com/lib/pq/ssl.go @@ -0,0 +1,204 @@ +package pq + +import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" + "net" + "os" + "os/user" + "path/filepath" + "strings" +) + +// ssl generates a function to upgrade a net.Conn based on the "sslmode" and +// related settings. The function is nil when no upgrade should take place. +func ssl(o values) (func(net.Conn) (net.Conn, error), error) { + verifyCaOnly := false + tlsConf := tls.Config{} + switch mode := o["sslmode"]; mode { + // "require" is the default. + case "", "require": + // We must skip TLS's own verification since it requires full + // verification since Go 1.3. + tlsConf.InsecureSkipVerify = true + + // From http://www.postgresql.org/docs/current/static/libpq-ssl.html: + // + // Note: For backwards compatibility with earlier versions of + // PostgreSQL, if a root CA file exists, the behavior of + // sslmode=require will be the same as that of verify-ca, meaning the + // server certificate is validated against the CA. Relying on this + // behavior is discouraged, and applications that need certificate + // validation should always use verify-ca or verify-full. + if sslrootcert, ok := o["sslrootcert"]; ok { + if _, err := os.Stat(sslrootcert); err == nil { + verifyCaOnly = true + } else { + delete(o, "sslrootcert") + } + } + case "verify-ca": + // We must skip TLS's own verification since it requires full + // verification since Go 1.3. + tlsConf.InsecureSkipVerify = true + verifyCaOnly = true + case "verify-full": + tlsConf.ServerName = o["host"] + case "disable": + return nil, nil + default: + return nil, fmterrorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode) + } + + // Set Server Name Indication (SNI), if enabled by connection parameters. + // By default SNI is on, any value which is not starting with "1" disables + // SNI -- that is the same check vanilla libpq uses. + if sslsni := o["sslsni"]; sslsni == "" || strings.HasPrefix(sslsni, "1") { + // RFC 6066 asks to not set SNI if the host is a literal IP address (IPv4 + // or IPv6). This check is coded already crypto.tls.hostnameInSNI, so + // just always set ServerName here and let crypto/tls do the filtering. + tlsConf.ServerName = o["host"] + } + + err := sslClientCertificates(&tlsConf, o) + if err != nil { + return nil, err + } + err = sslCertificateAuthority(&tlsConf, o) + if err != nil { + return nil, err + } + + // Accept renegotiation requests initiated by the backend. + // + // Renegotiation was deprecated then removed from PostgreSQL 9.5, but + // the default configuration of older versions has it enabled. Redshift + // also initiates renegotiations and cannot be reconfigured. + tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient + + return func(conn net.Conn) (net.Conn, error) { + client := tls.Client(conn, &tlsConf) + if verifyCaOnly { + err := sslVerifyCertificateAuthority(client, &tlsConf) + if err != nil { + return nil, err + } + } + return client, nil + }, nil +} + +// sslClientCertificates adds the certificate specified in the "sslcert" and +// "sslkey" settings, or if they aren't set, from the .postgresql directory +// in the user's home directory. The configured files must exist and have +// the correct permissions. +func sslClientCertificates(tlsConf *tls.Config, o values) error { + sslinline := o["sslinline"] + if sslinline == "true" { + cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"])) + if err != nil { + return err + } + tlsConf.Certificates = []tls.Certificate{cert} + return nil + } + + // user.Current() might fail when cross-compiling. We have to ignore the + // error and continue without home directory defaults, since we wouldn't + // know from where to load them. + user, _ := user.Current() + + // In libpq, the client certificate is only loaded if the setting is not blank. + // + // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037 + sslcert := o["sslcert"] + if len(sslcert) == 0 && user != nil { + sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt") + } + // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1045 + if len(sslcert) == 0 { + return nil + } + // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1050:L1054 + if _, err := os.Stat(sslcert); os.IsNotExist(err) { + return nil + } else if err != nil { + return err + } + + // In libpq, the ssl key is only loaded if the setting is not blank. + // + // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1123-L1222 + sslkey := o["sslkey"] + if len(sslkey) == 0 && user != nil { + sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key") + } + + if len(sslkey) > 0 { + if err := sslKeyPermissions(sslkey); err != nil { + return err + } + } + + cert, err := tls.LoadX509KeyPair(sslcert, sslkey) + if err != nil { + return err + } + + tlsConf.Certificates = []tls.Certificate{cert} + return nil +} + +// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting. +func sslCertificateAuthority(tlsConf *tls.Config, o values) error { + // In libpq, the root certificate is only loaded if the setting is not blank. + // + // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951 + if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 { + tlsConf.RootCAs = x509.NewCertPool() + + sslinline := o["sslinline"] + + var cert []byte + if sslinline == "true" { + cert = []byte(sslrootcert) + } else { + var err error + cert, err = ioutil.ReadFile(sslrootcert) + if err != nil { + return err + } + } + + if !tlsConf.RootCAs.AppendCertsFromPEM(cert) { + return fmterrorf("couldn't parse pem in sslrootcert") + } + } + + return nil +} + +// sslVerifyCertificateAuthority carries out a TLS handshake to the server and +// verifies the presented certificate against the CA, i.e. the one specified in +// sslrootcert or the system CA if sslrootcert was not specified. +func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) error { + err := client.Handshake() + if err != nil { + return err + } + certs := client.ConnectionState().PeerCertificates + opts := x509.VerifyOptions{ + DNSName: client.ConnectionState().ServerName, + Intermediates: x509.NewCertPool(), + Roots: tlsConf.RootCAs, + } + for i, cert := range certs { + if i == 0 { + continue + } + opts.Intermediates.AddCert(cert) + } + _, err = certs[0].Verify(opts) + return err +} diff --git a/vendor/github.com/lib/pq/ssl_permissions.go b/vendor/github.com/lib/pq/ssl_permissions.go new file mode 100644 index 0000000..d587f10 --- /dev/null +++ b/vendor/github.com/lib/pq/ssl_permissions.go @@ -0,0 +1,93 @@ +//go:build !windows +// +build !windows + +package pq + +import ( + "errors" + "os" + "syscall" +) + +const ( + rootUserID = uint32(0) + + // The maximum permissions that a private key file owned by a regular user + // is allowed to have. This translates to u=rw. + maxUserOwnedKeyPermissions os.FileMode = 0600 + + // The maximum permissions that a private key file owned by root is allowed + // to have. This translates to u=rw,g=r. + maxRootOwnedKeyPermissions os.FileMode = 0640 +) + +var ( + errSSLKeyHasUnacceptableUserPermissions = errors.New("permissions for files not owned by root should be u=rw (0600) or less") + errSSLKeyHasUnacceptableRootPermissions = errors.New("permissions for root owned files should be u=rw,g=r (0640) or less") +) + +// sslKeyPermissions checks the permissions on user-supplied ssl key files. +// The key file should have very little access. +// +// libpq does not check key file permissions on Windows. +func sslKeyPermissions(sslkey string) error { + info, err := os.Stat(sslkey) + if err != nil { + return err + } + + err = hasCorrectPermissions(info) + + // return ErrSSLKeyHasWorldPermissions for backwards compatability with + // existing code. + if err == errSSLKeyHasUnacceptableUserPermissions || err == errSSLKeyHasUnacceptableRootPermissions { + err = ErrSSLKeyHasWorldPermissions + } + return err +} + +// hasCorrectPermissions checks the file info (and the unix-specific stat_t +// output) to verify that the permissions on the file are correct. +// +// If the file is owned by the same user the process is running as, +// the file should only have 0600 (u=rw). If the file is owned by root, +// and the group matches the group that the process is running in, the +// permissions cannot be more than 0640 (u=rw,g=r). The file should +// never have world permissions. +// +// Returns an error when the permission check fails. +func hasCorrectPermissions(info os.FileInfo) error { + // if file's permission matches 0600, allow access. + userPermissionMask := (os.FileMode(0777) ^ maxUserOwnedKeyPermissions) + + // regardless of if we're running as root or not, 0600 is acceptable, + // so we return if we match the regular user permission mask. + if info.Mode().Perm()&userPermissionMask == 0 { + return nil + } + + // We need to pull the Unix file information to get the file's owner. + // If we can't access it, there's some sort of operating system level error + // and we should fail rather than attempting to use faulty information. + sysInfo := info.Sys() + if sysInfo == nil { + return ErrSSLKeyUnknownOwnership + } + + unixStat, ok := sysInfo.(*syscall.Stat_t) + if !ok { + return ErrSSLKeyUnknownOwnership + } + + // if the file is owned by root, we allow 0640 (u=rw,g=r) to match what + // Postgres does. + if unixStat.Uid == rootUserID { + rootPermissionMask := (os.FileMode(0777) ^ maxRootOwnedKeyPermissions) + if info.Mode().Perm()&rootPermissionMask != 0 { + return errSSLKeyHasUnacceptableRootPermissions + } + return nil + } + + return errSSLKeyHasUnacceptableUserPermissions +} diff --git a/vendor/github.com/lib/pq/ssl_windows.go b/vendor/github.com/lib/pq/ssl_windows.go new file mode 100644 index 0000000..73663c8 --- /dev/null +++ b/vendor/github.com/lib/pq/ssl_windows.go @@ -0,0 +1,10 @@ +//go:build windows +// +build windows + +package pq + +// sslKeyPermissions checks the permissions on user-supplied ssl key files. +// The key file should have very little access. +// +// libpq does not check key file permissions on Windows. +func sslKeyPermissions(string) error { return nil } diff --git a/vendor/github.com/lib/pq/url.go b/vendor/github.com/lib/pq/url.go new file mode 100644 index 0000000..aec6e95 --- /dev/null +++ b/vendor/github.com/lib/pq/url.go @@ -0,0 +1,76 @@ +package pq + +import ( + "fmt" + "net" + nurl "net/url" + "sort" + "strings" +) + +// ParseURL no longer needs to be used by clients of this library since supplying a URL as a +// connection string to sql.Open() is now supported: +// +// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full") +// +// It remains exported here for backwards-compatibility. +// +// ParseURL converts a url to a connection string for driver.Open. +// Example: +// +// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full" +// +// converts to: +// +// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full" +// +// A minimal example: +// +// "postgres://" +// +// This will be blank, causing driver.Open to use all of the defaults +func ParseURL(url string) (string, error) { + u, err := nurl.Parse(url) + if err != nil { + return "", err + } + + if u.Scheme != "postgres" && u.Scheme != "postgresql" { + return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme) + } + + var kvs []string + escaper := strings.NewReplacer(`'`, `\'`, `\`, `\\`) + accrue := func(k, v string) { + if v != "" { + kvs = append(kvs, k+"='"+escaper.Replace(v)+"'") + } + } + + if u.User != nil { + v := u.User.Username() + accrue("user", v) + + v, _ = u.User.Password() + accrue("password", v) + } + + if host, port, err := net.SplitHostPort(u.Host); err != nil { + accrue("host", u.Host) + } else { + accrue("host", host) + accrue("port", port) + } + + if u.Path != "" { + accrue("dbname", u.Path[1:]) + } + + q := u.Query() + for k := range q { + accrue(k, q.Get(k)) + } + + sort.Strings(kvs) // Makes testing easier (not a performance concern) + return strings.Join(kvs, " "), nil +} diff --git a/vendor/github.com/lib/pq/user_other.go b/vendor/github.com/lib/pq/user_other.go new file mode 100644 index 0000000..3dae8f5 --- /dev/null +++ b/vendor/github.com/lib/pq/user_other.go @@ -0,0 +1,10 @@ +// Package pq is a pure Go Postgres driver for the database/sql package. + +//go:build js || android || hurd || zos +// +build js android hurd zos + +package pq + +func userCurrent() (string, error) { + return "", ErrCouldNotDetectUsername +} diff --git a/vendor/github.com/lib/pq/user_posix.go b/vendor/github.com/lib/pq/user_posix.go new file mode 100644 index 0000000..5f2d439 --- /dev/null +++ b/vendor/github.com/lib/pq/user_posix.go @@ -0,0 +1,25 @@ +// Package pq is a pure Go Postgres driver for the database/sql package. + +//go:build aix || darwin || dragonfly || freebsd || (linux && !android) || nacl || netbsd || openbsd || plan9 || solaris || rumprun || illumos +// +build aix darwin dragonfly freebsd linux,!android nacl netbsd openbsd plan9 solaris rumprun illumos + +package pq + +import ( + "os" + "os/user" +) + +func userCurrent() (string, error) { + u, err := user.Current() + if err == nil { + return u.Username, nil + } + + name := os.Getenv("USER") + if name != "" { + return name, nil + } + + return "", ErrCouldNotDetectUsername +} diff --git a/vendor/github.com/lib/pq/user_windows.go b/vendor/github.com/lib/pq/user_windows.go new file mode 100644 index 0000000..2b69126 --- /dev/null +++ b/vendor/github.com/lib/pq/user_windows.go @@ -0,0 +1,27 @@ +// Package pq is a pure Go Postgres driver for the database/sql package. +package pq + +import ( + "path/filepath" + "syscall" +) + +// Perform Windows user name lookup identically to libpq. +// +// The PostgreSQL code makes use of the legacy Win32 function +// GetUserName, and that function has not been imported into stock Go. +// GetUserNameEx is available though, the difference being that a +// wider range of names are available. To get the output to be the +// same as GetUserName, only the base (or last) component of the +// result is returned. +func userCurrent() (string, error) { + pw_name := make([]uint16, 128) + pwname_size := uint32(len(pw_name)) - 1 + err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size) + if err != nil { + return "", ErrCouldNotDetectUsername + } + s := syscall.UTF16ToString(pw_name) + u := filepath.Base(s) + return u, nil +} diff --git a/vendor/github.com/lib/pq/uuid.go b/vendor/github.com/lib/pq/uuid.go new file mode 100644 index 0000000..9a1b9e0 --- /dev/null +++ b/vendor/github.com/lib/pq/uuid.go @@ -0,0 +1,23 @@ +package pq + +import ( + "encoding/hex" + "fmt" +) + +// decodeUUIDBinary interprets the binary format of a uuid, returning it in text format. +func decodeUUIDBinary(src []byte) ([]byte, error) { + if len(src) != 16 { + return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src)) + } + + dst := make([]byte, 36) + dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-' + hex.Encode(dst[0:], src[0:4]) + hex.Encode(dst[9:], src[4:6]) + hex.Encode(dst[14:], src[6:8]) + hex.Encode(dst[19:], src[8:10]) + hex.Encode(dst[24:], src[10:16]) + + return dst, nil +} diff --git a/vendor/github.com/playwright-community/playwright-go/.gitattributes b/vendor/github.com/playwright-community/playwright-go/.gitattributes deleted file mode 100644 index c976050..0000000 --- a/vendor/github.com/playwright-community/playwright-go/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# text files must be lf for golden file tests to work -* text=auto eol=lf - diff --git a/vendor/github.com/playwright-community/playwright-go/.gitignore b/vendor/github.com/playwright-community/playwright-go/.gitignore deleted file mode 100644 index 83e2355..0000000 --- a/vendor/github.com/playwright-community/playwright-go/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/go -# Edit at https://www.toptal.com/developers/gitignore?templates=go - -### Go ### -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -### Go Patch ### -/vendor/ -/Godeps/ - -# End of https://www.toptal.com/developers/gitignore/api/go -covprofile -.idea/ -.DS_Store - -api.json -_site/ -.jekyll-cache/ - -.vscode/settings.json \ No newline at end of file diff --git a/vendor/github.com/playwright-community/playwright-go/.gitmodules b/vendor/github.com/playwright-community/playwright-go/.gitmodules deleted file mode 100644 index 9ab899d..0000000 --- a/vendor/github.com/playwright-community/playwright-go/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "playwright"] - path = playwright - url = https://github.com/microsoft/playwright diff --git a/vendor/github.com/playwright-community/playwright-go/.golangci.yaml b/vendor/github.com/playwright-community/playwright-go/.golangci.yaml deleted file mode 100644 index 1557a3f..0000000 --- a/vendor/github.com/playwright-community/playwright-go/.golangci.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -linters: - enable-all: false - disable-all: false - enable: - - gofumpt \ No newline at end of file diff --git a/vendor/github.com/playwright-community/playwright-go/.nojekyll b/vendor/github.com/playwright-community/playwright-go/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/playwright-community/playwright-go/404.html b/vendor/github.com/playwright-community/playwright-go/404.html deleted file mode 100644 index 086a5c9..0000000 --- a/vendor/github.com/playwright-community/playwright-go/404.html +++ /dev/null @@ -1,25 +0,0 @@ ---- -permalink: /404.html -layout: default ---- - - - -
-

404

- -

Page not found :(

-

The requested page could not be found.

-
diff --git a/vendor/github.com/playwright-community/playwright-go/CONTRIBUTING.md b/vendor/github.com/playwright-community/playwright-go/CONTRIBUTING.md deleted file mode 100644 index 3b11995..0000000 --- a/vendor/github.com/playwright-community/playwright-go/CONTRIBUTING.md +++ /dev/null @@ -1,39 +0,0 @@ -# Contributing - -## Code style -The Go code is linted with [golangci-lint](https://golangci-lint.run/) and formatted with [gofumpt](https://github.com/mvdan/gofumpt). Please configure your editor to run the tools while developing and make sure to run the tools before committing any code. - -## Tests - -### Test coverage - -For every Pull Request on GitHub and on the main branch the coverage data will get sent over to [Coveralls](https://coveralls.io/github/playwright-community/playwright-go), this is helpful for finding functions that aren't covered by tests. - -### Running tests - -You can use the `BROWSER` environment variable to use a different browser than Chromium for the tests and use the `HEADLESS` environment variable which is useful for debugging. - -``` -BROWSER=chromium HEADLESS=1 go test -v --race ./... -``` - -### Roll - -1. Find out to which upstream version you want to roll, and change the value of `playwrightCliVersion` in the **run.go** to the new version. -1. Download current version of Playwright driver `go run scripts/install-browsers/main.go` -1. Apply patch `bash scripts/apply-patch.sh` -1. Fix merge conflicts if any, otherwise ignore this step. Once you are happy you can commit the changes `cd playwright; git commit -am "apply patch" && cd ..` -1. Regenerate a new patch `bash scripts/update-patch.sh` -1. Generate go code `go generate ./...` - -To adapt to the new version of Playwright's protocol and feature updates, you may need to modify the patch. Refer to the following steps: - -1. Apply patch `bash scripts/apply-patch.sh` -1. `cd playwright` -1. Revert the patch`git reset HEAD~1` -1. Modify the files under `docs/src/api`, etc. as needed. Available references: - - Protocol `packages/protocol/src/protocol.yml` - - [Playwright python](https://github.com/microsoft/playwright-python) -1. Commit the changes `git commit -am "apply patch"` -1. Regenerate a new patch `bash scripts/update-patch.sh` -1. Generate go code `go generate ./...`. diff --git a/vendor/github.com/playwright-community/playwright-go/Dockerfile.example b/vendor/github.com/playwright-community/playwright-go/Dockerfile.example deleted file mode 100644 index 3077cf3..0000000 --- a/vendor/github.com/playwright-community/playwright-go/Dockerfile.example +++ /dev/null @@ -1,25 +0,0 @@ -# Stage 1: Modules caching -FROM golang:1.22 as modules -COPY go.mod go.sum /modules/ -WORKDIR /modules -RUN go mod download - -# Stage 2: Build -FROM golang:1.22 as builder -COPY --from=modules /go/pkg /go/pkg -COPY . /workdir -WORKDIR /workdir -# Install playwright cli with right version for later use -RUN PWGO_VER=$(grep -oE "playwright-go v\S+" /workdir/go.mod | sed 's/playwright-go //g') \ - && go install github.com/playwright-community/playwright-go/cmd/playwright@${PWGO_VER} -# Build your app -RUN GOOS=linux GOARCH=amd64 go build -o /bin/myapp - -# Stage 3: Final -FROM ubuntu:noble -COPY --from=builder /go/bin/playwright /bin/myapp / -RUN apt-get update && apt-get install -y ca-certificates tzdata \ - # Install dependencies and all browsers (or specify one) - && /playwright install --with-deps \ - && rm -rf /var/lib/apt/lists/* -CMD ["/myapp"] \ No newline at end of file diff --git a/vendor/github.com/playwright-community/playwright-go/LICENSE b/vendor/github.com/playwright-community/playwright-go/LICENSE deleted file mode 100644 index d4f29b3..0000000 --- a/vendor/github.com/playwright-community/playwright-go/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Max Schmitt - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/playwright-community/playwright-go/README.md b/vendor/github.com/playwright-community/playwright-go/README.md deleted file mode 100644 index a9e3dd7..0000000 --- a/vendor/github.com/playwright-community/playwright-go/README.md +++ /dev/null @@ -1,148 +0,0 @@ -# 🎭 [Playwright](https://github.com/microsoft/playwright#readme) for - -## Looking for maintainers and see [here](https://github.com/playwright-community/playwright-go/issues/122). Thanks! - -[![PkgGoDev](https://pkg.go.dev/badge/github.com/playwright-community/playwright-go)](https://pkg.go.dev/github.com/playwright-community/playwright-go) -[![License](https://img.shields.io/badge/License-MIT-blue.svg)](http://opensource.org/licenses/MIT) -[![Go Report Card](https://goreportcard.com/badge/github.com/playwright-community/playwright-go)](https://goreportcard.com/report/github.com/playwright-community/playwright-go) ![Build Status](https://github.com/playwright-community/playwright-go/workflows/Go/badge.svg) -[![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://aka.ms/playwright-slack) [![Coverage Status](https://coveralls.io/repos/github/playwright-community/playwright-go/badge.svg?branch=main)](https://coveralls.io/github/playwright-community/playwright-go?branch=main) [![Chromium version](https://img.shields.io/badge/chromium-136.0.7103.25-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-137.0-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.4-blue.svg?logo=safari)](https://webkit.org/) - -[API reference](https://playwright.dev/docs/api/class-playwright) | [Example recipes](https://github.com/playwright-community/playwright-go/tree/main/examples) - -Playwright is a Go library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**. - -| | Linux | macOS | Windows | -| :--- | :---: | :---: | :---: | -| Chromium 136.0.7103.25 | ✅ | ✅ | ✅ | -| WebKit 18.4 | ✅ | ✅ | ✅ | -| Firefox 137.0 | ✅ | ✅ | ✅ | - -Headless execution is supported for all the browsers on all platforms. - -## Installation - -```txt -go get -u github.com/playwright-community/playwright-go -``` - -Install the browsers and OS dependencies: - -```bash -go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps -# Or -go install github.com/playwright-community/playwright-go/cmd/playwright@latest -playwright install --with-deps -``` - -Alternatively you can do it inside your program via which downloads the driver and browsers: - -```go -err := playwright.Install() -``` - -## Capabilities - -Playwright is built to automate the broad and growing set of web browser capabilities used by Single Page Apps and Progressive Web Apps. - -* Scenarios that span multiple page, domains and iframes -* Auto-wait for elements to be ready before executing actions (like click, fill) -* Intercept network activity for stubbing and mocking network requests -* Emulate mobile devices, geolocation, permissions -* Support for web components via shadow-piercing selectors -* Native input events for mouse and keyboard -* Upload and download files - -## Example - -The following example crawls the current top voted items from [Hacker News](https://news.ycombinator.com). - -```go - -package main - -import ( - "fmt" - "log" - - "github.com/playwright-community/playwright-go" -) - -func main() { - pw, err := playwright.Run() - if err != nil { - log.Fatalf("could not start playwright: %v", err) - } - browser, err := pw.Chromium.Launch() - if err != nil { - log.Fatalf("could not launch browser: %v", err) - } - page, err := browser.NewPage() - if err != nil { - log.Fatalf("could not create page: %v", err) - } - if _, err = page.Goto("https://news.ycombinator.com"); err != nil { - log.Fatalf("could not goto: %v", err) - } - entries, err := page.Locator(".athing").All() - if err != nil { - log.Fatalf("could not get entries: %v", err) - } - for i, entry := range entries { - title, err := entry.Locator("td.title > span > a").TextContent() - if err != nil { - log.Fatalf("could not get text content: %v", err) - } - fmt.Printf("%d: %s\n", i+1, title) - } - if err = browser.Close(); err != nil { - log.Fatalf("could not close browser: %v", err) - } - if err = pw.Stop(); err != nil { - log.Fatalf("could not stop Playwright: %v", err) - } -} -``` - -## Docker -Refer to the [Dockerfile.example](./Dockerfile.example) to build your own docker image. - -## More examples - -* Refer to [helper_test.go](./tests/helper_test.go) for End-To-End testing -* [Downloading files](./examples/download/main.go) -* [End-To-End testing a website](./examples/end-to-end-testing/main.go) -* [Executing JavaScript in the browser](./examples/javascript/main.go) -* [Emulate mobile and geolocation](./examples/mobile-and-geolocation/main.go) -* [Parallel scraping using a WaitGroup](./examples/parallel-scraping/main.go) -* [Rendering a PDF of a website](./examples/pdf/main.go) -* [Scraping HackerNews](./examples/scraping/main.go) -* [Take a screenshot](./examples/screenshot/main.go) -* [Record a video](./examples/video/main.go) -* [Monitor network activity](./examples/network-monitoring/main.go) - -## How does it work? - -Playwright is a Node.js library which uses: - -* Chrome DevTools Protocol to communicate with Chromium -* Patched Firefox to communicate with Firefox -* Patched WebKit to communicate with WebKit - -These patches are based on the original sources of the browsers and don't modify the browser behaviour so the browsers are basically the same (see [here](https://github.com/microsoft/playwright/tree/main/browser_patches)) as you see them in the wild. The support for different programming languages is based on exposing a RPC server in the Node.js land which can be used to allow other languages to use Playwright without implementing all the custom logic: - -* [Playwright for Python](https://github.com/microsoft/playwright-python) -* [Playwright for .NET](https://github.com/microsoft/playwright-sharp) -* [Playwright for Java](https://github.com/microsoft/playwright-java) -* [Playwright for Go](https://github.com/playwright-community/playwright-go) - -The bridge between Node.js and the other languages is basically a Node.js runtime combined with Playwright which gets shipped for each of these languages (around 50MB) and then communicates over stdio to send the relevant commands. This will also download the pre-compiled browsers. - -## Is Playwright for Go ready? - -We are ready for your feedback, but we are still covering Playwright Go with the tests. - -## Resources - -* [Playwright for Go Documentation](https://pkg.go.dev/github.com/playwright-community/playwright-go) -* [Playwright Documentation](https://playwright.dev/docs/api/class-playwright) -* [Example recipes](https://github.com/playwright-community/playwright-go/tree/main/examples) diff --git a/vendor/github.com/playwright-community/playwright-go/_config.yml b/vendor/github.com/playwright-community/playwright-go/_config.yml deleted file mode 100644 index 15ec3b2..0000000 --- a/vendor/github.com/playwright-community/playwright-go/_config.yml +++ /dev/null @@ -1,21 +0,0 @@ -title: Playwright for Go -email: max@schmitt.mx -description: >- # this means to ignore newlines until "baseurl:" - Playwright is a Node.js library to automate Chromium, Firefox and WebKit with a single API. - Playwright is built to enable cross-browser web automation that is ever-green, capable, reliable and fast. -baseurl: "/playwright-go" -url: "https://playwright-community.github.io/" -twitter_username: maxibanki -github_username: playwright-community -remote_theme: pages-themes/cayman@v0.2.0 -plugins: - - jekyll-remote-theme - - jekyll-optional-front-matter - - jekyll-readme-index -exclude: - - playwright/ -defaults: - - scope: - path: "" # an empty string here means all files in the project - values: - layout: "default" diff --git a/vendor/github.com/playwright-community/playwright-go/apiresponse_assertions.go b/vendor/github.com/playwright-community/playwright-go/apiresponse_assertions.go deleted file mode 100644 index 187618e..0000000 --- a/vendor/github.com/playwright-community/playwright-go/apiresponse_assertions.go +++ /dev/null @@ -1,75 +0,0 @@ -package playwright - -import ( - "errors" - "fmt" - "regexp" - "strings" -) - -type apiResponseAssertionsImpl struct { - actual APIResponse - isNot bool -} - -func newAPIResponseAssertions(actual APIResponse, isNot bool) *apiResponseAssertionsImpl { - return &apiResponseAssertionsImpl{ - actual: actual, - isNot: isNot, - } -} - -func (ar *apiResponseAssertionsImpl) Not() APIResponseAssertions { - return newAPIResponseAssertions(ar.actual, true) -} - -func (ar *apiResponseAssertionsImpl) ToBeOK() error { - if ar.isNot != ar.actual.Ok() { - return nil - } - message := fmt.Sprintf(`Response status expected to be within [200..299] range, was %v`, ar.actual.Status()) - if ar.isNot { - message = strings.ReplaceAll(message, "expected to", "expected not to") - } - logList, err := ar.actual.(*apiResponseImpl).fetchLog() - if err != nil { - return err - } - log := strings.Join(logList, "\n") - if log != "" { - message += "\nCall log:\n" + log - } - - isTextEncoding := false - contentType, ok := ar.actual.Headers()["content-type"] - if ok { - isTextEncoding = isTexualMimeType(contentType) - } - if isTextEncoding { - text, err := ar.actual.Text() - if err == nil { - message += fmt.Sprintf(`\n Response Text:\n %s`, subString(text, 0, 1000)) - } - } - return errors.New(message) -} - -func isTexualMimeType(mimeType string) bool { - re := regexp.MustCompile(`^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$`) - return re.MatchString(mimeType) -} - -func subString(s string, start, length int) string { - if start < 0 { - start = 0 - } - if length < 0 { - length = 0 - } - rs := []rune(s) - end := start + length - if end > len(rs) { - end = len(rs) - } - return string(rs[start:end]) -} diff --git a/vendor/github.com/playwright-community/playwright-go/artifact.go b/vendor/github.com/playwright-community/playwright-go/artifact.go deleted file mode 100644 index c76b892..0000000 --- a/vendor/github.com/playwright-community/playwright-go/artifact.go +++ /dev/null @@ -1,70 +0,0 @@ -package playwright - -import ( - "errors" - "fmt" -) - -type artifactImpl struct { - channelOwner -} - -func (a *artifactImpl) AbsolutePath() string { - return a.initializer["absolutePath"].(string) -} - -func (a *artifactImpl) PathAfterFinished() (string, error) { - if a.connection.isRemote { - return "", errors.New("Path is not available when connecting remotely. Use SaveAs() to save a local copy") - } - path, err := a.channel.Send("pathAfterFinished") - return path.(string), err -} - -func (a *artifactImpl) SaveAs(path string) error { - if !a.connection.isRemote { - _, err := a.channel.Send("saveAs", map[string]interface{}{ - "path": path, - }) - return err - } - streamChannel, err := a.channel.Send("saveAsStream") - if err != nil { - return err - } - stream := fromChannel(streamChannel).(*streamImpl) - return stream.SaveAs(path) -} - -func (a *artifactImpl) Failure() error { - reason, err := a.channel.Send("failure") - if reason == nil { - return err - } - return fmt.Errorf("%w: %v", ErrPlaywright, reason) -} - -func (a *artifactImpl) Delete() error { - _, err := a.channel.Send("delete") - return err -} - -func (a *artifactImpl) Cancel() error { - _, err := a.channel.Send("cancel") - return err -} - -func (a *artifactImpl) ReadIntoBuffer() ([]byte, error) { - streamChannel, err := a.channel.Send("stream") - if err != nil { - return nil, err - } - stream := fromChannel(streamChannel) - return stream.(*streamImpl).ReadAll() -} - -func newArtifact(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *artifactImpl { - artifact := &artifactImpl{} - artifact.createChannelOwner(artifact, parent, objectType, guid, initializer) - return artifact -} diff --git a/vendor/github.com/playwright-community/playwright-go/assertions.go b/vendor/github.com/playwright-community/playwright-go/assertions.go deleted file mode 100644 index 5e0e710..0000000 --- a/vendor/github.com/playwright-community/playwright-go/assertions.go +++ /dev/null @@ -1,146 +0,0 @@ -package playwright - -import ( - "errors" - "fmt" - "reflect" - "regexp" - "strings" -) - -const assertionsDefaultTimeout = 5000 // 5s - -type playwrightAssertionsImpl struct { - defaultTimeout *float64 -} - -// NewPlaywrightAssertions creates a new instance of PlaywrightAssertions -// - timeout: default value is 5000 (ms) -func NewPlaywrightAssertions(timeout ...float64) PlaywrightAssertions { - if len(timeout) > 0 { - return &playwrightAssertionsImpl{Float(timeout[0])} - } - return &playwrightAssertionsImpl{Float(assertionsDefaultTimeout)} -} - -func (pa *playwrightAssertionsImpl) APIResponse(response APIResponse) APIResponseAssertions { - return newAPIResponseAssertions(response, false) -} - -func (pa *playwrightAssertionsImpl) Locator(locator Locator) LocatorAssertions { - return newLocatorAssertions(locator, false, pa.defaultTimeout) -} - -func (pa *playwrightAssertionsImpl) Page(page Page) PageAssertions { - return newPageAssertions(page, false, pa.defaultTimeout) -} - -type expectedTextValue struct { - Str *string `json:"string,omitempty"` - RegexSource *string `json:"regexSource,omitempty"` - RegexFlags *string `json:"regexFlags,omitempty"` - MatchSubstring *bool `json:"matchSubstring,omitempty"` - IgnoreCase *bool `json:"ignoreCase,omitempty"` - NormalizeWhiteSpace *bool `json:"normalizeWhiteSpace,omitempty"` -} - -type frameExpectOptions struct { - ExpressionArg interface{} `json:"expressionArg,omitempty"` - ExpectedText []expectedTextValue `json:"expectedText,omitempty"` - ExpectedNumber *float64 `json:"expectedNumber,omitempty"` - ExpectedValue interface{} `json:"expectedValue,omitempty"` - UseInnerText *bool `json:"useInnerText,omitempty"` - IsNot bool `json:"isNot"` - Timeout *float64 `json:"timeout"` -} - -type frameExpectResult struct { - Matches bool `json:"matches"` - Received interface{} `json:"received,omitempty"` - TimedOut *bool `json:"timedOut,omitempty"` - Log []string `json:"log,omitempty"` -} - -type assertionsBase struct { - actualLocator Locator - isNot bool - defaultTimeout *float64 -} - -func (b *assertionsBase) expect( - expression string, - options frameExpectOptions, - expected interface{}, - message string, -) error { - options.IsNot = b.isNot - if options.Timeout == nil { - options.Timeout = b.defaultTimeout - } - if options.IsNot { - message = strings.ReplaceAll(message, "expected to", "expected not to") - } - result, err := b.actualLocator.(*locatorImpl).expect(expression, options) - if err != nil { - return err - } - - if result.Matches == b.isNot { - actual := result.Received - log := strings.Join(result.Log, "\n") - if log != "" { - log = "\nCall log:\n" + log - } - if expected != nil { - return fmt.Errorf("%s '%v'\nActual value: %v %s", message, expected, actual, log) - } - return fmt.Errorf("%s\nActual value: %v %s", message, actual, log) - } - - return nil -} - -func toExpectedTextValues( - items []interface{}, - matchSubstring bool, - normalizeWhiteSpace bool, - ignoreCase *bool, -) ([]expectedTextValue, error) { - var out []expectedTextValue - for _, item := range items { - switch item := item.(type) { - case string: - out = append(out, expectedTextValue{ - Str: String(item), - MatchSubstring: Bool(matchSubstring), - NormalizeWhiteSpace: Bool(normalizeWhiteSpace), - IgnoreCase: ignoreCase, - }) - case *regexp.Regexp: - pattern, flags := convertRegexp(item) - out = append(out, expectedTextValue{ - RegexSource: String(pattern), - RegexFlags: String(flags), - MatchSubstring: Bool(matchSubstring), - NormalizeWhiteSpace: Bool(normalizeWhiteSpace), - IgnoreCase: ignoreCase, - }) - default: - return nil, errors.New("value must be a string or regexp") - } - } - return out, nil -} - -func convertToInterfaceList(v interface{}) []interface{} { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Slice { - return []interface{}{v} - } - - list := make([]interface{}, rv.Len()) - for i := 0; i < rv.Len(); i++ { - list[i] = rv.Index(i).Interface() - } - return list -} diff --git a/vendor/github.com/playwright-community/playwright-go/binding_call.go b/vendor/github.com/playwright-community/playwright-go/binding_call.go deleted file mode 100644 index 8468992..0000000 --- a/vendor/github.com/playwright-community/playwright-go/binding_call.go +++ /dev/null @@ -1,87 +0,0 @@ -package playwright - -import ( - "fmt" - "strings" - - "github.com/go-stack/stack" -) - -type BindingCall interface { - Call(f BindingCallFunction) -} - -type bindingCallImpl struct { - channelOwner -} - -// BindingSource is the value passed to a binding call execution -type BindingSource struct { - Context BrowserContext - Page Page - Frame Frame -} - -// ExposedFunction represents the func signature of an exposed function -type ExposedFunction = func(args ...interface{}) interface{} - -// BindingCallFunction represents the func signature of an exposed binding call func -type BindingCallFunction func(source *BindingSource, args ...interface{}) interface{} - -func (b *bindingCallImpl) Call(f BindingCallFunction) { - defer func() { - if r := recover(); r != nil { - if _, err := b.channel.Send("reject", map[string]interface{}{ - "error": serializeError(r.(error)), - }); err != nil { - logger.Error("could not reject BindingCall", "error", err) - } - } - }() - - frame := fromChannel(b.initializer["frame"]).(*frameImpl) - source := &BindingSource{ - Context: frame.Page().Context(), - Page: frame.Page(), - Frame: frame, - } - var result interface{} - if handle, ok := b.initializer["handle"]; ok { - result = f(source, fromChannel(handle)) - } else { - initializerArgs := b.initializer["args"].([]interface{}) - funcArgs := []interface{}{} - for i := 0; i < len(initializerArgs); i++ { - funcArgs = append(funcArgs, parseResult(initializerArgs[i])) - } - result = f(source, funcArgs...) - } - _, err := b.channel.Send("resolve", map[string]interface{}{ - "result": serializeArgument(result), - }) - if err != nil { - logger.Error("could not resolve BindingCall", "error", err) - } -} - -func serializeError(err error) map[string]interface{} { - st := stack.Trace().TrimRuntime() - if len(st) == 0 { // https://github.com/go-stack/stack/issues/27 - st = stack.Trace() - } - return map[string]interface{}{ - "error": &Error{ - Name: "Playwright for Go Error", - Message: err.Error(), - Stack: strings.ReplaceAll(strings.TrimFunc(fmt.Sprintf("%+v", st), func(r rune) bool { - return r == '[' || r == ']' - }), " ", "\n"), - }, - } -} - -func newBindingCall(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *bindingCallImpl { - bt := &bindingCallImpl{} - bt.createChannelOwner(bt, parent, objectType, guid, initializer) - return bt -} diff --git a/vendor/github.com/playwright-community/playwright-go/browser.go b/vendor/github.com/playwright-community/playwright-go/browser.go deleted file mode 100644 index c87540a..0000000 --- a/vendor/github.com/playwright-community/playwright-go/browser.go +++ /dev/null @@ -1,274 +0,0 @@ -package playwright - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" -) - -type browserImpl struct { - channelOwner - isConnected bool - shouldCloseConnectionOnClose bool - contexts []BrowserContext - browserType BrowserType - chromiumTracingPath *string - closeReason *string -} - -func (b *browserImpl) BrowserType() BrowserType { - return b.browserType -} - -func (b *browserImpl) IsConnected() bool { - b.RLock() - defer b.RUnlock() - return b.isConnected -} - -func (b *browserImpl) NewContext(options ...BrowserNewContextOptions) (BrowserContext, error) { - overrides := map[string]interface{}{} - option := BrowserNewContextOptions{} - if len(options) == 1 { - option = options[0] - } - if option.AcceptDownloads != nil { - if *option.AcceptDownloads { - overrides["acceptDownloads"] = "accept" - } else { - overrides["acceptDownloads"] = "deny" - } - options[0].AcceptDownloads = nil - } - if option.ExtraHttpHeaders != nil { - overrides["extraHTTPHeaders"] = serializeMapToNameAndValue(options[0].ExtraHttpHeaders) - options[0].ExtraHttpHeaders = nil - } - if option.ClientCertificates != nil { - certs, err := transformClientCertificate(option.ClientCertificates) - if err != nil { - return nil, err - } - overrides["clientCertificates"] = certs - options[0].ClientCertificates = nil - } - if option.StorageStatePath != nil { - var storageState *OptionalStorageState - storageString, err := os.ReadFile(*options[0].StorageStatePath) - if err != nil { - return nil, fmt.Errorf("could not read storage state file: %w", err) - } - err = json.Unmarshal(storageString, &storageState) - if err != nil { - return nil, fmt.Errorf("could not parse storage state file: %w", err) - } - options[0].StorageState = storageState - options[0].StorageStatePath = nil - } - if option.NoViewport != nil && *options[0].NoViewport { - overrides["noDefaultViewport"] = true - options[0].NoViewport = nil - } - if option.RecordHarPath != nil { - overrides["recordHar"] = prepareRecordHarOptions(recordHarInputOptions{ - Path: *options[0].RecordHarPath, - URL: options[0].RecordHarURLFilter, - Mode: options[0].RecordHarMode, - Content: options[0].RecordHarContent, - OmitContent: options[0].RecordHarOmitContent, - }) - options[0].RecordHarPath = nil - options[0].RecordHarURLFilter = nil - options[0].RecordHarMode = nil - options[0].RecordHarContent = nil - options[0].RecordHarOmitContent = nil - } - channel, err := b.channel.Send("newContext", options, overrides) - if err != nil { - return nil, err - } - context := fromChannel(channel).(*browserContextImpl) - context.browser = b - b.browserType.(*browserTypeImpl).didCreateContext(context, &option, nil) - return context, nil -} - -func (b *browserImpl) NewPage(options ...BrowserNewPageOptions) (Page, error) { - opts := make([]BrowserNewContextOptions, 0) - if len(options) == 1 { - opts = append(opts, BrowserNewContextOptions(options[0])) - } - context, err := b.NewContext(opts...) - if err != nil { - return nil, err - } - page, err := context.NewPage() - if err != nil { - return nil, err - } - page.(*pageImpl).ownedContext = context - context.(*browserContextImpl).ownedPage = page - return page, nil -} - -func (b *browserImpl) NewBrowserCDPSession() (CDPSession, error) { - channel, err := b.channel.Send("newBrowserCDPSession") - if err != nil { - return nil, err - } - - cdpSession := fromChannel(channel).(*cdpSessionImpl) - - return cdpSession, nil -} - -func (b *browserImpl) Contexts() []BrowserContext { - b.Lock() - defer b.Unlock() - return b.contexts -} - -func (b *browserImpl) Close(options ...BrowserCloseOptions) (err error) { - if len(options) == 1 { - b.closeReason = options[0].Reason - } - - if b.shouldCloseConnectionOnClose { - err = b.connection.Stop() - } else if b.closeReason != nil { - _, err = b.channel.Send("close", map[string]interface{}{ - "reason": b.closeReason, - }) - } else { - _, err = b.channel.Send("close") - } - if err != nil && !errors.Is(err, ErrTargetClosed) { - return fmt.Errorf("close browser failed: %w", err) - } - return nil -} - -func (b *browserImpl) Version() string { - return b.initializer["version"].(string) -} - -func (b *browserImpl) StartTracing(options ...BrowserStartTracingOptions) error { - overrides := map[string]interface{}{} - option := BrowserStartTracingOptions{} - if len(options) == 1 { - option = options[0] - } - if option.Page != nil { - overrides["page"] = option.Page.(*pageImpl).channel - option.Page = nil - } - if option.Path != nil { - b.chromiumTracingPath = option.Path - option.Path = nil - } - _, err := b.channel.Send("startTracing", option, overrides) - return err -} - -func (b *browserImpl) StopTracing() ([]byte, error) { - channel, err := b.channel.Send("stopTracing") - if err != nil { - return nil, err - } - artifact := fromChannel(channel).(*artifactImpl) - binary, err := artifact.ReadIntoBuffer() - if err != nil { - return nil, err - } - err = artifact.Delete() - if err != nil { - return binary, err - } - if b.chromiumTracingPath != nil { - err := os.MkdirAll(filepath.Dir(*b.chromiumTracingPath), 0o777) - if err != nil { - return binary, err - } - err = os.WriteFile(*b.chromiumTracingPath, binary, 0o644) - if err != nil { - return binary, err - } - } - return binary, nil -} - -func (b *browserImpl) onClose() { - b.Lock() - if b.isConnected { - b.isConnected = false - b.Unlock() - b.Emit("disconnected", b) - return - } - b.Unlock() -} - -func (b *browserImpl) OnDisconnected(fn func(Browser)) { - b.On("disconnected", fn) -} - -func newBrowser(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *browserImpl { - b := &browserImpl{ - isConnected: true, - contexts: make([]BrowserContext, 0), - } - b.createChannelOwner(b, parent, objectType, guid, initializer) - // convert parent to *browserTypeImpl - b.browserType = newBrowserType(parent.parent, parent.objectType, parent.guid, parent.initializer) - b.channel.On("close", b.onClose) - return b -} - -func transformClientCertificate(clientCertificates []ClientCertificate) ([]map[string]interface{}, error) { - results := make([]map[string]interface{}, 0) - - for _, cert := range clientCertificates { - data := map[string]interface{}{ - "origin": cert.Origin, - "passphrase": cert.Passphrase, - } - if len(cert.Cert) > 0 { - data["cert"] = base64.StdEncoding.EncodeToString(cert.Cert) - } else if cert.CertPath != nil { - content, err := os.ReadFile(*cert.CertPath) - if err != nil { - return nil, err - } - data["cert"] = base64.StdEncoding.EncodeToString(content) - } - - if len(cert.Key) > 0 { - data["key"] = base64.StdEncoding.EncodeToString(cert.Key) - } else if cert.KeyPath != nil { - content, err := os.ReadFile(*cert.KeyPath) - if err != nil { - return nil, err - } - data["key"] = base64.StdEncoding.EncodeToString(content) - } - - if len(cert.Pfx) > 0 { - data["pfx"] = base64.StdEncoding.EncodeToString(cert.Pfx) - } else if cert.PfxPath != nil { - content, err := os.ReadFile(*cert.PfxPath) - if err != nil { - return nil, err - } - data["pfx"] = base64.StdEncoding.EncodeToString(content) - } - - results = append(results, data) - } - if len(results) == 0 { - return nil, nil - } - return results, nil -} diff --git a/vendor/github.com/playwright-community/playwright-go/browser_context.go b/vendor/github.com/playwright-community/playwright-go/browser_context.go deleted file mode 100644 index 1d420d3..0000000 --- a/vendor/github.com/playwright-community/playwright-go/browser_context.go +++ /dev/null @@ -1,914 +0,0 @@ -package playwright - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "regexp" - "slices" - "strings" - "sync" - - "github.com/playwright-community/playwright-go/internal/safe" -) - -type browserContextImpl struct { - channelOwner - timeoutSettings *timeoutSettings - closeWasCalled bool - options *BrowserNewContextOptions - pages []Page - routes []*routeHandlerEntry - webSocketRoutes []*webSocketRouteHandler - ownedPage Page - browser *browserImpl - serviceWorkers []Worker - backgroundPages []Page - bindings *safe.SyncMap[string, BindingCallFunction] - tracing *tracingImpl - request *apiRequestContextImpl - harRecorders map[string]harRecordingMetadata - closed chan struct{} - closeReason *string - harRouters []*harRouter - clock Clock -} - -func (b *browserContextImpl) Clock() Clock { - return b.clock -} - -func (b *browserContextImpl) SetDefaultNavigationTimeout(timeout float64) { - b.setDefaultNavigationTimeoutImpl(&timeout) -} - -func (b *browserContextImpl) setDefaultNavigationTimeoutImpl(timeout *float64) { - b.timeoutSettings.SetDefaultNavigationTimeout(timeout) - b.channel.SendNoReplyInternal("setDefaultNavigationTimeoutNoReply", map[string]interface{}{ - "timeout": timeout, - }) -} - -func (b *browserContextImpl) SetDefaultTimeout(timeout float64) { - b.setDefaultTimeoutImpl(&timeout) -} - -func (b *browserContextImpl) setDefaultTimeoutImpl(timeout *float64) { - b.timeoutSettings.SetDefaultTimeout(timeout) - b.channel.SendNoReplyInternal("setDefaultTimeoutNoReply", map[string]interface{}{ - "timeout": timeout, - }) -} - -func (b *browserContextImpl) Pages() []Page { - b.Lock() - defer b.Unlock() - return b.pages -} - -func (b *browserContextImpl) Browser() Browser { - return b.browser -} - -func (b *browserContextImpl) Tracing() Tracing { - return b.tracing -} - -func (b *browserContextImpl) NewCDPSession(page interface{}) (CDPSession, error) { - params := map[string]interface{}{} - - if p, ok := page.(*pageImpl); ok { - params["page"] = p.channel - } else if f, ok := page.(*frameImpl); ok { - params["frame"] = f.channel - } else { - return nil, fmt.Errorf("not page or frame: %v", page) - } - - channel, err := b.channel.Send("newCDPSession", params) - if err != nil { - return nil, err - } - - cdpSession := fromChannel(channel).(*cdpSessionImpl) - - return cdpSession, nil -} - -func (b *browserContextImpl) NewPage() (Page, error) { - if b.ownedPage != nil { - return nil, errors.New("Please use browser.NewContext()") - } - channel, err := b.channel.Send("newPage") - if err != nil { - return nil, err - } - return fromChannel(channel).(*pageImpl), nil -} - -func (b *browserContextImpl) Cookies(urls ...string) ([]Cookie, error) { - result, err := b.channel.Send("cookies", map[string]interface{}{ - "urls": urls, - }) - if err != nil { - return nil, err - } - cookies := make([]Cookie, len(result.([]interface{}))) - for i, item := range result.([]interface{}) { - cookie := &Cookie{} - remapMapToStruct(item, cookie) - cookies[i] = *cookie - } - return cookies, nil -} - -func (b *browserContextImpl) AddCookies(cookies []OptionalCookie) error { - _, err := b.channel.Send("addCookies", map[string]interface{}{ - "cookies": cookies, - }) - return err -} - -func (b *browserContextImpl) ClearCookies(options ...BrowserContextClearCookiesOptions) error { - params := map[string]interface{}{} - if len(options) == 1 { - if options[0].Domain != nil { - switch t := options[0].Domain.(type) { - case string: - params["domain"] = t - case *string: - params["domain"] = t - case *regexp.Regexp: - pattern, flag := convertRegexp(t) - params["domainRegexSource"] = pattern - params["domainRegexFlags"] = flag - default: - return errors.New("invalid type for domain, expected string or *regexp.Regexp") - } - } - if options[0].Name != nil { - switch t := options[0].Name.(type) { - case string: - params["name"] = t - case *string: - params["name"] = t - case *regexp.Regexp: - pattern, flag := convertRegexp(t) - params["nameRegexSource"] = pattern - params["nameRegexFlags"] = flag - default: - return errors.New("invalid type for name, expected string or *regexp.Regexp") - } - } - if options[0].Path != nil { - switch t := options[0].Path.(type) { - case string: - params["path"] = t - case *string: - params["path"] = t - case *regexp.Regexp: - pattern, flag := convertRegexp(t) - params["pathRegexSource"] = pattern - params["pathRegexFlags"] = flag - default: - return errors.New("invalid type for path, expected string or *regexp.Regexp") - } - } - } - _, err := b.channel.Send("clearCookies", params) - return err -} - -func (b *browserContextImpl) GrantPermissions(permissions []string, options ...BrowserContextGrantPermissionsOptions) error { - _, err := b.channel.Send("grantPermissions", map[string]interface{}{ - "permissions": permissions, - }, options) - return err -} - -func (b *browserContextImpl) ClearPermissions() error { - _, err := b.channel.Send("clearPermissions") - return err -} - -func (b *browserContextImpl) SetGeolocation(geolocation *Geolocation) error { - _, err := b.channel.Send("setGeolocation", map[string]interface{}{ - "geolocation": geolocation, - }) - return err -} - -func (b *browserContextImpl) ResetGeolocation() error { - _, err := b.channel.Send("setGeolocation", map[string]interface{}{}) - return err -} - -func (b *browserContextImpl) SetExtraHTTPHeaders(headers map[string]string) error { - _, err := b.channel.Send("setExtraHTTPHeaders", map[string]interface{}{ - "headers": serializeMapToNameAndValue(headers), - }) - return err -} - -func (b *browserContextImpl) SetOffline(offline bool) error { - _, err := b.channel.Send("setOffline", map[string]interface{}{ - "offline": offline, - }) - return err -} - -func (b *browserContextImpl) AddInitScript(script Script) error { - var source string - if script.Content != nil { - source = *script.Content - } - if script.Path != nil { - content, err := os.ReadFile(*script.Path) - if err != nil { - return err - } - source = string(content) - } - _, err := b.channel.Send("addInitScript", map[string]interface{}{ - "source": source, - }) - return err -} - -func (b *browserContextImpl) ExposeBinding(name string, binding BindingCallFunction, handle ...bool) error { - needsHandle := false - if len(handle) == 1 { - needsHandle = handle[0] - } - for _, page := range b.Pages() { - if _, ok := page.(*pageImpl).bindings.Load(name); ok { - return fmt.Errorf("Function '%s' has been already registered in one of the pages", name) - } - } - if _, ok := b.bindings.Load(name); ok { - return fmt.Errorf("Function '%s' has been already registered", name) - } - _, err := b.channel.Send("exposeBinding", map[string]interface{}{ - "name": name, - "needsHandle": needsHandle, - }) - if err != nil { - return err - } - b.bindings.Store(name, binding) - return err -} - -func (b *browserContextImpl) ExposeFunction(name string, binding ExposedFunction) error { - return b.ExposeBinding(name, func(source *BindingSource, args ...interface{}) interface{} { - return binding(args...) - }) -} - -func (b *browserContextImpl) Route(url interface{}, handler routeHandler, times ...int) error { - b.Lock() - defer b.Unlock() - b.routes = slices.Insert(b.routes, 0, newRouteHandlerEntry(newURLMatcher(url, b.options.BaseURL), handler, times...)) - return b.updateInterceptionPatterns() -} - -func (b *browserContextImpl) Unroute(url interface{}, handlers ...routeHandler) error { - removed, remaining, err := unroute(b.routes, url, handlers...) - if err != nil { - return err - } - return b.unrouteInternal(removed, remaining, UnrouteBehaviorDefault) -} - -func (b *browserContextImpl) unrouteInternal(removed []*routeHandlerEntry, remaining []*routeHandlerEntry, behavior *UnrouteBehavior) error { - b.Lock() - defer b.Unlock() - b.routes = remaining - if err := b.updateInterceptionPatterns(); err != nil { - return err - } - if behavior == nil || behavior == UnrouteBehaviorDefault { - return nil - } - wg := &sync.WaitGroup{} - for _, entry := range removed { - wg.Add(1) - go func(entry *routeHandlerEntry) { - defer wg.Done() - entry.Stop(string(*behavior)) - }(entry) - } - wg.Wait() - return nil -} - -func (b *browserContextImpl) UnrouteAll(options ...BrowserContextUnrouteAllOptions) error { - var behavior *UnrouteBehavior - if len(options) == 1 { - behavior = options[0].Behavior - } - defer b.disposeHarRouters() - return b.unrouteInternal(b.routes, []*routeHandlerEntry{}, behavior) -} - -func (b *browserContextImpl) disposeHarRouters() { - for _, router := range b.harRouters { - router.dispose() - } - b.harRouters = make([]*harRouter, 0) -} - -func (b *browserContextImpl) Request() APIRequestContext { - return b.request -} - -func (b *browserContextImpl) RouteFromHAR(har string, options ...BrowserContextRouteFromHAROptions) error { - opt := BrowserContextRouteFromHAROptions{} - if len(options) == 1 { - opt = options[0] - } - if opt.Update != nil && *opt.Update { - var updateContent *HarContentPolicy - switch opt.UpdateContent { - case RouteFromHarUpdateContentPolicyAttach: - updateContent = HarContentPolicyAttach - case RouteFromHarUpdateContentPolicyEmbed: - updateContent = HarContentPolicyEmbed - } - return b.recordIntoHar(har, browserContextRecordIntoHarOptions{ - URL: opt.URL, - UpdateContent: updateContent, - UpdateMode: opt.UpdateMode, - }) - } - notFound := opt.NotFound - if notFound == nil { - notFound = HarNotFoundAbort - } - router := newHarRouter(b.connection.localUtils, har, *notFound, opt.URL) - b.harRouters = append(b.harRouters, router) - return router.addContextRoute(b) -} - -func (b *browserContextImpl) WaitForEvent(event string, options ...BrowserContextWaitForEventOptions) (interface{}, error) { - return b.waiterForEvent(event, options...).Wait() -} - -func (b *browserContextImpl) waiterForEvent(event string, options ...BrowserContextWaitForEventOptions) *waiter { - timeout := b.timeoutSettings.Timeout() - var predicate interface{} = nil - if len(options) == 1 { - if options[0].Timeout != nil { - timeout = *options[0].Timeout - } - predicate = options[0].Predicate - } - waiter := newWaiter().WithTimeout(timeout) - waiter.RejectOnEvent(b, "close", ErrTargetClosed) - return waiter.WaitForEvent(b, event, predicate) -} - -func (b *browserContextImpl) ExpectConsoleMessage(cb func() error, options ...BrowserContextExpectConsoleMessageOptions) (ConsoleMessage, error) { - var w *waiter - if len(options) == 1 { - w = b.waiterForEvent("console", BrowserContextWaitForEventOptions{ - Predicate: options[0].Predicate, - Timeout: options[0].Timeout, - }) - } else { - w = b.waiterForEvent("console") - } - ret, err := w.RunAndWait(cb) - if err != nil { - return nil, err - } - return ret.(ConsoleMessage), nil -} - -func (b *browserContextImpl) ExpectEvent(event string, cb func() error, options ...BrowserContextExpectEventOptions) (interface{}, error) { - if len(options) == 1 { - return b.waiterForEvent(event, BrowserContextWaitForEventOptions(options[0])).RunAndWait(cb) - } - return b.waiterForEvent(event).RunAndWait(cb) -} - -func (b *browserContextImpl) ExpectPage(cb func() error, options ...BrowserContextExpectPageOptions) (Page, error) { - var w *waiter - if len(options) == 1 { - w = b.waiterForEvent("page", BrowserContextWaitForEventOptions{ - Predicate: options[0].Predicate, - Timeout: options[0].Timeout, - }) - } else { - w = b.waiterForEvent("page") - } - ret, err := w.RunAndWait(cb) - if err != nil { - return nil, err - } - return ret.(Page), nil -} - -func (b *browserContextImpl) Close(options ...BrowserContextCloseOptions) error { - if b.closeWasCalled { - return nil - } - if len(options) == 1 { - b.closeReason = options[0].Reason - } - b.closeWasCalled = true - - _, err := b.channel.connection.WrapAPICall(func() (interface{}, error) { - return nil, b.request.Dispose(APIRequestContextDisposeOptions{ - Reason: b.closeReason, - }) - }, true) - if err != nil { - return err - } - - innerClose := func() (interface{}, error) { - for harId, harMetaData := range b.harRecorders { - overrides := map[string]interface{}{} - if harId != "" { - overrides["harId"] = harId - } - response, err := b.channel.Send("harExport", overrides) - if err != nil { - return nil, err - } - artifact := fromChannel(response).(*artifactImpl) - // Server side will compress artifact if content is attach or if file is .zip. - needCompressed := strings.HasSuffix(strings.ToLower(harMetaData.Path), ".zip") - if !needCompressed && harMetaData.Content == HarContentPolicyAttach { - tmpPath := harMetaData.Path + ".tmp" - if err := artifact.SaveAs(tmpPath); err != nil { - return nil, err - } - err = b.connection.localUtils.HarUnzip(tmpPath, harMetaData.Path) - if err != nil { - return nil, err - } - } else { - if err := artifact.SaveAs(harMetaData.Path); err != nil { - return nil, err - } - } - if err := artifact.Delete(); err != nil { - return nil, err - } - } - return nil, nil - } - - _, err = b.channel.connection.WrapAPICall(innerClose, true) - if err != nil { - return err - } - - _, err = b.channel.Send("close", map[string]interface{}{ - "reason": b.closeReason, - }) - if err != nil { - return err - } - <-b.closed - return err -} - -type browserContextRecordIntoHarOptions struct { - Page Page - URL interface{} - UpdateContent *HarContentPolicy - UpdateMode *HarMode -} - -func (b *browserContextImpl) recordIntoHar(har string, options ...browserContextRecordIntoHarOptions) error { - overrides := map[string]interface{}{} - harOptions := recordHarInputOptions{ - Path: har, - Content: HarContentPolicyAttach, - Mode: HarModeMinimal, - } - if len(options) == 1 { - if options[0].UpdateContent != nil { - harOptions.Content = options[0].UpdateContent - } - if options[0].UpdateMode != nil { - harOptions.Mode = options[0].UpdateMode - } - harOptions.URL = options[0].URL - overrides["options"] = prepareRecordHarOptions(harOptions) - if options[0].Page != nil { - overrides["page"] = options[0].Page.(*pageImpl).channel - } - } - harId, err := b.channel.Send("harStart", overrides) - if err != nil { - return err - } - b.harRecorders[harId.(string)] = harRecordingMetadata{ - Path: har, - Content: harOptions.Content, - } - return nil -} - -func (b *browserContextImpl) StorageState(paths ...string) (*StorageState, error) { - result, err := b.channel.SendReturnAsDict("storageState") - if err != nil { - return nil, err - } - if len(paths) == 1 { - file, err := os.Create(paths[0]) - if err != nil { - return nil, err - } - if err := json.NewEncoder(file).Encode(result); err != nil { - return nil, err - } - if err := file.Close(); err != nil { - return nil, err - } - } - var storageState StorageState - remapMapToStruct(result, &storageState) - return &storageState, nil -} - -func (b *browserContextImpl) onBinding(binding *bindingCallImpl) { - function, ok := b.bindings.Load(binding.initializer["name"].(string)) - if !ok || function == nil { - return - } - go binding.Call(function) -} - -func (b *browserContextImpl) onClose() { - if b.browser != nil { - contexts := make([]BrowserContext, 0) - b.browser.Lock() - for _, context := range b.browser.contexts { - if context != b { - contexts = append(contexts, context) - } - } - b.browser.contexts = contexts - b.browser.Unlock() - } - b.disposeHarRouters() - b.Emit("close", b) -} - -func (b *browserContextImpl) onPage(page Page) { - b.Lock() - b.pages = append(b.pages, page) - b.Unlock() - b.Emit("page", page) - opener, _ := page.Opener() - if opener != nil && !opener.IsClosed() { - opener.Emit("popup", page) - } -} - -func (b *browserContextImpl) onRoute(route *routeImpl) { - b.Lock() - route.context = b - page := route.Request().(*requestImpl).safePage() - routes := make([]*routeHandlerEntry, len(b.routes)) - copy(routes, b.routes) - b.Unlock() - - checkInterceptionIfNeeded := func() { - b.Lock() - defer b.Unlock() - if len(b.routes) == 0 { - _, err := b.connection.WrapAPICall(func() (interface{}, error) { - err := b.updateInterceptionPatterns() - return nil, err - }, true) - if err != nil { - logger.Error("could not update interception patterns", "error", err) - } - } - } - - url := route.Request().URL() - for _, handlerEntry := range routes { - // If the page or the context was closed we stall all requests right away. - if (page != nil && page.closeWasCalled) || b.closeWasCalled { - return - } - if !handlerEntry.Matches(url) { - continue - } - if !slices.ContainsFunc(b.routes, func(entry *routeHandlerEntry) bool { - return entry == handlerEntry - }) { - continue - } - if handlerEntry.WillExceed() { - b.routes = slices.DeleteFunc(b.routes, func(rhe *routeHandlerEntry) bool { - return rhe == handlerEntry - }) - } - handled := handlerEntry.Handle(route) - checkInterceptionIfNeeded() - yes := <-handled - if yes { - return - } - } - // If the page is closed or unrouteAll() was called without waiting and interception disabled, - // the method will throw an error - silence it. - _ = route.internalContinue(true) -} - -func (b *browserContextImpl) updateInterceptionPatterns() error { - patterns := prepareInterceptionPatterns(b.routes) - _, err := b.channel.Send("setNetworkInterceptionPatterns", map[string]interface{}{ - "patterns": patterns, - }) - return err -} - -func (b *browserContextImpl) pause() <-chan error { - ret := make(chan error, 1) - go func() { - _, err := b.channel.Send("pause") - ret <- err - }() - return ret -} - -func (b *browserContextImpl) onBackgroundPage(ev map[string]interface{}) { - b.Lock() - p := fromChannel(ev["page"]).(*pageImpl) - p.browserContext = b - b.backgroundPages = append(b.backgroundPages, p) - b.Unlock() - b.Emit("backgroundpage", p) -} - -func (b *browserContextImpl) onServiceWorker(worker *workerImpl) { - worker.context = b - b.serviceWorkers = append(b.serviceWorkers, worker) - b.Emit("serviceworker", worker) -} - -func (b *browserContextImpl) setOptions(options *BrowserNewContextOptions, tracesDir *string) { - if options == nil { - options = &BrowserNewContextOptions{} - } - b.options = options - if b.options != nil && b.options.RecordHarPath != nil { - b.harRecorders[""] = harRecordingMetadata{ - Path: *b.options.RecordHarPath, - Content: b.options.RecordHarContent, - } - } - if tracesDir != nil { - b.tracing.tracesDir = *tracesDir - } -} - -func (b *browserContextImpl) BackgroundPages() []Page { - b.Lock() - defer b.Unlock() - return b.backgroundPages -} - -func (b *browserContextImpl) ServiceWorkers() []Worker { - b.Lock() - defer b.Unlock() - return b.serviceWorkers -} - -func (b *browserContextImpl) OnBackgroundPage(fn func(Page)) { - b.On("backgroundpage", fn) -} - -func (b *browserContextImpl) OnClose(fn func(BrowserContext)) { - b.On("close", fn) -} - -func (b *browserContextImpl) OnConsole(fn func(ConsoleMessage)) { - b.On("console", fn) -} - -func (b *browserContextImpl) OnDialog(fn func(Dialog)) { - b.On("dialog", fn) -} - -func (b *browserContextImpl) OnPage(fn func(Page)) { - b.On("page", fn) -} - -func (b *browserContextImpl) OnRequest(fn func(Request)) { - b.On("request", fn) -} - -func (b *browserContextImpl) OnRequestFailed(fn func(Request)) { - b.On("requestfailed", fn) -} - -func (b *browserContextImpl) OnRequestFinished(fn func(Request)) { - b.On("requestfinished", fn) -} - -func (b *browserContextImpl) OnResponse(fn func(Response)) { - b.On("response", fn) -} - -func (b *browserContextImpl) OnWebError(fn func(WebError)) { - b.On("weberror", fn) -} - -func (b *browserContextImpl) RouteWebSocket(url interface{}, handler func(WebSocketRoute)) error { - b.Lock() - defer b.Unlock() - b.webSocketRoutes = slices.Insert(b.webSocketRoutes, 0, newWebSocketRouteHandler(newURLMatcher(url, b.options.BaseURL), handler)) - - return b.updateWebSocketInterceptionPatterns() -} - -func (b *browserContextImpl) onWebSocketRoute(wr WebSocketRoute) { - b.Lock() - index := slices.IndexFunc(b.webSocketRoutes, func(r *webSocketRouteHandler) bool { - return r.Matches(wr.URL()) - }) - if index == -1 { - b.Unlock() - _, err := wr.ConnectToServer() - if err != nil { - logger.Error("could not connect to WebSocket server", "error", err) - } - return - } - handler := b.webSocketRoutes[index] - b.Unlock() - handler.Handle(wr) -} - -func (b *browserContextImpl) updateWebSocketInterceptionPatterns() error { - patterns := prepareWebSocketRouteHandlerInterceptionPatterns(b.webSocketRoutes) - _, err := b.channel.Send("setWebSocketInterceptionPatterns", map[string]interface{}{ - "patterns": patterns, - }) - return err -} - -func (b *browserContextImpl) effectiveCloseReason() *string { - b.Lock() - defer b.Unlock() - if b.closeReason != nil { - return b.closeReason - } - if b.browser != nil { - return b.browser.closeReason - } - return nil -} - -func newBrowserContext(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *browserContextImpl { - bt := &browserContextImpl{ - timeoutSettings: newTimeoutSettings(nil), - pages: make([]Page, 0), - backgroundPages: make([]Page, 0), - routes: make([]*routeHandlerEntry, 0), - bindings: safe.NewSyncMap[string, BindingCallFunction](), - harRecorders: make(map[string]harRecordingMetadata), - closed: make(chan struct{}, 1), - harRouters: make([]*harRouter, 0), - } - bt.createChannelOwner(bt, parent, objectType, guid, initializer) - if parent.objectType == "Browser" { - bt.browser = fromChannel(parent.channel).(*browserImpl) - bt.browser.contexts = append(bt.browser.contexts, bt) - } - bt.tracing = fromChannel(initializer["tracing"]).(*tracingImpl) - bt.request = fromChannel(initializer["requestContext"]).(*apiRequestContextImpl) - bt.clock = newClock(bt) - bt.channel.On("bindingCall", func(params map[string]interface{}) { - bt.onBinding(fromChannel(params["binding"]).(*bindingCallImpl)) - }) - - bt.channel.On("close", bt.onClose) - bt.channel.On("page", func(payload map[string]interface{}) { - bt.onPage(fromChannel(payload["page"]).(*pageImpl)) - }) - bt.channel.On("route", func(params map[string]interface{}) { - bt.channel.CreateTask(func() { - bt.onRoute(fromChannel(params["route"]).(*routeImpl)) - }) - }) - bt.channel.On("webSocketRoute", func(params map[string]interface{}) { - bt.channel.CreateTask(func() { - bt.onWebSocketRoute(fromChannel(params["webSocketRoute"]).(*webSocketRouteImpl)) - }) - }) - bt.channel.On("backgroundPage", bt.onBackgroundPage) - bt.channel.On("serviceWorker", func(params map[string]interface{}) { - bt.onServiceWorker(fromChannel(params["worker"]).(*workerImpl)) - }) - bt.channel.On("console", func(ev map[string]interface{}) { - message := newConsoleMessage(ev) - bt.Emit("console", message) - if message.page != nil { - message.page.Emit("console", message) - } - }) - bt.channel.On("dialog", func(params map[string]interface{}) { - dialog := fromChannel(params["dialog"]).(*dialogImpl) - go func() { - hasListeners := bt.Emit("dialog", dialog) - page := dialog.page - if page != nil { - if page.Emit("dialog", dialog) { - hasListeners = true - } - } - if !hasListeners { - // Although we do similar handling on the server side, we still need this logic - // on the client side due to a possible race condition between two async calls: - // a) removing "dialog" listener subscription (client->server) - // b) actual "dialog" event (server->client) - if dialog.Type() == "beforeunload" { - _ = dialog.Accept() - } else { - _ = dialog.Dismiss() - } - } - }() - }) - bt.channel.On( - "pageError", func(ev map[string]interface{}) { - pwErr := &Error{} - remapMapToStruct(ev["error"].(map[string]interface{})["error"], pwErr) - err := parseError(*pwErr) - page := fromNullableChannel(ev["page"]) - if page != nil { - bt.Emit("weberror", newWebError(page.(*pageImpl), err)) - page.(*pageImpl).Emit("pageerror", err) - } else { - bt.Emit("weberror", newWebError(nil, err)) - } - }, - ) - bt.channel.On("request", func(ev map[string]interface{}) { - request := fromChannel(ev["request"]).(*requestImpl) - page := fromNullableChannel(ev["page"]) - bt.Emit("request", request) - if page != nil { - page.(*pageImpl).Emit("request", request) - } - }) - bt.channel.On("requestFailed", func(ev map[string]interface{}) { - request := fromChannel(ev["request"]).(*requestImpl) - failureText := ev["failureText"] - if failureText != nil { - request.failureText = failureText.(string) - } - page := fromNullableChannel(ev["page"]) - request.setResponseEndTiming(ev["responseEndTiming"].(float64)) - bt.Emit("requestfailed", request) - if page != nil { - page.(*pageImpl).Emit("requestfailed", request) - } - }) - - bt.channel.On("requestFinished", func(ev map[string]interface{}) { - request := fromChannel(ev["request"]).(*requestImpl) - response := fromNullableChannel(ev["response"]) - page := fromNullableChannel(ev["page"]) - request.setResponseEndTiming(ev["responseEndTiming"].(float64)) - bt.Emit("requestfinished", request) - if page != nil { - page.(*pageImpl).Emit("requestfinished", request) - } - if response != nil { - close(response.(*responseImpl).finished) - } - }) - bt.channel.On("response", func(ev map[string]interface{}) { - response := fromChannel(ev["response"]).(*responseImpl) - page := fromNullableChannel(ev["page"]) - bt.Emit("response", response) - if page != nil { - page.(*pageImpl).Emit("response", response) - } - }) - bt.Once("close", func() { - bt.closed <- struct{}{} - }) - bt.setEventSubscriptionMapping(map[string]string{ - "console": "console", - "dialog": "dialog", - "request": "request", - "response": "response", - "requestfinished": "requestFinished", - "responsefailed": "responseFailed", - }) - return bt -} diff --git a/vendor/github.com/playwright-community/playwright-go/browser_type.go b/vendor/github.com/playwright-community/playwright-go/browser_type.go deleted file mode 100644 index 41a8b18..0000000 --- a/vendor/github.com/playwright-community/playwright-go/browser_type.go +++ /dev/null @@ -1,181 +0,0 @@ -package playwright - -import ( - "fmt" -) - -type browserTypeImpl struct { - channelOwner - playwright *Playwright -} - -func (b *browserTypeImpl) Name() string { - return b.initializer["name"].(string) -} - -func (b *browserTypeImpl) ExecutablePath() string { - return b.initializer["executablePath"].(string) -} - -func (b *browserTypeImpl) Launch(options ...BrowserTypeLaunchOptions) (Browser, error) { - overrides := map[string]interface{}{} - if len(options) == 1 && options[0].Env != nil { - overrides["env"] = serializeMapToNameAndValue(options[0].Env) - options[0].Env = nil - } - channel, err := b.channel.Send("launch", options, overrides) - if err != nil { - return nil, err - } - browser := fromChannel(channel).(*browserImpl) - b.didLaunchBrowser(browser) - return browser, nil -} - -func (b *browserTypeImpl) LaunchPersistentContext(userDataDir string, options ...BrowserTypeLaunchPersistentContextOptions) (BrowserContext, error) { - overrides := map[string]interface{}{ - "userDataDir": userDataDir, - } - option := &BrowserNewContextOptions{} - var tracesDir *string = nil - if len(options) == 1 { - tracesDir = options[0].TracesDir - err := assignStructFields(option, options[0], true) - if err != nil { - return nil, fmt.Errorf("can not convert options: %w", err) - } - if options[0].AcceptDownloads != nil { - if *options[0].AcceptDownloads { - overrides["acceptDownloads"] = "accept" - } else { - overrides["acceptDownloads"] = "deny" - } - options[0].AcceptDownloads = nil - } - if options[0].ClientCertificates != nil { - certs, err := transformClientCertificate(options[0].ClientCertificates) - if err != nil { - return nil, err - } - overrides["clientCertificates"] = certs - options[0].ClientCertificates = nil - } - if options[0].ExtraHttpHeaders != nil { - overrides["extraHTTPHeaders"] = serializeMapToNameAndValue(options[0].ExtraHttpHeaders) - options[0].ExtraHttpHeaders = nil - } - if options[0].Env != nil { - overrides["env"] = serializeMapToNameAndValue(options[0].Env) - options[0].Env = nil - } - if options[0].NoViewport != nil && *options[0].NoViewport { - overrides["noDefaultViewport"] = true - options[0].NoViewport = nil - } - if options[0].RecordHarPath != nil { - overrides["recordHar"] = prepareRecordHarOptions(recordHarInputOptions{ - Path: *options[0].RecordHarPath, - URL: options[0].RecordHarURLFilter, - Mode: options[0].RecordHarMode, - Content: options[0].RecordHarContent, - OmitContent: options[0].RecordHarOmitContent, - }) - options[0].RecordHarPath = nil - options[0].RecordHarURLFilter = nil - options[0].RecordHarMode = nil - options[0].RecordHarContent = nil - options[0].RecordHarOmitContent = nil - } - } - channel, err := b.channel.Send("launchPersistentContext", options, overrides) - if err != nil { - return nil, err - } - context := fromChannel(channel).(*browserContextImpl) - b.didCreateContext(context, option, tracesDir) - return context, nil -} - -func (b *browserTypeImpl) Connect(wsEndpoint string, options ...BrowserTypeConnectOptions) (Browser, error) { - overrides := map[string]interface{}{ - "wsEndpoint": wsEndpoint, - "headers": map[string]string{ - "x-playwright-browser": b.Name(), - }, - } - if len(options) == 1 { - if options[0].Headers != nil { - for k, v := range options[0].Headers { - overrides["headers"].(map[string]string)[k] = v - } - options[0].Headers = nil - } - } - localUtils := b.connection.LocalUtils() - pipe, err := localUtils.channel.SendReturnAsDict("connect", options, overrides) - if err != nil { - return nil, err - } - jsonPipe := fromChannel(pipe["pipe"]).(*jsonPipe) - connection := newConnection(jsonPipe, localUtils) - - playwright, err := connection.Start() - if err != nil { - return nil, err - } - playwright.setSelectors(b.playwright.Selectors) - browser := fromChannel(playwright.initializer["preLaunchedBrowser"]).(*browserImpl) - browser.shouldCloseConnectionOnClose = true - pipeClosed := func() { - for _, context := range browser.Contexts() { - pages := context.Pages() - for _, page := range pages { - page.(*pageImpl).onClose() - } - context.(*browserContextImpl).onClose() - } - browser.onClose() - connection.cleanup() - } - jsonPipe.On("closed", pipeClosed) - - b.didLaunchBrowser(browser) - return browser, nil -} - -func (b *browserTypeImpl) ConnectOverCDP(endpointURL string, options ...BrowserTypeConnectOverCDPOptions) (Browser, error) { - overrides := map[string]interface{}{ - "endpointURL": endpointURL, - } - if len(options) == 1 { - if options[0].Headers != nil { - overrides["headers"] = serializeMapToNameAndValue(options[0].Headers) - options[0].Headers = nil - } - } - response, err := b.channel.SendReturnAsDict("connectOverCDP", options, overrides) - if err != nil { - return nil, err - } - browser := fromChannel(response["browser"]).(*browserImpl) - b.didLaunchBrowser(browser) - if defaultContext, ok := response["defaultContext"]; ok { - context := fromChannel(defaultContext).(*browserContextImpl) - b.didCreateContext(context, nil, nil) - } - return browser, nil -} - -func (b *browserTypeImpl) didCreateContext(context *browserContextImpl, contextOptions *BrowserNewContextOptions, tracesDir *string) { - context.setOptions(contextOptions, tracesDir) -} - -func (b *browserTypeImpl) didLaunchBrowser(browser *browserImpl) { - browser.browserType = b -} - -func newBrowserType(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *browserTypeImpl { - bt := &browserTypeImpl{} - bt.createChannelOwner(bt, parent, objectType, guid, initializer) - return bt -} diff --git a/vendor/github.com/playwright-community/playwright-go/cdp_session.go b/vendor/github.com/playwright-community/playwright-go/cdp_session.go deleted file mode 100644 index e9bba82..0000000 --- a/vendor/github.com/playwright-community/playwright-go/cdp_session.go +++ /dev/null @@ -1,38 +0,0 @@ -package playwright - -type cdpSessionImpl struct { - channelOwner -} - -func (c *cdpSessionImpl) Detach() error { - _, err := c.channel.Send("detach") - return err -} - -func (c *cdpSessionImpl) Send(method string, params map[string]interface{}) (interface{}, error) { - result, err := c.channel.Send("send", map[string]interface{}{ - "method": method, - "params": params, - }) - if err != nil { - return nil, err - } - - return result, err -} - -func (c *cdpSessionImpl) onEvent(params map[string]interface{}) { - c.Emit(params["method"].(string), params["params"]) -} - -func newCDPSession(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *cdpSessionImpl { - bt := &cdpSessionImpl{} - - bt.createChannelOwner(bt, parent, objectType, guid, initializer) - - bt.channel.On("event", func(params map[string]interface{}) { - bt.onEvent(params) - }) - - return bt -} diff --git a/vendor/github.com/playwright-community/playwright-go/channel.go b/vendor/github.com/playwright-community/playwright-go/channel.go deleted file mode 100644 index b0bded4..0000000 --- a/vendor/github.com/playwright-community/playwright-go/channel.go +++ /dev/null @@ -1,92 +0,0 @@ -package playwright - -import ( - "encoding/json" - "fmt" -) - -type channel struct { - eventEmitter - guid string - connection *connection - owner *channelOwner // to avoid type conversion - object interface{} // retain type info (for fromChannel needed) -} - -func (c *channel) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "guid": c.guid, - }) -} - -// for catch errors of route handlers etc. -func (c *channel) CreateTask(fn func()) { - go func() { - defer func() { - if e := recover(); e != nil { - err, ok := e.(error) - if ok { - c.connection.err.Set(err) - } else { - c.connection.err.Set(fmt.Errorf("%v", e)) - } - } - }() - fn() - }() -} - -func (c *channel) Send(method string, options ...interface{}) (interface{}, error) { - return c.connection.WrapAPICall(func() (interface{}, error) { - return c.innerSend(method, options...).GetResultValue() - }, c.owner.isInternalType) -} - -func (c *channel) SendReturnAsDict(method string, options ...interface{}) (map[string]interface{}, error) { - ret, err := c.connection.WrapAPICall(func() (interface{}, error) { - return c.innerSend(method, options...).GetResult() - }, c.owner.isInternalType) - return ret.(map[string]interface{}), err -} - -func (c *channel) innerSend(method string, options ...interface{}) *protocolCallback { - if err := c.connection.err.Get(); err != nil { - c.connection.err.Set(nil) - pc := newProtocolCallback(false, c.connection.abort) - pc.SetError(err) - return pc - } - params := transformOptions(options...) - return c.connection.sendMessageToServer(c.owner, method, params, false) -} - -// SendNoReply ignores return value and errors -// almost equivalent to `send(...).catch(() => {})` -func (c *channel) SendNoReply(method string, options ...interface{}) { - c.innerSendNoReply(method, c.owner.isInternalType, options...) -} - -func (c *channel) SendNoReplyInternal(method string, options ...interface{}) { - c.innerSendNoReply(method, true, options...) -} - -func (c *channel) innerSendNoReply(method string, isInternal bool, options ...interface{}) { - params := transformOptions(options...) - _, err := c.connection.WrapAPICall(func() (interface{}, error) { - return c.connection.sendMessageToServer(c.owner, method, params, true).GetResult() - }, isInternal) - if err != nil { - // ignore error actively, log only for debug - logger.Error("SendNoReply failed", "error", err) - } -} - -func newChannel(owner *channelOwner, object interface{}) *channel { - channel := &channel{ - connection: owner.connection, - guid: owner.guid, - owner: owner, - object: object, - } - return channel -} diff --git a/vendor/github.com/playwright-community/playwright-go/channel_owner.go b/vendor/github.com/playwright-community/playwright-go/channel_owner.go deleted file mode 100644 index 5159eb2..0000000 --- a/vendor/github.com/playwright-community/playwright-go/channel_owner.go +++ /dev/null @@ -1,122 +0,0 @@ -package playwright - -import ( - "sync" -) - -type channelOwner struct { - sync.RWMutex - eventEmitter - objectType string - guid string - channel *channel - objects map[string]*channelOwner - eventToSubscriptionMapping map[string]string - connection *connection - initializer map[string]interface{} - parent *channelOwner - wasCollected bool - isInternalType bool -} - -func (c *channelOwner) dispose(reason ...string) { - // Clean up from parent and connection. - if c.parent != nil { - delete(c.parent.objects, c.guid) - } - c.connection.objects.Delete(c.guid) - if len(reason) > 0 { - c.wasCollected = reason[0] == "gc" - } - - // Dispose all children. - for _, object := range c.objects { - object.dispose(reason...) - } - c.objects = make(map[string]*channelOwner) -} - -func (c *channelOwner) adopt(child *channelOwner) { - delete(child.parent.objects, child.guid) - c.objects[child.guid] = child - child.parent = c -} - -func (c *channelOwner) setEventSubscriptionMapping(mapping map[string]string) { - c.eventToSubscriptionMapping = mapping -} - -func (c *channelOwner) updateSubscription(event string, enabled bool) { - protocolEvent, ok := c.eventToSubscriptionMapping[event] - if ok { - c.channel.SendNoReplyInternal("updateSubscription", map[string]interface{}{ - "event": protocolEvent, - "enabled": enabled, - }) - } -} - -func (c *channelOwner) Once(name string, handler interface{}) { - c.addEvent(name, handler, true) -} - -func (c *channelOwner) On(name string, handler interface{}) { - c.addEvent(name, handler, false) -} - -func (c *channelOwner) addEvent(name string, handler interface{}, once bool) { - if c.ListenerCount(name) == 0 { - c.updateSubscription(name, true) - } - c.eventEmitter.addEvent(name, handler, once) -} - -func (c *channelOwner) RemoveListener(name string, handler interface{}) { - c.eventEmitter.RemoveListener(name, handler) - if c.ListenerCount(name) == 0 { - c.updateSubscription(name, false) - } -} - -func (c *channelOwner) createChannelOwner(self interface{}, parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) { - c.objectType = objectType - c.guid = guid - c.wasCollected = false - c.parent = parent - c.objects = make(map[string]*channelOwner) - c.initializer = initializer - if c.parent != nil { - c.connection = parent.connection - c.parent.objects[guid] = c - } - if c.connection != nil { - c.connection.objects.Store(guid, c) - } - c.channel = newChannel(c, self) - c.eventToSubscriptionMapping = map[string]string{} -} - -func (c *channelOwner) markAsInternalType() { - c.isInternalType = true -} - -type rootChannelOwner struct { - channelOwner -} - -func (r *rootChannelOwner) initialize() (*Playwright, error) { - ret, err := r.channel.SendReturnAsDict("initialize", map[string]interface{}{ - "sdkLanguage": "javascript", - }) - if err != nil { - return nil, err - } - return fromChannel(ret["playwright"]).(*Playwright), nil -} - -func newRootChannelOwner(connection *connection) *rootChannelOwner { - c := &rootChannelOwner{} - c.connection = connection - c.createChannelOwner(c, nil, "Root", "", make(map[string]interface{})) - return c -} diff --git a/vendor/github.com/playwright-community/playwright-go/clock.go b/vendor/github.com/playwright-community/playwright-go/clock.go deleted file mode 100644 index 8bab037..0000000 --- a/vendor/github.com/playwright-community/playwright-go/clock.go +++ /dev/null @@ -1,111 +0,0 @@ -package playwright - -import ( - "errors" - "time" -) - -type clockImpl struct { - browserCtx *browserContextImpl -} - -func newClock(bCtx *browserContextImpl) Clock { - return &clockImpl{ - browserCtx: bCtx, - } -} - -func (c *clockImpl) FastForward(ticks interface{}) error { - params, err := parseTicks(ticks) - if err != nil { - return err - } - - _, err = c.browserCtx.channel.Send("clockFastForward", params) - return err -} - -func (c *clockImpl) Install(options ...ClockInstallOptions) (err error) { - params := map[string]any{} - if len(options) == 1 { - if options[0].Time != nil { - params, err = parseTime(options[0].Time) - if err != nil { - return err - } - } - } - - _, err = c.browserCtx.channel.Send("clockInstall", params) - - return err -} - -func (c *clockImpl) PauseAt(time interface{}) error { - params, err := parseTime(time) - if err != nil { - return err - } - - _, err = c.browserCtx.channel.Send("clockPauseAt", params) - return err -} - -func (c *clockImpl) Resume() error { - _, err := c.browserCtx.channel.Send("clockResume") - return err -} - -func (c *clockImpl) RunFor(ticks interface{}) error { - params, err := parseTicks(ticks) - if err != nil { - return err - } - - _, err = c.browserCtx.channel.Send("clockRunFor", params) - return err -} - -func (c *clockImpl) SetFixedTime(time interface{}) error { - params, err := parseTime(time) - if err != nil { - return err - } - - _, err = c.browserCtx.channel.Send("clockSetFixedTime", params) - return err -} - -func (c *clockImpl) SetSystemTime(time interface{}) error { - params, err := parseTime(time) - if err != nil { - return err - } - - _, err = c.browserCtx.channel.Send("clockSetSystemTime", params) - return err -} - -func parseTime(t interface{}) (map[string]any, error) { - switch v := t.(type) { - case int, int64: - return map[string]any{"timeNumber": v}, nil - case string: - return map[string]any{"timeString": v}, nil - case time.Time: - return map[string]any{"timeNumber": v.UnixMilli()}, nil - default: - return nil, errors.New("time should be one of: int, int64, string, time.Time") - } -} - -func parseTicks(ticks interface{}) (map[string]any, error) { - switch v := ticks.(type) { - case int, int64: - return map[string]any{"ticksNumber": v}, nil - case string: - return map[string]any{"ticksString": v}, nil - default: - return nil, errors.New("ticks should be one of: int, int64, string") - } -} diff --git a/vendor/github.com/playwright-community/playwright-go/cmd/playwright/main.go b/vendor/github.com/playwright-community/playwright-go/cmd/playwright/main.go deleted file mode 100644 index 95e7d85..0000000 --- a/vendor/github.com/playwright-community/playwright-go/cmd/playwright/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "log" - "os" - - "github.com/playwright-community/playwright-go" -) - -func main() { - driver, err := playwright.NewDriver(&playwright.RunOptions{}) - if err != nil { - log.Fatalf("could not start driver: %v", err) - } - if err = driver.DownloadDriver(); err != nil { - log.Fatalf("could not download driver: %v", err) - } - cmd := driver.Command(os.Args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - log.Fatalf("could not run driver: %v", err) - } - os.Exit(cmd.ProcessState.ExitCode()) -} diff --git a/vendor/github.com/playwright-community/playwright-go/connection.go b/vendor/github.com/playwright-community/playwright-go/connection.go deleted file mode 100644 index ba1e365..0000000 --- a/vendor/github.com/playwright-community/playwright-go/connection.go +++ /dev/null @@ -1,401 +0,0 @@ -package playwright - -import ( - "errors" - "fmt" - "reflect" - "regexp" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/go-stack/stack" - "github.com/playwright-community/playwright-go/internal/safe" -) - -var ( - pkgSourcePathPattern = regexp.MustCompile(`.+[\\/]playwright-go[\\/][^\\/]+\.go`) - apiNameTransform = regexp.MustCompile(`(?U)\(\*(.+)(Impl)?\)`) -) - -type connection struct { - transport transport - apiZone sync.Map - objects *safe.SyncMap[string, *channelOwner] - lastID atomic.Uint32 - rootObject *rootChannelOwner - callbacks *safe.SyncMap[uint32, *protocolCallback] - afterClose func() - onClose func() error - isRemote bool - localUtils *localUtilsImpl - tracingCount atomic.Int32 - abort chan struct{} - abortOnce sync.Once - err *safeValue[error] // for event listener error - closedError *safeValue[error] -} - -func (c *connection) Start() (*Playwright, error) { - go func() { - for { - msg, err := c.transport.Poll() - if err != nil { - _ = c.transport.Close() - c.cleanup(err) - return - } - c.Dispatch(msg) - } - }() - - c.onClose = func() error { - if err := c.transport.Close(); err != nil { - return err - } - return nil - } - - return c.rootObject.initialize() -} - -func (c *connection) Stop() error { - if err := c.onClose(); err != nil { - return err - } - c.cleanup() - return nil -} - -func (c *connection) cleanup(cause ...error) { - if len(cause) > 0 { - c.closedError.Set(fmt.Errorf("%w: %w", ErrTargetClosed, cause[0])) - } else { - c.closedError.Set(ErrTargetClosed) - } - if c.afterClose != nil { - c.afterClose() - } - c.abortOnce.Do(func() { - select { - case <-c.abort: - default: - close(c.abort) - } - }) -} - -func (c *connection) Dispatch(msg *message) { - if c.closedError.Get() != nil { - return - } - method := msg.Method - if msg.ID != 0 { - cb, _ := c.callbacks.LoadAndDelete(uint32(msg.ID)) - if cb.noReply { - return - } - if msg.Error != nil { - cb.SetError(parseError(msg.Error.Error)) - } else { - cb.SetResult(c.replaceGuidsWithChannels(msg.Result).(map[string]interface{})) - } - return - } - object, _ := c.objects.Load(msg.GUID) - if method == "__create__" { - c.createRemoteObject( - object, msg.Params["type"].(string), msg.Params["guid"].(string), msg.Params["initializer"], - ) - return - } - if object == nil { - return - } - if method == "__adopt__" { - child, ok := c.objects.Load(msg.Params["guid"].(string)) - if !ok { - return - } - object.adopt(child) - return - } - if method == "__dispose__" { - reason, ok := msg.Params["reason"] - if ok { - object.dispose(reason.(string)) - } else { - object.dispose() - } - return - } - if object.objectType == "JsonPipe" { - object.channel.Emit(method, msg.Params) - } else { - object.channel.Emit(method, c.replaceGuidsWithChannels(msg.Params)) - } -} - -func (c *connection) LocalUtils() *localUtilsImpl { - return c.localUtils -} - -func (c *connection) createRemoteObject(parent *channelOwner, objectType string, guid string, initializer interface{}) interface{} { - initializer = c.replaceGuidsWithChannels(initializer) - result := createObjectFactory(parent, objectType, guid, initializer.(map[string]interface{})) - return result -} - -func (c *connection) WrapAPICall(cb func() (interface{}, error), isInternal bool) (interface{}, error) { - if _, ok := c.apiZone.Load("apiZone"); ok { - return cb() - } - c.apiZone.Store("apiZone", serializeCallStack(isInternal)) - return cb() -} - -func (c *connection) replaceGuidsWithChannels(payload interface{}) interface{} { - if payload == nil { - return nil - } - v := reflect.ValueOf(payload) - if v.Kind() == reflect.Slice { - listV := payload.([]interface{}) - for i := 0; i < len(listV); i++ { - listV[i] = c.replaceGuidsWithChannels(listV[i]) - } - return listV - } - if v.Kind() == reflect.Map { - mapV := payload.(map[string]interface{}) - if guid, hasGUID := mapV["guid"]; hasGUID { - if channelOwner, ok := c.objects.Load(guid.(string)); ok { - return channelOwner.channel - } - } - for key := range mapV { - mapV[key] = c.replaceGuidsWithChannels(mapV[key]) - } - return mapV - } - return payload -} - -func (c *connection) sendMessageToServer(object *channelOwner, method string, params interface{}, noReply bool) (cb *protocolCallback) { - cb = newProtocolCallback(noReply, c.abort) - - if err := c.closedError.Get(); err != nil { - cb.SetError(err) - return - } - if object.wasCollected { - cb.SetError(errors.New("The object has been collected to prevent unbounded heap growth.")) - return - } - - id := c.lastID.Add(1) - c.callbacks.Store(id, cb) - var ( - metadata = make(map[string]interface{}, 0) - stack = make([]map[string]interface{}, 0) - ) - apiZone, ok := c.apiZone.LoadAndDelete("apiZone") - if ok { - for k, v := range apiZone.(parsedStackTrace).metadata { - metadata[k] = v - } - stack = append(stack, apiZone.(parsedStackTrace).frames...) - } - metadata["wallTime"] = time.Now().UnixMilli() - message := map[string]interface{}{ - "id": id, - "guid": object.guid, - "method": method, - "params": params, // channel.MarshalJSON will replace channel with guid - "metadata": metadata, - } - if c.tracingCount.Load() > 0 && len(stack) > 0 && object.guid != "localUtils" { - c.LocalUtils().AddStackToTracingNoReply(id, stack) - } - - if err := c.transport.Send(message); err != nil { - cb.SetError(fmt.Errorf("could not send message: %w", err)) - return - } - - return -} - -func (c *connection) setInTracing(isTracing bool) { - if isTracing { - c.tracingCount.Add(1) - } else { - c.tracingCount.Add(-1) - } -} - -type parsedStackTrace struct { - frames []map[string]interface{} - metadata map[string]interface{} -} - -func serializeCallStack(isInternal bool) parsedStackTrace { - st := stack.Trace().TrimRuntime() - if len(st) == 0 { // https://github.com/go-stack/stack/issues/27 - st = stack.Trace() - } - - lastInternalIndex := 0 - for i, s := range st { - if pkgSourcePathPattern.MatchString(s.Frame().File) { - lastInternalIndex = i - } - } - apiName := "" - if !isInternal { - apiName = fmt.Sprintf("%n", st[lastInternalIndex]) - } - st = st.TrimBelow(st[lastInternalIndex]) - - callStack := make([]map[string]interface{}, 0) - for i, s := range st { - if i == 0 { - continue - } - callStack = append(callStack, map[string]interface{}{ - "file": s.Frame().File, - "line": s.Frame().Line, - "column": 0, - "function": s.Frame().Function, - }) - } - metadata := make(map[string]interface{}) - if len(st) > 1 { - metadata["location"] = serializeCallLocation(st[1]) - } - apiName = apiNameTransform.ReplaceAllString(apiName, "$1") - if len(apiName) > 1 { - apiName = strings.ToUpper(apiName[:1]) + apiName[1:] - } - metadata["apiName"] = apiName - metadata["isInternal"] = isInternal - return parsedStackTrace{ - metadata: metadata, - frames: callStack, - } -} - -func serializeCallLocation(caller stack.Call) map[string]interface{} { - line, _ := strconv.Atoi(fmt.Sprintf("%d", caller)) - return map[string]interface{}{ - "file": fmt.Sprintf("%s", caller), - "line": line, - } -} - -func newConnection(transport transport, localUtils ...*localUtilsImpl) *connection { - connection := &connection{ - abort: make(chan struct{}, 1), - callbacks: safe.NewSyncMap[uint32, *protocolCallback](), - objects: safe.NewSyncMap[string, *channelOwner](), - transport: transport, - isRemote: false, - err: &safeValue[error]{}, - closedError: &safeValue[error]{}, - } - if len(localUtils) > 0 { - connection.localUtils = localUtils[0] - connection.isRemote = true - } - connection.rootObject = newRootChannelOwner(connection) - return connection -} - -func fromChannel(v interface{}) interface{} { - return v.(*channel).object -} - -func fromNullableChannel(v interface{}) interface{} { - if v == nil { - return nil - } - return fromChannel(v) -} - -type protocolCallback struct { - done chan struct{} - noReply bool - abort <-chan struct{} - once sync.Once - value map[string]interface{} - err error -} - -func (pc *protocolCallback) setResultOnce(result map[string]interface{}, err error) { - pc.once.Do(func() { - pc.value = result - pc.err = err - close(pc.done) - }) -} - -func (pc *protocolCallback) waitResult() { - if pc.noReply { - return - } - select { - case <-pc.done: // wait for result - return - case <-pc.abort: - select { - case <-pc.done: - return - default: - pc.err = errors.New("Connection closed") - return - } - } -} - -func (pc *protocolCallback) SetError(err error) { - pc.setResultOnce(nil, err) -} - -func (pc *protocolCallback) SetResult(result map[string]interface{}) { - pc.setResultOnce(result, nil) -} - -func (pc *protocolCallback) GetResult() (map[string]interface{}, error) { - pc.waitResult() - return pc.value, pc.err -} - -// GetResultValue returns value if the map has only one element -func (pc *protocolCallback) GetResultValue() (interface{}, error) { - pc.waitResult() - if len(pc.value) == 0 { // empty map treated as nil - return nil, pc.err - } - if len(pc.value) == 1 { - for key := range pc.value { - return pc.value[key], pc.err - } - } - - return pc.value, pc.err -} - -func newProtocolCallback(noReply bool, abort <-chan struct{}) *protocolCallback { - if noReply { - return &protocolCallback{ - noReply: true, - abort: abort, - } - } - return &protocolCallback{ - done: make(chan struct{}, 1), - abort: abort, - } -} diff --git a/vendor/github.com/playwright-community/playwright-go/console_message.go b/vendor/github.com/playwright-community/playwright-go/console_message.go deleted file mode 100644 index 4baf3f1..0000000 --- a/vendor/github.com/playwright-community/playwright-go/console_message.go +++ /dev/null @@ -1,47 +0,0 @@ -package playwright - -type consoleMessageImpl struct { - event map[string]interface{} - page Page -} - -func (c *consoleMessageImpl) Type() string { - return c.event["type"].(string) -} - -func (c *consoleMessageImpl) Text() string { - return c.event["text"].(string) -} - -func (c *consoleMessageImpl) String() string { - return c.Text() -} - -func (c *consoleMessageImpl) Args() []JSHandle { - args := c.event["args"].([]interface{}) - out := []JSHandle{} - for idx := range args { - out = append(out, fromChannel(args[idx]).(*jsHandleImpl)) - } - return out -} - -func (c *consoleMessageImpl) Location() *ConsoleMessageLocation { - location := &ConsoleMessageLocation{} - remapMapToStruct(c.event["location"], location) - return location -} - -func (c *consoleMessageImpl) Page() Page { - return c.page -} - -func newConsoleMessage(event map[string]interface{}) *consoleMessageImpl { - bt := &consoleMessageImpl{} - bt.event = event - page := fromNullableChannel(event["page"]) - if page != nil { - bt.page = page.(*pageImpl) - } - return bt -} diff --git a/vendor/github.com/playwright-community/playwright-go/dialog.go b/vendor/github.com/playwright-community/playwright-go/dialog.go deleted file mode 100644 index 8d13234..0000000 --- a/vendor/github.com/playwright-community/playwright-go/dialog.go +++ /dev/null @@ -1,48 +0,0 @@ -package playwright - -type dialogImpl struct { - channelOwner - page Page -} - -func (d *dialogImpl) Type() string { - return d.initializer["type"].(string) -} - -func (d *dialogImpl) Message() string { - return d.initializer["message"].(string) -} - -func (d *dialogImpl) DefaultValue() string { - return d.initializer["defaultValue"].(string) -} - -func (d *dialogImpl) Accept(promptTextInput ...string) error { - var promptText *string - if len(promptTextInput) == 1 { - promptText = &promptTextInput[0] - } - _, err := d.channel.Send("accept", map[string]interface{}{ - "promptText": promptText, - }) - return err -} - -func (d *dialogImpl) Dismiss() error { - _, err := d.channel.Send("dismiss") - return err -} - -func (d *dialogImpl) Page() Page { - return d.page -} - -func newDialog(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *dialogImpl { - bt := &dialogImpl{} - bt.createChannelOwner(bt, parent, objectType, guid, initializer) - page := fromNullableChannel(initializer["page"]) - if page != nil { - bt.page = page.(*pageImpl) - } - return bt -} diff --git a/vendor/github.com/playwright-community/playwright-go/download.go b/vendor/github.com/playwright-community/playwright-go/download.go deleted file mode 100644 index b9d2024..0000000 --- a/vendor/github.com/playwright-community/playwright-go/download.go +++ /dev/null @@ -1,56 +0,0 @@ -package playwright - -type downloadImpl struct { - page *pageImpl - url string - suggestedFilename string - artifact *artifactImpl -} - -func (d *downloadImpl) String() string { - return d.SuggestedFilename() -} - -func (d *downloadImpl) Page() Page { - return d.page -} - -func (d *downloadImpl) URL() string { - return d.url -} - -func (d *downloadImpl) SuggestedFilename() string { - return d.suggestedFilename -} - -func (d *downloadImpl) Delete() error { - err := d.artifact.Delete() - return err -} - -func (d *downloadImpl) Failure() error { - return d.artifact.Failure() -} - -func (d *downloadImpl) Path() (string, error) { - path, err := d.artifact.PathAfterFinished() - return path, err -} - -func (d *downloadImpl) SaveAs(path string) error { - err := d.artifact.SaveAs(path) - return err -} - -func (d *downloadImpl) Cancel() error { - return d.artifact.Cancel() -} - -func newDownload(page *pageImpl, url string, suggestedFilename string, artifact *artifactImpl) *downloadImpl { - return &downloadImpl{ - page: page, - url: url, - suggestedFilename: suggestedFilename, - artifact: artifact, - } -} diff --git a/vendor/github.com/playwright-community/playwright-go/element_handle.go b/vendor/github.com/playwright-community/playwright-go/element_handle.go deleted file mode 100644 index 62c41ba..0000000 --- a/vendor/github.com/playwright-community/playwright-go/element_handle.go +++ /dev/null @@ -1,403 +0,0 @@ -package playwright - -import ( - "encoding/base64" - "errors" - "fmt" - "os" -) - -type elementHandleImpl struct { - jsHandleImpl -} - -func (e *elementHandleImpl) AsElement() ElementHandle { - return e -} - -func (e *elementHandleImpl) OwnerFrame() (Frame, error) { - channel, err := e.channel.Send("ownerFrame") - if err != nil { - return nil, err - } - channelOwner := fromNullableChannel(channel) - if channelOwner == nil { - return nil, nil - } - return channelOwner.(*frameImpl), nil -} - -func (e *elementHandleImpl) ContentFrame() (Frame, error) { - channel, err := e.channel.Send("contentFrame") - if err != nil { - return nil, err - } - channelOwner := fromNullableChannel(channel) - if channelOwner == nil { - return nil, nil - } - return channelOwner.(*frameImpl), nil -} - -func (e *elementHandleImpl) GetAttribute(name string) (string, error) { - attribute, err := e.channel.Send("getAttribute", map[string]interface{}{ - "name": name, - }) - if attribute == nil { - return "", err - } - return attribute.(string), err -} - -func (e *elementHandleImpl) TextContent() (string, error) { - textContent, err := e.channel.Send("textContent") - if textContent == nil { - return "", err - } - return textContent.(string), err -} - -func (e *elementHandleImpl) InnerText() (string, error) { - innerText, err := e.channel.Send("innerText") - if innerText == nil { - return "", err - } - return innerText.(string), err -} - -func (e *elementHandleImpl) InnerHTML() (string, error) { - innerHTML, err := e.channel.Send("innerHTML") - if innerHTML == nil { - return "", err - } - return innerHTML.(string), err -} - -func (e *elementHandleImpl) DispatchEvent(typ string, initObjects ...interface{}) error { - var initObject interface{} - if len(initObjects) == 1 { - initObject = initObjects[0] - } - _, err := e.channel.Send("dispatchEvent", map[string]interface{}{ - "type": typ, - "eventInit": serializeArgument(initObject), - }) - return err -} - -func (e *elementHandleImpl) Hover(options ...ElementHandleHoverOptions) error { - _, err := e.channel.Send("hover", options) - return err -} - -func (e *elementHandleImpl) Click(options ...ElementHandleClickOptions) error { - _, err := e.channel.Send("click", options) - return err -} - -func (e *elementHandleImpl) Dblclick(options ...ElementHandleDblclickOptions) error { - _, err := e.channel.Send("dblclick", options) - return err -} - -func (e *elementHandleImpl) QuerySelector(selector string) (ElementHandle, error) { - channel, err := e.channel.Send("querySelector", map[string]interface{}{ - "selector": selector, - }) - if err != nil { - return nil, err - } - if channel == nil { - return nil, nil - } - return fromChannel(channel).(*elementHandleImpl), nil -} - -func (e *elementHandleImpl) QuerySelectorAll(selector string) ([]ElementHandle, error) { - channels, err := e.channel.Send("querySelectorAll", map[string]interface{}{ - "selector": selector, - }) - if err != nil { - return nil, err - } - elements := make([]ElementHandle, 0) - for _, channel := range channels.([]interface{}) { - elements = append(elements, fromChannel(channel).(*elementHandleImpl)) - } - return elements, nil -} - -func (e *elementHandleImpl) EvalOnSelector(selector string, expression string, options ...interface{}) (interface{}, error) { - var arg interface{} - if len(options) == 1 { - arg = options[0] - } - result, err := e.channel.Send("evalOnSelector", map[string]interface{}{ - "selector": selector, - "expression": expression, - "arg": serializeArgument(arg), - }) - if err != nil { - return nil, err - } - return parseResult(result), nil -} - -func (e *elementHandleImpl) EvalOnSelectorAll(selector string, expression string, options ...interface{}) (interface{}, error) { - var arg interface{} - if len(options) == 1 { - arg = options[0] - } - result, err := e.channel.Send("evalOnSelectorAll", map[string]interface{}{ - "selector": selector, - "expression": expression, - "arg": serializeArgument(arg), - }) - if err != nil { - return nil, err - } - return parseResult(result), nil -} - -func (e *elementHandleImpl) ScrollIntoViewIfNeeded(options ...ElementHandleScrollIntoViewIfNeededOptions) error { - _, err := e.channel.Send("scrollIntoViewIfNeeded", options) - if err != nil { - return err - } - return err -} - -func (e *elementHandleImpl) SetInputFiles(files interface{}, options ...ElementHandleSetInputFilesOptions) error { - frame, err := e.OwnerFrame() - if err != nil { - return err - } - if frame == nil { - return errors.New("Cannot set input files to detached element") - } - - params, err := convertInputFiles(files, frame.(*frameImpl).page.browserContext) - if err != nil { - return err - } - _, err = e.channel.Send("setInputFiles", params, options) - return err -} - -func (e *elementHandleImpl) BoundingBox() (*Rect, error) { - boundingBox, err := e.channel.Send("boundingBox") - if err != nil { - return nil, err - } - - if boundingBox == nil { - return nil, nil - } - - out := &Rect{} - remapMapToStruct(boundingBox, out) - return out, nil -} - -func (e *elementHandleImpl) Check(options ...ElementHandleCheckOptions) error { - _, err := e.channel.Send("check", options) - return err -} - -func (e *elementHandleImpl) Uncheck(options ...ElementHandleUncheckOptions) error { - _, err := e.channel.Send("uncheck", options) - return err -} - -func (e *elementHandleImpl) Press(key string, options ...ElementHandlePressOptions) error { - _, err := e.channel.Send("press", map[string]interface{}{ - "key": key, - }, options) - return err -} - -func (e *elementHandleImpl) Fill(value string, options ...ElementHandleFillOptions) error { - _, err := e.channel.Send("fill", map[string]interface{}{ - "value": value, - }, options) - return err -} - -func (e *elementHandleImpl) Type(value string, options ...ElementHandleTypeOptions) error { - _, err := e.channel.Send("type", map[string]interface{}{ - "text": value, - }, options) - return err -} - -func (e *elementHandleImpl) Focus() error { - _, err := e.channel.Send("focus") - return err -} - -func (e *elementHandleImpl) SelectText(options ...ElementHandleSelectTextOptions) error { - _, err := e.channel.Send("selectText", options) - return err -} - -func (e *elementHandleImpl) Screenshot(options ...ElementHandleScreenshotOptions) ([]byte, error) { - var path *string - overrides := map[string]interface{}{} - if len(options) == 1 { - path = options[0].Path - options[0].Path = nil - if options[0].Mask != nil { - masks := make([]map[string]interface{}, 0) - for _, m := range options[0].Mask { - if m.Err() != nil { // ErrLocatorNotSameFrame - return nil, m.Err() - } - l, ok := m.(*locatorImpl) - if ok { - masks = append(masks, map[string]interface{}{ - "selector": l.selector, - "frame": l.frame.channel, - }) - } - } - overrides["mask"] = masks - options[0].Mask = nil - } - } - data, err := e.channel.Send("screenshot", options, overrides) - if err != nil { - return nil, err - } - image, err := base64.StdEncoding.DecodeString(data.(string)) - if err != nil { - return nil, fmt.Errorf("could not decode base64 :%w", err) - } - if path != nil { - if err := os.WriteFile(*path, image, 0o644); err != nil { - return nil, err - } - } - return image, nil -} - -func (e *elementHandleImpl) Tap(options ...ElementHandleTapOptions) error { - _, err := e.channel.Send("tap", options) - return err -} - -func (e *elementHandleImpl) SelectOption(values SelectOptionValues, options ...ElementHandleSelectOptionOptions) ([]string, error) { - opts := convertSelectOptionSet(values) - selected, err := e.channel.Send("selectOption", opts, options) - if err != nil { - return nil, err - } - - return transformToStringList(selected), nil -} - -func (e *elementHandleImpl) IsChecked() (bool, error) { - checked, err := e.channel.Send("isChecked") - if err != nil { - return false, err - } - return checked.(bool), nil -} - -func (e *elementHandleImpl) IsDisabled() (bool, error) { - disabled, err := e.channel.Send("isDisabled") - if err != nil { - return false, err - } - return disabled.(bool), nil -} - -func (e *elementHandleImpl) IsEditable() (bool, error) { - editable, err := e.channel.Send("isEditable") - if err != nil { - return false, err - } - return editable.(bool), nil -} - -func (e *elementHandleImpl) IsEnabled() (bool, error) { - enabled, err := e.channel.Send("isEnabled") - if err != nil { - return false, err - } - return enabled.(bool), nil -} - -func (e *elementHandleImpl) IsHidden() (bool, error) { - hidden, err := e.channel.Send("isHidden") - if err != nil { - return false, err - } - return hidden.(bool), nil -} - -func (e *elementHandleImpl) IsVisible() (bool, error) { - visible, err := e.channel.Send("isVisible") - if err != nil { - return false, err - } - return visible.(bool), nil -} - -func (e *elementHandleImpl) WaitForElementState(state ElementState, options ...ElementHandleWaitForElementStateOptions) error { - _, err := e.channel.Send("waitForElementState", map[string]interface{}{ - "state": state, - }, options) - if err != nil { - return err - } - return nil -} - -func (e *elementHandleImpl) WaitForSelector(selector string, options ...ElementHandleWaitForSelectorOptions) (ElementHandle, error) { - ch, err := e.channel.Send("waitForSelector", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return nil, err - } - - channelOwner := fromNullableChannel(ch) - if channelOwner == nil { - return nil, nil - } - return channelOwner.(*elementHandleImpl), nil -} - -func (e *elementHandleImpl) InputValue(options ...ElementHandleInputValueOptions) (string, error) { - result, err := e.channel.Send("inputValue", options) - if result == nil { - return "", err - } - return result.(string), err -} - -func (e *elementHandleImpl) SetChecked(checked bool, options ...ElementHandleSetCheckedOptions) error { - if checked { - _, err := e.channel.Send("check", options) - return err - } else { - _, err := e.channel.Send("uncheck", options) - return err - } -} - -func newElementHandle(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *elementHandleImpl { - bt := &elementHandleImpl{} - bt.createChannelOwner(bt, parent, objectType, guid, initializer) - return bt -} - -func transformToStringList(in interface{}) []string { - s := in.([]interface{}) - - var out []string - for _, v := range s { - out = append(out, v.(string)) - } - return out -} diff --git a/vendor/github.com/playwright-community/playwright-go/errors.go b/vendor/github.com/playwright-community/playwright-go/errors.go deleted file mode 100644 index 36f7396..0000000 --- a/vendor/github.com/playwright-community/playwright-go/errors.go +++ /dev/null @@ -1,58 +0,0 @@ -package playwright - -import ( - "errors" - "fmt" -) - -var ( - // ErrPlaywright wraps all Playwright errors. - // - Use errors.Is to check if the error is a Playwright error. - // - Use errors.As to cast an error to [Error] if you want to access "Stack". - ErrPlaywright = errors.New("playwright") - // ErrTargetClosed usually wraps a reason. - ErrTargetClosed = errors.New("target closed") - // ErrTimeout wraps timeout errors. It can be either Playwright TimeoutError or client timeout. - ErrTimeout = errors.New("timeout") -) - -// Error represents a Playwright error -type Error struct { - Name string `json:"name"` - Message string `json:"message"` - Stack string `json:"stack"` -} - -func (e *Error) Error() string { - return e.Message -} - -func (e *Error) Is(target error) bool { - err, ok := target.(*Error) - if !ok { - return false - } - if err.Name != e.Name { - return false - } - if e.Name != "Error" { - return true // same name and not normal error - } - return e.Message == err.Message -} - -func parseError(err Error) error { - if err.Name == "TimeoutError" { - return fmt.Errorf("%w: %w: %w", ErrPlaywright, ErrTimeout, &err) - } else if err.Name == "TargetClosedError" { - return fmt.Errorf("%w: %w: %w", ErrPlaywright, ErrTargetClosed, &err) - } - return fmt.Errorf("%w: %w", ErrPlaywright, &err) -} - -func targetClosedError(reason *string) error { - if reason == nil { - return ErrTargetClosed - } - return fmt.Errorf("%w: %s", ErrTargetClosed, *reason) -} diff --git a/vendor/github.com/playwright-community/playwright-go/event_emitter.go b/vendor/github.com/playwright-community/playwright-go/event_emitter.go deleted file mode 100644 index d4d62ef..0000000 --- a/vendor/github.com/playwright-community/playwright-go/event_emitter.go +++ /dev/null @@ -1,163 +0,0 @@ -package playwright - -import ( - "math" - "reflect" - "slices" - "sync" -) - -type EventEmitter interface { - Emit(name string, payload ...interface{}) bool - ListenerCount(name string) int - On(name string, handler interface{}) - Once(name string, handler interface{}) - RemoveListener(name string, handler interface{}) - RemoveListeners(name string) -} - -type ( - eventEmitter struct { - eventsMutex sync.Mutex - events map[string]*eventRegister - hasInit bool - } - eventRegister struct { - sync.Mutex - listeners []listener - } - listener struct { - handler interface{} - once bool - } -) - -func NewEventEmitter() EventEmitter { - return &eventEmitter{} -} - -func (e *eventEmitter) Emit(name string, payload ...interface{}) (hasListener bool) { - e.eventsMutex.Lock() - e.init() - - evt, ok := e.events[name] - if !ok { - e.eventsMutex.Unlock() - return - } - e.eventsMutex.Unlock() - return evt.callHandlers(payload...) > 0 -} - -func (e *eventEmitter) Once(name string, handler interface{}) { - e.addEvent(name, handler, true) -} - -func (e *eventEmitter) On(name string, handler interface{}) { - e.addEvent(name, handler, false) -} - -func (e *eventEmitter) RemoveListener(name string, handler interface{}) { - e.eventsMutex.Lock() - defer e.eventsMutex.Unlock() - e.init() - - if evt, ok := e.events[name]; ok { - evt.Lock() - defer evt.Unlock() - evt.removeHandler(handler) - } -} - -func (e *eventEmitter) RemoveListeners(name string) { - e.eventsMutex.Lock() - defer e.eventsMutex.Unlock() - e.init() - delete(e.events, name) -} - -// ListenerCount count the listeners by name, count all if name is empty -func (e *eventEmitter) ListenerCount(name string) int { - e.eventsMutex.Lock() - defer e.eventsMutex.Unlock() - e.init() - - if name != "" { - evt, ok := e.events[name] - if !ok { - return 0 - } - return evt.count() - } - - count := 0 - for key := range e.events { - count += e.events[key].count() - } - - return count -} - -func (e *eventEmitter) addEvent(name string, handler interface{}, once bool) { - e.eventsMutex.Lock() - defer e.eventsMutex.Unlock() - e.init() - - if _, ok := e.events[name]; !ok { - e.events[name] = &eventRegister{ - listeners: make([]listener, 0), - } - } - e.events[name].addHandler(handler, once) -} - -func (e *eventEmitter) init() { - if !e.hasInit { - e.events = make(map[string]*eventRegister, 0) - e.hasInit = true - } -} - -func (er *eventRegister) addHandler(handler interface{}, once bool) { - er.Lock() - defer er.Unlock() - er.listeners = append(er.listeners, listener{handler: handler, once: once}) -} - -func (er *eventRegister) count() int { - er.Lock() - defer er.Unlock() - return len(er.listeners) -} - -func (er *eventRegister) removeHandler(handler interface{}) { - handlerPtr := reflect.ValueOf(handler).Pointer() - - er.listeners = slices.DeleteFunc(er.listeners, func(l listener) bool { - return reflect.ValueOf(l.handler).Pointer() == handlerPtr - }) -} - -func (er *eventRegister) callHandlers(payloads ...interface{}) int { - payloadV := make([]reflect.Value, 0) - - for _, p := range payloads { - payloadV = append(payloadV, reflect.ValueOf(p)) - } - - handle := func(l listener) { - handlerV := reflect.ValueOf(l.handler) - handlerV.Call(payloadV[:int(math.Min(float64(handlerV.Type().NumIn()), float64(len(payloadV))))]) - } - - er.Lock() - defer er.Unlock() - count := len(er.listeners) - for _, l := range er.listeners { - if l.once { - defer er.removeHandler(l.handler) - } - handle(l) - } - return count -} diff --git a/vendor/github.com/playwright-community/playwright-go/fetch.go b/vendor/github.com/playwright-community/playwright-go/fetch.go deleted file mode 100644 index fc7f79f..0000000 --- a/vendor/github.com/playwright-community/playwright-go/fetch.go +++ /dev/null @@ -1,451 +0,0 @@ -package playwright - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "os" - "strings" -) - -type apiRequestImpl struct { - *Playwright -} - -func (r *apiRequestImpl) NewContext(options ...APIRequestNewContextOptions) (APIRequestContext, error) { - overrides := map[string]interface{}{} - if len(options) == 1 { - if options[0].ClientCertificates != nil { - certs, err := transformClientCertificate(options[0].ClientCertificates) - if err != nil { - return nil, err - } - overrides["clientCertificates"] = certs - options[0].ClientCertificates = nil - } - if options[0].ExtraHttpHeaders != nil { - overrides["extraHTTPHeaders"] = serializeMapToNameAndValue(options[0].ExtraHttpHeaders) - options[0].ExtraHttpHeaders = nil - } - if options[0].StorageStatePath != nil { - var storageState *StorageState - storageString, err := os.ReadFile(*options[0].StorageStatePath) - if err != nil { - return nil, fmt.Errorf("could not read storage state file: %w", err) - } - err = json.Unmarshal(storageString, &storageState) - if err != nil { - return nil, fmt.Errorf("could not parse storage state file: %w", err) - } - options[0].StorageState = storageState - options[0].StorageStatePath = nil - } - } - - channel, err := r.channel.Send("newRequest", options, overrides) - if err != nil { - return nil, err - } - return fromChannel(channel).(*apiRequestContextImpl), nil -} - -func newApiRequestImpl(pw *Playwright) *apiRequestImpl { - return &apiRequestImpl{pw} -} - -type apiRequestContextImpl struct { - channelOwner - tracing *tracingImpl - closeReason *string -} - -func (r *apiRequestContextImpl) Dispose(options ...APIRequestContextDisposeOptions) error { - if len(options) == 1 { - r.closeReason = options[0].Reason - } - _, err := r.channel.Send("dispose", map[string]interface{}{ - "reason": r.closeReason, - }) - if errors.Is(err, ErrTargetClosed) { - return nil - } - return err -} - -func (r *apiRequestContextImpl) Delete(url string, options ...APIRequestContextDeleteOptions) (APIResponse, error) { - opts := APIRequestContextFetchOptions{ - Method: String("DELETE"), - } - if len(options) == 1 { - err := assignStructFields(&opts, options[0], false) - if err != nil { - return nil, err - } - } - - return r.Fetch(url, opts) -} - -func (r *apiRequestContextImpl) Fetch(urlOrRequest interface{}, options ...APIRequestContextFetchOptions) (APIResponse, error) { - switch v := urlOrRequest.(type) { - case string: - return r.innerFetch(v, nil, options...) - case Request: - return r.innerFetch("", v, options...) - default: - return nil, fmt.Errorf("urlOrRequest has unsupported type: %T", urlOrRequest) - } -} - -func (r *apiRequestContextImpl) innerFetch(url string, request Request, options ...APIRequestContextFetchOptions) (APIResponse, error) { - if r.closeReason != nil { - return nil, fmt.Errorf("%w: %s", ErrTargetClosed, *r.closeReason) - } - overrides := map[string]interface{}{} - if url != "" { - overrides["url"] = url - } else if request != nil { - overrides["url"] = request.URL() - } - - if len(options) == 1 { - if options[0].MaxRedirects != nil && *options[0].MaxRedirects < 0 { - return nil, errors.New("maxRedirects must be non-negative") - } - if options[0].MaxRetries != nil && *options[0].MaxRetries < 0 { - return nil, errors.New("maxRetries must be non-negative") - } - // only one of them can be specified - if countNonNil(options[0].Data, options[0].Form, options[0].Multipart) > 1 { - return nil, errors.New("only one of 'data', 'form' or 'multipart' can be specified") - } - if options[0].Method == nil { - if request != nil { - options[0].Method = String(request.Method()) - } else { - options[0].Method = String("GET") - } - } - if options[0].Headers == nil { - if request != nil { - overrides["headers"] = serializeMapToNameAndValue(request.Headers()) - } - } else { - overrides["headers"] = serializeMapToNameAndValue(options[0].Headers) - options[0].Headers = nil - } - if options[0].Data != nil { - switch v := options[0].Data.(type) { - case string: - headersArray, ok := overrides["headers"].([]map[string]string) - if ok && isJsonContentType(headersArray) { - if json.Valid([]byte(v)) { - overrides["jsonData"] = v - } else { - data, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("could not marshal data: %w", err) - } - overrides["jsonData"] = string(data) - } - } else { - overrides["postData"] = base64.StdEncoding.EncodeToString([]byte(v)) - } - case []byte: - overrides["postData"] = base64.StdEncoding.EncodeToString(v) - case interface{}: - data, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("could not marshal data: %w", err) - } - overrides["jsonData"] = string(data) - default: - return nil, errors.New("data must be a string, []byte, or interface{} that can marshal to json") - } - options[0].Data = nil - } else if options[0].Form != nil { - form, ok := options[0].Form.(map[string]interface{}) - if !ok { - return nil, errors.New("form must be a map") - } - overrides["formData"] = serializeMapToNameValue(form) - options[0].Form = nil - } else if options[0].Multipart != nil { - _, ok := options[0].Multipart.(map[string]interface{}) - if !ok { - return nil, errors.New("multipart must be a map") - } - multipartData := []map[string]interface{}{} - for name, value := range options[0].Multipart.(map[string]interface{}) { - switch v := value.(type) { - case InputFile: - multipartData = append(multipartData, map[string]interface{}{ - "name": name, - "file": map[string]string{ - "name": v.Name, - "mimeType": v.MimeType, - "buffer": base64.StdEncoding.EncodeToString(v.Buffer), - }, - }) - default: - multipartData = append(multipartData, map[string]interface{}{ - "name": name, - "value": String(fmt.Sprintf("%v", v)), - }) - } - } - overrides["multipartData"] = multipartData - options[0].Multipart = nil - } else if request != nil { - postDataBuf, err := request.PostDataBuffer() - if err == nil { - overrides["postData"] = base64.StdEncoding.EncodeToString(postDataBuf) - } - } - if options[0].Params != nil { - overrides["params"] = serializeMapToNameValue(options[0].Params) - options[0].Params = nil - } - } - - response, err := r.channel.Send("fetch", options, overrides) - if err != nil { - return nil, err - } - - return newAPIResponse(r, response.(map[string]interface{})), nil -} - -func (r *apiRequestContextImpl) Get(url string, options ...APIRequestContextGetOptions) (APIResponse, error) { - opts := APIRequestContextFetchOptions{ - Method: String("GET"), - } - if len(options) == 1 { - err := assignStructFields(&opts, options[0], false) - if err != nil { - return nil, err - } - } - - return r.Fetch(url, opts) -} - -func (r *apiRequestContextImpl) Head(url string, options ...APIRequestContextHeadOptions) (APIResponse, error) { - opts := APIRequestContextFetchOptions{ - Method: String("HEAD"), - } - if len(options) == 1 { - err := assignStructFields(&opts, options[0], false) - if err != nil { - return nil, err - } - } - - return r.Fetch(url, opts) -} - -func (r *apiRequestContextImpl) Patch(url string, options ...APIRequestContextPatchOptions) (APIResponse, error) { - opts := APIRequestContextFetchOptions{ - Method: String("PATCH"), - } - if len(options) == 1 { - err := assignStructFields(&opts, options[0], false) - if err != nil { - return nil, err - } - } - - return r.Fetch(url, opts) -} - -func (r *apiRequestContextImpl) Put(url string, options ...APIRequestContextPutOptions) (APIResponse, error) { - opts := APIRequestContextFetchOptions{ - Method: String("PUT"), - } - if len(options) == 1 { - err := assignStructFields(&opts, options[0], false) - if err != nil { - return nil, err - } - } - - return r.Fetch(url, opts) -} - -func (r *apiRequestContextImpl) Post(url string, options ...APIRequestContextPostOptions) (APIResponse, error) { - opts := APIRequestContextFetchOptions{ - Method: String("POST"), - } - if len(options) == 1 { - err := assignStructFields(&opts, options[0], false) - if err != nil { - return nil, err - } - } - - return r.Fetch(url, opts) -} - -func (r *apiRequestContextImpl) StorageState(path ...string) (*StorageState, error) { - result, err := r.channel.SendReturnAsDict("storageState") - if err != nil { - return nil, err - } - if len(path) == 1 { - file, err := os.Create(path[0]) - if err != nil { - return nil, err - } - if err := json.NewEncoder(file).Encode(result); err != nil { - return nil, err - } - if err := file.Close(); err != nil { - return nil, err - } - } - var storageState StorageState - remapMapToStruct(result, &storageState) - return &storageState, nil -} - -func newAPIRequestContext(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *apiRequestContextImpl { - rc := &apiRequestContextImpl{} - rc.createChannelOwner(rc, parent, objectType, guid, initializer) - rc.tracing = fromChannel(initializer["tracing"]).(*tracingImpl) - return rc -} - -type apiResponseImpl struct { - request *apiRequestContextImpl - initializer map[string]interface{} - headers *rawHeaders -} - -func (r *apiResponseImpl) Body() ([]byte, error) { - result, err := r.request.channel.SendReturnAsDict("fetchResponseBody", []map[string]interface{}{ - { - "fetchUid": r.fetchUid(), - }, - }) - if err != nil { - if errors.Is(err, ErrTargetClosed) { - return nil, errors.New("response has been disposed") - } - return nil, err - } - body := result["binary"] - if body == nil { - return nil, errors.New("response has been disposed") - } - return base64.StdEncoding.DecodeString(body.(string)) -} - -func (r *apiResponseImpl) Dispose() error { - _, err := r.request.channel.Send("disposeAPIResponse", []map[string]interface{}{ - { - "fetchUid": r.fetchUid(), - }, - }) - return err -} - -func (r *apiResponseImpl) Headers() map[string]string { - return r.headers.Headers() -} - -func (r *apiResponseImpl) HeadersArray() []NameValue { - return r.headers.HeadersArray() -} - -func (r *apiResponseImpl) JSON(v interface{}) error { - body, err := r.Body() - if err != nil { - return err - } - return json.Unmarshal(body, &v) -} - -func (r *apiResponseImpl) Ok() bool { - return r.Status() == 0 || (r.Status() >= 200 && r.Status() <= 299) -} - -func (r *apiResponseImpl) Status() int { - return int(r.initializer["status"].(float64)) -} - -func (r *apiResponseImpl) StatusText() string { - return r.initializer["statusText"].(string) -} - -func (r *apiResponseImpl) Text() (string, error) { - body, err := r.Body() - if err != nil { - return "", err - } - return string(body), nil -} - -func (r *apiResponseImpl) URL() string { - return r.initializer["url"].(string) -} - -func (r *apiResponseImpl) fetchUid() string { - return r.initializer["fetchUid"].(string) -} - -func (r *apiResponseImpl) fetchLog() ([]string, error) { - ret, err := r.request.channel.Send("fetchLog", map[string]interface{}{ - "fetchUid": r.fetchUid(), - }) - if err != nil { - return nil, err - } - result := make([]string, len(ret.([]interface{}))) - for i, v := range ret.([]interface{}) { - result[i] = v.(string) - } - return result, nil -} - -func newAPIResponse(context *apiRequestContextImpl, initializer map[string]interface{}) *apiResponseImpl { - return &apiResponseImpl{ - request: context, - initializer: initializer, - headers: newRawHeaders(initializer["headers"]), - } -} - -func countNonNil(args ...interface{}) int { - count := 0 - for _, v := range args { - if v != nil { - count++ - } - } - return count -} - -func isJsonContentType(headers []map[string]string) bool { - if len(headers) > 0 { - for _, v := range headers { - if strings.ToLower(v["name"]) == "content-type" { - if v["value"] == "application/json" { - return true - } - } - } - } - return false -} - -func serializeMapToNameValue(data map[string]interface{}) []map[string]string { - serialized := make([]map[string]string, 0, len(data)) - for k, v := range data { - serialized = append(serialized, map[string]string{ - "name": k, - "value": fmt.Sprintf("%v", v), - }) - } - return serialized -} diff --git a/vendor/github.com/playwright-community/playwright-go/file_chooser.go b/vendor/github.com/playwright-community/playwright-go/file_chooser.go deleted file mode 100644 index 119e885..0000000 --- a/vendor/github.com/playwright-community/playwright-go/file_chooser.go +++ /dev/null @@ -1,44 +0,0 @@ -package playwright - -type fileChooserImpl struct { - page Page - elementHandle ElementHandle - isMultiple bool -} - -func (f *fileChooserImpl) Page() Page { - return f.page -} - -func (f *fileChooserImpl) Element() ElementHandle { - return f.elementHandle -} - -func (f *fileChooserImpl) IsMultiple() bool { - return f.isMultiple -} - -// InputFile represents the input file for: -// - FileChooser.SetFiles() -// - ElementHandle.SetInputFiles() -// - Page.SetInputFiles() -type InputFile struct { - Name string `json:"name"` - MimeType string `json:"mimeType,omitempty"` - Buffer []byte `json:"buffer"` -} - -func (f *fileChooserImpl) SetFiles(files interface{}, options ...FileChooserSetFilesOptions) error { - if len(options) == 1 { - return f.elementHandle.SetInputFiles(files, ElementHandleSetInputFilesOptions(options[0])) - } - return f.elementHandle.SetInputFiles(files) -} - -func newFileChooser(page Page, elementHandle ElementHandle, isMultiple bool) *fileChooserImpl { - return &fileChooserImpl{ - page: page, - elementHandle: elementHandle, - isMultiple: isMultiple, - } -} diff --git a/vendor/github.com/playwright-community/playwright-go/frame.go b/vendor/github.com/playwright-community/playwright-go/frame.go deleted file mode 100644 index b571c8e..0000000 --- a/vendor/github.com/playwright-community/playwright-go/frame.go +++ /dev/null @@ -1,792 +0,0 @@ -package playwright - -import ( - "errors" - "fmt" - "os" - "time" - - mapset "github.com/deckarep/golang-set/v2" -) - -type frameImpl struct { - channelOwner - detached bool - page *pageImpl - name string - url string - parentFrame Frame - childFrames []Frame - loadStates mapset.Set[string] -} - -func newFrame(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *frameImpl { - var loadStates mapset.Set[string] - - if ls, ok := initializer["loadStates"].([]string); ok { - loadStates = mapset.NewSet[string](ls...) - } else { - loadStates = mapset.NewSet[string]() - } - f := &frameImpl{ - name: initializer["name"].(string), - url: initializer["url"].(string), - loadStates: loadStates, - childFrames: make([]Frame, 0), - } - f.createChannelOwner(f, parent, objectType, guid, initializer) - - channelOwner := fromNullableChannel(initializer["parentFrame"]) - if channelOwner != nil { - f.parentFrame = channelOwner.(*frameImpl) - f.parentFrame.(*frameImpl).childFrames = append(f.parentFrame.(*frameImpl).childFrames, f) - } - - f.channel.On("navigated", f.onFrameNavigated) - f.channel.On("loadstate", f.onLoadState) - return f -} - -func (f *frameImpl) URL() string { - f.RLock() - defer f.RUnlock() - return f.url -} - -func (f *frameImpl) Name() string { - f.RLock() - defer f.RUnlock() - return f.name -} - -func (f *frameImpl) SetContent(content string, options ...FrameSetContentOptions) error { - _, err := f.channel.Send("setContent", map[string]interface{}{ - "html": content, - }, options) - return err -} - -func (f *frameImpl) Content() (string, error) { - content, err := f.channel.Send("content") - if content == nil { - return "", err - } - return content.(string), err -} - -func (f *frameImpl) Goto(url string, options ...FrameGotoOptions) (Response, error) { - channel, err := f.channel.Send("goto", map[string]interface{}{ - "url": url, - }, options) - if err != nil { - return nil, fmt.Errorf("Frame.Goto %s: %w", url, err) - } - channelOwner := fromNullableChannel(channel) - if channelOwner == nil { - // navigation to about:blank or navigation to the same URL with a different hash - return nil, nil - } - return channelOwner.(*responseImpl), nil -} - -func (f *frameImpl) AddScriptTag(options FrameAddScriptTagOptions) (ElementHandle, error) { - if options.Path != nil { - file, err := os.ReadFile(*options.Path) - if err != nil { - return nil, err - } - options.Content = String(string(file)) - options.Path = nil - } - channel, err := f.channel.Send("addScriptTag", options) - if err != nil { - return nil, err - } - return fromChannel(channel).(*elementHandleImpl), nil -} - -func (f *frameImpl) AddStyleTag(options FrameAddStyleTagOptions) (ElementHandle, error) { - if options.Path != nil { - file, err := os.ReadFile(*options.Path) - if err != nil { - return nil, err - } - options.Content = String(string(file)) - options.Path = nil - } - channel, err := f.channel.Send("addStyleTag", options) - if err != nil { - return nil, err - } - return fromChannel(channel).(*elementHandleImpl), nil -} - -func (f *frameImpl) Page() Page { - return f.page -} - -func (f *frameImpl) WaitForLoadState(options ...FrameWaitForLoadStateOptions) error { - option := FrameWaitForLoadStateOptions{} - if len(options) == 1 { - option = options[0] - } - if option.State == nil { - option.State = LoadStateLoad - } - return f.waitForLoadStateImpl(string(*option.State), option.Timeout, nil) -} - -func (f *frameImpl) waitForLoadStateImpl(state string, timeout *float64, cb func() error) error { - if f.loadStates.ContainsOne(state) { - return nil - } - waiter, err := f.setNavigationWaiter(timeout) - if err != nil { - return err - } - waiter.WaitForEvent(f, "loadstate", func(payload interface{}) bool { - gotState := payload.(string) - return gotState == state - }) - if cb == nil { - _, err := waiter.Wait() - return err - } else { - _, err := waiter.RunAndWait(cb) - return err - } -} - -func (f *frameImpl) WaitForURL(url interface{}, options ...FrameWaitForURLOptions) error { - if f.page == nil { - return errors.New("frame is detached") - } - matcher := newURLMatcher(url, f.page.browserContext.options.BaseURL) - if matcher.Matches(f.URL()) { - state := "load" - timeout := Float(f.page.timeoutSettings.NavigationTimeout()) - if len(options) == 1 { - if options[0].WaitUntil != nil { - state = string(*options[0].WaitUntil) - } - if options[0].Timeout != nil { - timeout = options[0].Timeout - } - } - return f.waitForLoadStateImpl(state, timeout, nil) - } - navigationOptions := FrameExpectNavigationOptions{URL: url} - if len(options) > 0 { - navigationOptions.Timeout = options[0].Timeout - navigationOptions.WaitUntil = options[0].WaitUntil - } - if _, err := f.ExpectNavigation(nil, navigationOptions); err != nil { - return err - } - return nil -} - -func (f *frameImpl) ExpectNavigation(cb func() error, options ...FrameExpectNavigationOptions) (Response, error) { - if f.page == nil { - return nil, errors.New("frame is detached") - } - option := FrameExpectNavigationOptions{} - if len(options) == 1 { - option = options[0] - } - if option.WaitUntil == nil { - option.WaitUntil = WaitUntilStateLoad - } - if option.Timeout == nil { - option.Timeout = Float(f.page.timeoutSettings.NavigationTimeout()) - } - deadline := time.Now().Add(time.Duration(*option.Timeout) * time.Millisecond) - var matcher *urlMatcher - if option.URL != nil { - matcher = newURLMatcher(option.URL, f.page.browserContext.options.BaseURL) - } - predicate := func(events ...interface{}) bool { - ev := events[0].(map[string]interface{}) - err, ok := ev["error"] - if ok { - // Any failed navigation results in a rejection. - logger.Error("navigation error", "url", ev["url"].(string), "error", err) - return true - } - return matcher == nil || matcher.Matches(ev["url"].(string)) - } - waiter, err := f.setNavigationWaiter(option.Timeout) - if err != nil { - return nil, err - } - - eventData, err := waiter.WaitForEvent(f, "navigated", predicate).RunAndWait(cb) - if err != nil || eventData == nil { - return nil, err - } - - t := time.Until(deadline).Milliseconds() - if t > 0 { - err = f.waitForLoadStateImpl(string(*option.WaitUntil), Float(float64(t)), nil) - if err != nil { - return nil, err - } - } - event := eventData.(map[string]interface{}) - if event["newDocument"] != nil && event["newDocument"].(map[string]interface{})["request"] != nil { - request := fromChannel(event["newDocument"].(map[string]interface{})["request"]).(*requestImpl) - return request.Response() - } - return nil, nil -} - -func (f *frameImpl) setNavigationWaiter(timeout *float64) (*waiter, error) { - if f.page == nil { - return nil, errors.New("page does not exist") - } - waiter := newWaiter() - if timeout != nil { - waiter.WithTimeout(*timeout) - } else { - waiter.WithTimeout(f.page.timeoutSettings.NavigationTimeout()) - } - waiter.RejectOnEvent(f.page, "close", f.page.closeErrorWithReason()) - waiter.RejectOnEvent(f.page, "crash", fmt.Errorf("Navigation failed because page crashed!")) - waiter.RejectOnEvent(f.page, "framedetached", fmt.Errorf("Navigating frame was detached!"), func(payload interface{}) bool { - frame, ok := payload.(*frameImpl) - if ok && frame == f { - return true - } - return false - }) - return waiter, nil -} - -func (f *frameImpl) onFrameNavigated(ev map[string]interface{}) { - f.Lock() - f.url = ev["url"].(string) - f.name = ev["name"].(string) - f.Unlock() - f.Emit("navigated", ev) - _, ok := ev["error"] - if !ok && f.page != nil { - f.page.Emit("framenavigated", f) - } -} - -func (f *frameImpl) onLoadState(ev map[string]interface{}) { - if ev["add"] != nil { - add := ev["add"].(string) - f.loadStates.Add(add) - f.Emit("loadstate", add) - if f.parentFrame == nil && f.page != nil { - if add == "load" || add == "domcontentloaded" { - f.Page().Emit(add, f.page) - } - } - } else if ev["remove"] != nil { - remove := ev["remove"].(string) - f.loadStates.Remove(remove) - } -} - -func (f *frameImpl) QuerySelector(selector string, options ...FrameQuerySelectorOptions) (ElementHandle, error) { - params := map[string]interface{}{ - "selector": selector, - } - if len(options) == 1 { - params["strict"] = options[0].Strict - } - channel, err := f.channel.Send("querySelector", params) - if err != nil { - return nil, err - } - if channel == nil { - return nil, nil - } - return fromChannel(channel).(*elementHandleImpl), nil -} - -func (f *frameImpl) QuerySelectorAll(selector string) ([]ElementHandle, error) { - channels, err := f.channel.Send("querySelectorAll", map[string]interface{}{ - "selector": selector, - }) - if err != nil { - return nil, err - } - elements := make([]ElementHandle, 0) - for _, channel := range channels.([]interface{}) { - elements = append(elements, fromChannel(channel).(*elementHandleImpl)) - } - return elements, nil -} - -func (f *frameImpl) Evaluate(expression string, options ...interface{}) (interface{}, error) { - var arg interface{} - if len(options) == 1 { - arg = options[0] - } - result, err := f.channel.Send("evaluateExpression", map[string]interface{}{ - "expression": expression, - "arg": serializeArgument(arg), - }) - if err != nil { - return nil, err - } - return parseResult(result), nil -} - -func (f *frameImpl) EvalOnSelector(selector string, expression string, arg interface{}, options ...FrameEvalOnSelectorOptions) (interface{}, error) { - params := map[string]interface{}{ - "selector": selector, - "expression": expression, - "arg": serializeArgument(arg), - } - if len(options) == 1 && options[0].Strict != nil { - params["strict"] = *options[0].Strict - } - - result, err := f.channel.Send("evalOnSelector", params) - if err != nil { - return nil, err - } - return parseResult(result), nil -} - -func (f *frameImpl) EvalOnSelectorAll(selector string, expression string, options ...interface{}) (interface{}, error) { - var arg interface{} - if len(options) == 1 { - arg = options[0] - } - result, err := f.channel.Send("evalOnSelectorAll", map[string]interface{}{ - "selector": selector, - "expression": expression, - "arg": serializeArgument(arg), - }) - if err != nil { - return nil, err - } - return parseResult(result), nil -} - -func (f *frameImpl) EvaluateHandle(expression string, options ...interface{}) (JSHandle, error) { - var arg interface{} - if len(options) == 1 { - arg = options[0] - } - result, err := f.channel.Send("evaluateExpressionHandle", map[string]interface{}{ - "expression": expression, - "arg": serializeArgument(arg), - }) - if err != nil { - return nil, err - } - channelOwner := fromChannel(result) - if channelOwner == nil { - return nil, nil - } - return channelOwner.(JSHandle), nil -} - -func (f *frameImpl) Click(selector string, options ...FrameClickOptions) error { - _, err := f.channel.Send("click", map[string]interface{}{ - "selector": selector, - }, options) - return err -} - -func (f *frameImpl) WaitForSelector(selector string, options ...FrameWaitForSelectorOptions) (ElementHandle, error) { - channel, err := f.channel.Send("waitForSelector", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return nil, err - } - channelOwner := fromNullableChannel(channel) - if channelOwner == nil { - return nil, nil - } - return channelOwner.(*elementHandleImpl), nil -} - -func (f *frameImpl) DispatchEvent(selector, typ string, eventInit interface{}, options ...FrameDispatchEventOptions) error { - _, err := f.channel.Send("dispatchEvent", map[string]interface{}{ - "selector": selector, - "type": typ, - "eventInit": serializeArgument(eventInit), - }) - return err -} - -func (f *frameImpl) InnerText(selector string, options ...FrameInnerTextOptions) (string, error) { - innerText, err := f.channel.Send("innerText", map[string]interface{}{ - "selector": selector, - }, options) - if innerText == nil { - return "", err - } - return innerText.(string), err -} - -func (f *frameImpl) InnerHTML(selector string, options ...FrameInnerHTMLOptions) (string, error) { - innerHTML, err := f.channel.Send("innerHTML", map[string]interface{}{ - "selector": selector, - }, options) - if innerHTML == nil { - return "", err - } - return innerHTML.(string), err -} - -func (f *frameImpl) GetAttribute(selector string, name string, options ...FrameGetAttributeOptions) (string, error) { - attribute, err := f.channel.Send("getAttribute", map[string]interface{}{ - "selector": selector, - "name": name, - }, options) - if attribute == nil { - return "", err - } - return attribute.(string), err -} - -func (f *frameImpl) Hover(selector string, options ...FrameHoverOptions) error { - _, err := f.channel.Send("hover", map[string]interface{}{ - "selector": selector, - }, options) - return err -} - -func (f *frameImpl) SetInputFiles(selector string, files interface{}, options ...FrameSetInputFilesOptions) error { - params, err := convertInputFiles(files, f.page.browserContext) - if err != nil { - return err - } - params.Selector = &selector - _, err = f.channel.Send("setInputFiles", params, options) - return err -} - -func (f *frameImpl) Type(selector, text string, options ...FrameTypeOptions) error { - _, err := f.channel.Send("type", map[string]interface{}{ - "selector": selector, - "text": text, - }, options) - return err -} - -func (f *frameImpl) Press(selector, key string, options ...FramePressOptions) error { - _, err := f.channel.Send("press", map[string]interface{}{ - "selector": selector, - "key": key, - }, options) - return err -} - -func (f *frameImpl) Check(selector string, options ...FrameCheckOptions) error { - _, err := f.channel.Send("check", map[string]interface{}{ - "selector": selector, - }, options) - return err -} - -func (f *frameImpl) Uncheck(selector string, options ...FrameUncheckOptions) error { - _, err := f.channel.Send("uncheck", map[string]interface{}{ - "selector": selector, - }, options) - return err -} - -func (f *frameImpl) WaitForTimeout(timeout float64) { - time.Sleep(time.Duration(timeout) * time.Millisecond) -} - -func (f *frameImpl) WaitForFunction(expression string, arg interface{}, options ...FrameWaitForFunctionOptions) (JSHandle, error) { - var option FrameWaitForFunctionOptions - if len(options) == 1 { - option = options[0] - } - result, err := f.channel.Send("waitForFunction", map[string]interface{}{ - "expression": expression, - "arg": serializeArgument(arg), - "timeout": option.Timeout, - "polling": option.Polling, - }) - if err != nil { - return nil, err - } - handle := fromChannel(result) - if handle == nil { - return nil, nil - } - return handle.(*jsHandleImpl), nil -} - -func (f *frameImpl) Title() (string, error) { - title, err := f.channel.Send("title") - if title == nil { - return "", err - } - return title.(string), err -} - -func (f *frameImpl) ChildFrames() []Frame { - return f.childFrames -} - -func (f *frameImpl) Dblclick(selector string, options ...FrameDblclickOptions) error { - _, err := f.channel.Send("dblclick", map[string]interface{}{ - "selector": selector, - }, options) - return err -} - -func (f *frameImpl) Fill(selector string, value string, options ...FrameFillOptions) error { - _, err := f.channel.Send("fill", map[string]interface{}{ - "selector": selector, - "value": value, - }, options) - return err -} - -func (f *frameImpl) Focus(selector string, options ...FrameFocusOptions) error { - _, err := f.channel.Send("focus", map[string]interface{}{ - "selector": selector, - }, options) - return err -} - -func (f *frameImpl) FrameElement() (ElementHandle, error) { - channel, err := f.channel.Send("frameElement") - if err != nil { - return nil, err - } - return fromChannel(channel).(*elementHandleImpl), nil -} - -func (f *frameImpl) IsDetached() bool { - return f.detached -} - -func (f *frameImpl) ParentFrame() Frame { - return f.parentFrame -} - -func (f *frameImpl) TextContent(selector string, options ...FrameTextContentOptions) (string, error) { - textContent, err := f.channel.Send("textContent", map[string]interface{}{ - "selector": selector, - }, options) - if textContent == nil { - return "", err - } - return textContent.(string), err -} - -func (f *frameImpl) Tap(selector string, options ...FrameTapOptions) error { - _, err := f.channel.Send("tap", map[string]interface{}{ - "selector": selector, - }, options) - return err -} - -func (f *frameImpl) SelectOption(selector string, values SelectOptionValues, options ...FrameSelectOptionOptions) ([]string, error) { - opts := convertSelectOptionSet(values) - - m := make(map[string]interface{}) - m["selector"] = selector - for k, v := range opts { - m[k] = v - } - selected, err := f.channel.Send("selectOption", m, options) - if err != nil { - return nil, err - } - - return transformToStringList(selected), nil -} - -func (f *frameImpl) IsChecked(selector string, options ...FrameIsCheckedOptions) (bool, error) { - checked, err := f.channel.Send("isChecked", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return false, err - } - return checked.(bool), nil -} - -func (f *frameImpl) IsDisabled(selector string, options ...FrameIsDisabledOptions) (bool, error) { - disabled, err := f.channel.Send("isDisabled", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return false, err - } - return disabled.(bool), nil -} - -func (f *frameImpl) IsEditable(selector string, options ...FrameIsEditableOptions) (bool, error) { - editable, err := f.channel.Send("isEditable", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return false, err - } - return editable.(bool), nil -} - -func (f *frameImpl) IsEnabled(selector string, options ...FrameIsEnabledOptions) (bool, error) { - enabled, err := f.channel.Send("isEnabled", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return false, err - } - return enabled.(bool), nil -} - -func (f *frameImpl) IsHidden(selector string, options ...FrameIsHiddenOptions) (bool, error) { - hidden, err := f.channel.Send("isHidden", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return false, err - } - return hidden.(bool), nil -} - -func (f *frameImpl) IsVisible(selector string, options ...FrameIsVisibleOptions) (bool, error) { - visible, err := f.channel.Send("isVisible", map[string]interface{}{ - "selector": selector, - }, options) - if err != nil { - return false, err - } - return visible.(bool), nil -} - -func (f *frameImpl) InputValue(selector string, options ...FrameInputValueOptions) (string, error) { - value, err := f.channel.Send("inputValue", map[string]interface{}{ - "selector": selector, - }, options) - if value == nil { - return "", err - } - return value.(string), err -} - -func (f *frameImpl) DragAndDrop(source, target string, options ...FrameDragAndDropOptions) error { - _, err := f.channel.Send("dragAndDrop", map[string]interface{}{ - "source": source, - "target": target, - }, options) - return err -} - -func (f *frameImpl) SetChecked(selector string, checked bool, options ...FrameSetCheckedOptions) error { - if checked { - _, err := f.channel.Send("check", map[string]interface{}{ - "selector": selector, - }, options) - return err - } else { - _, err := f.channel.Send("uncheck", map[string]interface{}{ - "selector": selector, - }, options) - return err - } -} - -func (f *frameImpl) Locator(selector string, options ...FrameLocatorOptions) Locator { - var option LocatorOptions - if len(options) == 1 { - option = LocatorOptions{ - Has: options[0].Has, - HasNot: options[0].HasNot, - HasText: options[0].HasText, - HasNotText: options[0].HasNotText, - } - } - return newLocator(f, selector, option) -} - -func (f *frameImpl) GetByAltText(text interface{}, options ...FrameGetByAltTextOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return f.Locator(getByAltTextSelector(text, exact)) -} - -func (f *frameImpl) GetByLabel(text interface{}, options ...FrameGetByLabelOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return f.Locator(getByLabelSelector(text, exact)) -} - -func (f *frameImpl) GetByPlaceholder(text interface{}, options ...FrameGetByPlaceholderOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return f.Locator(getByPlaceholderSelector(text, exact)) -} - -func (f *frameImpl) GetByRole(role AriaRole, options ...FrameGetByRoleOptions) Locator { - if len(options) == 1 { - return f.Locator(getByRoleSelector(role, LocatorGetByRoleOptions(options[0]))) - } - return f.Locator(getByRoleSelector(role)) -} - -func (f *frameImpl) GetByTestId(testId interface{}) Locator { - return f.Locator(getByTestIdSelector(getTestIdAttributeName(), testId)) -} - -func (f *frameImpl) GetByText(text interface{}, options ...FrameGetByTextOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return f.Locator(getByTextSelector(text, exact)) -} - -func (f *frameImpl) GetByTitle(text interface{}, options ...FrameGetByTitleOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return f.Locator(getByTitleSelector(text, exact)) -} - -func (f *frameImpl) FrameLocator(selector string) FrameLocator { - return newFrameLocator(f, selector) -} - -func (f *frameImpl) highlight(selector string) error { - _, err := f.channel.Send("highlight", map[string]interface{}{ - "selector": selector, - }) - return err -} - -func (f *frameImpl) queryCount(selector string) (int, error) { - response, err := f.channel.Send("queryCount", map[string]interface{}{ - "selector": selector, - }) - if err != nil { - return 0, err - } - return int(response.(float64)), nil -} diff --git a/vendor/github.com/playwright-community/playwright-go/frame_locator.go b/vendor/github.com/playwright-community/playwright-go/frame_locator.go deleted file mode 100644 index d4b8fd0..0000000 --- a/vendor/github.com/playwright-community/playwright-go/frame_locator.go +++ /dev/null @@ -1,130 +0,0 @@ -package playwright - -import ( - "errors" - "fmt" - "strconv" -) - -type frameLocatorImpl struct { - frame *frameImpl - frameSelector string -} - -func newFrameLocator(frame *frameImpl, frameSelector string) *frameLocatorImpl { - return &frameLocatorImpl{frame: frame, frameSelector: frameSelector} -} - -func (fl *frameLocatorImpl) First() FrameLocator { - return newFrameLocator(fl.frame, fl.frameSelector+" >> nth=0") -} - -func (fl *frameLocatorImpl) FrameLocator(selector string) FrameLocator { - return newFrameLocator(fl.frame, fl.frameSelector+" >> internal:control=enter-frame >> "+selector) -} - -func (fl *frameLocatorImpl) GetByAltText(text interface{}, options ...FrameLocatorGetByAltTextOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return fl.Locator(getByAltTextSelector(text, exact)) -} - -func (fl *frameLocatorImpl) GetByLabel(text interface{}, options ...FrameLocatorGetByLabelOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return fl.Locator(getByLabelSelector(text, exact)) -} - -func (fl *frameLocatorImpl) GetByPlaceholder(text interface{}, options ...FrameLocatorGetByPlaceholderOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return fl.Locator(getByPlaceholderSelector(text, exact)) -} - -func (fl *frameLocatorImpl) GetByRole(role AriaRole, options ...FrameLocatorGetByRoleOptions) Locator { - if len(options) == 1 { - return fl.Locator(getByRoleSelector(role, LocatorGetByRoleOptions(options[0]))) - } - return fl.Locator(getByRoleSelector(role)) -} - -func (fl *frameLocatorImpl) GetByTestId(testId interface{}) Locator { - return fl.Locator(getByTestIdSelector(getTestIdAttributeName(), testId)) -} - -func (fl *frameLocatorImpl) GetByText(text interface{}, options ...FrameLocatorGetByTextOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return fl.Locator(getByTextSelector(text, exact)) -} - -func (fl *frameLocatorImpl) GetByTitle(text interface{}, options ...FrameLocatorGetByTitleOptions) Locator { - exact := false - if len(options) == 1 { - if *options[0].Exact { - exact = true - } - } - return fl.Locator(getByTitleSelector(text, exact)) -} - -func (fl *frameLocatorImpl) Last() FrameLocator { - return newFrameLocator(fl.frame, fl.frameSelector+" >> nth=-1") -} - -func (fl *frameLocatorImpl) Locator(selectorOrLocator interface{}, options ...FrameLocatorLocatorOptions) Locator { - var option LocatorOptions - if len(options) == 1 { - option = LocatorOptions{ - Has: options[0].Has, - HasNot: options[0].HasNot, - HasText: options[0].HasText, - HasNotText: options[0].HasNotText, - } - } - - selector, ok := selectorOrLocator.(string) - if ok { - return newLocator(fl.frame, fl.frameSelector+" >> internal:control=enter-frame >> "+selector, option) - } - locator, ok := selectorOrLocator.(*locatorImpl) - if ok { - if fl.frame != locator.frame { - locator.err = errors.Join(locator.err, ErrLocatorNotSameFrame) - return locator - } - return newLocator(locator.frame, - fmt.Sprintf("%s >> internal:control=enter-frame >> %s", fl.frameSelector, locator.selector), - option, - ) - } - return &locatorImpl{ - frame: fl.frame, - selector: fl.frameSelector, - err: fmt.Errorf("invalid locator parameter: %v", selectorOrLocator), - } -} - -func (fl *frameLocatorImpl) Nth(index int) FrameLocator { - return newFrameLocator(fl.frame, fl.frameSelector+" >> nth="+strconv.Itoa(index)) -} - -func (fl *frameLocatorImpl) Owner() Locator { - return newLocator(fl.frame, fl.frameSelector) -} diff --git a/vendor/github.com/playwright-community/playwright-go/generated-enums.go b/vendor/github.com/playwright-community/playwright-go/generated-enums.go deleted file mode 100644 index 92c20c6..0000000 --- a/vendor/github.com/playwright-community/playwright-go/generated-enums.go +++ /dev/null @@ -1,404 +0,0 @@ -package playwright - -func getMixedState(in string) *MixedState { - v := MixedState(in) - return &v -} - -type MixedState string - -var ( - MixedStateOn *MixedState = getMixedState("On") - MixedStateOff = getMixedState("Off") - MixedStateMixed = getMixedState("Mixed") -) - -func getElementState(in string) *ElementState { - v := ElementState(in) - return &v -} - -type ElementState string - -var ( - ElementStateVisible *ElementState = getElementState("visible") - ElementStateHidden = getElementState("hidden") - ElementStateStable = getElementState("stable") - ElementStateEnabled = getElementState("enabled") - ElementStateDisabled = getElementState("disabled") - ElementStateEditable = getElementState("editable") -) - -func getAriaRole(in string) *AriaRole { - v := AriaRole(in) - return &v -} - -type AriaRole string - -var ( - AriaRoleAlert *AriaRole = getAriaRole("alert") - AriaRoleAlertdialog = getAriaRole("alertdialog") - AriaRoleApplication = getAriaRole("application") - AriaRoleArticle = getAriaRole("article") - AriaRoleBanner = getAriaRole("banner") - AriaRoleBlockquote = getAriaRole("blockquote") - AriaRoleButton = getAriaRole("button") - AriaRoleCaption = getAriaRole("caption") - AriaRoleCell = getAriaRole("cell") - AriaRoleCheckbox = getAriaRole("checkbox") - AriaRoleCode = getAriaRole("code") - AriaRoleColumnheader = getAriaRole("columnheader") - AriaRoleCombobox = getAriaRole("combobox") - AriaRoleComplementary = getAriaRole("complementary") - AriaRoleContentinfo = getAriaRole("contentinfo") - AriaRoleDefinition = getAriaRole("definition") - AriaRoleDeletion = getAriaRole("deletion") - AriaRoleDialog = getAriaRole("dialog") - AriaRoleDirectory = getAriaRole("directory") - AriaRoleDocument = getAriaRole("document") - AriaRoleEmphasis = getAriaRole("emphasis") - AriaRoleFeed = getAriaRole("feed") - AriaRoleFigure = getAriaRole("figure") - AriaRoleForm = getAriaRole("form") - AriaRoleGeneric = getAriaRole("generic") - AriaRoleGrid = getAriaRole("grid") - AriaRoleGridcell = getAriaRole("gridcell") - AriaRoleGroup = getAriaRole("group") - AriaRoleHeading = getAriaRole("heading") - AriaRoleImg = getAriaRole("img") - AriaRoleInsertion = getAriaRole("insertion") - AriaRoleLink = getAriaRole("link") - AriaRoleList = getAriaRole("list") - AriaRoleListbox = getAriaRole("listbox") - AriaRoleListitem = getAriaRole("listitem") - AriaRoleLog = getAriaRole("log") - AriaRoleMain = getAriaRole("main") - AriaRoleMarquee = getAriaRole("marquee") - AriaRoleMath = getAriaRole("math") - AriaRoleMeter = getAriaRole("meter") - AriaRoleMenu = getAriaRole("menu") - AriaRoleMenubar = getAriaRole("menubar") - AriaRoleMenuitem = getAriaRole("menuitem") - AriaRoleMenuitemcheckbox = getAriaRole("menuitemcheckbox") - AriaRoleMenuitemradio = getAriaRole("menuitemradio") - AriaRoleNavigation = getAriaRole("navigation") - AriaRoleNone = getAriaRole("none") - AriaRoleNote = getAriaRole("note") - AriaRoleOption = getAriaRole("option") - AriaRoleParagraph = getAriaRole("paragraph") - AriaRolePresentation = getAriaRole("presentation") - AriaRoleProgressbar = getAriaRole("progressbar") - AriaRoleRadio = getAriaRole("radio") - AriaRoleRadiogroup = getAriaRole("radiogroup") - AriaRoleRegion = getAriaRole("region") - AriaRoleRow = getAriaRole("row") - AriaRoleRowgroup = getAriaRole("rowgroup") - AriaRoleRowheader = getAriaRole("rowheader") - AriaRoleScrollbar = getAriaRole("scrollbar") - AriaRoleSearch = getAriaRole("search") - AriaRoleSearchbox = getAriaRole("searchbox") - AriaRoleSeparator = getAriaRole("separator") - AriaRoleSlider = getAriaRole("slider") - AriaRoleSpinbutton = getAriaRole("spinbutton") - AriaRoleStatus = getAriaRole("status") - AriaRoleStrong = getAriaRole("strong") - AriaRoleSubscript = getAriaRole("subscript") - AriaRoleSuperscript = getAriaRole("superscript") - AriaRoleSwitch = getAriaRole("switch") - AriaRoleTab = getAriaRole("tab") - AriaRoleTable = getAriaRole("table") - AriaRoleTablist = getAriaRole("tablist") - AriaRoleTabpanel = getAriaRole("tabpanel") - AriaRoleTerm = getAriaRole("term") - AriaRoleTextbox = getAriaRole("textbox") - AriaRoleTime = getAriaRole("time") - AriaRoleTimer = getAriaRole("timer") - AriaRoleToolbar = getAriaRole("toolbar") - AriaRoleTooltip = getAriaRole("tooltip") - AriaRoleTree = getAriaRole("tree") - AriaRoleTreegrid = getAriaRole("treegrid") - AriaRoleTreeitem = getAriaRole("treeitem") -) - -func getColorScheme(in string) *ColorScheme { - v := ColorScheme(in) - return &v -} - -type ColorScheme string - -var ( - ColorSchemeLight *ColorScheme = getColorScheme("light") - ColorSchemeDark = getColorScheme("dark") - ColorSchemeNoPreference = getColorScheme("no-preference") - ColorSchemeNoOverride = getColorScheme("no-override") -) - -func getForcedColors(in string) *ForcedColors { - v := ForcedColors(in) - return &v -} - -type ForcedColors string - -var ( - ForcedColorsActive *ForcedColors = getForcedColors("active") - ForcedColorsNone = getForcedColors("none") - ForcedColorsNoOverride = getForcedColors("no-override") -) - -func getHarContentPolicy(in string) *HarContentPolicy { - v := HarContentPolicy(in) - return &v -} - -type HarContentPolicy string - -var ( - HarContentPolicyOmit *HarContentPolicy = getHarContentPolicy("omit") - HarContentPolicyEmbed = getHarContentPolicy("embed") - HarContentPolicyAttach = getHarContentPolicy("attach") -) - -func getHarMode(in string) *HarMode { - v := HarMode(in) - return &v -} - -type HarMode string - -var ( - HarModeFull *HarMode = getHarMode("full") - HarModeMinimal = getHarMode("minimal") -) - -func getReducedMotion(in string) *ReducedMotion { - v := ReducedMotion(in) - return &v -} - -type ReducedMotion string - -var ( - ReducedMotionReduce *ReducedMotion = getReducedMotion("reduce") - ReducedMotionNoPreference = getReducedMotion("no-preference") - ReducedMotionNoOverride = getReducedMotion("no-override") -) - -func getServiceWorkerPolicy(in string) *ServiceWorkerPolicy { - v := ServiceWorkerPolicy(in) - return &v -} - -type ServiceWorkerPolicy string - -var ( - ServiceWorkerPolicyAllow *ServiceWorkerPolicy = getServiceWorkerPolicy("allow") - ServiceWorkerPolicyBlock = getServiceWorkerPolicy("block") -) - -func getSameSiteAttribute(in string) *SameSiteAttribute { - v := SameSiteAttribute(in) - return &v -} - -type SameSiteAttribute string - -var ( - SameSiteAttributeStrict *SameSiteAttribute = getSameSiteAttribute("Strict") - SameSiteAttributeLax = getSameSiteAttribute("Lax") - SameSiteAttributeNone = getSameSiteAttribute("None") -) - -func getHarNotFound(in string) *HarNotFound { - v := HarNotFound(in) - return &v -} - -type HarNotFound string - -var ( - HarNotFoundAbort *HarNotFound = getHarNotFound("abort") - HarNotFoundFallback = getHarNotFound("fallback") -) - -func getRouteFromHarUpdateContentPolicy(in string) *RouteFromHarUpdateContentPolicy { - v := RouteFromHarUpdateContentPolicy(in) - return &v -} - -type RouteFromHarUpdateContentPolicy string - -var ( - RouteFromHarUpdateContentPolicyEmbed *RouteFromHarUpdateContentPolicy = getRouteFromHarUpdateContentPolicy("embed") - RouteFromHarUpdateContentPolicyAttach = getRouteFromHarUpdateContentPolicy("attach") -) - -func getUnrouteBehavior(in string) *UnrouteBehavior { - v := UnrouteBehavior(in) - return &v -} - -type UnrouteBehavior string - -var ( - UnrouteBehaviorWait *UnrouteBehavior = getUnrouteBehavior("wait") - UnrouteBehaviorIgnoreErrors = getUnrouteBehavior("ignoreErrors") - UnrouteBehaviorDefault = getUnrouteBehavior("default") -) - -func getMouseButton(in string) *MouseButton { - v := MouseButton(in) - return &v -} - -type MouseButton string - -var ( - MouseButtonLeft *MouseButton = getMouseButton("left") - MouseButtonRight = getMouseButton("right") - MouseButtonMiddle = getMouseButton("middle") -) - -func getKeyboardModifier(in string) *KeyboardModifier { - v := KeyboardModifier(in) - return &v -} - -type KeyboardModifier string - -var ( - KeyboardModifierAlt *KeyboardModifier = getKeyboardModifier("Alt") - KeyboardModifierControl = getKeyboardModifier("Control") - KeyboardModifierControlOrMeta = getKeyboardModifier("ControlOrMeta") - KeyboardModifierMeta = getKeyboardModifier("Meta") - KeyboardModifierShift = getKeyboardModifier("Shift") -) - -func getScreenshotAnimations(in string) *ScreenshotAnimations { - v := ScreenshotAnimations(in) - return &v -} - -type ScreenshotAnimations string - -var ( - ScreenshotAnimationsDisabled *ScreenshotAnimations = getScreenshotAnimations("disabled") - ScreenshotAnimationsAllow = getScreenshotAnimations("allow") -) - -func getScreenshotCaret(in string) *ScreenshotCaret { - v := ScreenshotCaret(in) - return &v -} - -type ScreenshotCaret string - -var ( - ScreenshotCaretHide *ScreenshotCaret = getScreenshotCaret("hide") - ScreenshotCaretInitial = getScreenshotCaret("initial") -) - -func getScreenshotScale(in string) *ScreenshotScale { - v := ScreenshotScale(in) - return &v -} - -type ScreenshotScale string - -var ( - ScreenshotScaleCss *ScreenshotScale = getScreenshotScale("css") - ScreenshotScaleDevice = getScreenshotScale("device") -) - -func getScreenshotType(in string) *ScreenshotType { - v := ScreenshotType(in) - return &v -} - -type ScreenshotType string - -var ( - ScreenshotTypePng *ScreenshotType = getScreenshotType("png") - ScreenshotTypeJpeg = getScreenshotType("jpeg") -) - -func getWaitForSelectorState(in string) *WaitForSelectorState { - v := WaitForSelectorState(in) - return &v -} - -type WaitForSelectorState string - -var ( - WaitForSelectorStateAttached *WaitForSelectorState = getWaitForSelectorState("attached") - WaitForSelectorStateDetached = getWaitForSelectorState("detached") - WaitForSelectorStateVisible = getWaitForSelectorState("visible") - WaitForSelectorStateHidden = getWaitForSelectorState("hidden") -) - -func getWaitUntilState(in string) *WaitUntilState { - v := WaitUntilState(in) - return &v -} - -type WaitUntilState string - -var ( - WaitUntilStateLoad *WaitUntilState = getWaitUntilState("load") - WaitUntilStateDomcontentloaded = getWaitUntilState("domcontentloaded") - WaitUntilStateNetworkidle = getWaitUntilState("networkidle") - WaitUntilStateCommit = getWaitUntilState("commit") -) - -func getLoadState(in string) *LoadState { - v := LoadState(in) - return &v -} - -type LoadState string - -var ( - LoadStateLoad *LoadState = getLoadState("load") - LoadStateDomcontentloaded = getLoadState("domcontentloaded") - LoadStateNetworkidle = getLoadState("networkidle") -) - -func getContrast(in string) *Contrast { - v := Contrast(in) - return &v -} - -type Contrast string - -var ( - ContrastNoPreference *Contrast = getContrast("no-preference") - ContrastMore = getContrast("more") - ContrastNoOverride = getContrast("no-override") -) - -func getMedia(in string) *Media { - v := Media(in) - return &v -} - -type Media string - -var ( - MediaScreen *Media = getMedia("screen") - MediaPrint = getMedia("print") - MediaNoOverride = getMedia("no-override") -) - -func getHttpCredentialsSend(in string) *HttpCredentialsSend { - v := HttpCredentialsSend(in) - return &v -} - -type HttpCredentialsSend string - -var ( - HttpCredentialsSendUnauthorized *HttpCredentialsSend = getHttpCredentialsSend("unauthorized") - HttpCredentialsSendAlways = getHttpCredentialsSend("always") -) diff --git a/vendor/github.com/playwright-community/playwright-go/generated-interfaces.go b/vendor/github.com/playwright-community/playwright-go/generated-interfaces.go deleted file mode 100644 index 187dc91..0000000 --- a/vendor/github.com/playwright-community/playwright-go/generated-interfaces.go +++ /dev/null @@ -1,4658 +0,0 @@ -package playwright - -// Exposes API that can be used for the Web API testing. This class is used for creating [APIRequestContext] instance -// which in turn can be used for sending web requests. An instance of this class can be obtained via -// [Playwright.Request]. For more information see [APIRequestContext]. -type APIRequest interface { - // Creates new instances of [APIRequestContext]. - NewContext(options ...APIRequestNewContextOptions) (APIRequestContext, error) -} - -// This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, -// prepare environment or the service to your e2e test. -// Each Playwright browser context has associated with it [APIRequestContext] instance which shares cookie storage -// with the browser context and can be accessed via [BrowserContext.Request] or [Page.Request]. It is also possible to -// create a new APIRequestContext instance manually by calling [APIRequest.NewContext]. -// **Cookie management** -// [APIRequestContext] returned by [BrowserContext.Request] and [Page.Request] shares cookie storage with the -// corresponding [BrowserContext]. Each API request will have `Cookie` header populated with the values from the -// browser context. If the API response contains `Set-Cookie` header it will automatically update [BrowserContext] -// cookies and requests made from the page will pick them up. This means that if you log in using this API, your e2e -// test will be logged in and vice versa. -// If you want API requests to not interfere with the browser cookies you should create a new [APIRequestContext] by -// calling [APIRequest.NewContext]. Such `APIRequestContext` object will have its own isolated cookie storage. -type APIRequestContext interface { - // Sends HTTP(S) [DELETE] request and returns its - // response. The method will populate request cookies from the context and update context cookies from the response. - // The method will automatically follow redirects. - // - // url: Target URL. - // - // [DELETE]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE - Delete(url string, options ...APIRequestContextDeleteOptions) (APIResponse, error) - - // All responses returned by [APIRequestContext.Get] and similar methods are stored in the memory, so that you can - // later call [APIResponse.Body].This method discards all its resources, calling any method on disposed - // [APIRequestContext] will throw an exception. - Dispose(options ...APIRequestContextDisposeOptions) error - - // Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and - // update context cookies from the response. The method will automatically follow redirects. - // - // urlOrRequest: Target URL or Request to get all parameters from. - Fetch(urlOrRequest interface{}, options ...APIRequestContextFetchOptions) (APIResponse, error) - - // Sends HTTP(S) [GET] request and returns its - // response. The method will populate request cookies from the context and update context cookies from the response. - // The method will automatically follow redirects. - // - // url: Target URL. - // - // [GET]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET - Get(url string, options ...APIRequestContextGetOptions) (APIResponse, error) - - // Sends HTTP(S) [HEAD] request and returns its - // response. The method will populate request cookies from the context and update context cookies from the response. - // The method will automatically follow redirects. - // - // url: Target URL. - // - // [HEAD]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD - Head(url string, options ...APIRequestContextHeadOptions) (APIResponse, error) - - // Sends HTTP(S) [PATCH] request and returns its - // response. The method will populate request cookies from the context and update context cookies from the response. - // The method will automatically follow redirects. - // - // url: Target URL. - // - // [PATCH]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH - Patch(url string, options ...APIRequestContextPatchOptions) (APIResponse, error) - - // Sends HTTP(S) [POST] request and returns its - // response. The method will populate request cookies from the context and update context cookies from the response. - // The method will automatically follow redirects. - // - // url: Target URL. - // - // [POST]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST - Post(url string, options ...APIRequestContextPostOptions) (APIResponse, error) - - // Sends HTTP(S) [PUT] request and returns its - // response. The method will populate request cookies from the context and update context cookies from the response. - // The method will automatically follow redirects. - // - // url: Target URL. - // - // [PUT]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT - Put(url string, options ...APIRequestContextPutOptions) (APIResponse, error) - - // Returns storage state for this request context, contains current cookies and local storage snapshot if it was - // passed to the constructor. - StorageState(path ...string) (*StorageState, error) -} - -// [APIResponse] class represents responses returned by [APIRequestContext.Get] and similar methods. -type APIResponse interface { - // Returns the buffer with response body. - Body() ([]byte, error) - - // Disposes the body of this response. If not called then the body will stay in memory until the context closes. - Dispose() error - - // An object with all the response HTTP headers associated with this response. - Headers() map[string]string - - // An array with all the response HTTP headers associated with this response. Header names are not lower-cased. - // Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times. - HeadersArray() []NameValue - - // Returns the JSON representation of response body. - // This method will throw if the response body is not parsable via `JSON.parse`. - JSON(v interface{}) error - - // Contains a boolean stating whether the response was successful (status in the range 200-299) or not. - Ok() bool - - // Contains the status code of the response (e.g., 200 for a success). - Status() int - - // Contains the status text of the response (e.g. usually an "OK" for a success). - StatusText() string - - // Returns the text representation of response body. - Text() (string, error) - - // Contains the URL of the response. - URL() string -} - -// The [APIResponseAssertions] class provides assertion methods that can be used to make assertions about the -// [APIResponse] in the tests. -type APIResponseAssertions interface { - // Makes the assertion check for the opposite condition. For example, this code tests that the response status is not - // successful: - Not() APIResponseAssertions - - // Ensures the response status code is within `200..299` range. - ToBeOK() error -} - -// A Browser is created via [BrowserType.Launch]. An example of using a [Browser] to create a [Page]: -type Browser interface { - EventEmitter - // Emitted when Browser gets disconnected from the browser application. This might happen because of one of the - // following: - // - Browser application is closed or crashed. - // - The [Browser.Close] method was called. - OnDisconnected(fn func(Browser)) - - // Get the browser type (chromium, firefox or webkit) that the browser belongs to. - BrowserType() BrowserType - - // In case this browser is obtained using [BrowserType.Launch], closes the browser and all of its pages (if any were - // opened). - // In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from - // the browser server. - // **NOTE** This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close - // events, call [BrowserContext.Close] on any [BrowserContext] instances you explicitly created earlier using - // [Browser.NewContext] **before** calling [Browser.Close]. - // The [Browser] object itself is considered to be disposed and cannot be used anymore. - Close(options ...BrowserCloseOptions) error - - // Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts. - Contexts() []BrowserContext - - // Indicates that the browser is connected. - IsConnected() bool - - // **NOTE** CDP Sessions are only supported on Chromium-based browsers. - // Returns the newly created browser session. - NewBrowserCDPSession() (CDPSession, error) - - // Creates a new browser context. It won't share cookies/cache with other browser contexts. - // **NOTE** If directly using this method to create [BrowserContext]s, it is best practice to explicitly close the - // returned context via [BrowserContext.Close] when your code is done with the [BrowserContext], and before calling - // [Browser.Close]. This will ensure the `context` is closed gracefully and any artifacts—like HARs and videos—are - // fully flushed and saved. - NewContext(options ...BrowserNewContextOptions) (BrowserContext, error) - - // Creates a new page in a new browser context. Closing this page will close the context as well. - // This is a convenience API that should only be used for the single-page scenarios and short snippets. Production - // code and testing frameworks should explicitly create [Browser.NewContext] followed by the [BrowserContext.NewPage] - // to control their exact life times. - NewPage(options ...BrowserNewPageOptions) (Page, error) - - // **NOTE** This API controls - // [Chromium Tracing] which is a low-level - // chromium-specific debugging tool. API to control [Playwright Tracing] could be found - // [here]. - // You can use [Browser.StartTracing] and [Browser.StopTracing] to create a trace file that can be opened in Chrome - // DevTools performance panel. - // - // [Chromium Tracing]: https://www.chromium.org/developers/how-tos/trace-event-profiling-tool - // [Playwright Tracing]: ../trace-viewer - // [here]: ./class-tracing - StartTracing(options ...BrowserStartTracingOptions) error - - // **NOTE** This API controls - // [Chromium Tracing] which is a low-level - // chromium-specific debugging tool. API to control [Playwright Tracing] could be found - // [here]. - // Returns the buffer with trace data. - // - // [Chromium Tracing]: https://www.chromium.org/developers/how-tos/trace-event-profiling-tool - // [Playwright Tracing]: ../trace-viewer - // [here]: ./class-tracing - StopTracing() ([]byte, error) - - // Returns the browser version. - Version() string -} - -// BrowserContexts provide a way to operate multiple independent browser sessions. -// If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser -// context. -// Playwright allows creating isolated non-persistent browser contexts with [Browser.NewContext] method. -// Non-persistent browser contexts don't write any browsing data to disk. -type BrowserContext interface { - EventEmitter - // **NOTE** Only works with Chromium browser's persistent context. - // Emitted when new background page is created in the context. - OnBackgroundPage(fn func(Page)) - - // Playwright has ability to mock clock and passage of time. - Clock() Clock - - // Emitted when Browser context gets closed. This might happen because of one of the following: - // - Browser context is closed. - // - Browser application is closed or crashed. - // - The [Browser.Close] method was called. - OnClose(fn func(BrowserContext)) - - // Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. - // The arguments passed into `console.log` and the page are available on the [ConsoleMessage] event handler argument. - OnConsole(fn func(ConsoleMessage)) - - // Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** - // either [Dialog.Accept] or [Dialog.Dismiss] the dialog - otherwise the page will - // [freeze] waiting for the dialog, - // and actions like click will never finish. - // - // [freeze]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking - OnDialog(fn func(Dialog)) - - // The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event - // will also fire for popup pages. See also [Page.OnPopup] to receive events about popups relevant to a specific page. - // The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a - // popup with `window.open('http://example.com')`, this event will fire when the network request to - // "http://example.com" is done and its response has started loading in the popup. If you would like to route/listen - // to this network request, use [BrowserContext.Route] and [BrowserContext.OnRequest] respectively instead of similar - // methods on the [Page]. - // **NOTE** Use [Page.WaitForLoadState] to wait until the page gets to a particular state (you should not need it in - // most cases). - OnPage(fn func(Page)) - - // Emitted when exception is unhandled in any of the pages in this context. To listen for errors from a particular - // page, use [Page.OnPageError] instead. - OnWebError(fn func(WebError)) - - // Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To - // only listen for requests from a particular page, use [Page.OnRequest]. - // In order to intercept and mutate requests, see [BrowserContext.Route] or [Page.Route]. - OnRequest(fn func(Request)) - - // Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, - // use [Page.OnRequestFailed]. - // **NOTE** HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request - // will complete with [BrowserContext.OnRequestFinished] event and not with [BrowserContext.OnRequestFailed]. - OnRequestFailed(fn func(Request)) - - // Emitted when a request finishes successfully after downloading the response body. For a successful response, the - // sequence of events is `request`, `response` and `requestfinished`. To listen for successful requests from a - // particular page, use [Page.OnRequestFinished]. - OnRequestFinished(fn func(Request)) - - // Emitted when [response] status and headers are received for a request. For a successful response, the sequence of - // events is `request`, `response` and `requestfinished`. To listen for response events from a particular page, use - // [Page.OnResponse]. - OnResponse(fn func(Response)) - - // Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies - // can be obtained via [BrowserContext.Cookies]. - AddCookies(cookies []OptionalCookie) error - - // Adds a script which would be evaluated in one of the following scenarios: - // - Whenever a page is created in the browser context or is navigated. - // - Whenever a child frame is attached or navigated in any page in the browser context. In this case, the script is - // evaluated in the context of the newly attached frame. - // The script is evaluated after the document was created but before any of its scripts were run. This is useful to - // amend the JavaScript environment, e.g. to seed `Math.random`. - // - // script: Script to be evaluated in all pages in the browser context. - AddInitScript(script Script) error - - // **NOTE** Background pages are only supported on Chromium-based browsers. - // All existing background pages in the context. - BackgroundPages() []Page - - // Returns the browser instance of the context. If it was launched as a persistent context null gets returned. - Browser() Browser - - // Removes cookies from context. Accepts optional filter. - ClearCookies(options ...BrowserContextClearCookiesOptions) error - - // Clears all permission overrides for the browser context. - ClearPermissions() error - - // Closes the browser context. All the pages that belong to the browser context will be closed. - // **NOTE** The default browser context cannot be closed. - Close(options ...BrowserContextCloseOptions) error - - // If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those - // URLs are returned. - Cookies(urls ...string) ([]Cookie, error) - - // The method adds a function called “[object Object]” on the `window` object of every frame in every page in the - // context. When called, the function executes “[object Object]” and returns a [Promise] which resolves to the return - // value of “[object Object]”. If the “[object Object]” returns a [Promise], it will be awaited. - // The first argument of the “[object Object]” function contains information about the caller: `{ browserContext: - // BrowserContext, page: Page, frame: Frame }`. - // See [Page.ExposeBinding] for page-only version. - // - // 1. name: Name of the function on the window object. - // 2. binding: Callback function that will be called in the Playwright's context. - ExposeBinding(name string, binding BindingCallFunction, handle ...bool) error - - // The method adds a function called “[object Object]” on the `window` object of every frame in every page in the - // context. When called, the function executes “[object Object]” and returns a [Promise] which resolves to the return - // value of “[object Object]”. - // If the “[object Object]” returns a [Promise], it will be awaited. - // See [Page.ExposeFunction] for page-only version. - // - // 1. name: Name of the function on the window object. - // 2. binding: Callback function that will be called in the Playwright's context. - ExposeFunction(name string, binding ExposedFunction) error - - // Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if - // specified. - // - // permissions: A list of permissions to grant. - // - // **NOTE** Supported permissions differ between browsers, and even between different versions of the same browser. - // Any permission may stop working after an update. - // - // Here are some permissions that may be supported by some browsers: - // - `'accelerometer'` - // - `'ambient-light-sensor'` - // - `'background-sync'` - // - `'camera'` - // - `'clipboard-read'` - // - `'clipboard-write'` - // - `'geolocation'` - // - `'gyroscope'` - // - `'magnetometer'` - // - `'microphone'` - // - `'midi-sysex'` (system-exclusive midi) - // - `'midi'` - // - `'notifications'` - // - `'payment-handler'` - // - `'storage-access'` - GrantPermissions(permissions []string, options ...BrowserContextGrantPermissionsOptions) error - - // **NOTE** CDP sessions are only supported on Chromium-based browsers. - // Returns the newly created session. - // - // page: Target to create new session for. For backwards-compatibility, this parameter is named `page`, but it can be a - // `Page` or `Frame` type. - NewCDPSession(page interface{}) (CDPSession, error) - - // Creates a new page in the browser context. - NewPage() (Page, error) - - // Returns all open pages in the context. - Pages() []Page - - // API testing helper associated with this context. Requests made with this API will use context cookies. - Request() APIRequestContext - - // Routing provides the capability to modify network requests that are made by any page in the browser context. Once - // route is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. - // **NOTE** [BrowserContext.Route] will not intercept requests intercepted by Service Worker. See - // [this] issue. We recommend disabling Service Workers when - // using request interception by setting “[object Object]” to `block`. - // - // 1. url: A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If “[object Object]” is - // set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the - // [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. - // 2. handler: handler function to route the request. - // - // [this]: https://github.com/microsoft/playwright/issues/1090 - Route(url interface{}, handler routeHandler, times ...int) error - - // If specified the network requests that are made in the context will be served from the HAR file. Read more about - // [Replaying from HAR]. - // Playwright will not serve requests intercepted by Service Worker from the HAR file. See - // [this] issue. We recommend disabling Service Workers when - // using request interception by setting “[object Object]” to `block`. - // - // har: Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a - // relative path, then it is resolved relative to the current working directory. - // - // [Replaying from HAR]: https://playwright.dev/docs/mock#replaying-from-har - // [this]: https://github.com/microsoft/playwright/issues/1090 - RouteFromHAR(har string, options ...BrowserContextRouteFromHAROptions) error - - // This method allows to modify websocket connections that are made by any page in the browser context. - // Note that only `WebSocket`s created after this method was called will be routed. It is recommended to call this - // method before creating any pages. - // - // 1. url: Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the - // “[object Object]” context option. - // 2. handler: Handler function to route the WebSocket. - RouteWebSocket(url interface{}, handler func(WebSocketRoute)) error - - // **NOTE** Service workers are only supported on Chromium-based browsers. - // All existing service workers in the context. - ServiceWorkers() []Worker - - // This setting will change the default maximum navigation time for the following methods and related shortcuts: - // - [Page.GoBack] - // - [Page.GoForward] - // - [Page.Goto] - // - [Page.Reload] - // - [Page.SetContent] - // - [Page.ExpectNavigation] - // **NOTE** [Page.SetDefaultNavigationTimeout] and [Page.SetDefaultTimeout] take priority over - // [BrowserContext.SetDefaultNavigationTimeout]. - // - // timeout: Maximum navigation time in milliseconds - SetDefaultNavigationTimeout(timeout float64) - - // This setting will change the default maximum time for all the methods accepting “[object Object]” option. - // **NOTE** [Page.SetDefaultNavigationTimeout], [Page.SetDefaultTimeout] and - // [BrowserContext.SetDefaultNavigationTimeout] take priority over [BrowserContext.SetDefaultTimeout]. - // - // timeout: Maximum time in milliseconds. Pass `0` to disable timeout. - SetDefaultTimeout(timeout float64) - - // The extra HTTP headers will be sent with every request initiated by any page in the context. These headers are - // merged with page-specific extra HTTP headers set with [Page.SetExtraHTTPHeaders]. If page overrides a particular - // header, page-specific header value will be used instead of the browser context header value. - // **NOTE** [BrowserContext.SetExtraHTTPHeaders] does not guarantee the order of headers in the outgoing requests. - // - // headers: An object containing additional HTTP headers to be sent with every request. All header values must be strings. - SetExtraHTTPHeaders(headers map[string]string) error - - // Sets the context's geolocation. Passing `null` or `undefined` emulates position unavailable. - SetGeolocation(geolocation *Geolocation) error - - // - // offline: Whether to emulate network being offline for the browser context. - SetOffline(offline bool) error - - // Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB - // snapshot. - StorageState(path ...string) (*StorageState, error) - - Tracing() Tracing - - // Removes all routes created with [BrowserContext.Route] and [BrowserContext.RouteFromHAR]. - UnrouteAll(options ...BrowserContextUnrouteAllOptions) error - - // Removes a route created with [BrowserContext.Route]. When “[object Object]” is not specified, removes all routes - // for the “[object Object]”. - // - // 1. url: A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with [BrowserContext.Route]. - // 2. handler: Optional handler function used to register a routing with [BrowserContext.Route]. - Unroute(url interface{}, handler ...routeHandler) error - - // Performs action and waits for a [ConsoleMessage] to be logged by in the pages in the context. If predicate is - // provided, it passes [ConsoleMessage] value into the `predicate` function and waits for `predicate(message)` to - // return a truthy value. Will throw an error if the page is closed before the [BrowserContext.OnConsole] event is - // fired. - ExpectConsoleMessage(cb func() error, options ...BrowserContextExpectConsoleMessageOptions) (ConsoleMessage, error) - - // Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy - // value. Will throw an error if the context closes before the event is fired. Returns the event data value. - // - // event: Event name, same one would pass into `browserContext.on(event)`. - ExpectEvent(event string, cb func() error, options ...BrowserContextExpectEventOptions) (interface{}, error) - - // Performs action and waits for a new [Page] to be created in the context. If predicate is provided, it passes [Page] - // value into the `predicate` function and waits for `predicate(event)` to return a truthy value. Will throw an error - // if the context closes before new [Page] is created. - ExpectPage(cb func() error, options ...BrowserContextExpectPageOptions) (Page, error) - - // **NOTE** In most cases, you should use [BrowserContext.ExpectEvent]. - // Waits for given `event` to fire. If predicate is provided, it passes event's value into the `predicate` function - // and waits for `predicate(event)` to return a truthy value. Will throw an error if the browser context is closed - // before the `event` is fired. - // - // event: Event name, same one typically passed into `*.on(event)`. - WaitForEvent(event string, options ...BrowserContextWaitForEventOptions) (interface{}, error) -} - -// BrowserType provides methods to launch a specific browser instance or connect to an existing one. The following is -// a typical example of using Playwright to drive automation: -type BrowserType interface { - // This method attaches Playwright to an existing browser instance created via `BrowserType.launchServer` in Node.js. - // **NOTE** The major and minor version of the Playwright instance that connects needs to match the version of - // Playwright that launches the browser (1.2.3 → is compatible with 1.2.x). - // - // wsEndpoint: A Playwright browser websocket endpoint to connect to. You obtain this endpoint via `BrowserServer.wsEndpoint`. - Connect(wsEndpoint string, options ...BrowserTypeConnectOptions) (Browser, error) - - // This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol. - // The default browser context is accessible via [Browser.Contexts]. - // **NOTE** Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers. - // **NOTE** This connection is significantly lower fidelity than the Playwright protocol connection via - // [BrowserType.Connect]. If you are experiencing issues or attempting to use advanced functionality, you probably - // want to use [BrowserType.Connect]. - // - // endpointURL: A CDP websocket endpoint or http url to connect to. For example `http://localhost:9222/` or - // `ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4`. - ConnectOverCDP(endpointURL string, options ...BrowserTypeConnectOverCDPOptions) (Browser, error) - - // A path where Playwright expects to find a bundled browser executable. - ExecutablePath() string - - // Returns the browser instance. - // - // [Chrome Canary]: https://www.google.com/chrome/browser/canary.html - // [Dev Channel]: https://www.chromium.org/getting-involved/dev-channel - // [this article]: https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/ - // [This article]: https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md - Launch(options ...BrowserTypeLaunchOptions) (Browser, error) - - // Returns the persistent browser context instance. - // Launches browser that uses persistent storage located at “[object Object]” and returns the only context. Closing - // this context will automatically close the browser. - // - // userDataDir: Path to a User Data Directory, which stores browser session data like cookies and local storage. Pass an empty - // string to create a temporary directory. - // - // More details for - // [Chromium](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction) and - // [Firefox](https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile). Chromium's user data directory is the - // **parent** directory of the "Profile Path" seen at `chrome://version`. - // - // Note that browsers do not allow launching multiple instances with the same User Data Directory. - LaunchPersistentContext(userDataDir string, options ...BrowserTypeLaunchPersistentContextOptions) (BrowserContext, error) - - // Returns browser name. For example: `chromium`, `webkit` or `firefox`. - Name() string -} - -// The `CDPSession` instances are used to talk raw Chrome Devtools Protocol: -// - protocol methods can be called with `session.send` method. -// - protocol events can be subscribed to with `session.on` method. -// -// Useful links: -// - Documentation on DevTools Protocol can be found here: -// [DevTools Protocol Viewer]. -// - Getting Started with DevTools Protocol: -// https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md -// -// [DevTools Protocol Viewer]: https://chromedevtools.github.io/devtools-protocol/ -type CDPSession interface { - EventEmitter - // Detaches the CDPSession from the target. Once detached, the CDPSession object won't emit any events and can't be - // used to send messages. - Detach() error - - // - // 1. method: Protocol method name. - // 2. params: Optional method parameters. - Send(method string, params map[string]interface{}) (interface{}, error) -} - -// Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Learn -// more about [clock emulation]. -// Note that clock is installed for the entire [BrowserContext], so the time in all the pages and iframes is -// controlled by the same clock. -// -// [clock emulation]: https://playwright.dev/docs/clock -type Clock interface { - // Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user - // closing the laptop lid for a while and reopening it later, after given time. - // - // ticks: Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are - // "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. - FastForward(ticks interface{}) error - - // Install fake implementations for the following time-related functions: - // - `Date` - // - `setTimeout` - // - `clearTimeout` - // - `setInterval` - // - `clearInterval` - // - `requestAnimationFrame` - // - `cancelAnimationFrame` - // - `requestIdleCallback` - // - `cancelIdleCallback` - // - `performance` - // Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, - // and control the behavior of time-dependent functions. See [Clock.RunFor] and [Clock.FastForward] for more - // information. - Install(options ...ClockInstallOptions) error - - // Advance the clock, firing all the time-related callbacks. - // - // ticks: Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are - // "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. - RunFor(ticks interface{}) error - - // Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired - // unless [Clock.RunFor], [Clock.FastForward], [Clock.PauseAt] or [Clock.Resume] is called. - // Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it - // at the specified time and pausing. - // - // time: Time to pause at. - PauseAt(time interface{}) error - - // Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual. - Resume() error - - // Makes `Date.now` and `new Date()` return fixed fake time at all times, keeps all the timers running. - // Use this method for simple scenarios where you only need to test with a predefined time. For more advanced - // scenarios, use [Clock.Install] instead. Read docs on [clock emulation] to learn more. - // - // time: Time to be set. - // - // [clock emulation]: https://playwright.dev/docs/clock - SetFixedTime(time interface{}) error - - // Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for - // example switching from summer to winter time, or changing time zones. - // - // time: Time to be set. - SetSystemTime(time interface{}) error -} - -// [ConsoleMessage] objects are dispatched by page via the [Page.OnConsole] event. For each console message logged in -// the page there will be corresponding event in the Playwright context. -type ConsoleMessage interface { - // List of arguments passed to a `console` function call. See also [Page.OnConsole]. - Args() []JSHandle - - Location() *ConsoleMessageLocation - - // The page that produced this console message, if any. - Page() Page - - // The text of the console message. - Text() string - - // The text of the console message. - String() string - - // One of the following values: `log`, `debug`, `info`, `error`, `warning`, `dir`, `dirxml`, `table`, - // `trace`, `clear`, `startGroup`, `startGroupCollapsed`, `endGroup`, `assert`, `profile`, - // `profileEnd`, `count`, `timeEnd`. - Type() string -} - -// [Dialog] objects are dispatched by page via the [Page.OnDialog] event. -// An example of using `Dialog` class: -// **NOTE** Dialogs are dismissed automatically, unless there is a [Page.OnDialog] listener. When listener is present, -// it **must** either [Dialog.Accept] or [Dialog.Dismiss] the dialog - otherwise the page will -// [freeze] waiting for the dialog, -// and actions like click will never finish. -// -// [freeze]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking -type Dialog interface { - // Returns when the dialog has been accepted. - Accept(promptText ...string) error - - // If dialog is prompt, returns default prompt value. Otherwise, returns empty string. - DefaultValue() string - - // Returns when the dialog has been dismissed. - Dismiss() error - - // A message displayed in the dialog. - Message() string - - // The page that initiated this dialog, if available. - Page() Page - - // Returns dialog's type, can be one of `alert`, `beforeunload`, `confirm` or `prompt`. - Type() string -} - -// [Download] objects are dispatched by page via the [Page.OnDownload] event. -// All the downloaded files belonging to the browser context are deleted when the browser context is closed. -// Download event is emitted once the download starts. Download path becomes available once download completes. -type Download interface { - // Cancels a download. Will not fail if the download is already finished or canceled. Upon successful cancellations, - // `download.failure()` would resolve to `canceled`. - Cancel() error - - // Deletes the downloaded file. Will wait for the download to finish if necessary. - Delete() error - - // Returns download error if any. Will wait for the download to finish if necessary. - Failure() error - - // Get the page that the download belongs to. - Page() Page - - // Returns path to the downloaded file for a successful download, or throws for a failed/canceled download. The method - // will wait for the download to finish if necessary. The method throws when connected remotely. - // Note that the download's file name is a random GUID, use [Download.SuggestedFilename] to get suggested file name. - Path() (string, error) - - // Copy the download to a user-specified path. It is safe to call this method while the download is still in progress. - // Will wait for the download to finish if necessary. - // - // path: Path where the download should be copied. - SaveAs(path string) error - - // Returns suggested filename for this download. It is typically computed by the browser from the - // [`Content-Disposition`] response - // header or the `download` attribute. See the spec on [whatwg]. - // Different browsers can use different logic for computing it. - // - // [`Content-Disposition`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition - // [whatwg]: https://html.spec.whatwg.org/#downloading-resources - SuggestedFilename() string - - // Returns downloaded url. - URL() string - - String() string -} - -// ElementHandle represents an in-page DOM element. ElementHandles can be created with the [Page.QuerySelector] -// -// method. -// **NOTE** The use of ElementHandle is discouraged, use [Locator] objects and web-first assertions instead. -// ElementHandle prevents DOM element from garbage collection unless the handle is disposed with [JSHandle.Dispose]. -// ElementHandles are auto-disposed when their origin frame gets navigated. -// ElementHandle instances can be used as an argument in [Page.EvalOnSelector] and [Page.Evaluate] methods. -// The difference between the [Locator] and ElementHandle is that the ElementHandle points to a particular element, -// while [Locator] captures the logic of how to retrieve an element. -// In the example below, handle points to a particular DOM element on page. If that element changes text or is used by -// React to render an entirely different component, handle is still pointing to that very DOM element. This can lead -// to unexpected behaviors. -// With the locator, every time the `element` is used, up-to-date DOM element is located in the page using the -// selector. So in the snippet below, underlying DOM element is going to be located twice. -type ElementHandle interface { - JSHandle - // This method returns the bounding box of the element, or `null` if the element is not visible. The bounding box is - // calculated relative to the main frame viewport - which is usually the same as the browser window. - // Scrolling affects the returned bounding box, similarly to - // [Element.GetBoundingClientRect]. - // That means `x` and/or `y` may be negative. - // Elements from child frames return the bounding box relative to the main frame, unlike the - // [Element.GetBoundingClientRect]. - // Assuming the page is static, it is safe to use bounding box coordinates to perform input. For example, the - // following snippet should click the center of the element. - // - // [Element.GetBoundingClientRect]: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect - // [Element.GetBoundingClientRect]: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect - BoundingBox() (*Rect, error) - - // This method checks the element by performing the following steps: - // 1. Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already - // checked, this method returns immediately. - // 2. Wait for [actionability] checks on the element, unless “[object Object]” option is set. - // 3. Scroll the element into view if needed. - // 4. Use [Page.Mouse] to click in the center of the element. - // 5. Ensure that the element is now checked. If not, this method throws. - // If the element is detached from the DOM at any moment during the action, this method throws. - // When all steps combined have not finished during the specified “[object Object]”, this method throws a - // [TimeoutError]. Passing zero timeout disables this. - // - // Deprecated: Use locator-based [Locator.Check] instead. Read more about [locators]. - // - // [actionability]: https://playwright.dev/docs/actionability - // [locators]: https://playwright.dev/docs/locators - Check(options ...ElementHandleCheckOptions) error - - // This method clicks the element by performing the following steps: - // 1. Wait for [actionability] checks on the element, unless “[object Object]” option is set. - // 2. Scroll the element into view if needed. - // 3. Use [Page.Mouse] to click in the center of the element, or the specified “[object Object]”. - // 4. Wait for initiated navigations to either succeed or fail, unless “[object Object]” option is set. - // If the element is detached from the DOM at any moment during the action, this method throws. - // When all steps combined have not finished during the specified “[object Object]”, this method throws a - // [TimeoutError]. Passing zero timeout disables this. - // - // Deprecated: Use locator-based [Locator.Click] instead. Read more about [locators]. - // - // [actionability]: https://playwright.dev/docs/actionability - // [locators]: https://playwright.dev/docs/locators - Click(options ...ElementHandleClickOptions) error - - // Returns the content frame for element handles referencing iframe nodes, or `null` otherwise - ContentFrame() (Frame, error) - - // This method double clicks the element by performing the following steps: - // 1. Wait for [actionability] checks on the element, unless “[object Object]” option is set. - // 2. Scroll the element into view if needed. - // 3. Use [Page.Mouse] to double click in the center of the element, or the specified “[object Object]”. - // If the element is detached from the DOM at any moment during the action, this method throws. - // When all steps combined have not finished during the specified “[object Object]”, this method throws a - // [TimeoutError]. Passing zero timeout disables this. - // **NOTE** `elementHandle.dblclick()` dispatches two `click` events and a single `dblclick` event. - // - // Deprecated: Use locator-based [Locator.Dblclick] instead. Read more about [locators]. - // - // [actionability]: https://playwright.dev/docs/actionability - // [locators]: https://playwright.dev/docs/locators - Dblclick(options ...ElementHandleDblclickOptions) error - - // The snippet below dispatches the `click` event on the element. Regardless of the visibility state of the element, - // `click` is dispatched. This is equivalent to calling - // [element.Click()]. - // - // Deprecated: Use locator-based [Locator.DispatchEvent] instead. Read more about [locators]. - // - // 1. typ: DOM event type: `"click"`, `"dragstart"`, etc. - // 2. eventInit: Optional event-specific initialization properties. - // - // [element.Click()]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click - // [DeviceMotionEvent]: https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent - // [DeviceOrientationEvent]: https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent - // [DragEvent]: https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent - // [Event]: https://developer.mozilla.org/en-US/docs/Web/API/Event/Event - // [FocusEvent]: https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent - // [KeyboardEvent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent - // [MouseEvent]: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent - // [PointerEvent]: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent - // [TouchEvent]: https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent - // [WheelEvent]: https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent - // [locators]: https://playwright.dev/docs/locators - DispatchEvent(typ string, eventInit ...interface{}) error - - // Returns the return value of “[object Object]”. - // The method finds an element matching the specified selector in the `ElementHandle`s subtree and passes it as a - // first argument to “[object Object]”. If no elements match the selector, the method throws an error. - // If “[object Object]” returns a [Promise], then [ElementHandle.EvalOnSelector] would wait for the promise to resolve - // and return its value. - // - // Deprecated: This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. Use [Locator.Evaluate], other [Locator] helper methods or web-first assertions instead. - // - // 1. selector: A selector to query for. - // 2. expression: JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the - // function is automatically invoked. - // 3. arg: Optional argument to pass to “[object Object]”. - EvalOnSelector(selector string, expression string, arg ...interface{}) (interface{}, error) - - // Returns the return value of “[object Object]”. - // The method finds all elements matching the specified selector in the `ElementHandle`'s subtree and passes an array - // of matched elements as a first argument to “[object Object]”. - // If “[object Object]” returns a [Promise], then [ElementHandle.EvalOnSelectorAll] would wait for the promise to - // resolve and return its value. - // - // Deprecated: In most cases, [Locator.EvaluateAll], other [Locator] helper methods and web-first assertions do a better job. - // - // 1. selector: A selector to query for. - // 2. expression: JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the - // function is automatically invoked. - // 3. arg: Optional argument to pass to “[object Object]”. - EvalOnSelectorAll(selector string, expression string, arg ...interface{}) (interface{}, error) - - // This method waits for [actionability] checks, focuses the element, fills it and triggers an - // `input` event after filling. Note that you can pass an empty string to clear the input field. - // If the target element is not an ``, `