diff --git a/.drone.yml b/.drone.yml index 589c20b24..cd56a9603 100644 --- a/.drone.yml +++ b/.drone.yml @@ -12,7 +12,7 @@ steps: # We use golangci-lint for linting. # See: https://golangci-lint.run/ - name: lint - image: golangci/golangci-lint:v1.57.2 + image: golangci/golangci-lint:v1.62.0 volumes: - name: go-build-cache path: /root/.cache/go-build @@ -28,7 +28,7 @@ steps: - pull_request - name: test - image: golang:1.22-alpine + image: golang:1.23-alpine volumes: - name: go-build-cache path: /root/.cache/go-build @@ -94,7 +94,7 @@ steps: - pull_request - name: snapshot - image: superseriousbusiness/gotosocial-drone-build:0.6.2 # https://github.com/superseriousbusiness/gotosocial-drone-build + image: superseriousbusiness/gotosocial-drone-build:0.7.0 # https://github.com/superseriousbusiness/gotosocial-drone-build volumes: - name: go-build-cache path: /root/.cache/go-build @@ -141,7 +141,7 @@ steps: - main - name: release - image: superseriousbusiness/gotosocial-drone-build:0.6.2 # https://github.com/superseriousbusiness/gotosocial-drone-build + image: superseriousbusiness/gotosocial-drone-build:0.7.0 # https://github.com/superseriousbusiness/gotosocial-drone-build volumes: - name: go-build-cache path: /root/.cache/go-build @@ -210,7 +210,7 @@ clone: steps: - name: mirror - image: superseriousbusiness/gotosocial-drone-build:0.6.2 + image: superseriousbusiness/gotosocial-drone-build:0.7.0 environment: ORIGIN_REPO: https://github.com/superseriousbusiness/gotosocial TARGET_REPO: https://codeberg.org/superseriousbusiness/gotosocial @@ -223,6 +223,6 @@ steps: --- kind: signature -hmac: 1b89e3a538fbca72eb9a0b398cd82f09a774ba3649013e19d36012eda327e83f +hmac: c79f1c3b16db8da7e3b01b960021a583ec81069aff8afd4425f049dd140f0620 ... diff --git a/.github/ISSUE_TEMPLATE/bug_frontend.yaml b/.github/ISSUE_TEMPLATE/bug_frontend.yaml index 7a6591763..23ddb4d31 100644 --- a/.github/ISSUE_TEMPLATE/bug_frontend.yaml +++ b/.github/ISSUE_TEMPLATE/bug_frontend.yaml @@ -1,6 +1,6 @@ name: Frontend Bug Report description: Report an issue related to the web frontend -title: "[bug] Issue Title" +title: "[bug/frontend] Issue Title" labels: ["bug", "frontend"] assignees: [] diff --git a/.goreleaser.yml b/.goreleaser.yml index 8b4589395..91e6afca9 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,12 +1,12 @@ -# https://goreleaser.com -project_name: gotosocial +# Version 2 of GoReleaser: https://goreleaser.com/errors/version/ version: 2 +project_name: gotosocial # https://goreleaser.com/customization/hooks/ before: hooks: # generate the swagger.yaml file using go-swagger and bundle it into the assets directory - - swagger generate spec --scan-models --exclude-deps -o web/assets/swagger.yaml + - go run ./vendor/github.com/go-swagger/go-swagger/cmd/swagger generate spec --scan-models --exclude-deps -o web/assets/swagger.yaml - sed -i "s/REPLACE_ME/{{ incpatch .Version }}/" web/assets/swagger.yaml # Install web deps + bundle web assets - yarn --cwd ./web/source install diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b52d6b59..4fa148f37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ These contribution guidelines were adapted from / inspired by those of Gitea (ht - [Finding your way around the code](#finding-your-way-around-the-code) - [Style / Linting / Formatting](#style--linting--formatting) - [Testing](#testing) - - [Standalone Testrig with Semaphore](#standalone-testrig-with-semaphore) + - [Standalone Testrig with Pinafore](#standalone-testrig-with-pinafore) - [Running automated tests](#running-automated-tests) - [SQLite](#sqlite) - [Postgres](#postgres) @@ -401,9 +401,9 @@ GoToSocial provides a [testrig](https://github.com/superseriousbusiness/gotosoci One thing that *isn't* mocked is the Database interface because it's just easier to use an in-memory SQLite database than to mock everything out. -#### Standalone Testrig with Semaphore +#### Standalone Testrig with Pinafore -You can launch a testrig as a standalone server running at localhost, which you can connect to using something like [Semaphore](https://github.com/NickColley/semaphore/). +You can launch a testrig as a standalone server running at localhost, which you can connect to using something like [Pinafore](https://github.com/nolanlawson/pinafore/). To do this, first build the gotosocial binary with `DEBUG=1 ./scripts/build.sh`. @@ -413,14 +413,14 @@ Then, launch the testrig with the `DEBUG` environment variable set by invoking t DEBUG=1 ./gotosocial testrig start ``` -To run Semaphore locally in dev mode, first clone the [Semaphore](https://github.com/NickColley/semaphore/) repository, and then run the following commands in the cloned directory: +To run Pinafore locally in dev mode, first clone the [Pinafore](https://github.com/nolanlawson/pinafore/) repository, and then run the following commands in the cloned directory: ```bash yarn # install dependencies yarn run dev ``` -The Semaphore instance will start running on `localhost:4002`. +The Pinafore instance will start running on `localhost:4002`. To connect to the testrig, navigate to `http://localhost:4002` and enter your instance name as `localhost:8080`. diff --git a/Dockerfile b/Dockerfile index df4fc56da..7b7728a53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # Dockerfile reference: https://docs.docker.com/engine/reference/builder/ # stage 1: generate up-to-date swagger.yaml to put in the final container -FROM --platform=${BUILDPLATFORM} golang:1.22-alpine AS swagger +FROM --platform=${BUILDPLATFORM} golang:1.23-alpine AS swagger RUN \ ### Installs goswagger for building swagger definitions inside this container @@ -28,7 +28,7 @@ RUN yarn --cwd ./web/source install && \ rm -rf ./web/source # stage 3: build the executor container -FROM --platform=${TARGETPLATFORM} alpine:3.19.1 as executor +FROM --platform=${TARGETPLATFORM} alpine:3.20 as executor # switch to non-root user:group for GtS USER 1000:1000 diff --git a/README.md b/README.md index c37af7533..3f9f357e2 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ The Mastodon API has become the de facto standard for client communication with Though most apps that implement the Mastodon API should work, GoToSocial is tested and works reliably with beautiful apps like: * [Tusky](https://tusky.app/) for Android -* [Semaphore](https://semaphore.social/) in the browser +* [Pinafore](https://pinafore.social/) in the browser * [Feditext](https://github.com/feditext/feditext) (beta) on iOS, iPadOS and macOS If you've used Mastodon with a third-party app before, you'll find using GoToSocial a breeze. diff --git a/docs/admin/settings.md b/docs/admin/settings.md index 0efb5bf45..170d07e6a 100644 --- a/docs/admin/settings.md +++ b/docs/admin/settings.md @@ -167,3 +167,11 @@ Links to the set contact account and/or email address will appear on the footer The selected **contact user** must be an active (not suspended) admin and/or moderator on the instance. If you're on a single-user instance and you give admin privileges to your main account, you can just fill in your own username here; you don't need to make a separate admin account just for this. + +### Instance Custom CSS + +custom CSS allows you to further customize the way your instance looks when visited through a browser. + +This custom CSS will be applied to all pages of your instance. Users themes and CSS still take precedence over this customization. + +See the [Custom CSS](./custom_css.md) page for some tips on writing custom CSS for your instance. diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml index 699460434..ab24ed5f3 100644 --- a/docs/api/swagger.yaml +++ b/docs/api/swagger.yaml @@ -1570,6 +1570,10 @@ definitions: $ref: '#/definitions/instanceV1Configuration' contact_account: $ref: '#/definitions/account' + custom_css: + description: Custom CSS for the instance. + type: string + x-go-name: CustomCSS debug: description: Whether or not instance is running in DEBUG mode. Omitted if false. type: boolean @@ -1750,6 +1754,10 @@ definitions: $ref: '#/definitions/instanceV2Configuration' contact: $ref: '#/definitions/instanceV2Contact' + custom_css: + description: Instance Custom Css + type: string + x-go-name: CustomCSS debug: description: Whether or not instance is running in DEBUG mode. Omitted if false. type: boolean diff --git a/docs/faq.md b/docs/faq.md index 521c97531..72fed557d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## Where's the user interface? -GoToSocial is just a bare server for the most part and is designed to be used thru external applications. [Semaphore](https://semaphore.social/) in the browser, [Tusky](https://tusky.app/) for Android and [Feditext](https://github.com/feditext/feditext) for iOS, iPadOS and macOS are the best-supported. Anything that supports the Mastodon API should work, other than the features GoToSocial doesn't yet have. Permalinks and profile pages are served directly through GoToSocial as well as the settings panel, but most interaction goes through the apps. +GoToSocial is just a bare server for the most part and is designed to be used thru external applications. [Pinafore](https://pinafore.social/) in the browser, [Tusky](https://tusky.app/) for Android and [Feditext](https://github.com/feditext/feditext) for iOS, iPadOS and macOS are the best-supported. Anything that supports the Mastodon API should work, other than the features GoToSocial doesn't yet have. Permalinks and profile pages are served directly through GoToSocial as well as the settings panel, but most interaction goes through the apps. ## Why aren't my posts showing up on my profile page? diff --git a/docs/getting_started/reverse_proxy/websocket.md b/docs/getting_started/reverse_proxy/websocket.md index ec7c107a9..68d28bc5c 100644 --- a/docs/getting_started/reverse_proxy/websocket.md +++ b/docs/getting_started/reverse_proxy/websocket.md @@ -1,6 +1,6 @@ # WebSocket -GoToSocial uses the secure [WebSocket protocol](https://en.wikipedia.org/wiki/WebSocket) (aka `wss`) to allow for streaming updates of statuses and notifications via client apps like Semaphore. +GoToSocial uses the secure [WebSocket protocol](https://en.wikipedia.org/wiki/WebSocket) (aka `wss`) to allow for streaming updates of statuses and notifications via client apps like Pinafore. In order to use this functionality, you need to ensure that whatever proxy you've configured GoToSocial to run behind allows WebSocket connections through. diff --git a/docs/locales/zh/api/swagger.yaml b/docs/locales/zh/api/swagger.yaml index 7751c47e3..070a9448c 100644 --- a/docs/locales/zh/api/swagger.yaml +++ b/docs/locales/zh/api/swagger.yaml @@ -4980,7 +4980,7 @@ paths: - description: 此表情的代码,将被实例居民用于选定对应表情。此代码在实例上必须是唯一的。 in: formData name: shortcode - pattern: \w{2,30} + pattern: \w{1,30} required: true type: string - description: 此表情的 png 或 gif 图像。动画 png 也可以!为了确保与其他 fedi 实现的兼容性,默认情况下表情大小限制为 50kb。 @@ -5130,7 +5130,7 @@ paths: - description: 用于表情的代码,将被实例居民用于选定表情。此代码在实例上必须是唯一的。仅适用于 `copy` 操作类型。 in: formData name: shortcode - pattern: \w{2,30} + pattern: \w{1,30} type: string - description: 此表情的新 png 或 gif 图像。动画 png 也可以!为了确保与其他 fedi 实现的兼容性,默认情况下表情大小限制为 50kb。仅适用于 **本站** 表情。 in: formData @@ -5639,6 +5639,417 @@ paths: summary: 吊销实例密钥 tags: - admin + /api/v1/admin/domain_permission_drafts: + get: + description: |- + 该端点将返回按时间倒序排序(最新优先),并带有连续 ID 的域名权限草案(ID 值越大,草稿越新)。可以通过返回的 Link 标头解析下一页与上一页查询。 + + 示例: + ``` + ; rel="next", ; rel="prev" + ```` + operationId: domainPermissionDraftsGet + parameters: + - description: 仅显示给定订阅 ID 创建的草案。 + in: query + name: subscription_id + type: string + - description: 仅显示针对特定域名的草案。 + in: query + name: domain + type: string + - description: 筛选“屏蔽”与“放行”类型的草案。 + in: query + name: permission_type + type: string + - description: 仅返回早于给定 max ID 的条目(用于向下分页)。具有对应 ID 的条目不会包含在响应中。 + in: query + name: max_id + type: string + - description: 仅返回晚于给定 since ID 的条目(用于向上分页)。具有对应 ID 的条目不会包含在响应中。 + in: query + name: since_id + type: string + - description: 仅返回相邻且晚于给定 min ID 的条目(用于向上分页)。具有对应 ID 的条目不会包含在响应中。 + in: query + name: min_id + type: string + - default: 20 + description: 要返回的条目数量。 + in: query + maximum: 100 + minimum: 1 + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: 域名权限草案。 + headers: + Link: + description: 下一查询与上一查询的链接。 + type: string + schema: + items: + $ref: '#/definitions/domainPermission' + type: array + "400": + description: bad request 无效请求 + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止 + "404": + description: not found 未找到 + "406": + description: not acceptable 不可接受 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 查看域名权限草案。 + tags: + - admin + post: + consumes: + - multipart/form-data + - application/json + operationId: domainPermissionDraftCreate + parameters: + - description: 该草案要针对的域名。 + in: formData + name: domain + type: string + - description: 草案类型为“放行”或“屏蔽”。 + in: formData + name: permission_type + type: string + - description: 对外公开展示时混淆具体域名。例如:`example.org` 将变为类似 `ex***e.org` 的字符串。 + in: formData + name: obfuscate + type: boolean + - description: 对此域名权限的公开评注。若您选择分享此权限设定,此评注将与权限条目一起显示。 + in: formData + name: public_comment + type: string + - description: 对此域名权限的私人评注。仅显示给其他管理员,因此这是一个可用于记录为什么某个域名最终被添加此权限设定的有用的内部手段。 + in: formData + name: private_comment + type: string + produces: + - application/json + responses: + "200": + description: 新创建的域名权限草案。 + schema: + $ref: '#/definitions/domainPermission' + "400": + description: bad request 无效请求 + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "406": + description: not acceptable 不可接受 + "409": + description: conflict 冲突 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 使用给定参数创建一条域名权限草案。 + tags: + - admin + /api/v1/admin/domain_permission_drafts/{id}: + get: + operationId: domainPermissionDraftGet + parameters: + - description: 域名权限草案的 ID。 + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: 域名权限草案。 + schema: + $ref: '#/definitions/domainPermission' + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "404": + description: not found 未找到 + "406": + description: not acceptable 不可接受 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 获取具有给定 ID 的域名权限草案。 + tags: + - admin + /api/v1/admin/domain_permission_drafts/{id}/accept: + post: + consumes: + - multipart/form-data + - application/json + operationId: domainPermissionDraftAccept + parameters: + - description: 域名权限草案的 ID。 + in: path + name: id + required: true + type: string + - default: false + description: 若已经存在一条具有相同域名与权限设定类型的草案,使用新草案的字段覆盖现有权限设定。 + in: formData + name: overwrite + type: boolean + produces: + - application/json + responses: + "200": + description: 新创建的域名权限草案。 + schema: + $ref: '#/definitions/domainPermission' + "400": + description: bad request 无效请求 + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "406": + description: not acceptable 不可接受 + "409": + description: conflict 冲突 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 接受一条域名权限草案,将其转换为会得到强制执行的域名权限。 + tags: + - admin + /api/v1/admin/domain_permission_drafts/{id}/remove: + post: + consumes: + - multipart/form-data + - application/json + operationId: domainPermissionDraftRemove + parameters: + - description: 域名权限草案的 ID。 + in: path + name: id + required: true + type: string + - default: false + description: 删除此域名权限草案时,为目标域名创建一个域名排除条目,以确保之后不会为此域名创建草案。 + in: formData + name: exclude_target + type: boolean + produces: + - application/json + responses: + "200": + description: 被移除的域名权限草案。 + schema: + $ref: '#/definitions/domainPermission' + "400": + description: bad request 无效请求 + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "406": + description: not acceptable 不可接受 + "409": + description: conflict 冲突 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 移除一条域名权限草案,可选择忽略所有之后的针对给定域名的草案。 + tags: + - admin + /api/v1/admin/domain_permission_excludes: + get: + description: |- + 返回按时间倒序排序(新创建的条目优先),并带有连续 ID 的域名权限排除条目(ID 值越大,排除条目越新)。可以通过返回的 Link 标头解析下一页与上一页查询。 + 示例: + ``` + ; rel="next", ; rel="prev" + ``` + operationId: domainPermissionExcludesGet + parameters: + - description: 仅返回针对给定域名的排除条目。 + in: query + name: domain + type: string + - description: 仅返回比给定 max ID 新的条目(用于向下分页)。具有对应 ID 的条目不会包含在响应中。 + in: query + name: max_id + type: string + - description: 仅返回比给定 since ID 新的条目(用于向上分页)。具有对应 ID 的条目不会包含在响应中。 + in: query + name: since_id + type: string + - description: 仅返回比给定 min ID 相邻且更新的条目(用于向上分页)。具有对应 ID 的条目不会包含在响应中。 + in: query + name: min_id + type: string + - default: 20 + description: 要返回的条目数量。 + in: query + maximum: 100 + minimum: 1 + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: 域名权限排除条目。 + headers: + Link: + description: 下一查询与上一查询的链接。 + type: string + schema: + items: + $ref: '#/definitions/domainPermission' + type: array + "400": + description: bad request 无效请求 + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "404": + description: not found 未找到 + "406": + description: not acceptable 不可接受 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 查看域名权限排除条目。 + tags: + - admin + post: + consumes: + - multipart/form-data + - application/json + description: |- + 被排除的域名(及其子域名)在导入或订阅域名权限列表时不会被自动屏蔽或放行。 + 您仍然可以为被排除的域名手动创建域名屏蔽条目或域名放行条目,被排除之后,与该域名关联的任何的已有或新创建的域名屏蔽条目或域名放行条目都将被继续执行。 + operationId: domainPermissionExcludeCreate + parameters: + - description: 要创建权限排除的域名。 + in: formData + name: domain + type: string + - description: 对该域名排除条目的私密评论。 + in: formData + name: private_comment + type: string + produces: + - application/json + responses: + "200": + description: 新创建的域名排除条目。 + schema: + $ref: '#/definitions/domainPermission' + "400": + description: bad request 无效请求 + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "406": + description: not acceptable 不可接受 + "409": + description: conflict 冲突 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 使用给定参数创建一个域名权限排除条目。 + tags: + - admin + /api/v1/admin/domain_permission_excludes/{id}: + delete: + operationId: domainPermissionExcludeDelete + parameters: + - description: 该域名权限排除条目的 ID。 + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: 被移除的域名权限排除条目。 + schema: + $ref: '#/definitions/domainPermission' + "400": + description: bad request 无效请求 + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "406": + description: not acceptable 不可接受 + "409": + description: conflict 冲突 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 移除一个域名权限排除条目。 + tags: + - admin + get: + operationId: domainPermissionExcludeGet + parameters: + - description: 域名权限排除条目的 ID。 + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: 域名权限排除条目。 + schema: + $ref: '#/definitions/domainPermission' + "401": + description: unauthorized 未授权 + "403": + description: forbidden 禁止访问 + "404": + description: not found 未找到 + "406": + description: not acceptable 不可接受 + "500": + description: internal server error 服务器内部错误 + security: + - OAuth2 Bearer: + - admin + summary: 获取具有给定 ID 的域名权限排除。 + tags: + - admin /api/v1/admin/email/test: post: consumes: diff --git a/docs/locales/zh/configuration/storage.md b/docs/locales/zh/configuration/storage.md index 24bb40d45..c2f1c467f 100644 --- a/docs/locales/zh/configuration/storage.md +++ b/docs/locales/zh/configuration/storage.md @@ -1,5 +1,9 @@ # 存储 +When configuring an object storage backend, the `storage-s3-endpoint` **must not** include the bucket name. That's what `s3-bucket-name` is for. Using subfolders in a bucket isn't currently supported. + +配置对象存储后端时,`storage-s3-endpoint` **不得** 包含存储桶名称。`s3-bucket-name`负责配置存储桶名称。目前不支持使用特定存储桶的子目录作为存储后端。 + ## 设置 ```yaml diff --git a/docs/locales/zh/faq.md b/docs/locales/zh/faq.md index 8383d7462..a49272202 100644 --- a/docs/locales/zh/faq.md +++ b/docs/locales/zh/faq.md @@ -2,7 +2,7 @@ ## 用户界面在哪? -GoToSocial 的大部分内容只是一个裸服务端,用于通过第三方应用程序进行使用。可通过 [Semaphore](https://semaphore.social/) 在浏览器使用,可通过 [Tusky](https://tusky.app/) 在 Android 使用,可通过 [Feditext](https://github.com/feditext/feditext) 在 iOS、iPadOS 和 macOS 使用。这些应用程序兼容性最好。任何由 Mastodon API 提供的实例功能都应该可以工作,除非它们是 GoToSocial 尚不具备的功能。永久链接和个账户页是通过 GoToSocial 直接提供的,设置面板也是如此,但大多数交互都是通过应用程序完成的。 +GoToSocial 的大部分内容只是一个裸服务端,用于通过第三方应用程序进行使用。可通过 [Pinafore](https://pinafore.social/) 在浏览器使用,可通过 [Tusky](https://tusky.app/) 在 Android 使用,可通过 [Feditext](https://github.com/feditext/feditext) 在 iOS、iPadOS 和 macOS 使用。这些应用程序兼容性最好。任何由 Mastodon API 提供的实例功能都应该可以工作,除非它们是 GoToSocial 尚不具备的功能。永久链接和个账户页是通过 GoToSocial 直接提供的,设置面板也是如此,但大多数交互都是通过应用程序完成的。 ## 为什么我的贴文没有显示在我的账户页面上? diff --git a/docs/locales/zh/getting_started/reverse_proxy/websocket.md b/docs/locales/zh/getting_started/reverse_proxy/websocket.md index e1391ec45..f265edf37 100644 --- a/docs/locales/zh/getting_started/reverse_proxy/websocket.md +++ b/docs/locales/zh/getting_started/reverse_proxy/websocket.md @@ -1,6 +1,6 @@ # WebSocket -GoToSocial 使用安全的 [WebSocket 协议](https://en.wikipedia.org/wiki/WebSocket)(即 `wss`)来通过客户端应用程序(如 Semaphore)实现贴文和通知的实时更新。 +GoToSocial 使用安全的 [WebSocket 协议](https://en.wikipedia.org/wiki/WebSocket)(即 `wss`)来通过客户端应用程序(如 Pinafore)实现贴文和通知的实时更新。 为了使用此功能,你需要确保配置 GoToSocial 所在的代理允许 WebSocket 连接通过。 diff --git a/docs/locales/zh/repo/CONTRIBUTING.md b/docs/locales/zh/repo/CONTRIBUTING.md index 4d05b180e..4da713ad3 100644 --- a/docs/locales/zh/repo/CONTRIBUTING.md +++ b/docs/locales/zh/repo/CONTRIBUTING.md @@ -24,7 +24,7 @@ - [浏览代码结构](#浏览代码结构) - [风格/代码检查/格式化](#风格代码检查格式化) - [测试](#测试) - - [独立测试环境与 Semaphore](#独立测试环境与-semaphore) + - [独立测试环境与 Pinafore](#独立测试环境与-pinafore) - [运行自动化测试](#运行自动化测试) - [SQLite](#sqlite) - [Postgres](#postgres) @@ -400,9 +400,9 @@ GoToSocial 提供了一个 [testrig](https://github.com/superseriousbusiness/got 没有模拟的一个东西是数据库接口,因为使用内存中的 SQLite 数据库比模拟所有东西要简单得多。 -#### 独立测试环境与 Semaphore +#### 独立测试环境与 Pinafore -你可以启动一个在本地主机运行的独立测试服务器 testrig,可以通过 [Semaphore](https://github.com/NickColley/semaphore/) 连接。 +你可以启动一个在本地主机运行的独立测试服务器 testrig,可以通过 [Pinafore](https://github.com/NickColley/pinafore/) 连接。 要做到这一点,首先用 `DEBUG=1 ./scripts/build.sh` 构建 gotosocial 二进制文件。 @@ -412,14 +412,14 @@ GoToSocial 提供了一个 [testrig](https://github.com/superseriousbusiness/got DEBUG=1 ./gotosocial testrig start ``` -要在本地开发模式下运行 Semaphore,首先克隆 [Semaphore](https://github.com/NickColley/semaphore/) 存储库,然后在克隆的目录中运行以下命令: +要在本地开发模式下运行 Pinafore,首先克隆 [Pinafore](https://github.com/nolanlawson/pinafore/) 存储库,然后在克隆的目录中运行以下命令: ```bash yarn # 安装依赖 yarn run dev ``` -Semaphore 实例将在 `localhost:4002` 上启动。 +Pinafore 实例将在 `localhost:4002` 上启动。 要连接到 testrig,导航至 `http://localhost:4002`,并将在实例域名栏输入 `localhost:8080`。 diff --git a/docs/locales/zh/repo/README.md b/docs/locales/zh/repo/README.md index 82761a4b5..24b5591fc 100644 --- a/docs/locales/zh/repo/README.md +++ b/docs/locales/zh/repo/README.md @@ -113,7 +113,7 @@ Mastodon API 已成为客户端与联邦宇宙服务端通信的事实标准, 大多数实现 Mastodon API 的应用程序都应该可以使用 GoToSocial,但以下这些优秀的应用程序已经过测试,可与 GoToSocial 可靠地配合使用: * [Tusky](https://tusky.app/) 适用于 Android -* [Semaphore](https://semaphore.social/) 适用于浏览器 +* [Pinafore](https://pinafore.social/) 适用于浏览器 * [Feditext](https://github.com/feditext/feditext) (beta) 适用于 iOS, iPadOS 和 macOS 如果你之前通过第三方应用来使用 Mastodon,使用 GoToSocial 将是轻而易举的。 diff --git a/docs/locales/zh/user_guide/posts.md b/docs/locales/zh/user_guide/posts.md index 8c41dcd77..911eb2beb 100644 --- a/docs/locales/zh/user_guide/posts.md +++ b/docs/locales/zh/user_guide/posts.md @@ -36,6 +36,9 @@ GoToSocial 为贴文提供 Mastodon 风格的隐私设置。从最私密到最 ### 互关可见 +!!! warning + 目前暂时无法将帖文可见性设为“互关可见”。 + `互关可见` 的贴文只会显示给贴文作者和与作者*互相关注*的人。换句话说,只有在满足两个条件时,其他人才能看到: 1. 其他账户关注贴文作者。 diff --git a/docs/overrides/public/admin-settings-instance.png b/docs/overrides/public/admin-settings-instance.png index 181a35a7c..1203e8a41 100644 Binary files a/docs/overrides/public/admin-settings-instance.png and b/docs/overrides/public/admin-settings-instance.png differ diff --git a/docs/user_guide/posts.md b/docs/user_guide/posts.md index 6e2f57e6f..1f718cfae 100644 --- a/docs/user_guide/posts.md +++ b/docs/user_guide/posts.md @@ -36,6 +36,9 @@ Direct posts are **not** accessible via a web URL on your GoToSocial instance. ### Mutuals-only +!!! warning + Mutuals-only posts are not currently functional. + Posts with a visibility of `mutuals_only` will only appear to the post author, and to *mutual follows* of the post author. In other words, they can only be seen by others if two conditions are met: 1. The other account follows the post author. diff --git a/go.mod b/go.mod index 9e6db71f1..6b81958e7 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,12 @@ module github.com/superseriousbusiness/gotosocial -go 1.22.2 +go 1.23 + +// Replace go-swagger with our version that fixes (ours particularly) use of Go1.23 +replace github.com/go-swagger/go-swagger => github.com/superseriousbusiness/go-swagger v0.31.0-gts-go1.23-fix // Replace modernc/sqlite with our version that fixes the concurrency INTERRUPT issue -replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround +replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.34.2-concurrency-workaround // Below pin otel libraries to v1.29.0 until we can figure out issues replace go.opentelemetry.io/otel => go.opentelemetry.io/otel v1.29.0 @@ -28,7 +31,7 @@ require ( codeberg.org/gruf/go-debug v1.3.0 codeberg.org/gruf/go-errors/v2 v2.3.2 codeberg.org/gruf/go-fastcopy v1.1.3 - codeberg.org/gruf/go-ffmpreg v0.6.0 + codeberg.org/gruf/go-ffmpreg v0.6.1 codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf codeberg.org/gruf/go-kv v1.6.5 codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f @@ -38,7 +41,7 @@ require ( codeberg.org/gruf/go-sched v1.2.4 codeberg.org/gruf/go-storage v0.2.0 codeberg.org/gruf/go-structr v0.8.11 - codeberg.org/superseriousbusiness/exif-terminator v0.9.0 + codeberg.org/superseriousbusiness/exif-terminator v0.9.1 github.com/DmitriyVTitov/size v1.5.0 github.com/KimMachineGun/automemlimit v0.6.1 github.com/buckket/go-blurhash v1.1.0 @@ -52,31 +55,31 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/gorilla/feeds v1.2.0 - github.com/gorilla/websocket v1.5.2 + github.com/gorilla/websocket v1.5.3 github.com/jackc/pgx/v5 v5.7.1 github.com/k3a/html2text v1.2.1 github.com/microcosm-cc/bluemonday v1.0.27 github.com/miekg/dns v1.1.62 - github.com/minio/minio-go/v7 v7.0.80 + github.com/minio/minio-go/v7 v7.0.81 github.com/mitchellh/mapstructure v1.5.0 - github.com/ncruces/go-sqlite3 v0.20.2 + github.com/ncruces/go-sqlite3 v0.20.3 github.com/oklog/ulid v1.3.1 github.com/prometheus/client_golang v1.20.5 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/superseriousbusiness/activity v1.9.0-gts github.com/superseriousbusiness/httpsig v1.2.0-SSB github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 - github.com/tdewolff/minify/v2 v2.21.1 + github.com/tdewolff/minify/v2 v2.21.2 github.com/technologize/otel-go-contrib v1.1.1 - github.com/tetratelabs/wazero v1.8.1 + github.com/tetratelabs/wazero v1.8.2 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/ulule/limiter/v3 v3.11.2 - github.com/uptrace/bun v1.2.5 - github.com/uptrace/bun/dialect/pgdialect v1.2.5 - github.com/uptrace/bun/dialect/sqlitedialect v1.2.5 - github.com/uptrace/bun/extra/bunotel v1.2.5 + github.com/uptrace/bun v1.2.6 + github.com/uptrace/bun/dialect/pgdialect v1.2.6 + github.com/uptrace/bun/dialect/sqlitedialect v1.2.6 + github.com/uptrace/bun/extra/bunotel v1.2.6 github.com/wagslane/go-password-validator v0.3.0 github.com/yuin/goldmark v1.7.8 go.opentelemetry.io/otel v1.32.0 @@ -108,7 +111,9 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -213,7 +218,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect - github.com/tdewolff/parse/v2 v2.7.18 // indirect + github.com/tdewolff/parse/v2 v2.7.19 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -221,6 +226,7 @@ require ( github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect diff --git a/go.sum b/go.sum index f00a34326..e2e671e54 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ codeberg.org/gruf/go-fastcopy v1.1.3 h1:Jo9VTQjI6KYimlw25PPc7YLA3Xm+XMQhaHwKnM7x codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s= codeberg.org/gruf/go-fastpath/v2 v2.0.0 h1:iAS9GZahFhyWEH0KLhFEJR+txx1ZhMXxYzu2q5Qo9c0= codeberg.org/gruf/go-fastpath/v2 v2.0.0/go.mod h1:3pPqu5nZjpbRrOqvLyAK7puS1OfEtQvjd6342Cwz56Q= -codeberg.org/gruf/go-ffmpreg v0.6.0 h1:/cfUJ9bFKEoXT9LDYZy3eZ0HF60YWcO+0nGciepJKMw= -codeberg.org/gruf/go-ffmpreg v0.6.0/go.mod h1:Ar5nbt3tB2Wr0uoaqV3wDBNwAx+H+AB/mV7Kw7NlZTI= +codeberg.org/gruf/go-ffmpreg v0.6.1 h1:HitxOKPhbwARV469h6jY9a3IlROiwAN6QTbTxLRBnC8= +codeberg.org/gruf/go-ffmpreg v0.6.1/go.mod h1:HQmEaBF83rHOt2Jo1yJv9D0JApoSLFtVR9Uzu7aVglk= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A= codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk= codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0= @@ -72,8 +72,8 @@ codeberg.org/gruf/go-storage v0.2.0 h1:mKj3Lx6AavEkuXXtxqPhdq+akW9YwrnP16yQBF7K5 codeberg.org/gruf/go-storage v0.2.0/go.mod h1:o3GzMDE5QNUaRnm/daUzFqvuAaC4utlgXDXYO79sWKU= codeberg.org/gruf/go-structr v0.8.11 h1:I3cQCHpK3fQSXWaaUfksAJRN4+efULiuF11Oi/m8c+o= codeberg.org/gruf/go-structr v0.8.11/go.mod h1:zkoXVrAnKosh8VFAsbP/Hhs8FmLBjbVVy5w/Ngm8ApM= -codeberg.org/superseriousbusiness/exif-terminator v0.9.0 h1:/EfyGI6HIrbkhFwgXGSjZ9o1kr/+k8v4mKdfXTH02Go= -codeberg.org/superseriousbusiness/exif-terminator v0.9.0/go.mod h1:gCWKduudUWFzsnixoMzu0FYVdxHWG+AbXnZ50DqxsUE= +codeberg.org/superseriousbusiness/exif-terminator v0.9.1 h1:8Pss29AVuvljHAYLnZUyoqJp/8IN1cD3Jz30bJbxme8= +codeberg.org/superseriousbusiness/exif-terminator v0.9.1/go.mod h1:gCWKduudUWFzsnixoMzu0FYVdxHWG+AbXnZ50DqxsUE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -97,10 +97,14 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buckket/go-blurhash v1.1.0 h1:X5M6r0LIvwdvKiUtiNcRL2YlmOfMzYobI3VCKCZc9Do= github.com/buckket/go-blurhash v1.1.0/go.mod h1:aT2iqo5W9vu9GpyoLErKfTHwgODsZp3bQfXjXJUxNb8= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= @@ -238,8 +242,6 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0= -github.com/go-swagger/go-swagger v0.31.0 h1:H8eOYQnY2u7vNKWDNykv2xJP3pBhRG/R+SOCAmKrLlc= -github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= @@ -330,8 +332,8 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw= -github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -411,8 +413,8 @@ github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.80 h1:2mdUHXEykRdY/BigLt3Iuu1otL0JTogT0Nmltg0wujk= -github.com/minio/minio-go/v7 v7.0.80/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= +github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA= +github.com/minio/minio-go/v7 v7.0.81/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -432,8 +434,8 @@ github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/ncruces/go-sqlite3 v0.20.2 h1:cMLIwrLZQuCWVCEOowSqlIlpzgbag3jnYVW4NM5u01M= -github.com/ncruces/go-sqlite3 v0.20.2/go.mod h1:yL4ZNWGsr1/8pcLfpPW1RT1WFdvyeHonrgIwwi4rvkg= +github.com/ncruces/go-sqlite3 v0.20.3 h1:+4G4uEqOeusF0yRuQVUl9fuoEebUolwQSnBUjYBLYIw= +github.com/ncruces/go-sqlite3 v0.20.3/go.mod h1:ojLIAB243gtz68Eo283Ps+k9PyR3dvzS+9/RgId4+AA= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= @@ -525,8 +527,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/superseriousbusiness/activity v1.9.0-gts h1:qWMDeiGdnVi+XG7CfuM7ET87qe9adousU6utWItBX/o= @@ -535,21 +538,23 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430 github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4= github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB h1:8psprYSK1KdOSH7yQ4PbJq0YYaGQY+gzdW/B0ExDb/8= github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB/go.mod h1:ymKGfy9kg4dIdraeZRAdobMS/flzLk3VcRPLpEWOAXg= +github.com/superseriousbusiness/go-swagger v0.31.0-gts-go1.23-fix h1:CXcjArOyxBPFgsNAu4As+RK9BwOUEG1LL7ja4g7iax0= +github.com/superseriousbusiness/go-swagger v0.31.0-gts-go1.23-fix/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po= github.com/superseriousbusiness/httpsig v1.2.0-SSB h1:BinBGKbf2LSuVT5+MuH0XynHN9f0XVshx2CTDtkaWj0= github.com/superseriousbusiness/httpsig v1.2.0-SSB/go.mod h1:+rxfATjFaDoDIVaJOTSP0gj6UrbicaYPEptvCLC9F28= github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ= github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo= -github.com/tdewolff/minify/v2 v2.21.1 h1:AAf5iltw6+KlUvjRNPAPrANIXl3XEJNBBzuZom5iCAM= -github.com/tdewolff/minify/v2 v2.21.1/go.mod h1:PoqFH8ugcuTUvKqVM9vOqXw4msxvuhL/DTmV5ZXhSCI= -github.com/tdewolff/parse/v2 v2.7.18 h1:uSqjEMT2lwCj5oifBHDcWU2kN1pbLrRENgFWDJa57eI= -github.com/tdewolff/parse/v2 v2.7.18/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/minify/v2 v2.21.2 h1:VfTvmGVtBYhMTlUAeHtXM7XOsW0JT/6uMwUPPqgUs9k= +github.com/tdewolff/minify/v2 v2.21.2/go.mod h1:Olje3eHdBnrMjINKffDsil/3NV98Iv7MhWf7556WQVg= +github.com/tdewolff/parse/v2 v2.7.19 h1:7Ljh26yj+gdLFEq/7q9LT4SYyKtwQX4ocNrj45UCePg= +github.com/tdewolff/parse/v2 v2.7.19/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw= github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So= -github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= -github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= +github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= @@ -580,14 +585,14 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA= github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI= -github.com/uptrace/bun v1.2.5 h1:gSprL5xiBCp+tzcZHgENzJpXnmQwRM/A6s4HnBF85mc= -github.com/uptrace/bun v1.2.5/go.mod h1:vkQMS4NNs4VNZv92y53uBSHXRqYyJp4bGhMHgaNCQpY= -github.com/uptrace/bun/dialect/pgdialect v1.2.5 h1:dWLUxpjTdglzfBks2x+U2WIi+nRVjuh7Z3DLYVFswJk= -github.com/uptrace/bun/dialect/pgdialect v1.2.5/go.mod h1:stwnlE8/6x8cuQ2aXcZqwDK/d+6jxgO3iQewflJT6C4= -github.com/uptrace/bun/dialect/sqlitedialect v1.2.5 h1:liDvMaIWrN8DrHcxVbviOde/VDss9uhcqpcTSL3eJjc= -github.com/uptrace/bun/dialect/sqlitedialect v1.2.5/go.mod h1:Mw6IDL/jNUL5ozcREAezOJSZ9Jm4LJlfoaXxBEfNBlM= -github.com/uptrace/bun/extra/bunotel v1.2.5 h1:kkuuTbrG9d5leYZuSBKhq2gtq346lIrxf98Mig2y128= -github.com/uptrace/bun/extra/bunotel v1.2.5/go.mod h1:rCHLszRZwppWE9cGDodO2FCI1qCrLwDjONp38KD3bA8= +github.com/uptrace/bun v1.2.6 h1:lyGBQAhNiClchb97HA2cBnDeRxwTRLhSIgiFPXVisV8= +github.com/uptrace/bun v1.2.6/go.mod h1:xMgnVFf+/5xsrFBU34HjDJmzZnXbVuNEt/Ih56I8qBU= +github.com/uptrace/bun/dialect/pgdialect v1.2.6 h1:iNd1YLx619K+sZK+dRcWPzluurXYK1QwIkp9FEfNB/8= +github.com/uptrace/bun/dialect/pgdialect v1.2.6/go.mod h1:OL7d3qZLxKYP8kxNhMg3IheN1pDR3UScGjoUP+ivxJQ= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.6 h1:p8vA39kR9Ypw0so+gUhFhd8NOufx3MzvoxJeUpwieQU= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.6/go.mod h1:sdGy8eCv9WVGDrPhagE9i7FASeyj3BFkHzkRMF/qK3w= +github.com/uptrace/bun/extra/bunotel v1.2.6 h1:6m90acv9hsDuTYRo3oiKCWMatGPmi+feKAx8Y/GPj9A= +github.com/uptrace/bun/extra/bunotel v1.2.6/go.mod h1:QGqnFNJ2H88juh7DmgdPJZVN9bSTpj7UaGllSO9JDKk= github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -602,6 +607,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= +github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41 h1:rnB8ZLMeAr3VcqjfRkAm27qb8y6zFKNfuHvy1Gfe7KI= +github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo= 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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -623,8 +630,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround h1:pFMJnlc1PuH+jcVz4vz53vcpnoZG+NqFBr3qikDmEB4= -gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +gitlab.com/NyaaaWhatsUpDoc/sqlite v1.34.2-concurrency-workaround h1:Z/9vgdPNZm8ZDANnIJ7ZGeYKJ5biqPY1OQbN+DLCtec= +gitlab.com/NyaaaWhatsUpDoc/sqlite v1.34.2-concurrency-workaround/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/internal/ap/extract.go b/internal/ap/extract.go index f5486a051..543ee8dca 100644 --- a/internal/ap/extract.go +++ b/internal/ap/extract.go @@ -1027,7 +1027,7 @@ func ExtractVisibility(addressable Addressable, actorFollowersURI string) (gtsmo ) if len(to) == 0 && len(cc) == 0 { - return "", gtserror.Newf("message wasn't TO or CC anyone") + return 0, gtserror.Newf("message wasn't TO or CC anyone") } // Assume most restrictive visibility, diff --git a/internal/api/client/accounts/accountverify_test.go b/internal/api/client/accounts/accountverify_test.go index af8c2346c..3f67cdefb 100644 --- a/internal/api/client/accounts/accountverify_test.go +++ b/internal/api/client/accounts/accountverify_test.go @@ -28,7 +28,6 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -99,7 +98,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() { suite.Equal(2, apimodelAccount.FollowersCount) suite.Equal(2, apimodelAccount.FollowingCount) suite.Equal(8, apimodelAccount.StatusesCount) - suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy) + suite.EqualValues(apimodel.VisibilityPublic, apimodelAccount.Source.Privacy) suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language) suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note) } diff --git a/internal/api/client/admin/domainpermissiondraftsget.go b/internal/api/client/admin/domainpermissiondraftsget.go index dd3315857..d63179afc 100644 --- a/internal/api/client/admin/domainpermissiondraftsget.go +++ b/internal/api/client/admin/domainpermissiondraftsget.go @@ -147,16 +147,12 @@ func (m *Module) DomainPermissionDraftsGETHandler(c *gin.Context) { return } - permType := c.Query(apiutil.DomainPermissionPermTypeKey) - switch permType { - case "", "block", "allow": - // No problem. - - default: - // Invalid. + permTypeStr := c.Query(apiutil.DomainPermissionPermTypeKey) + permType := gtsmodel.ParseDomainPermissionType(permTypeStr) + if permType == gtsmodel.DomainPermissionUnknown { text := fmt.Sprintf( "permission_type %s not recognized, valid values are empty string, block, or allow", - permType, + permTypeStr, ) errWithCode := gtserror.NewErrorBadRequest(errors.New(text), text) apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) @@ -173,7 +169,7 @@ func (m *Module) DomainPermissionDraftsGETHandler(c *gin.Context) { c.Request.Context(), c.Query(apiutil.DomainPermissionSubscriptionIDKey), c.Query(apiutil.DomainPermissionDomainKey), - gtsmodel.NewDomainPermissionType(permType), + permType, page, ) if errWithCode != nil { diff --git a/internal/api/client/filters/v1/filterpost_test.go b/internal/api/client/filters/v1/filterpost_test.go index 2b18abf13..b7aecc573 100644 --- a/internal/api/client/filters/v1/filterpost_test.go +++ b/internal/api/client/filters/v1/filterpost_test.go @@ -41,6 +41,7 @@ func (suite *FiltersTestSuite) postFilter( irreversible *bool, wholeWord *bool, expiresIn *int, + expiresInStr *string, requestJson *string, expectedHTTPStatus int, expectedBody string, @@ -75,6 +76,8 @@ func (suite *FiltersTestSuite) postFilter( } if expiresIn != nil { ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)} + } else if expiresInStr != nil { + ctx.Request.Form["expires_in"] = []string{*expiresInStr} } } @@ -124,7 +127,7 @@ func (suite *FiltersTestSuite) TestPostFilterFull() { irreversible := false wholeWord := true expiresIn := 86400 - filter, err := suite.postFilter(&phrase, &context, &irreversible, &wholeWord, &expiresIn, nil, http.StatusOK, "") + filter, err := suite.postFilter(&phrase, &context, &irreversible, &wholeWord, &expiresIn, nil, nil, http.StatusOK, "") if err != nil { suite.FailNow(err.Error()) } @@ -155,7 +158,7 @@ func (suite *FiltersTestSuite) TestPostFilterFullJSON() { "whole_word": true, "expires_in": 86400.1 }` - filter, err := suite.postFilter(nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "") + filter, err := suite.postFilter(nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "") if err != nil { suite.FailNow(err.Error()) } @@ -182,7 +185,7 @@ func (suite *FiltersTestSuite) TestPostFilterMinimal() { phrase := "GNU/Linux" context := []string{"home"} - filter, err := suite.postFilter(&phrase, &context, nil, nil, nil, nil, http.StatusOK, "") + filter, err := suite.postFilter(&phrase, &context, nil, nil, nil, nil, nil, http.StatusOK, "") if err != nil { suite.FailNow(err.Error()) } @@ -203,7 +206,7 @@ func (suite *FiltersTestSuite) TestPostFilterMinimal() { func (suite *FiltersTestSuite) TestPostFilterEmptyPhrase() { phrase := "" context := []string{"home"} - _, err := suite.postFilter(&phrase, &context, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&phrase, &context, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -211,7 +214,7 @@ func (suite *FiltersTestSuite) TestPostFilterEmptyPhrase() { func (suite *FiltersTestSuite) TestPostFilterMissingPhrase() { context := []string{"home"} - _, err := suite.postFilter(nil, &context, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(nil, &context, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -220,7 +223,7 @@ func (suite *FiltersTestSuite) TestPostFilterMissingPhrase() { func (suite *FiltersTestSuite) TestPostFilterEmptyContext() { phrase := "GNU/Linux" context := []string{} - _, err := suite.postFilter(&phrase, &context, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&phrase, &context, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -228,7 +231,7 @@ func (suite *FiltersTestSuite) TestPostFilterEmptyContext() { func (suite *FiltersTestSuite) TestPostFilterMissingContext() { phrase := "GNU/Linux" - _, err := suite.postFilter(&phrase, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&phrase, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -237,8 +240,37 @@ func (suite *FiltersTestSuite) TestPostFilterMissingContext() { // There should be a filter with this phrase as its title in our test fixtures. Creating another should fail. func (suite *FiltersTestSuite) TestPostFilterTitleConflict() { phrase := "fnord" - _, err := suite.postFilter(&phrase, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&phrase, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } } + +// postFilterWithExpiration creates a filter with optional expiration. +func (suite *FiltersTestSuite) postFilterWithExpiration(phrase *string, expiresIn *int, expiresInStr *string, requestJson *string) *apimodel.FilterV1 { + context := []string{"home"} + filter, err := suite.postFilter(phrase, &context, nil, nil, expiresIn, expiresInStr, requestJson, http.StatusOK, "") + if err != nil { + suite.FailNow(err.Error()) + } + return filter +} + +// Regression test for https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPostFilterWithEmptyStringExpiration() { + title := "Form Sins" + expiresInStr := "" + filter := suite.postFilterWithExpiration(&title, nil, &expiresInStr, nil) + suite.Nil(filter.ExpiresAt) +} + +// Regression test related to https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPostFilterWithNullExpirationJSON() { + requestJson := `{ + "phrase": "JSON Sins", + "context": ["home"], + "expires_in": null + }` + filter := suite.postFilterWithExpiration(nil, nil, nil, &requestJson) + suite.Nil(filter.ExpiresAt) +} diff --git a/internal/api/client/filters/v1/filterput_test.go b/internal/api/client/filters/v1/filterput_test.go index 40b52ee43..626bd52eb 100644 --- a/internal/api/client/filters/v1/filterput_test.go +++ b/internal/api/client/filters/v1/filterput_test.go @@ -42,6 +42,7 @@ func (suite *FiltersTestSuite) putFilter( irreversible *bool, wholeWord *bool, expiresIn *int, + expiresInStr *string, requestJson *string, expectedHTTPStatus int, expectedBody string, @@ -76,6 +77,8 @@ func (suite *FiltersTestSuite) putFilter( } if expiresIn != nil { ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)} + } else if expiresInStr != nil { + ctx.Request.Form["expires_in"] = []string{*expiresInStr} } } @@ -128,7 +131,7 @@ func (suite *FiltersTestSuite) TestPutFilterFull() { irreversible := false wholeWord := true expiresIn := 86400 - filter, err := suite.putFilter(id, &phrase, &context, &irreversible, &wholeWord, &expiresIn, nil, http.StatusOK, "") + filter, err := suite.putFilter(id, &phrase, &context, &irreversible, &wholeWord, &expiresIn, nil, nil, http.StatusOK, "") if err != nil { suite.FailNow(err.Error()) } @@ -160,7 +163,7 @@ func (suite *FiltersTestSuite) TestPutFilterFullJSON() { "whole_word": true, "expires_in": 86400.1 }` - filter, err := suite.putFilter(id, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "") + filter, err := suite.putFilter(id, nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "") if err != nil { suite.FailNow(err.Error()) } @@ -188,7 +191,7 @@ func (suite *FiltersTestSuite) TestPutFilterMinimal() { id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID phrase := "GNU/Linux" context := []string{"home"} - filter, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, http.StatusOK, "") + filter, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusOK, "") if err != nil { suite.FailNow(err.Error()) } @@ -210,7 +213,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyPhrase() { id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID phrase := "" context := []string{"home"} - _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -219,7 +222,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyPhrase() { func (suite *FiltersTestSuite) TestPutFilterMissingPhrase() { id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID context := []string{"home"} - _, err := suite.putFilter(id, nil, &context, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.putFilter(id, nil, &context, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -229,7 +232,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyContext() { id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID phrase := "GNU/Linux" context := []string{} - _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -238,7 +241,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyContext() { func (suite *FiltersTestSuite) TestPutFilterMissingContext() { id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID phrase := "GNU/Linux" - _, err := suite.putFilter(id, &phrase, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.putFilter(id, &phrase, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -248,7 +251,7 @@ func (suite *FiltersTestSuite) TestPutFilterMissingContext() { func (suite *FiltersTestSuite) TestPutFilterTitleConflict() { id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID phrase := "metasyntactic variables" - _, err := suite.putFilter(id, &phrase, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.putFilter(id, &phrase, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") if err != nil { suite.FailNow(err.Error()) } @@ -258,7 +261,7 @@ func (suite *FiltersTestSuite) TestPutAnotherAccountsFilter() { id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID phrase := "GNU/Linux" context := []string{"home"} - _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`) + _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`) if err != nil { suite.FailNow(err.Error()) } @@ -268,8 +271,60 @@ func (suite *FiltersTestSuite) TestPutNonexistentFilter() { id := "not_even_a_real_ULID" phrase := "GNU/Linux" context := []string{"home"} - _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`) + _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`) if err != nil { suite.FailNow(err.Error()) } } + +// setFilterExpiration sets filter expiration. +func (suite *FiltersTestSuite) setFilterExpiration(id string, phrase *string, expiresIn *int, expiresInStr *string, requestJson *string) *apimodel.FilterV1 { + context := []string{"home"} + filter, err := suite.putFilter(id, phrase, &context, nil, nil, expiresIn, expiresInStr, requestJson, http.StatusOK, "") + if err != nil { + suite.FailNow(err.Error()) + } + return filter +} + +// Regression test for https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPutFilterUnsetExpirationDateEmptyString() { + filterKeyword := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"] + id := filterKeyword.ID + phrase := filterKeyword.Keyword + + // Setup: set an expiration date for the filter. + expiresIn := 86400 + filter := suite.setFilterExpiration(id, &phrase, &expiresIn, nil, nil) + if !suite.NotNil(filter.ExpiresAt) { + suite.FailNow("Test precondition failed") + } + + // Unset the filter's expiration date by setting it to an empty string. + expiresInStr := "" + filter = suite.setFilterExpiration(id, &phrase, nil, &expiresInStr, nil) + suite.Nil(filter.ExpiresAt) +} + +// Regression test related to https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPutFilterUnsetExpirationDateNullJSON() { + filterKeyword := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"] + id := filterKeyword.ID + phrase := filterKeyword.Keyword + + // Setup: set an expiration date for the filter. + expiresIn := 86400 + filter := suite.setFilterExpiration(id, &phrase, &expiresIn, nil, nil) + if !suite.NotNil(filter.ExpiresAt) { + suite.FailNow("Test precondition failed") + } + + // Unset the filter's expiration date by setting it to a null literal. + requestJson := `{ + "phrase": "fnord", + "context": ["home"], + "expires_in": null + }` + filter = suite.setFilterExpiration(id, nil, nil, nil, &requestJson) + suite.Nil(filter.ExpiresAt) +} diff --git a/internal/api/client/filters/v1/validate.go b/internal/api/client/filters/v1/validate.go index 9e876c8cf..9e31abb89 100644 --- a/internal/api/client/filters/v1/validate.go +++ b/internal/api/client/filters/v1/validate.go @@ -46,12 +46,11 @@ func validateNormalizeCreateUpdateFilter(form *apimodel.FilterCreateUpdateReques return errors.New("irreversible aka server-side drop filters are not supported yet") } - // Normalize filter expiry if necessary. - if form.ExpiresInI != nil { - // If we parsed this as JSON, expires_in - // may be either a float64 or a string. + // If `expires_in` was provided + // as JSON, then normalize it. + if form.ExpiresInI.IsSpecified() { var err error - form.ExpiresIn, err = apiutil.ParseDuration( + form.ExpiresIn, err = apiutil.ParseNullableDuration( form.ExpiresInI, "expires_in", ) @@ -60,10 +59,5 @@ func validateNormalizeCreateUpdateFilter(form *apimodel.FilterCreateUpdateReques } } - // Interpret zero as indefinite duration. - if form.ExpiresIn != nil && *form.ExpiresIn == 0 { - form.ExpiresIn = nil - } - return nil } diff --git a/internal/api/client/filters/v2/filterpost.go b/internal/api/client/filters/v2/filterpost.go index 632c4402f..ad6e83060 100644 --- a/internal/api/client/filters/v2/filterpost.go +++ b/internal/api/client/filters/v2/filterpost.go @@ -225,12 +225,11 @@ func validateNormalizeCreateFilter(form *apimodel.FilterCreateRequestV2) error { // Apply defaults for missing fields. form.FilterAction = util.Ptr(action) - // Normalize filter expiry if necessary. - if form.ExpiresInI != nil { - // If we parsed this as JSON, expires_in - // may be either a float64 or a string. + // If `expires_in` was provided + // as JSON, then normalize it. + if form.ExpiresInI.IsSpecified() { var err error - form.ExpiresIn, err = apiutil.ParseDuration( + form.ExpiresIn, err = apiutil.ParseNullableDuration( form.ExpiresInI, "expires_in", ) @@ -239,11 +238,6 @@ func validateNormalizeCreateFilter(form *apimodel.FilterCreateRequestV2) error { } } - // Interpret zero as indefinite duration. - if form.ExpiresIn != nil && *form.ExpiresIn == 0 { - form.ExpiresIn = nil - } - // Normalize and validate new keywords and statuses. for i, formKeyword := range form.Keywords { if err := validate.FilterKeyword(formKeyword.Keyword); err != nil { diff --git a/internal/api/client/filters/v2/filterpost_test.go b/internal/api/client/filters/v2/filterpost_test.go index 6e378874c..7a79f4665 100644 --- a/internal/api/client/filters/v2/filterpost_test.go +++ b/internal/api/client/filters/v2/filterpost_test.go @@ -36,7 +36,7 @@ import ( "github.com/superseriousbusiness/gotosocial/testrig" ) -func (suite *FiltersTestSuite) postFilter(title *string, context *[]string, action *string, expiresIn *int, keywordsAttributesKeyword *[]string, keywordsAttributesWholeWord *[]bool, statusesAttributesStatusID *[]string, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) { +func (suite *FiltersTestSuite) postFilter(title *string, context *[]string, action *string, expiresIn *int, expiresInStr *string, keywordsAttributesWholeWord *[]bool, statusesAttributesStatusID *[]string, requestJson *string, expectedHTTPStatus int, expectedBody string, keywordsAttributesKeyword *[]string) (*apimodel.FilterV2, error) { // instantiate recorder + test context recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) @@ -64,6 +64,8 @@ func (suite *FiltersTestSuite) postFilter(title *string, context *[]string, acti } if expiresIn != nil { ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)} + } else if expiresInStr != nil { + ctx.Request.Form["expires_in"] = []string{*expiresInStr} } if keywordsAttributesKeyword != nil { ctx.Request.Form["keywords_attributes[][keyword]"] = *keywordsAttributesKeyword @@ -130,7 +132,7 @@ func (suite *FiltersTestSuite) TestPostFilterFull() { keywordsAttributesWholeWord := []bool{true, false} // Checked in lexical order by status ID, so keep this sorted. statusAttributesStatusID := []string{"01HEN2QRFA8H3C6QPN7RD4KSR6", "01HEWV37MHV8BAC8ANFGVRRM5D"} - filter, err := suite.postFilter(&title, &context, &action, &expiresIn, &keywordsAttributesKeyword, &keywordsAttributesWholeWord, &statusAttributesStatusID, nil, http.StatusOK, "") + filter, err := suite.postFilter(&title, &context, &action, &expiresIn, nil, &keywordsAttributesWholeWord, &statusAttributesStatusID, nil, http.StatusOK, "", &keywordsAttributesKeyword) if err != nil { suite.FailNow(err.Error()) } @@ -197,7 +199,7 @@ func (suite *FiltersTestSuite) TestPostFilterFullJSON() { } ] }` - filter, err := suite.postFilter(nil, nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "") + filter, err := suite.postFilter(nil, nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -245,7 +247,7 @@ func (suite *FiltersTestSuite) TestPostFilterMinimal() { title := "GNU/Linux" context := []string{"home"} - filter, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusOK, "") + filter, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusOK, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -267,7 +269,7 @@ func (suite *FiltersTestSuite) TestPostFilterMinimal() { func (suite *FiltersTestSuite) TestPostFilterEmptyTitle() { title := "" context := []string{"home"} - _, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -275,7 +277,7 @@ func (suite *FiltersTestSuite) TestPostFilterEmptyTitle() { func (suite *FiltersTestSuite) TestPostFilterMissingTitle() { context := []string{"home"} - _, err := suite.postFilter(nil, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(nil, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -284,7 +286,7 @@ func (suite *FiltersTestSuite) TestPostFilterMissingTitle() { func (suite *FiltersTestSuite) TestPostFilterEmptyContext() { title := "GNU/Linux" context := []string{} - _, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&title, &context, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -292,7 +294,7 @@ func (suite *FiltersTestSuite) TestPostFilterEmptyContext() { func (suite *FiltersTestSuite) TestPostFilterMissingContext() { title := "GNU/Linux" - _, err := suite.postFilter(&title, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&title, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -301,8 +303,37 @@ func (suite *FiltersTestSuite) TestPostFilterMissingContext() { // Creating another filter with the same title should fail. func (suite *FiltersTestSuite) TestPostFilterTitleConflict() { title := suite.testFilters["local_account_1_filter_1"].Title - _, err := suite.postFilter(&title, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "") + _, err := suite.postFilter(&title, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, "", nil) if err != nil { suite.FailNow(err.Error()) } } + +// postFilterWithExpiration creates a filter with optional expiration. +func (suite *FiltersTestSuite) postFilterWithExpiration(title *string, expiresIn *int, expiresInStr *string, requestJson *string) *apimodel.FilterV2 { + context := []string{"home"} + filter, err := suite.postFilter(title, &context, nil, expiresIn, expiresInStr, nil, nil, requestJson, http.StatusOK, "", nil) + if err != nil { + suite.FailNow(err.Error()) + } + return filter +} + +// Regression test for https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPostFilterWithEmptyStringExpiration() { + title := "Form Crimes" + expiresInStr := "" + filter := suite.postFilterWithExpiration(&title, nil, &expiresInStr, nil) + suite.Nil(filter.ExpiresAt) +} + +// Regression test related to https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPostFilterWithNullExpirationJSON() { + requestJson := `{ + "title": "JSON Crimes", + "context": ["home"], + "expires_in": null + }` + filter := suite.postFilterWithExpiration(nil, nil, nil, &requestJson) + suite.Nil(filter.ExpiresAt) +} diff --git a/internal/api/client/filters/v2/filterput.go b/internal/api/client/filters/v2/filterput.go index cde03360d..9c1b56dd6 100644 --- a/internal/api/client/filters/v2/filterput.go +++ b/internal/api/client/filters/v2/filterput.go @@ -269,12 +269,11 @@ func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error { } } - // Normalize filter expiry if necessary. - if form.ExpiresInI != nil { - // If we parsed this as JSON, expires_in - // may be either a float64 or a string. + // If `expires_in` was provided + // as JSON, then normalize it. + if form.ExpiresInI.IsSpecified() { var err error - form.ExpiresIn, err = apiutil.ParseDuration( + form.ExpiresIn, err = apiutil.ParseNullableDuration( form.ExpiresInI, "expires_in", ) @@ -283,11 +282,6 @@ func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error { } } - // Interpret zero as indefinite duration. - if form.ExpiresIn != nil && *form.ExpiresIn == 0 { - form.ExpiresIn = nil - } - // Normalize and validate updates. for i, formKeyword := range form.Keywords { if formKeyword.Keyword != nil { diff --git a/internal/api/client/filters/v2/filterput_test.go b/internal/api/client/filters/v2/filterput_test.go index d82d84b20..afa858ba9 100644 --- a/internal/api/client/filters/v2/filterput_test.go +++ b/internal/api/client/filters/v2/filterput_test.go @@ -36,7 +36,7 @@ import ( "github.com/superseriousbusiness/gotosocial/testrig" ) -func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context *[]string, action *string, expiresIn *int, keywordsAttributesID *[]string, keywordsAttributesKeyword *[]string, keywordsAttributesWholeWord *[]bool, keywordsAttributesDestroy *[]bool, statusesAttributesID *[]string, statusesAttributesStatusID *[]string, statusesAttributesDestroy *[]bool, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) { +func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context *[]string, action *string, expiresIn *int, expiresInStr *string, keywordsAttributesKeyword *[]string, keywordsAttributesWholeWord *[]bool, keywordsAttributesDestroy *[]bool, statusesAttributesID *[]string, statusesAttributesStatusID *[]string, statusesAttributesDestroy *[]bool, requestJson *string, expectedHTTPStatus int, expectedBody string, keywordsAttributesID *[]string) (*apimodel.FilterV2, error) { // instantiate recorder + test context recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) @@ -64,6 +64,8 @@ func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context } if expiresIn != nil { ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)} + } else if expiresInStr != nil { + ctx.Request.Form["expires_in"] = []string{*expiresInStr} } if keywordsAttributesID != nil { ctx.Request.Form["keywords_attributes[][id]"] = *keywordsAttributesID @@ -159,7 +161,7 @@ func (suite *FiltersTestSuite) TestPutFilterFull() { keywordsAttributesWholeWord := []bool{true, false, true} keywordsAttributesDestroy := []bool{false, true} statusesAttributesStatusID := []string{suite.testStatuses["remote_account_1_status_2"].ID} - filter, err := suite.putFilter(id, &title, &context, &action, &expiresIn, &keywordsAttributesID, &keywordsAttributesKeyword, &keywordsAttributesWholeWord, &keywordsAttributesDestroy, nil, &statusesAttributesStatusID, nil, nil, http.StatusOK, "") + filter, err := suite.putFilter(id, &title, &context, &action, &expiresIn, nil, &keywordsAttributesKeyword, &keywordsAttributesWholeWord, &keywordsAttributesDestroy, nil, &statusesAttributesStatusID, nil, nil, http.StatusOK, "", &keywordsAttributesID) if err != nil { suite.FailNow(err.Error()) } @@ -231,7 +233,7 @@ func (suite *FiltersTestSuite) TestPutFilterFullJSON() { } ] }` - filter, err := suite.putFilter(id, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "") + filter, err := suite.putFilter(id, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &requestJson, http.StatusOK, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -281,7 +283,7 @@ func (suite *FiltersTestSuite) TestPutFilterMinimal() { id := suite.testFilters["local_account_1_filter_1"].ID title := "GNU/Linux" context := []string{"home"} - filter, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusOK, "") + filter, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusOK, "", nil) if err != nil { suite.FailNow(err.Error()) } @@ -302,7 +304,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyTitle() { id := suite.testFilters["local_account_1_filter_1"].ID title := "" context := []string{"home"} - _, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter title must be provided, and must be no more than 200 chars"}`) + _, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter title must be provided, and must be no more than 200 chars"}`, nil) if err != nil { suite.FailNow(err.Error()) } @@ -312,7 +314,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyContext() { id := suite.testFilters["local_account_1_filter_1"].ID title := "GNU/Linux" context := []string{} - _, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: at least one filter context is required"}`) + _, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: at least one filter context is required"}`, nil) if err != nil { suite.FailNow(err.Error()) } @@ -322,7 +324,7 @@ func (suite *FiltersTestSuite) TestPutFilterEmptyContext() { func (suite *FiltersTestSuite) TestPutFilterTitleConflict() { id := suite.testFilters["local_account_1_filter_1"].ID title := suite.testFilters["local_account_1_filter_2"].Title - _, err := suite.putFilter(id, &title, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusConflict, `{"error":"Conflict: you already have a filter with this title"}`) + _, err := suite.putFilter(id, &title, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusConflict, `{"error":"Conflict: you already have a filter with this title"}`, nil) if err != nil { suite.FailNow(err.Error()) } @@ -332,7 +334,7 @@ func (suite *FiltersTestSuite) TestPutAnotherAccountsFilter() { id := suite.testFilters["local_account_2_filter_1"].ID title := "GNU/Linux" context := []string{"home"} - _, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`) + _, err := suite.putFilter(id, &title, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`, nil) if err != nil { suite.FailNow(err.Error()) } @@ -342,8 +344,70 @@ func (suite *FiltersTestSuite) TestPutNonexistentFilter() { id := "not_even_a_real_ULID" phrase := "GNU/Linux" context := []string{"home"} - _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`) + _, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`, nil) if err != nil { suite.FailNow(err.Error()) } } + +// setFilterExpiration sets filter expiration. +func (suite *FiltersTestSuite) setFilterExpiration(id string, expiresIn *int, expiresInStr *string, requestJson *string) *apimodel.FilterV2 { + filter, err := suite.putFilter(id, nil, nil, nil, expiresIn, expiresInStr, nil, nil, nil, nil, nil, nil, requestJson, http.StatusOK, "", nil) + if err != nil { + suite.FailNow(err.Error()) + } + return filter +} + +// Regression test for https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPutFilterUnsetExpirationDateEmptyString() { + id := suite.testFilters["local_account_1_filter_2"].ID + + // Setup: set an expiration date for the filter. + expiresIn := 86400 + filter := suite.setFilterExpiration(id, &expiresIn, nil, nil) + if !suite.NotNil(filter.ExpiresAt) { + suite.FailNow("Test precondition failed") + } + + // Unset the filter's expiration date by setting it to an empty string. + expiresInStr := "" + filter = suite.setFilterExpiration(id, nil, &expiresInStr, nil) + suite.Nil(filter.ExpiresAt) +} + +// Regression test related to https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPutFilterUnsetExpirationDateNullJSON() { + id := suite.testFilters["local_account_1_filter_3"].ID + + // Setup: set an expiration date for the filter. + expiresIn := 86400 + filter := suite.setFilterExpiration(id, &expiresIn, nil, nil) + if !suite.NotNil(filter.ExpiresAt) { + suite.FailNow("Test precondition failed") + } + + // Unset the filter's expiration date by setting it to a null literal. + requestJson := `{ + "expires_in": null + }` + filter = suite.setFilterExpiration(id, nil, nil, &requestJson) + suite.Nil(filter.ExpiresAt) +} + +// Regression test related to https://github.com/superseriousbusiness/gotosocial/issues/3497 +func (suite *FiltersTestSuite) TestPutFilterUnalteredExpirationDateJSON() { + id := suite.testFilters["local_account_1_filter_4"].ID + + // Setup: set an expiration date for the filter. + expiresIn := 86400 + filter := suite.setFilterExpiration(id, &expiresIn, nil, nil) + if !suite.NotNil(filter.ExpiresAt) { + suite.FailNow("Test precondition failed") + } + + // Update nothing. There should still be an expiration date. + requestJson := `{}` + filter = suite.setFilterExpiration(id, nil, nil, &requestJson) + suite.NotNil(filter.ExpiresAt) +} diff --git a/internal/api/client/instance/instancepatch.go b/internal/api/client/instance/instancepatch.go index 64263caf6..5085399eb 100644 --- a/internal/api/client/instance/instancepatch.go +++ b/internal/api/client/instance/instancepatch.go @@ -175,6 +175,7 @@ func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error form.ContactEmail == nil && form.ShortDescription == nil && form.Description == nil && + form.CustomCSS == nil && form.Terms == nil && form.Avatar == nil && form.AvatarDescription == nil && diff --git a/internal/api/client/notifications/notificationsget.go b/internal/api/client/notifications/notificationsget.go index f7bcf1994..b530c515d 100644 --- a/internal/api/client/notifications/notificationsget.go +++ b/internal/api/client/notifications/notificationsget.go @@ -18,14 +18,16 @@ package notifications import ( - "fmt" + "context" "net/http" - "strconv" "github.com/gin-gonic/gin" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/internal/paging" ) // NotificationsGETHandler swagger:operation GET /api/v1/notifications notifications @@ -152,27 +154,23 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) { return } - limit := 20 - limitString := c.Query(LimitKey) - if limitString != "" { - i, err := strconv.ParseInt(limitString, 10, 32) - if err != nil { - err := fmt.Errorf("error parsing %s: %s", LimitKey, err) - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - return - } - limit = int(i) + page, errWithCode := paging.ParseIDPage(c, + 1, // min limit + 80, // max limit + 20, // no limit + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return } + ctx := c.Request.Context() resp, errWithCode := m.processor.Timeline().NotificationsGet( - c.Request.Context(), + ctx, authed, - c.Query(MaxIDKey), - c.Query(SinceIDKey), - c.Query(MinIDKey), - limit, - c.QueryArray(TypesKey), - c.QueryArray(ExcludeTypesKey), + page, + parseNotificationTypes(ctx, c.QueryArray(TypesKey)), // Include types. + parseNotificationTypes(ctx, c.QueryArray(ExcludeTypesKey)), // Exclude types. ) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) @@ -185,3 +183,28 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) { apiutil.JSON(c, http.StatusOK, resp.Items) } + +// parseNotificationTypes converts the given slice of string values +// to gtsmodel notification types, logging + skipping unknown types. +func parseNotificationTypes( + ctx context.Context, + values []string, +) []gtsmodel.NotificationType { + if len(values) == 0 { + return nil + } + + ntypes := make([]gtsmodel.NotificationType, 0, len(values)) + for _, value := range values { + ntype := gtsmodel.ParseNotificationType(value) + if ntype == gtsmodel.NotificationUnknown { + // Type we don't know about (yet), log and ignore it. + log.Warnf(ctx, "ignoring unknown type %s", value) + continue + } + + ntypes = append(ntypes, ntype) + } + + return ntypes +} diff --git a/internal/api/client/notifications/notificationsget_test.go b/internal/api/client/notifications/notificationsget_test.go index 97d0e854b..5a6f83959 100644 --- a/internal/api/client/notifications/notificationsget_test.go +++ b/internal/api/client/notifications/notificationsget_test.go @@ -248,6 +248,45 @@ func (suite *NotificationsTestSuite) TestGetNotificationsIncludeOneType() { } } +// Test including an unknown notification type, it should be ignored. +func (suite *NotificationsTestSuite) TestGetNotificationsIncludeUnknownType() { + testAccount := suite.testAccounts["local_account_1"] + testToken := suite.testTokens["local_account_1"] + testUser := suite.testUsers["local_account_1"] + + suite.addMoreNotifications(testAccount) + + maxID := "" + minID := "" + limit := 10 + types := []string{"favourite", "something.weird"} + excludeTypes := []string(nil) + expectedHTTPStatus := http.StatusOK + expectedBody := "" + + notifications, _, err := suite.getNotifications( + testAccount, + testToken, + testUser, + maxID, + minID, + limit, + types, + excludeTypes, + expectedHTTPStatus, + expectedBody, + ) + if err != nil { + suite.FailNow(err.Error()) + } + + // This should only include the fav notification. + suite.Len(notifications, 1) + for _, notification := range notifications { + suite.Equal("favourite", notification.Type) + } +} + func TestBookmarkTestSuite(t *testing.T) { suite.Run(t, new(NotificationsTestSuite)) } diff --git a/internal/api/model/filterv1.go b/internal/api/model/filterv1.go index 1c3b5fb8e..0b092627e 100644 --- a/internal/api/model/filterv1.go +++ b/internal/api/model/filterv1.go @@ -95,5 +95,5 @@ type FilterCreateUpdateRequestV1 struct { // Number of seconds from now that the filter should expire. If omitted, filter never expires. // // Example: 86400 - ExpiresInI interface{} `json:"expires_in"` + ExpiresInI Nullable[any] `json:"expires_in"` } diff --git a/internal/api/model/filterv2.go b/internal/api/model/filterv2.go index 242c569dc..26b1b22b3 100644 --- a/internal/api/model/filterv2.go +++ b/internal/api/model/filterv2.go @@ -134,7 +134,7 @@ type FilterCreateRequestV2 struct { // Number of seconds from now that the filter should expire. If omitted, filter never expires. // // Example: 86400 - ExpiresInI interface{} `json:"expires_in"` + ExpiresInI Nullable[any] `json:"expires_in"` // Keywords to be added to the newly created filter. Keywords []FilterKeywordCreateUpdateRequest `form:"-" json:"keywords_attributes" xml:"keywords_attributes"` @@ -199,7 +199,7 @@ type FilterUpdateRequestV2 struct { // Number of seconds from now that the filter should expire. If omitted, filter never expires. // // Example: 86400 - ExpiresInI interface{} `json:"expires_in"` + ExpiresInI Nullable[any] `json:"expires_in"` // Keywords to be added to the filter, modified, or removed. Keywords []FilterKeywordCreateUpdateDeleteRequest `form:"-" json:"keywords_attributes" xml:"keywords_attributes"` diff --git a/internal/api/model/instance.go b/internal/api/model/instance.go index 5232e8d66..d59424fa5 100644 --- a/internal/api/model/instance.go +++ b/internal/api/model/instance.go @@ -33,6 +33,8 @@ type InstanceSettingsUpdateRequest struct { ShortDescription *string `form:"short_description" json:"short_description" xml:"short_description"` // Longer description of the instance, max 5,000 chars. HTML formatting accepted. Description *string `form:"description" json:"description" xml:"description"` + // Custom CSS for the instance. + CustomCSS *string `form:"custom_css" json:"custom_css,omitempty" xml:"custom_css"` // Terms and conditions of the instance, max 5,000 chars. HTML formatting accepted. Terms *string `form:"terms" json:"terms" xml:"terms"` // Image to use as the instance thumbnail. diff --git a/internal/api/model/instancev1.go b/internal/api/model/instancev1.go index efa6d6faa..6dedd04cc 100644 --- a/internal/api/model/instancev1.go +++ b/internal/api/model/instancev1.go @@ -38,6 +38,8 @@ type InstanceV1 struct { // // This should be displayed on the 'about' page for an instance. Description string `json:"description"` + // Custom CSS for the instance. + CustomCSS string `json:"custom_css,omitempty"` // Raw (unparsed) version of description. DescriptionText string `json:"description_text,omitempty"` // A shorter description of the instance. diff --git a/internal/api/model/instancev2.go b/internal/api/model/instancev2.go index 8d6873497..982ed0c63 100644 --- a/internal/api/model/instancev2.go +++ b/internal/api/model/instancev2.go @@ -53,6 +53,8 @@ type InstanceV2 struct { Description string `json:"description"` // Raw (unparsed) version of description. DescriptionText string `json:"description_text,omitempty"` + // Instance Custom Css + CustomCSS string `json:"custom_css,omitempty"` // Basic anonymous usage data for this instance. Usage InstanceV2Usage `json:"usage"` // An image used to represent this instance. diff --git a/internal/api/model/nullable.go b/internal/api/model/nullable.go new file mode 100644 index 000000000..4dd02f854 --- /dev/null +++ b/internal/api/model/nullable.go @@ -0,0 +1,107 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package model + +import ( + "bytes" + "encoding/json" + "errors" +) + +// Nullable is a generic type, which implements a field that can be one of three states: +// +// - field is not set in the request +// - field is explicitly set to `null` in the request +// - field is explicitly set to a valid value in the request +// +// Nullable is intended to be used with JSON unmarshalling. +// +// Adapted from https://github.com/oapi-codegen/nullable/blob/main/nullable.go +type Nullable[T any] struct { + state nullableState + value T +} + +type nullableState uint8 + +const ( + nullableStateUnspecified nullableState = 0 + nullableStateNull nullableState = 1 + nullableStateSet nullableState = 2 +) + +// Get retrieves the underlying value, if present, +// and returns an error if the value was not present. +func (t Nullable[T]) Get() (T, error) { + var empty T + if t.IsNull() { + return empty, errors.New("value is null") + } + + if !t.IsSpecified() { + return empty, errors.New("value is not specified") + } + + return t.value, nil +} + +// IsNull indicates whether the field +// was sent, and had a value of `null` +func (t Nullable[T]) IsNull() bool { + return t.state == nullableStateNull +} + +// IsSpecified indicates whether the field +// was sent either as a value or as `null`. +func (t Nullable[T]) IsSpecified() bool { + return t.state != nullableStateUnspecified +} + +// If field is unspecified, +// UnmarshalJSON won't be called. +func (t *Nullable[T]) UnmarshalJSON(data []byte) error { + // If field is specified as `null`. + if bytes.Equal(data, []byte("null")) { + t.setNull() + return nil + } + + // Otherwise, we have an + // actual value, so parse it. + var v T + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + t.set(v) + return nil +} + +// setNull indicates that the field +// was sent, and had a value of `null` +func (t *Nullable[T]) setNull() { + *t = Nullable[T]{state: nullableStateNull} +} + +// set the underlying value to given value. +func (t *Nullable[T]) set(value T) { + *t = Nullable[T]{ + state: nullableStateSet, + value: value, + } +} diff --git a/internal/api/util/parseform.go b/internal/api/util/parseform.go index 19e24189f..3eab065f2 100644 --- a/internal/api/util/parseform.go +++ b/internal/api/util/parseform.go @@ -20,12 +20,13 @@ package util import ( "fmt" "strconv" + + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/util" ) -// ParseDuration parses the given raw interface belonging to +// ParseDuration parses the given raw interface belonging // the given fieldName as an integer duration. -// -// Will return nil, nil if rawI is the zero value of its type. func ParseDuration(rawI any, fieldName string) (*int, error) { var ( asInteger int @@ -60,11 +61,28 @@ func ParseDuration(rawI any, fieldName string) (*int, error) { return nil, err } - // Someone submitted 0, - // don't point to this. - if asInteger == 0 { - return nil, nil - } - return &asInteger, nil } + +// ParseNullableDuration is like ParseDuration, but +// for JSON values that may have been sent as `null`. +// +// IsSpecified should be checked and "true" on the +// given nullable before calling this function. +func ParseNullableDuration( + nullable apimodel.Nullable[any], + fieldName string, +) (*int, error) { + if nullable.IsNull() { + // Was specified as `null`, + // return pointer to zero value. + return util.Ptr(0), nil + } + + rawI, err := nullable.Get() + if err != nil { + return nil, err + } + + return ParseDuration(rawI, fieldName) +} diff --git a/internal/db/bundb/account_test.go b/internal/db/bundb/account_test.go index 2caffdeb1..7dcc0f9e7 100644 --- a/internal/db/bundb/account_test.go +++ b/internal/db/bundb/account_test.go @@ -106,7 +106,7 @@ func (suite *AccountTestSuite) populateTestStatus(testAccountKey string, status status.URI = fmt.Sprintf("http://localhost:8080/users/%s/statuses/%s", testAccount.Username, status.ID) status.Local = util.Ptr(true) - if status.Visibility == "" { + if status.Visibility == 0 { status.Visibility = gtsmodel.VisibilityDefault } if status.ActivityStreamsType == "" { diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go index d10f372fd..70132fe58 100644 --- a/internal/db/bundb/bundb.go +++ b/internal/db/bundb/bundb.go @@ -49,6 +49,7 @@ import ( "github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/dialect/sqlitedialect" "github.com/uptrace/bun/migrate" + "github.com/uptrace/bun/schema" ) // DBService satisfies the DB interface @@ -131,18 +132,18 @@ func doMigration(ctx context.Context, db *bun.DB) error { // NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface. // Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection. func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) { - var db *bun.DB + var sqldb *sql.DB + var dialect func() schema.Dialect var err error - t := strings.ToLower(config.GetDbType()) - switch t { + switch t := strings.ToLower(config.GetDbType()); t { case "postgres": - db, err = pgConn(ctx) + sqldb, dialect, err = pgConn(ctx) if err != nil { return nil, err } case "sqlite": - db, err = sqliteConn(ctx) + sqldb, dialect, err = sqliteConn(ctx) if err != nil { return nil, err } @@ -150,34 +151,20 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) { return nil, fmt.Errorf("database type %s not supported for bundb", t) } - // Add database query hooks. - db.AddQueryHook(queryHook{}) - if config.GetTracingEnabled() { - db.AddQueryHook(tracing.InstrumentBun()) - } - if config.GetMetricsEnabled() { - db.AddQueryHook(metrics.InstrumentBun()) - } - - // table registration is needed for many-to-many, see: - // https://bun.uptrace.dev/orm/many-to-many-relation/ - for _, t := range []interface{}{ - >smodel.AccountToEmoji{}, - >smodel.ConversationToStatus{}, - >smodel.StatusToEmoji{}, - >smodel.StatusToTag{}, - >smodel.ThreadToStatus{}, - } { - db.RegisterModel(t) - } - - // perform any pending database migrations: this includes - // the very first 'migration' on startup which just creates - // necessary tables - if err := doMigration(ctx, db); err != nil { + // perform any pending database migrations: this includes the first + // 'migration' on startup which just creates necessary db tables. + // + // Note this uses its own instance of bun.DB as bun will automatically + // store in-memory reflect type schema of any Go models passed to it, + // and we still maintain lots of old model versions in the migrations. + if err := doMigration(ctx, bunDB(sqldb, dialect)); err != nil { return nil, fmt.Errorf("db migration error: %s", err) } + // Wrap sql.DB as bun.DB type, + // adding any connection hooks. + db := bunDB(sqldb, dialect) + ps := &DBService{ Account: &accountDB{ db: db, @@ -319,17 +306,47 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) { return ps, nil } -func pgConn(ctx context.Context) (*bun.DB, error) { +// bunDB returns a new bun.DB for given sql.DB connection pool and dialect +// function. This can be used to apply any necessary opts / hooks as we +// initialize a bun.DB object both before and after performing migrations. +func bunDB(sqldb *sql.DB, dialect func() schema.Dialect) *bun.DB { + db := bun.NewDB(sqldb, dialect()) + + // Add our SQL connection hooks. + db.AddQueryHook(queryHook{}) + if config.GetTracingEnabled() { + db.AddQueryHook(tracing.InstrumentBun()) + } + if config.GetMetricsEnabled() { + db.AddQueryHook(metrics.InstrumentBun()) + } + + // table registration is needed for many-to-many, see: + // https://bun.uptrace.dev/orm/many-to-many-relation/ + for _, t := range []interface{}{ + >smodel.AccountToEmoji{}, + >smodel.ConversationToStatus{}, + >smodel.StatusToEmoji{}, + >smodel.StatusToTag{}, + >smodel.ThreadToStatus{}, + } { + db.RegisterModel(t) + } + + return db +} + +func pgConn(ctx context.Context) (*sql.DB, func() schema.Dialect, error) { opts, err := deriveBunDBPGOptions() //nolint:contextcheck if err != nil { - return nil, fmt.Errorf("could not create bundb postgres options: %w", err) + return nil, nil, fmt.Errorf("could not create bundb postgres options: %w", err) } cfg := stdlib.RegisterConnConfig(opts) sqldb, err := sql.Open("pgx-gts", cfg) if err != nil { - return nil, fmt.Errorf("could not open postgres db: %w", err) + return nil, nil, fmt.Errorf("could not open postgres db: %w", err) } // Tune db connections for postgres, see: @@ -339,22 +356,20 @@ func pgConn(ctx context.Context) (*bun.DB, error) { sqldb.SetMaxIdleConns(2) // assume default 2; if max idle is less than max open, it will be automatically adjusted sqldb.SetConnMaxLifetime(5 * time.Minute) // fine to kill old connections - db := bun.NewDB(sqldb, pgdialect.New()) - // ping to check the db is there and listening - if err := db.PingContext(ctx); err != nil { - return nil, fmt.Errorf("postgres ping: %w", err) + if err := sqldb.PingContext(ctx); err != nil { + return nil, nil, fmt.Errorf("postgres ping: %w", err) } log.Info(ctx, "connected to POSTGRES database") - return db, nil + return sqldb, func() schema.Dialect { return pgdialect.New() }, nil } -func sqliteConn(ctx context.Context) (*bun.DB, error) { +func sqliteConn(ctx context.Context) (*sql.DB, func() schema.Dialect, error) { // validate db address has actually been set address := config.GetDbAddress() if address == "" { - return nil, fmt.Errorf("'%s' was not set when attempting to start sqlite", config.DbAddressFlag()) + return nil, nil, fmt.Errorf("'%s' was not set when attempting to start sqlite", config.DbAddressFlag()) } // Build SQLite connection address with prefs. @@ -363,7 +378,7 @@ func sqliteConn(ctx context.Context) (*bun.DB, error) { // Open new DB instance sqldb, err := sql.Open("sqlite-gts", address) if err != nil { - return nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err) + return nil, nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err) } // Tune db connections for sqlite, see: @@ -379,16 +394,14 @@ func sqliteConn(ctx context.Context) (*bun.DB, error) { sqldb.SetConnMaxLifetime(5 * time.Minute) } - db := bun.NewDB(sqldb, sqlitedialect.New()) - // ping to check the db is there and listening - if err := db.PingContext(ctx); err != nil { - return nil, fmt.Errorf("sqlite ping: %w", err) + if err := sqldb.PingContext(ctx); err != nil { + return nil, nil, fmt.Errorf("sqlite ping: %w", err) } log.Infof(ctx, "connected to SQLITE database with address %s", address) - return db, nil + return sqldb, func() schema.Dialect { return sqlitedialect.New() }, nil } /* @@ -420,13 +433,12 @@ func maxOpenConns() int { // deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options // with sensible defaults, or an error if it's not satisfied by the provided config. func deriveBunDBPGOptions() (*pgx.ConnConfig, error) { - url := config.GetDbPostgresConnectionString() - - // if database URL is defined, ignore other DB related configuration fields - if url != "" { - cfg, err := pgx.ParseConfig(url) - return cfg, err + // If database URL is defined, ignore + // other DB-related configuration fields. + if url := config.GetDbPostgresConnectionString(); url != "" { + return pgx.ParseConfig(url) } + // these are all optional, the db adapter figures out defaults address := config.GetDbAddress() @@ -518,15 +530,12 @@ func buildSQLiteAddress(addr string) (string, bool) { // // - SQLite by itself supports setting a subset of its configuration options // via URI query arguments in the connection. Namely `mode` and `cache`. - // This is the same situation for the directly transpiled C->Go code in - // modernc.org/sqlite, i.e. modernc.org/sqlite/lib, NOT the Go SQL driver. + // This is the same situation for our supported SQLite implementations. // - // - `modernc.org/sqlite` has a "shim" around it to allow the directly - // transpiled C code to be usable with a more native Go API. This is in - // the form of a `database/sql/driver.Driver{}` implementation that calls - // through to the transpiled C code. + // - Both implementations have a "shim" around them in the form of a + // `database/sql/driver.Driver{}` implementation. // - // - The SQLite shim we interface with adds support for setting ANY of the + // - The SQLite shims we interface with add support for setting ANY of the // configuration options via query arguments, through using a special `_pragma` // query key that specifies SQLite PRAGMAs to set upon opening each connection. // As such you will see below that most config is set with the `_pragma` key. @@ -552,12 +561,6 @@ func buildSQLiteAddress(addr string) (string, bool) { // reached. And for whatever reason (:shrug:) SQLite is very particular about // setting this BEFORE the `journal_mode` is set, otherwise you can end up // running into more of these `SQLITE_BUSY` return codes than you might expect. - // - // - One final thing (I promise!): `SQLITE_BUSY` is only handled by the internal - // `busy_timeout` handler in the case that a data race occurs contending for - // table locks. THERE ARE STILL OTHER SITUATIONS IN WHICH THIS MAY BE RETURNED! - // As such, we use our wrapping DB{} and Tx{} types (in "db.go") which make use - // of our own retry-busy handler. // Drop anything fancy from DB address addr = strings.Split(addr, "?")[0] // drop any provided query strings diff --git a/internal/db/bundb/instance.go b/internal/db/bundb/instance.go index bbfd82ffb..613a2b13a 100644 --- a/internal/db/bundb/instance.go +++ b/internal/db/bundb/instance.go @@ -104,7 +104,7 @@ func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) ( q = q.Where("NOT ? = ?", bun.Ident("status.pending_approval"), true) // Ignore statuses that are direct messages. - q = q.Where("NOT ? = ?", bun.Ident("status.visibility"), "direct") + q = q.Where("NOT ? = ?", bun.Ident("status.visibility"), gtsmodel.VisibilityDirect) count, err := q.Count(ctx) if err != nil { diff --git a/internal/db/bundb/migrations/20230828101322_admin_action_locking.go b/internal/db/bundb/migrations/20230828101322_admin_action_locking.go index b72976cc9..29c29a747 100644 --- a/internal/db/bundb/migrations/20230828101322_admin_action_locking.go +++ b/internal/db/bundb/migrations/20230828101322_admin_action_locking.go @@ -77,7 +77,7 @@ func init() { UpdatedAt: oldAction.UpdatedAt, TargetCategory: gtsmodel.AdminActionCategoryAccount, TargetID: oldAction.TargetAccountID, - Type: gtsmodel.NewAdminActionType(string(oldAction.Type)), + Type: gtsmodel.ParseAdminActionType(string(oldAction.Type)), AccountID: oldAction.AccountID, Text: oldAction.Text, SendEmail: util.Ptr(oldAction.SendEmail), diff --git a/internal/db/bundb/migrations/20231002153327_add_status_polls.go b/internal/db/bundb/migrations/20231002153327_add_status_polls.go index 5e525cc27..019a369d4 100644 --- a/internal/db/bundb/migrations/20231002153327_add_status_polls.go +++ b/internal/db/bundb/migrations/20231002153327_add_status_polls.go @@ -21,7 +21,8 @@ import ( "context" "strings" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20231002153327_add_status_polls" + "github.com/uptrace/bun" ) diff --git a/internal/db/bundb/migrations/20231002153327_add_status_polls/polls.go b/internal/db/bundb/migrations/20231002153327_add_status_polls/polls.go new file mode 100644 index 000000000..c3e03d267 --- /dev/null +++ b/internal/db/bundb/migrations/20231002153327_add_status_polls/polls.go @@ -0,0 +1,48 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import ( + "time" +) + +// Poll represents an attached (to) Status poll, i.e. a questionaire. Can be remote / local. +type Poll struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // Unique identity string. + Multiple *bool `bun:",nullzero,notnull,default:false"` // Is this a multiple choice poll? i.e. can you vote on multiple options. + HideCounts *bool `bun:",nullzero,notnull,default:false"` // Hides vote counts until poll ends. + Options []string `bun:",nullzero,notnull"` // The available options for this poll. + Votes []int `bun:",nullzero,notnull"` // Vote counts per choice. + Voters *int `bun:",nullzero,notnull"` // Total no. voters count. + StatusID string `bun:"type:CHAR(26),nullzero,notnull,unique"` // Status ID of which this Poll is attached to. + ExpiresAt time.Time `bun:"type:timestamptz,nullzero,notnull"` // The expiry date of this Poll. + ClosedAt time.Time `bun:"type:timestamptz,nullzero"` // The closure date of this poll, will be zerotime until set. + Closing bool `bun:"-"` // An ephemeral field only set on Polls in the middle of closing. + // no creation date, use attached Status.CreatedAt. +} + +// PollVote represents a single instance of vote(s) in a Poll by an account. +// If the Poll is single-choice, len(.Choices) = 1, if multiple-choice then +// len(.Choices) >= 1. Can be remote or local. +type PollVote struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // Unique identity string. + Choices []int `bun:",nullzero,notnull"` // The Poll's option indices of which these are votes for. + AccountID string `bun:"type:CHAR(26),nullzero,notnull,unique:in_poll_by_account"` // Account ID from which this vote originated. + PollID string `bun:"type:CHAR(26),nullzero,notnull,unique:in_poll_by_account"` // Poll ID of which this is a vote in. + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // The creation date of this PollVote. +} diff --git a/internal/db/bundb/migrations/20231002153327_add_status_polls/status.go b/internal/db/bundb/migrations/20231002153327_add_status_polls/status.go new file mode 100644 index 000000000..8e3252e82 --- /dev/null +++ b/internal/db/bundb/migrations/20231002153327_add_status_polls/status.go @@ -0,0 +1,75 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import "time" + +// Status represents a user-created 'post' or 'status' in the database, either remote or local +type Status struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated + FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched. + PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time. + URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status + URL string `bun:",nullzero"` // web url for viewing this status + Content string `bun:""` // content of this status; likely html-formatted but not guaranteed + AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status + TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status + MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status + EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status + Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account? + AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status? + AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status + InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to + InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to + InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to + BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of + BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status + ThreadID string `bun:"type:CHAR(26),nullzero"` // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null + PollID string `bun:"type:CHAR(26),nullzero"` // + ContentWarning string `bun:",nullzero"` // cw string for this status + Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status + Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive? + Language string `bun:",nullzero"` // what language is this status written in? + CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status? + ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!. + Text string `bun:""` // Original text of the status without formatting + Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s) + Boostable *bool `bun:",notnull"` // This status can be boosted/reblogged + Replyable *bool `bun:",notnull"` // This status can be replied to + Likeable *bool `bun:",notnull"` // This status can be liked/faved +} + +// Visibility represents the visibility granularity of a status. +type Visibility string + +const ( + // VisibilityPublic means this status will be visible to everyone on all timelines. + VisibilityPublic Visibility = "public" + // VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. + VisibilityUnlocked Visibility = "unlocked" + // VisibilityFollowersOnly means this status is viewable to followers only. + VisibilityFollowersOnly Visibility = "followers_only" + // VisibilityMutualsOnly means this status is visible to mutual followers only. + VisibilityMutualsOnly Visibility = "mutuals_only" + // VisibilityDirect means this status is visible only to mentioned recipients. + VisibilityDirect Visibility = "direct" + // VisibilityDefault is used when no other setting can be found. + VisibilityDefault Visibility = VisibilityUnlocked +) diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy.go b/internal/db/bundb/migrations/20240620074530_interaction_policy.go index bbc75d9ec..7678af7ed 100644 --- a/internal/db/bundb/migrations/20240620074530_interaction_policy.go +++ b/internal/db/bundb/migrations/20240620074530_interaction_policy.go @@ -161,11 +161,14 @@ func init() { return err } + // Get the mapping of old enum string values to new integer values. + visibilityMapping := visibilityEnumMapping[oldmodel.Visibility]() + // For each status found in this way, update // to new version of interaction policy. for _, oldStatus := range oldStatuses { // Start with default policy for this visibility. - v := gtsmodel.Visibility(oldStatus.Visibility) + v := visibilityMapping[oldStatus.Visibility] policy := gtsmodel.DefaultInteractionPolicyFor(v) if !*oldStatus.Likeable { diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go index ae96d047d..615c81b66 100644 --- a/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go +++ b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go @@ -22,40 +22,61 @@ import ( ) type Status struct { - ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` - CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` - UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` - FetchedAt time.Time `bun:"type:timestamptz,nullzero"` - PinnedAt time.Time `bun:"type:timestamptz,nullzero"` - URI string `bun:",unique,nullzero,notnull"` - URL string `bun:",nullzero"` - Content string `bun:""` - AttachmentIDs []string `bun:"attachments,array"` - TagIDs []string `bun:"tags,array"` - MentionIDs []string `bun:"mentions,array"` - EmojiIDs []string `bun:"emojis,array"` - Local *bool `bun:",nullzero,notnull,default:false"` - AccountID string `bun:"type:CHAR(26),nullzero,notnull"` - AccountURI string `bun:",nullzero,notnull"` - InReplyToID string `bun:"type:CHAR(26),nullzero"` - InReplyToURI string `bun:",nullzero"` - InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` - InReplyTo *Status `bun:"-"` - BoostOfID string `bun:"type:CHAR(26),nullzero"` - BoostOfURI string `bun:"-"` - BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` - BoostOf *Status `bun:"-"` - ThreadID string `bun:"type:CHAR(26),nullzero"` - PollID string `bun:"type:CHAR(26),nullzero"` - ContentWarning string `bun:",nullzero"` - Visibility string `bun:",nullzero,notnull"` - Sensitive *bool `bun:",nullzero,notnull,default:false"` - Language string `bun:",nullzero"` - CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` - ActivityStreamsType string `bun:",nullzero,notnull"` - Text string `bun:""` - Federated *bool `bun:",notnull"` - Boostable *bool `bun:",notnull"` - Replyable *bool `bun:",notnull"` - Likeable *bool `bun:",notnull"` + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` + FetchedAt time.Time `bun:"type:timestamptz,nullzero"` + PinnedAt time.Time `bun:"type:timestamptz,nullzero"` + URI string `bun:",unique,nullzero,notnull"` + URL string `bun:",nullzero"` + Content string `bun:""` + AttachmentIDs []string `bun:"attachments,array"` + TagIDs []string `bun:"tags,array"` + MentionIDs []string `bun:"mentions,array"` + EmojiIDs []string `bun:"emojis,array"` + Local *bool `bun:",nullzero,notnull,default:false"` + AccountID string `bun:"type:CHAR(26),nullzero,notnull"` + AccountURI string `bun:",nullzero,notnull"` + InReplyToID string `bun:"type:CHAR(26),nullzero"` + InReplyToURI string `bun:",nullzero"` + InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` + InReplyTo *Status `bun:"-"` + BoostOfID string `bun:"type:CHAR(26),nullzero"` + BoostOfURI string `bun:"-"` + BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` + BoostOf *Status `bun:"-"` + ThreadID string `bun:"type:CHAR(26),nullzero"` + PollID string `bun:"type:CHAR(26),nullzero"` + ContentWarning string `bun:",nullzero"` + Visibility Visibility `bun:",nullzero,notnull"` + Sensitive *bool `bun:",nullzero,notnull,default:false"` + Language string `bun:",nullzero"` + CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` + ActivityStreamsType string `bun:",nullzero,notnull"` + Text string `bun:""` + Federated *bool `bun:",notnull"` + Boostable *bool `bun:",notnull"` + Replyable *bool `bun:",notnull"` + Likeable *bool `bun:",notnull"` } + +// Visibility represents the visibility granularity of a status. +type Visibility string + +const ( + // VisibilityNone means nobody can see this. + // It's only used for web status visibility. + VisibilityNone Visibility = "none" + // VisibilityPublic means this status will be visible to everyone on all timelines. + VisibilityPublic Visibility = "public" + // VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. + VisibilityUnlocked Visibility = "unlocked" + // VisibilityFollowersOnly means this status is viewable to followers only. + VisibilityFollowersOnly Visibility = "followers_only" + // VisibilityMutualsOnly means this status is visible to mutual followers only. + VisibilityMutualsOnly Visibility = "mutuals_only" + // VisibilityDirect means this status is visible only to mentioned recipients. + VisibilityDirect Visibility = "direct" + // VisibilityDefault is used when no other setting can be found. + VisibilityDefault Visibility = VisibilityUnlocked +) diff --git a/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go index d97d35372..d3c0f49a4 100644 --- a/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go +++ b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go @@ -20,7 +20,7 @@ package migrations import ( "context" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction" "github.com/uptrace/bun" ) diff --git a/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction/sinbinstatus.go b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction/sinbinstatus.go new file mode 100644 index 000000000..e18affa9b --- /dev/null +++ b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction/sinbinstatus.go @@ -0,0 +1,66 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import "time" + +// SinBinStatus represents a status that's been rejected and/or reported + quarantined. +// +// Automatically rejected statuses are not put in the sin bin, only statuses that were +// stored on the instance and which someone (local or remote) has subsequently rejected. +type SinBinStatus struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database. + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Creation time of this item. + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Last-updated time of this item. + URI string `bun:",unique,nullzero,notnull"` // ActivityPub URI/ID of this status. + URL string `bun:",nullzero"` // Web url for viewing this status. + Domain string `bun:",nullzero"` // Domain of the status, will be null if this is a local status, otherwise something like `example.org`. + AccountURI string `bun:",nullzero,notnull"` // ActivityPub uri of the author of this status. + InReplyToURI string `bun:",nullzero"` // ActivityPub uri of the status this status is a reply to. + Content string `bun:",nullzero"` // Content of this status. + AttachmentLinks []string `bun:",nullzero,array"` // Links to attachments of this status. + MentionTargetURIs []string `bun:",nullzero,array"` // URIs of mentioned accounts. + EmojiLinks []string `bun:",nullzero,array"` // Links to any emoji images used in this status. + PollOptions []string `bun:",nullzero,array"` // String values of any poll options used in this status. + ContentWarning string `bun:",nullzero"` // CW / subject string for this status. + Visibility Visibility `bun:",nullzero,notnull"` // Visibility level of this status. + Sensitive *bool `bun:",nullzero,notnull,default:false"` // Mark the status as sensitive. + Language string `bun:",nullzero"` // Language code for this status. + ActivityStreamsType string `bun:",nullzero,notnull"` // ActivityStreams type of this status. +} + +// Visibility represents the visibility granularity of a status. +type Visibility string + +const ( + // VisibilityNone means nobody can see this. + // It's only used for web status visibility. + VisibilityNone Visibility = "none" + // VisibilityPublic means this status will be visible to everyone on all timelines. + VisibilityPublic Visibility = "public" + // VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. + VisibilityUnlocked Visibility = "unlocked" + // VisibilityFollowersOnly means this status is viewable to followers only. + VisibilityFollowersOnly Visibility = "followers_only" + // VisibilityMutualsOnly means this status is visible to mutual followers only. + VisibilityMutualsOnly Visibility = "mutuals_only" + // VisibilityDirect means this status is visible only to mentioned recipients. + VisibilityDirect Visibility = "direct" + // VisibilityDefault is used when no other setting can be found. + VisibilityDefault Visibility = VisibilityUnlocked +) diff --git a/internal/db/bundb/migrations/20240924222938_add_instance_custom_css.go b/internal/db/bundb/migrations/20240924222938_add_instance_custom_css.go new file mode 100644 index 000000000..14231927a --- /dev/null +++ b/internal/db/bundb/migrations/20240924222938_add_instance_custom_css.go @@ -0,0 +1,44 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package migrations + +import ( + "context" + "strings" + + "github.com/uptrace/bun" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("instances"), bun.Ident("custom_css")) + if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) { + return err + } + return nil + } + + down := func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("instances"), bun.Ident("custom_css")) + return err + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go new file mode 100644 index 000000000..10ae95c17 --- /dev/null +++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go @@ -0,0 +1,249 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package migrations + +import ( + "context" + "errors" + + old_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + new_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/log" + "github.com/superseriousbusiness/gotosocial/internal/util" + + "github.com/uptrace/bun" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + + // Tables with visibility types. + var visTables = []struct { + Table string + Column string + Default *new_gtsmodel.Visibility + }{ + {Table: "statuses", Column: "visibility"}, + {Table: "sin_bin_statuses", Column: "visibility"}, + {Table: "account_settings", Column: "privacy", Default: util.Ptr(new_gtsmodel.VisibilityDefault)}, + {Table: "account_settings", Column: "web_visibility", Default: util.Ptr(new_gtsmodel.VisibilityDefault)}, + } + + // Visibility type indices. + var visIndices = []struct { + name string + cols []string + order string + }{ + { + name: "statuses_visibility_idx", + cols: []string{"visibility"}, + order: "", + }, + { + name: "statuses_profile_web_view_idx", + cols: []string{"account_id", "visibility"}, + order: "id DESC", + }, + { + name: "statuses_public_timeline_idx", + cols: []string{"visibility"}, + order: "id DESC", + }, + } + + // Before making changes to the visibility col + // we must drop all indices that rely on it. + for _, index := range visIndices { + if _, err := tx.NewDropIndex(). + Index(index.name). + Exec(ctx); err != nil { + return err + } + } + + // Get the mapping of old enum string values to new integer values. + visibilityMapping := visibilityEnumMapping[old_gtsmodel.Visibility]() + + // Convert all visibility tables. + for _, table := range visTables { + if err := convertEnums(ctx, tx, table.Table, table.Column, + visibilityMapping, table.Default); err != nil { + return err + } + } + + // Recreate the visibility indices. + for _, index := range visIndices { + q := tx.NewCreateIndex(). + Table("statuses"). + Index(index.name). + Column(index.cols...) + if index.order != "" { + q = q.ColumnExpr(index.order) + } + if _, err := q.Exec(ctx); err != nil { + return err + } + } + + // Get the mapping of old enum string values to the new integer value types. + notificationMapping := notificationEnumMapping[old_gtsmodel.NotificationType]() + + // Migrate over old notifications table column over to new column type. + if err := convertEnums(ctx, tx, "notifications", "notification_type", //nolint:revive + notificationMapping, nil); err != nil { + return err + } + + return nil + }) + } + + down := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + return nil + }) + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} + +// convertEnums performs a transaction that converts +// a table's column of our old-style enums (strings) to +// more performant and space-saving integer types. +func convertEnums[OldType ~string, NewType ~int16]( + ctx context.Context, + tx bun.Tx, + table string, + column string, + mapping map[OldType]NewType, + defaultValue *NewType, +) error { + if len(mapping) == 0 { + return errors.New("empty mapping") + } + + // Generate new column name. + newColumn := column + "_new" + + log.Infof(ctx, "converting %s.%s enums; "+ + "this may take a while, please don't interrupt!", + table, column, + ) + + // Ensure a default value. + if defaultValue == nil { + var zero NewType + defaultValue = &zero + } + + // Add new column to database. + if _, err := tx.NewAddColumn(). + Table(table). + ColumnExpr("? SMALLINT NOT NULL DEFAULT ?", + bun.Ident(newColumn), + *defaultValue). + Exec(ctx); err != nil { + return gtserror.Newf("error adding new column: %w", err) + } + + // Get a count of all in table. + total, err := tx.NewSelect(). + Table(table). + Count(ctx) + if err != nil { + return gtserror.Newf("error selecting total count: %w", err) + } + + var updated int + for old, new := range mapping { + + // Update old to new values. + res, err := tx.NewUpdate(). + Table(table). + Where("? = ?", bun.Ident(column), old). + Set("? = ?", bun.Ident(newColumn), new). + Exec(ctx) + if err != nil { + return gtserror.Newf("error updating old column values: %w", err) + } + + // Count number items updated. + n, _ := res.RowsAffected() + updated += int(n) + } + + // Check total updated. + if total != updated { + log.Warnf(ctx, "total=%d does not match updated=%d", total, updated) + } + + // Drop the old column from table. + if _, err := tx.NewDropColumn(). + Table(table). + ColumnExpr("?", bun.Ident(column)). + Exec(ctx); err != nil { + return gtserror.Newf("error dropping old column: %w", err) + } + + // Rename new to old name. + if _, err := tx.NewRaw( + "ALTER TABLE ? RENAME COLUMN ? TO ?", + bun.Ident(table), + bun.Ident(newColumn), + bun.Ident(column), + ).Exec(ctx); err != nil { + return gtserror.Newf("error renaming new column: %w", err) + } + + return nil +} + +// visibilityEnumMapping maps old Visibility enum values to their newer integer type. +func visibilityEnumMapping[T ~string]() map[T]new_gtsmodel.Visibility { + return map[T]new_gtsmodel.Visibility{ + T(old_gtsmodel.VisibilityNone): new_gtsmodel.VisibilityNone, + T(old_gtsmodel.VisibilityPublic): new_gtsmodel.VisibilityPublic, + T(old_gtsmodel.VisibilityUnlocked): new_gtsmodel.VisibilityUnlocked, + T(old_gtsmodel.VisibilityFollowersOnly): new_gtsmodel.VisibilityFollowersOnly, + T(old_gtsmodel.VisibilityMutualsOnly): new_gtsmodel.VisibilityMutualsOnly, + T(old_gtsmodel.VisibilityDirect): new_gtsmodel.VisibilityDirect, + } +} + +// notificationEnumMapping maps old NotificationType enum values to their newer integer type. +func notificationEnumMapping[T ~string]() map[T]new_gtsmodel.NotificationType { + return map[T]new_gtsmodel.NotificationType{ + T(old_gtsmodel.NotificationFollow): new_gtsmodel.NotificationFollow, + T(old_gtsmodel.NotificationFollowRequest): new_gtsmodel.NotificationFollowRequest, + T(old_gtsmodel.NotificationMention): new_gtsmodel.NotificationMention, + T(old_gtsmodel.NotificationReblog): new_gtsmodel.NotificationReblog, + T(old_gtsmodel.NotificationFave): new_gtsmodel.NotificationFave, + T(old_gtsmodel.NotificationPoll): new_gtsmodel.NotificationPoll, + T(old_gtsmodel.NotificationStatus): new_gtsmodel.NotificationStatus, + T(old_gtsmodel.NotificationSignup): new_gtsmodel.NotificationSignup, + T(old_gtsmodel.NotificationPendingFave): new_gtsmodel.NotificationPendingFave, + T(old_gtsmodel.NotificationPendingReply): new_gtsmodel.NotificationPendingReply, + T(old_gtsmodel.NotificationPendingReblog): new_gtsmodel.NotificationPendingReblog, + } +} diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/accountsettings.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/accountsettings.go new file mode 100644 index 000000000..9a9cfd8e1 --- /dev/null +++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/accountsettings.go @@ -0,0 +1,45 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import ( + "time" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// AccountSettings models settings / preferences for a local, non-instance account. +type AccountSettings struct { + AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // AccountID that owns this settings. + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created. + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated. + Privacy Visibility `bun:",nullzero,default:3"` // Default post privacy for this account + Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default? + Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in? + StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts). + Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set). + CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses. + EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed + HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections. + WebVisibility Visibility `bun:",nullzero,notnull,default:3"` // Visibility level of statuses that visitors can view via the web profile. + InteractionPolicyDirect *gtsmodel.InteractionPolicy `bun:""` // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy. + InteractionPolicyMutualsOnly *gtsmodel.InteractionPolicy `bun:""` // Interaction policy to use for new mutuals only visibility statuses. If null, assume default policy. + InteractionPolicyFollowersOnly *gtsmodel.InteractionPolicy `bun:""` // Interaction policy to use for new followers only visibility statuses. If null, assume default policy. + InteractionPolicyUnlocked *gtsmodel.InteractionPolicy `bun:""` // Interaction policy to use for new unlocked visibility statuses. If null, assume default policy. + InteractionPolicyPublic *gtsmodel.InteractionPolicy `bun:""` // Interaction policy to use for new public visibility statuses. If null, assume default policy. +} diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/notification.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/notification.go new file mode 100644 index 000000000..77166a35d --- /dev/null +++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/notification.go @@ -0,0 +1,57 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import ( + "time" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc. +type Notification struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated + NotificationType NotificationType `bun:",nullzero,notnull"` // Type of this notification + TargetAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // ID of the account targeted by the notification (ie., who will receive the notification?) + TargetAccount *gtsmodel.Account `bun:"-"` // Account corresponding to TargetAccountID. Can be nil, always check first + select using ID if necessary. + OriginAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // ID of the account that performed the action that created the notification. + OriginAccount *gtsmodel.Account `bun:"-"` // Account corresponding to OriginAccountID. Can be nil, always check first + select using ID if necessary. + StatusID string `bun:"type:CHAR(26),nullzero"` // If the notification pertains to a status, what is the database ID of that status? + Status *Status `bun:"-"` // Status corresponding to StatusID. Can be nil, always check first + select using ID if necessary. + Read *bool `bun:",nullzero,notnull,default:false"` // Notification has been seen/read +} + +// NotificationType describes the reason/type of this notification. +type NotificationType string + +// Notification Types +const ( + NotificationFollow NotificationType = "follow" // NotificationFollow -- someone followed you + NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you + NotificationMention NotificationType = "mention" // NotificationMention -- someone mentioned you in their status + NotificationReblog NotificationType = "reblog" // NotificationReblog -- someone boosted one of your statuses + NotificationFave NotificationType = "favourite" // NotificationFave -- someone faved/liked one of your statuses + NotificationPoll NotificationType = "poll" // NotificationPoll -- a poll you voted in or created has ended + NotificationStatus NotificationType = "status" // NotificationStatus -- someone you enabled notifications for has posted a status. + NotificationSignup NotificationType = "admin.sign_up" // NotificationSignup -- someone has submitted a new account sign-up to the instance. + NotificationPendingFave NotificationType = "pending.favourite" // Someone has faved a status of yours, which requires approval by you. + NotificationPendingReply NotificationType = "pending.reply" // Someone has replied to a status of yours, which requires approval by you. + NotificationPendingReblog NotificationType = "pending.reblog" // Someone has boosted a status of yours, which requires approval by you. +) diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/sinbinstatus.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/sinbinstatus.go new file mode 100644 index 000000000..d1dfcddd1 --- /dev/null +++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/sinbinstatus.go @@ -0,0 +1,45 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import "time" + +// SinBinStatus represents a status that's been rejected and/or reported + quarantined. +// +// Automatically rejected statuses are not put in the sin bin, only statuses that were +// stored on the instance and which someone (local or remote) has subsequently rejected. +type SinBinStatus struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database. + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Creation time of this item. + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Last-updated time of this item. + URI string `bun:",unique,nullzero,notnull"` // ActivityPub URI/ID of this status. + URL string `bun:",nullzero"` // Web url for viewing this status. + Domain string `bun:",nullzero"` // Domain of the status, will be null if this is a local status, otherwise something like `example.org`. + AccountURI string `bun:",nullzero,notnull"` // ActivityPub uri of the author of this status. + InReplyToURI string `bun:",nullzero"` // ActivityPub uri of the status this status is a reply to. + Content string `bun:",nullzero"` // Content of this status. + AttachmentLinks []string `bun:",nullzero,array"` // Links to attachments of this status. + MentionTargetURIs []string `bun:",nullzero,array"` // URIs of mentioned accounts. + EmojiLinks []string `bun:",nullzero,array"` // Links to any emoji images used in this status. + PollOptions []string `bun:",nullzero,array"` // String values of any poll options used in this status. + ContentWarning string `bun:",nullzero"` // CW / subject string for this status. + Visibility Visibility `bun:",nullzero,notnull"` // Visibility level of this status. + Sensitive *bool `bun:",nullzero,notnull,default:false"` // Mark the status as sensitive. + Language string `bun:",nullzero"` // Language code for this status. + ActivityStreamsType string `bun:",nullzero,notnull"` // ActivityStreams type of this status. +} diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/status.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/status.go new file mode 100644 index 000000000..38583c7fc --- /dev/null +++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/status.go @@ -0,0 +1,95 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import ( + "time" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// Status represents a user-created 'post' or 'status' in the database, either remote or local +type Status struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated + FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched. + PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time. + URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status + URL string `bun:",nullzero"` // web url for viewing this status + Content string `bun:""` // content of this status; likely html-formatted but not guaranteed + AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status + Attachments []*gtsmodel.MediaAttachment `bun:"attached_media,rel:has-many"` // Attachments corresponding to attachmentIDs + TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status + Tags []*gtsmodel.Tag `bun:"attached_tags,m2m:status_to_tags"` // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation + MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status + Mentions []*gtsmodel.Mention `bun:"attached_mentions,rel:has-many"` // Mentions corresponding to mentionIDs + EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status + Emojis []*gtsmodel.Emoji `bun:"attached_emojis,m2m:status_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation + Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account? + AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status? + Account *gtsmodel.Account `bun:"rel:belongs-to"` // account corresponding to accountID + AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status + InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to + InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to + InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to + InReplyTo *Status `bun:"-"` // status corresponding to inReplyToID + InReplyToAccount *gtsmodel.Account `bun:"rel:belongs-to"` // account corresponding to inReplyToAccountID + BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of + BoostOfURI string `bun:"-"` // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes. + BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status + BoostOf *Status `bun:"-"` // status that corresponds to boostOfID + BoostOfAccount *gtsmodel.Account `bun:"rel:belongs-to"` // account that corresponds to boostOfAccountID + ThreadID string `bun:"type:CHAR(26),nullzero"` // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null + PollID string `bun:"type:CHAR(26),nullzero"` // + Poll *gtsmodel.Poll `bun:"-"` // + ContentWarning string `bun:",nullzero"` // cw string for this status + Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status + Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive? + Language string `bun:",nullzero"` // what language is this status written in? + CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status? + CreatedWithApplication *gtsmodel.Application `bun:"rel:belongs-to"` // application corresponding to createdWithApplicationID + ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!. + Text string `bun:""` // Original text of the status without formatting + Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s) + InteractionPolicy *gtsmodel.InteractionPolicy `bun:""` // InteractionPolicy for this status. If null then the default InteractionPolicy should be assumed for this status's Visibility. Always null for boost wrappers. + PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed. + PreApproved bool `bun:"-"` // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB. + ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to. +} + +// Visibility represents the visibility granularity of a status. +type Visibility string + +const ( + // VisibilityNone means nobody can see this. + // It's only used for web status visibility. + VisibilityNone Visibility = "none" + // VisibilityPublic means this status will be visible to everyone on all timelines. + VisibilityPublic Visibility = "public" + // VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. + VisibilityUnlocked Visibility = "unlocked" + // VisibilityFollowersOnly means this status is viewable to followers only. + VisibilityFollowersOnly Visibility = "followers_only" + // VisibilityMutualsOnly means this status is visible to mutual followers only. + VisibilityMutualsOnly Visibility = "mutuals_only" + // VisibilityDirect means this status is visible only to mentioned recipients. + VisibilityDirect Visibility = "direct" + // VisibilityDefault is used when no other setting can be found. + VisibilityDefault Visibility = VisibilityUnlocked +) diff --git a/internal/db/bundb/notification.go b/internal/db/bundb/notification.go index ef2527637..d4f8799bd 100644 --- a/internal/db/bundb/notification.go +++ b/internal/db/bundb/notification.go @@ -26,8 +26,8 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/log" + "github.com/superseriousbusiness/gotosocial/internal/paging" "github.com/superseriousbusiness/gotosocial/internal/state" "github.com/superseriousbusiness/gotosocial/internal/util/xslices" "github.com/uptrace/bun" @@ -192,22 +192,19 @@ func (n *notificationDB) PopulateNotification(ctx context.Context, notif *gtsmod func (n *notificationDB) GetAccountNotifications( ctx context.Context, accountID string, - maxID string, - sinceID string, - minID string, - limit int, - types []string, - excludeTypes []string, + page *paging.Page, + types []gtsmodel.NotificationType, + excludeTypes []gtsmodel.NotificationType, ) ([]*gtsmodel.Notification, error) { - // Ensure reasonable - if limit < 0 { - limit = 0 - } - - // Make educated guess for slice size var ( - notifIDs = make([]string, 0, limit) - frontToBack = true + // Get paging params. + minID = page.GetMin() + maxID = page.GetMax() + limit = page.GetLimit() + order = page.GetOrder() + + // Make educated guess for slice size + notifIDs = make([]string, 0, limit) ) q := n.db. @@ -215,23 +212,14 @@ func (n *notificationDB) GetAccountNotifications( TableExpr("? AS ?", bun.Ident("notifications"), bun.Ident("notification")). Column("notification.id") - if maxID == "" { - maxID = id.Highest - } - - // Return only notifs LOWER (ie., older) than maxID. - q = q.Where("? < ?", bun.Ident("notification.id"), maxID) - - if sinceID != "" { - // Return only notifs HIGHER (ie., newer) than sinceID. - q = q.Where("? > ?", bun.Ident("notification.id"), sinceID) + if maxID != "" { + // Return only notifs LOWER (ie., older) than maxID. + q = q.Where("? < ?", bun.Ident("notification.id"), maxID) } if minID != "" { // Return only notifs HIGHER (ie., newer) than minID. q = q.Where("? > ?", bun.Ident("notification.id"), minID) - - frontToBack = false // page up } if len(types) > 0 { @@ -251,12 +239,12 @@ func (n *notificationDB) GetAccountNotifications( q = q.Limit(limit) } - if frontToBack { - // Page down. - q = q.Order("notification.id DESC") - } else { + if order == paging.OrderAscending { // Page up. q = q.Order("notification.id ASC") + } else { + // Page down. + q = q.Order("notification.id DESC") } if err := q.Scan(ctx, ¬ifIDs); err != nil { @@ -269,11 +257,8 @@ func (n *notificationDB) GetAccountNotifications( // If we're paging up, we still want notifications // to be sorted by ID desc, so reverse ids slice. - // https://zchee.github.io/golang-wiki/SliceTricks/#reversing - if !frontToBack { - for l, r := 0, len(notifIDs)-1; l < r; l, r = l+1, r-1 { - notifIDs[l], notifIDs[r] = notifIDs[r], notifIDs[l] - } + if order == paging.OrderAscending { + slices.Reverse(notifIDs) } // Fetch notification models by their IDs. @@ -303,7 +288,7 @@ func (n *notificationDB) DeleteNotificationByID(ctx context.Context, id string) return nil } -func (n *notificationDB) DeleteNotifications(ctx context.Context, types []string, targetAccountID string, originAccountID string) error { +func (n *notificationDB) DeleteNotifications(ctx context.Context, types []gtsmodel.NotificationType, targetAccountID string, originAccountID string) error { if targetAccountID == "" && originAccountID == "" { return gtserror.New("one of targetAccountID or originAccountID must be set") } diff --git a/internal/db/bundb/notification_test.go b/internal/db/bundb/notification_test.go index eb2c02066..8e2fb8031 100644 --- a/internal/db/bundb/notification_test.go +++ b/internal/db/bundb/notification_test.go @@ -28,6 +28,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/paging" "github.com/superseriousbusiness/gotosocial/internal/util" ) @@ -92,10 +93,11 @@ func (suite *NotificationTestSuite) TestGetAccountNotificationsWithSpam() { notifications, err := suite.db.GetAccountNotifications( gtscontext.SetBarebones(context.Background()), testAccount.ID, - id.Highest, - id.Lowest, - "", - 20, + &paging.Page{ + Min: paging.EitherMinID("", id.Lowest), + Max: paging.MaxID(id.Highest), + Limit: 20, + }, nil, nil, ) @@ -115,10 +117,11 @@ func (suite *NotificationTestSuite) TestGetAccountNotificationsWithoutSpam() { notifications, err := suite.db.GetAccountNotifications( gtscontext.SetBarebones(context.Background()), testAccount.ID, - id.Highest, - id.Lowest, - "", - 20, + &paging.Page{ + Min: paging.EitherMinID("", id.Lowest), + Max: paging.MaxID(id.Highest), + Limit: 20, + }, nil, nil, ) @@ -140,10 +143,11 @@ func (suite *NotificationTestSuite) TestDeleteNotificationsWithSpam() { notifications, err := suite.db.GetAccountNotifications( gtscontext.SetBarebones(context.Background()), testAccount.ID, - id.Highest, - id.Lowest, - "", - 20, + &paging.Page{ + Min: paging.EitherMinID("", id.Lowest), + Max: paging.MaxID(id.Highest), + Limit: 20, + }, nil, nil, ) @@ -161,10 +165,11 @@ func (suite *NotificationTestSuite) TestDeleteNotificationsWithSpam() { notifications, err = suite.db.GetAccountNotifications( gtscontext.SetBarebones(context.Background()), testAccount.ID, - id.Highest, - id.Lowest, - "", - 20, + &paging.Page{ + Min: paging.EitherMinID("", id.Lowest), + Max: paging.MaxID(id.Highest), + Limit: 20, + }, nil, nil, ) @@ -183,10 +188,11 @@ func (suite *NotificationTestSuite) TestDeleteNotificationsWithTwoAccounts() { notifications, err := suite.db.GetAccountNotifications( gtscontext.SetBarebones(context.Background()), testAccount.ID, - id.Highest, - id.Lowest, - "", - 20, + &paging.Page{ + Min: paging.EitherMinID("", id.Lowest), + Max: paging.MaxID(id.Highest), + Limit: 20, + }, nil, nil, ) diff --git a/internal/db/bundb/poll.go b/internal/db/bundb/poll.go index b9384774b..e8c3e7e54 100644 --- a/internal/db/bundb/poll.go +++ b/internal/db/bundb/poll.go @@ -88,12 +88,15 @@ func (p *pollDB) getPoll(ctx context.Context, lookup string, dbQuery func(*gtsmo func (p *pollDB) GetOpenPolls(ctx context.Context) ([]*gtsmodel.Poll, error) { var pollIDs []string - // Select all polls with unset `closed_at` time. + // Select all polls with: + // - UNSET `closed_at` + // - SET `expires_at` if err := p.db.NewSelect(). Table("polls"). Column("polls.id"). Join("JOIN ? ON ? = ?", bun.Ident("statuses"), bun.Ident("polls.id"), bun.Ident("statuses.poll_id")). Where("? = true", bun.Ident("statuses.local")). + Where("? IS NOT NULL", bun.Ident("polls.expires_at")). Where("? IS NULL", bun.Ident("polls.closed_at")). Scan(ctx, &pollIDs); err != nil { return nil, err diff --git a/internal/db/bundb/relationship_follow_req.go b/internal/db/bundb/relationship_follow_req.go index 030c99c58..f36d626ca 100644 --- a/internal/db/bundb/relationship_follow_req.go +++ b/internal/db/bundb/relationship_follow_req.go @@ -265,8 +265,8 @@ func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, sourceAccountI } // Delete original follow request notification - if err := r.state.DB.DeleteNotifications(ctx, []string{ - string(gtsmodel.NotificationFollowRequest), + if err := r.state.DB.DeleteNotifications(ctx, []gtsmodel.NotificationType{ + gtsmodel.NotificationFollowRequest, }, targetAccountID, sourceAccountID); err != nil { return nil, err } @@ -281,8 +281,8 @@ func (r *relationshipDB) RejectFollowRequest(ctx context.Context, sourceAccountI } // Delete follow request notification - return r.state.DB.DeleteNotifications(ctx, []string{ - string(gtsmodel.NotificationFollowRequest), + return r.state.DB.DeleteNotifications(ctx, []gtsmodel.NotificationType{ + gtsmodel.NotificationFollowRequest, }, targetAccountID, sourceAccountID) } diff --git a/internal/db/notification.go b/internal/db/notification.go index deb58835a..c608261dc 100644 --- a/internal/db/notification.go +++ b/internal/db/notification.go @@ -21,6 +21,7 @@ import ( "context" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/paging" ) // Notification contains functions for creating and getting notifications. @@ -29,7 +30,7 @@ type Notification interface { // // Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest). // If types is empty, *all* notification types will be included. - GetAccountNotifications(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, types []string, excludeTypes []string) ([]*gtsmodel.Notification, error) + GetAccountNotifications(ctx context.Context, accountID string, page *paging.Page, types []gtsmodel.NotificationType, excludeTypes []gtsmodel.NotificationType) ([]*gtsmodel.Notification, error) // GetNotificationByID returns one notification according to its id. GetNotificationByID(ctx context.Context, id string) (*gtsmodel.Notification, error) @@ -64,7 +65,7 @@ type Notification interface { // originate from originAccountID will be deleted. // // At least one parameter must not be an empty string. - DeleteNotifications(ctx context.Context, types []string, targetAccountID string, originAccountID string) error + DeleteNotifications(ctx context.Context, types []gtsmodel.NotificationType, targetAccountID string, originAccountID string) error // DeleteNotificationsForStatus deletes all notifications that relate to // the given statusID. This function is useful when a status has been deleted, diff --git a/internal/gtsmodel/accountsettings.go b/internal/gtsmodel/accountsettings.go index 3151ba5b7..4624aa0b1 100644 --- a/internal/gtsmodel/accountsettings.go +++ b/internal/gtsmodel/accountsettings.go @@ -26,7 +26,7 @@ type AccountSettings struct { AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // AccountID that owns this settings. CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created. UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated. - Privacy Visibility `bun:",nullzero"` // Default post privacy for this account + Privacy Visibility `bun:",nullzero,default:3"` // Default post privacy for this account Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default? Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in? StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts). @@ -34,7 +34,7 @@ type AccountSettings struct { CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses. EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections. - WebVisibility Visibility `bun:",nullzero,notnull,default:public"` // Visibility level of statuses that visitors can view via the web profile. + WebVisibility Visibility `bun:",nullzero,notnull,default:3"` // Visibility level of statuses that visitors can view via the web profile. InteractionPolicyDirect *InteractionPolicy `bun:""` // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy. InteractionPolicyMutualsOnly *InteractionPolicy `bun:""` // Interaction policy to use for new mutuals only visibility statuses. If null, assume default policy. InteractionPolicyFollowersOnly *InteractionPolicy `bun:""` // Interaction policy to use for new followers only visibility statuses. If null, assume default policy. diff --git a/internal/gtsmodel/adminaction.go b/internal/gtsmodel/adminaction.go index e8b82e495..5ca8244a0 100644 --- a/internal/gtsmodel/adminaction.go +++ b/internal/gtsmodel/adminaction.go @@ -19,6 +19,7 @@ package gtsmodel import ( "path" + "strings" "time" ) @@ -46,8 +47,8 @@ func (c AdminActionCategory) String() string { } } -func NewAdminActionCategory(in string) AdminActionCategory { - switch in { +func ParseAdminActionCategory(in string) AdminActionCategory { + switch strings.ToLower(in) { case "account": return AdminActionCategoryAccount case "domain": @@ -96,8 +97,8 @@ func (t AdminActionType) String() string { } } -func NewAdminActionType(in string) AdminActionType { - switch in { +func ParseAdminActionType(in string) AdminActionType { + switch strings.ToLower(in) { case "disable": return AdminActionDisable case "reenable": diff --git a/internal/gtsmodel/common.go b/internal/gtsmodel/common.go new file mode 100644 index 000000000..e740bbb81 --- /dev/null +++ b/internal/gtsmodel/common.go @@ -0,0 +1,24 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +// enumType is the type we (at least, should) use +// for database enum types. it is the largest size +// supported by a PostgreSQL SMALLINT, since an +// SQLite SMALLINT is actually variable in size. +type enumType int16 diff --git a/internal/gtsmodel/domainpermission.go b/internal/gtsmodel/domainpermission.go index 3d1ee873f..f1db4de59 100644 --- a/internal/gtsmodel/domainpermission.go +++ b/internal/gtsmodel/domainpermission.go @@ -17,7 +17,10 @@ package gtsmodel -import "time" +import ( + "strings" + "time" +) // DomainPermission models a domain permission // entry -- block / allow / draft / exclude. @@ -62,8 +65,8 @@ func (p DomainPermissionType) String() string { } } -func NewDomainPermissionType(in string) DomainPermissionType { - switch in { +func ParseDomainPermissionType(in string) DomainPermissionType { + switch strings.ToLower(in) { case "block": return DomainPermissionBlock case "allow": diff --git a/internal/gtsmodel/instance.go b/internal/gtsmodel/instance.go index 027d8fba4..97c0268ce 100644 --- a/internal/gtsmodel/instance.go +++ b/internal/gtsmodel/instance.go @@ -34,6 +34,7 @@ type Instance struct { ShortDescriptionText string `bun:""` // Raw text version of short description (before parsing). Description string `bun:""` // Longer description of this instance. DescriptionText string `bun:""` // Raw text version of long description (before parsing). + CustomCSS string `bun:",nullzero"` // Custom CSS for the instance. Terms string `bun:""` // Terms and conditions of this instance. TermsText string `bun:""` // Raw text version of terms (before parsing). ContactEmail string `bun:""` // Contact email address for this instance diff --git a/internal/gtsmodel/interactionpolicy.go b/internal/gtsmodel/interactionpolicy.go index d8d890e69..7fcafc80d 100644 --- a/internal/gtsmodel/interactionpolicy.go +++ b/internal/gtsmodel/interactionpolicy.go @@ -224,7 +224,7 @@ func DefaultInteractionPolicyFor(v Visibility) *InteractionPolicy { case VisibilityDirect: return DefaultInteractionPolicyDirect() default: - panic("visibility " + v + " not recognized") + panic("invalid visibility") } } diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go index 5cf6b061a..1ef805081 100644 --- a/internal/gtsmodel/notification.go +++ b/internal/gtsmodel/notification.go @@ -17,7 +17,10 @@ package gtsmodel -import "time" +import ( + "strings" + "time" +) // Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc. type Notification struct { @@ -34,20 +37,82 @@ type Notification struct { Read *bool `bun:",nullzero,notnull,default:false"` // Notification has been seen/read } -// NotificationType describes the reason/type of this notification. -type NotificationType string +// NotificationType describes the +// reason/type of this notification. +type NotificationType enumType -// Notification Types const ( - NotificationFollow NotificationType = "follow" // NotificationFollow -- someone followed you - NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you - NotificationMention NotificationType = "mention" // NotificationMention -- someone mentioned you in their status - NotificationReblog NotificationType = "reblog" // NotificationReblog -- someone boosted one of your statuses - NotificationFave NotificationType = "favourite" // NotificationFave -- someone faved/liked one of your statuses - NotificationPoll NotificationType = "poll" // NotificationPoll -- a poll you voted in or created has ended - NotificationStatus NotificationType = "status" // NotificationStatus -- someone you enabled notifications for has posted a status. - NotificationSignup NotificationType = "admin.sign_up" // NotificationSignup -- someone has submitted a new account sign-up to the instance. - NotificationPendingFave NotificationType = "pending.favourite" // Someone has faved a status of yours, which requires approval by you. - NotificationPendingReply NotificationType = "pending.reply" // Someone has replied to a status of yours, which requires approval by you. - NotificationPendingReblog NotificationType = "pending.reblog" // Someone has boosted a status of yours, which requires approval by you. + // Notification Types + NotificationUnknown NotificationType = 0 // NotificationUnknown -- unknown notification type, error if this occurs + NotificationFollow NotificationType = 1 // NotificationFollow -- someone followed you + NotificationFollowRequest NotificationType = 2 // NotificationFollowRequest -- someone requested to follow you + NotificationMention NotificationType = 3 // NotificationMention -- someone mentioned you in their status + NotificationReblog NotificationType = 4 // NotificationReblog -- someone boosted one of your statuses + NotificationFave NotificationType = 5 // NotificationFave -- someone faved/liked one of your statuses + NotificationPoll NotificationType = 6 // NotificationPoll -- a poll you voted in or created has ended + NotificationStatus NotificationType = 7 // NotificationStatus -- someone you enabled notifications for has posted a status. + NotificationSignup NotificationType = 8 // NotificationSignup -- someone has submitted a new account sign-up to the instance. + NotificationPendingFave NotificationType = 9 // Someone has faved a status of yours, which requires approval by you. + NotificationPendingReply NotificationType = 10 // Someone has replied to a status of yours, which requires approval by you. + NotificationPendingReblog NotificationType = 11 // Someone has boosted a status of yours, which requires approval by you. ) + +// String returns a stringified, frontend API compatible form of NotificationType. +func (t NotificationType) String() string { + switch t { + case NotificationFollow: + return "follow" + case NotificationFollowRequest: + return "follow_request" + case NotificationMention: + return "mention" + case NotificationReblog: + return "reblog" + case NotificationFave: + return "favourite" + case NotificationPoll: + return "poll" + case NotificationStatus: + return "status" + case NotificationSignup: + return "admin.sign_up" + case NotificationPendingFave: + return "pending.favourite" + case NotificationPendingReply: + return "pending.reply" + case NotificationPendingReblog: + return "pending.reblog" + default: + panic("invalid notification type") + } +} + +// ParseNotificationType returns a notification type from the given value. +func ParseNotificationType(in string) NotificationType { + switch strings.ToLower(in) { + case "follow": + return NotificationFollow + case "follow_request": + return NotificationFollowRequest + case "mention": + return NotificationMention + case "reblog": + return NotificationReblog + case "favourite": + return NotificationFave + case "poll": + return NotificationPoll + case "status": + return NotificationStatus + case "admin.sign_up": + return NotificationSignup + case "pending.favourite": + return NotificationPendingFave + case "pending.reply": + return NotificationPendingReply + case "pending.reblog": + return NotificationPendingReblog + default: + return NotificationUnknown + } +} diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index 91c0ada61..f8bd068ab 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -263,27 +263,58 @@ type StatusToEmoji struct { Emoji *Emoji `bun:"rel:belongs-to"` } -// Visibility represents the visibility granularity of a status. -type Visibility string +// Visibility represents the +// visibility granularity of a status. +type Visibility enumType const ( // VisibilityNone means nobody can see this. // It's only used for web status visibility. - VisibilityNone Visibility = "none" - // VisibilityPublic means this status will be visible to everyone on all timelines. - VisibilityPublic Visibility = "public" - // VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. - VisibilityUnlocked Visibility = "unlocked" + VisibilityNone Visibility = 1 + + // VisibilityPublic means this status will + // be visible to everyone on all timelines. + VisibilityPublic Visibility = 2 + + // VisibilityUnlocked means this status will be visible to everyone, + // but will only show on home timeline to followers, and in lists. + VisibilityUnlocked Visibility = 3 + // VisibilityFollowersOnly means this status is viewable to followers only. - VisibilityFollowersOnly Visibility = "followers_only" - // VisibilityMutualsOnly means this status is visible to mutual followers only. - VisibilityMutualsOnly Visibility = "mutuals_only" - // VisibilityDirect means this status is visible only to mentioned recipients. - VisibilityDirect Visibility = "direct" + VisibilityFollowersOnly Visibility = 4 + + // VisibilityMutualsOnly means this status + // is visible to mutual followers only. + VisibilityMutualsOnly Visibility = 5 + + // VisibilityDirect means this status is + // visible only to mentioned recipients. + VisibilityDirect Visibility = 6 + // VisibilityDefault is used when no other setting can be found. VisibilityDefault Visibility = VisibilityUnlocked ) +// String returns a stringified, frontend API compatible form of Visibility. +func (v Visibility) String() string { + switch v { + case VisibilityNone: + return "none" + case VisibilityPublic: + return "public" + case VisibilityUnlocked: + return "unlocked" + case VisibilityFollowersOnly: + return "followers_only" + case VisibilityMutualsOnly: + return "mutuals_only" + case VisibilityDirect: + return "direct" + default: + panic("invalid visibility") + } +} + // Content models the simple string content // of a status along with its ContentMap, // which contains content entries keyed by diff --git a/internal/processing/admin/accountaction.go b/internal/processing/admin/accountaction.go index 7fd1047c4..59d4b420e 100644 --- a/internal/processing/admin/accountaction.go +++ b/internal/processing/admin/accountaction.go @@ -40,7 +40,7 @@ func (p *Processor) AccountAction( return "", gtserror.NewErrorInternalError(err) } - switch gtsmodel.NewAdminActionType(request.Type) { + switch gtsmodel.ParseAdminActionType(request.Type) { case gtsmodel.AdminActionSuspend: return p.accountActionSuspend(ctx, adminAcct, targetAcct, request.Text) diff --git a/internal/processing/filters/v1/create.go b/internal/processing/filters/v1/create.go index 18367dfce..86019d2a6 100644 --- a/internal/processing/filters/v1/create.go +++ b/internal/processing/filters/v1/create.go @@ -43,7 +43,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form if *form.Irreversible { filter.Action = gtsmodel.FilterActionHide } - if form.ExpiresIn != nil { + if form.ExpiresIn != nil && *form.ExpiresIn != 0 { filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn)) } for _, context := range form.Context { diff --git a/internal/processing/filters/v1/update.go b/internal/processing/filters/v1/update.go index 81340b4be..15c5de365 100644 --- a/internal/processing/filters/v1/update.go +++ b/internal/processing/filters/v1/update.go @@ -67,7 +67,7 @@ func (p *Processor) Update( action = gtsmodel.FilterActionHide } expiresAt := time.Time{} - if form.ExpiresIn != nil { + if form.ExpiresIn != nil && *form.ExpiresIn != 0 { expiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn)) } contextHome := false diff --git a/internal/processing/filters/v2/create.go b/internal/processing/filters/v2/create.go index 7095a643c..60dd46f43 100644 --- a/internal/processing/filters/v2/create.go +++ b/internal/processing/filters/v2/create.go @@ -41,7 +41,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form Title: form.Title, Action: typeutils.APIFilterActionToFilterAction(*form.FilterAction), } - if form.ExpiresIn != nil { + if form.ExpiresIn != nil && *form.ExpiresIn != 0 { filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn)) } for _, context := range form.Context { diff --git a/internal/processing/filters/v2/update.go b/internal/processing/filters/v2/update.go index 0d443d58e..d5b5cce01 100644 --- a/internal/processing/filters/v2/update.go +++ b/internal/processing/filters/v2/update.go @@ -21,8 +21,6 @@ import ( "context" "errors" "fmt" - "time" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -30,6 +28,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/util" + "time" ) // Update an existing filter for the given account, using the provided parameters. @@ -68,10 +67,16 @@ func (p *Processor) Update( filterColumns = append(filterColumns, "action") filter.Action = typeutils.APIFilterActionToFilterAction(*form.FilterAction) } - // TODO: (Vyr) is it possible to unset a filter expiration with this API? if form.ExpiresIn != nil { + expiresIn := *form.ExpiresIn filterColumns = append(filterColumns, "expires_at") - filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn)) + if expiresIn == 0 { + // Unset the expiration date. + filter.ExpiresAt = time.Time{} + } else { + // Update the expiration date. + filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(expiresIn)) + } } if form.Context != nil { filterColumns = append(filterColumns, diff --git a/internal/processing/instance.go b/internal/processing/instance.go index a9be6db1d..fab71b1de 100644 --- a/internal/processing/instance.go +++ b/internal/processing/instance.go @@ -227,6 +227,17 @@ func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe columns = append(columns, []string{"description", "description_text"}...) } + // validate & update site custom css if it's set on the form + if form.CustomCSS != nil { + customCSS := *form.CustomCSS + if err := validate.InstanceCustomCSS(customCSS); err != nil { + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + instance.CustomCSS = text.SanitizeToPlaintext(customCSS) + columns = append(columns, []string{"custom_css"}...) + } + // Validate & update site // terms if set on the form. if form.Terms != nil { diff --git a/internal/processing/preferences.go b/internal/processing/preferences.go index 0a5f566ae..fb445ec5b 100644 --- a/internal/processing/preferences.go +++ b/internal/processing/preferences.go @@ -46,7 +46,7 @@ func (p *Processor) PreferencesGet(ctx context.Context, accountID string) (*apim func mastoPrefVisibility(vis gtsmodel.Visibility) string { switch vis { case gtsmodel.VisibilityPublic, gtsmodel.VisibilityDirect: - return string(vis) + return vis.String() case gtsmodel.VisibilityUnlocked: return "unlisted" default: diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index fbc2dadf7..340cf9ff3 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -85,25 +85,8 @@ func (p *Processor) Create( PendingApproval: util.Ptr(false), } - if form.Poll != nil { - // Update the status AS type to "Question". - status.ActivityStreamsType = ap.ActivityQuestion - - // Create new poll for status from form. - secs := time.Duration(form.Poll.ExpiresIn) - status.Poll = >smodel.Poll{ - ID: id.NewULID(), - Multiple: &form.Poll.Multiple, - HideCounts: &form.Poll.HideTotals, - Options: form.Poll.Options, - StatusID: statusID, - Status: status, - ExpiresAt: now.Add(secs * time.Second), - } - - // Set poll ID on the status. - status.PollID = status.Poll.ID - } + // Process any attached poll. + p.processPoll(status, form.Poll) // Check + attach in-reply-to status. if errWithCode := p.processInReplyTo(ctx, @@ -153,6 +136,14 @@ func (p *Processor) Create( return nil, gtserror.NewErrorInternalError(err) } + if status.Poll != nil && !status.Poll.ExpiresAt.IsZero() { + // Now that the status is inserted, and side effects queued, + // attempt to schedule an expiry handler for the status poll. + if err := p.polls.ScheduleExpiry(ctx, status.Poll); err != nil { + log.Errorf(ctx, "error scheduling poll expiry: %v", err) + } + } + // send it back to the client API worker for async side-effects. p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ APObjectType: ap.ObjectNote, @@ -161,14 +152,6 @@ func (p *Processor) Create( Origin: requester, }) - if status.Poll != nil { - // Now that the status is inserted, and side effects queued, - // attempt to schedule an expiry handler for the status poll. - if err := p.polls.ScheduleExpiry(ctx, status.Poll); err != nil { - log.Errorf(ctx, "error scheduling poll expiry: %v", err) - } - } - // If the new status replies to a status that // replies to us, use our reply as an implicit // accept of any pending interaction. @@ -189,6 +172,43 @@ func (p *Processor) Create( return p.c.GetAPIStatus(ctx, requester, status) } +func (p *Processor) processPoll(status *gtsmodel.Status, poll *apimodel.PollRequest) { + if poll == nil { + // No poll set. + // Nothing to do. + return + } + + var expiresAt time.Time + + // Now will have been set + // as the status creation. + now := status.CreatedAt + + // Update the status AS type to "Question". + status.ActivityStreamsType = ap.ActivityQuestion + + // Set an expiry time if one given. + if in := poll.ExpiresIn; in > 0 { + expiresIn := time.Duration(in) + expiresAt = now.Add(expiresIn * time.Second) + } + + // Create new poll for status. + status.Poll = >smodel.Poll{ + ID: id.NewULID(), + Multiple: &poll.Multiple, + HideCounts: &poll.HideTotals, + Options: poll.Options, + StatusID: status.ID, + Status: status, + ExpiresAt: expiresAt, + } + + // Set poll ID on the status. + status.PollID = status.Poll.ID +} + func (p *Processor) processInReplyTo(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status, inReplyToID string) gtserror.WithCode { if inReplyToID == "" { // Not a reply. @@ -372,7 +392,7 @@ func (p *Processor) processVisibility( // Fall back to account default, set // this back on the form for later use. - case accountDefaultVis != "": + case accountDefaultVis != 0: status.Visibility = accountDefaultVis form.Visibility = p.converter.VisToAPIVis(ctx, accountDefaultVis) diff --git a/internal/processing/timeline/notification.go b/internal/processing/timeline/notification.go index 34e6d865d..a242c7b74 100644 --- a/internal/processing/timeline/notification.go +++ b/internal/processing/timeline/notification.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "net/url" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -31,26 +32,21 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/internal/paging" "github.com/superseriousbusiness/gotosocial/internal/util" ) func (p *Processor) NotificationsGet( ctx context.Context, authed *oauth.Auth, - maxID string, - sinceID string, - minID string, - limit int, - types []string, - excludeTypes []string, + page *paging.Page, + types []gtsmodel.NotificationType, + excludeTypes []gtsmodel.NotificationType, ) (*apimodel.PageableResponse, gtserror.WithCode) { notifs, err := p.state.DB.GetAccountNotifications( ctx, authed.Account.ID, - maxID, - sinceID, - minID, - limit, + page, types, excludeTypes, ) @@ -78,22 +74,15 @@ func (p *Processor) NotificationsGet( compiledMutes := usermute.NewCompiledUserMuteList(mutes) var ( - items = make([]interface{}, 0, count) - nextMaxIDValue string - prevMinIDValue string + items = make([]interface{}, 0, count) + + // Get the lowest and highest + // ID values, used for paging. + lo = notifs[count-1].ID + hi = notifs[0].ID ) - for i, n := range notifs { - // Set next + prev values before filtering and API - // converting, so caller can still page properly. - if i == count-1 { - nextMaxIDValue = n.ID - } - - if i == 0 { - prevMinIDValue = n.ID - } - + for _, n := range notifs { visible, err := p.notifVisible(ctx, n, authed.Account) if err != nil { log.Debugf(ctx, "skipping notification %s because of an error checking notification visibility: %v", n.ID, err) @@ -115,13 +104,22 @@ func (p *Processor) NotificationsGet( items = append(items, item) } - return util.PackagePageableResponse(util.PageableResponseParams{ - Items: items, - Path: "api/v1/notifications", - NextMaxIDValue: nextMaxIDValue, - PrevMinIDValue: prevMinIDValue, - Limit: limit, - }) + // Build type query string. + query := make(url.Values) + for _, typ := range types { + query.Add("types[]", typ.String()) + } + for _, typ := range excludeTypes { + query.Add("exclude_types[]", typ.String()) + } + + return paging.PackageResponse(paging.ResponseParams{ + Items: items, + Path: "/api/v1/notifications", + Next: page.Next(lo, hi), + Prev: page.Prev(lo, hi), + Query: query, + }), nil } func (p *Processor) NotificationGet(ctx context.Context, account *gtsmodel.Account, targetNotifID string) (*apimodel.Notification, gtserror.WithCode) { diff --git a/internal/processing/workers/federate.go b/internal/processing/workers/federate.go index a0fd6bf69..8c08c42b7 100644 --- a/internal/processing/workers/federate.go +++ b/internal/processing/workers/federate.go @@ -217,18 +217,23 @@ func (f *federate) CreatePollVote(ctx context.Context, poll *gtsmodel.Poll, vote return err } - // Convert vote to AS Create with vote choices as Objects. - create, err := f.converter.PollVoteToASCreate(ctx, vote) + // Convert vote to AS Creates with vote choices as Objects. + creates, err := f.converter.PollVoteToASCreates(ctx, vote) if err != nil { return gtserror.Newf("error converting to notes: %w", err) } - // Send the Create via the Actor's outbox. - if _, err := f.FederatingActor().Send(ctx, outboxIRI, create); err != nil { - return gtserror.Newf("error sending Create activity via outbox %s: %w", outboxIRI, err) + var errs gtserror.MultiError + + // Send each create activity. + actor := f.FederatingActor() + for _, create := range creates { + if _, err := actor.Send(ctx, outboxIRI, create); err != nil { + errs.Appendf("error sending Create activity via outbox %s: %w", outboxIRI, err) + } } - return nil + return errs.Combine() } func (f *federate) DeleteStatus(ctx context.Context, status *gtsmodel.Status) error { diff --git a/internal/processing/workers/surfacenotify.go b/internal/processing/workers/surfacenotify.go index 872ccca65..1520d2ec0 100644 --- a/internal/processing/workers/surfacenotify.go +++ b/internal/processing/workers/surfacenotify.go @@ -542,7 +542,7 @@ func getNotifyLockURI( ) string { builder := strings.Builder{} builder.WriteString("notification:?") - builder.WriteString("type=" + string(notificationType)) + builder.WriteString("type=" + notificationType.String()) builder.WriteString("&target=" + targetAccount.URI) builder.WriteString("&origin=" + originAccount.URI) if statusID != "" { diff --git a/internal/processing/workers/surfacenotify_test.go b/internal/processing/workers/surfacenotify_test.go index dc445d0ac..52ee89e8b 100644 --- a/internal/processing/workers/surfacenotify_test.go +++ b/internal/processing/workers/surfacenotify_test.go @@ -89,7 +89,7 @@ func (suite *SurfaceNotifyTestSuite) TestSpamNotifs() { notifs, err := testStructs.State.DB.GetAccountNotifications( gtscontext.SetBarebones(ctx), targetAccount.ID, - "", "", "", 0, nil, nil, + nil, nil, nil, ) if err != nil { suite.FailNow(err.Error()) diff --git a/internal/typeutils/frontendtointernal.go b/internal/typeutils/frontendtointernal.go index 1f7d1877e..82957ee05 100644 --- a/internal/typeutils/frontendtointernal.go +++ b/internal/typeutils/frontendtointernal.go @@ -41,7 +41,7 @@ func APIVisToVis(m apimodel.Visibility) gtsmodel.Visibility { case apimodel.VisibilityNone: return gtsmodel.VisibilityNone } - return "" + return 0 } func APIMarkerNameToMarkerName(m apimodel.MarkerName) gtsmodel.MarkerName { diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index ed8bc1d8d..d46ce64e2 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -444,7 +444,7 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat poll := streams.NewActivityStreamsQuestion() // Add required status poll data to AS Question. - if err := c.addPollToAS(ctx, s.Poll, poll); err != nil { + if err := c.addPollToAS(s.Poll, poll); err != nil { return nil, gtserror.Newf("error converting poll: %w", err) } @@ -708,7 +708,7 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat return status, nil } -func (c *Converter) addPollToAS(ctx context.Context, poll *gtsmodel.Poll, dst ap.Pollable) error { +func (c *Converter) addPollToAS(poll *gtsmodel.Poll, dst ap.Pollable) error { var optionsProp interface { // the minimum interface for appending AS Notes // to an AS type options property of some kind. @@ -1701,10 +1701,14 @@ func (c *Converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (voc // PollVoteToASCreate converts a vote on a poll into a Create // activity, suitable for federation, with each choice in the // vote appended as a Note to the Create's Object field. -func (c *Converter) PollVoteToASCreate( +// +// TODO: as soon as other AP server implementations support +// the use of multiple objects in a single create, update this +// to return just the one create event again. +func (c *Converter) PollVoteToASCreates( ctx context.Context, vote *gtsmodel.PollVote, -) (vocab.ActivityStreamsCreate, error) { +) ([]vocab.ActivityStreamsCreate, error) { if len(vote.Choices) == 0 { panic("no vote.Choices") } @@ -1743,22 +1747,25 @@ func (c *Converter) PollVoteToASCreate( return nil, gtserror.Newf("invalid account uri: %w", err) } - // Allocate Create activity and address 'To' poll author. - create := streams.NewActivityStreamsCreate() - ap.AppendTo(create, pollAuthorIRI) + // Parse each choice to a Note and add it to the list of Creates. + creates := make([]vocab.ActivityStreamsCreate, len(vote.Choices)) + for i, choice := range vote.Choices { - // Create ID formatted as: {$voterIRI}/activity#vote/{$statusIRI}. - id := author.URI + "/activity#vote/" + poll.Status.URI - ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(create), id) + // Allocate Create activity and address 'To' poll author. + create := streams.NewActivityStreamsCreate() + ap.AppendTo(create, pollAuthorIRI) - // Set Create actor appropriately. - ap.AppendActorIRIs(create, authorIRI) + // Create ID formatted as: {$voterIRI}/activity#vote{$index}/{$statusIRI}. + createID := fmt.Sprintf("%s/activity#vote%d/%s", author.URI, i, poll.Status.URI) + ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(create), createID) - // Set publish time for activity. - ap.SetPublished(create, vote.CreatedAt) + // Set Create actor appropriately. + ap.AppendActorIRIs(create, authorIRI) - // Parse each choice to a Note and add it to the Create. - for _, choice := range vote.Choices { + // Set publish time for activity. + ap.SetPublished(create, vote.CreatedAt) + + // Allocate new note to hold the vote. note := streams.NewActivityStreamsNote() // For AP IRI generate from author URI + poll ID + vote choice. @@ -1775,11 +1782,14 @@ func (c *Converter) PollVoteToASCreate( ap.AppendInReplyTo(note, statusIRI) ap.AppendTo(note, pollAuthorIRI) - // Append this note as Create Object. + // Append this note to the Create Object. appendStatusableToActivity(create, note, false) + + // Set create in slice. + creates[i] = create } - return create, nil + return creates, nil } // populateValuesForProp appends the given PolicyValues diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go index a97eee2b8..c847cfc93 100644 --- a/internal/typeutils/internaltoas_test.go +++ b/internal/typeutils/internaltoas_test.go @@ -1104,43 +1104,55 @@ func (suite *InternalToASTestSuite) TestPinnedStatusesToASOneItem() { func (suite *InternalToASTestSuite) TestPollVoteToASCreate() { vote := suite.testPollVotes["remote_account_1_status_2_poll_vote_local_account_1"] - create, err := suite.typeconverter.PollVoteToASCreate(context.Background(), vote) - if err != nil { - suite.FailNow(err.Error()) - } + creates, err := suite.typeconverter.PollVoteToASCreates(context.Background(), vote) + suite.NoError(err) + suite.Len(creates, 2) - createI, err := ap.Serialize(create) + createI0, err := ap.Serialize(creates[0]) suite.NoError(err) - bytes, err := json.MarshalIndent(createI, "", " ") + createI1, err := ap.Serialize(creates[1]) + suite.NoError(err) + + bytes0, err := json.MarshalIndent(createI0, "", " ") + suite.NoError(err) + + bytes1, err := json.MarshalIndent(createI1, "", " ") suite.NoError(err) suite.Equal(`{ "@context": "https://www.w3.org/ns/activitystreams", "actor": "http://localhost:8080/users/the_mighty_zork", - "id": "http://localhost:8080/users/the_mighty_zork/activity#vote/http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", - "object": [ - { - "attributedTo": "http://localhost:8080/users/the_mighty_zork", - "id": "http://localhost:8080/users/the_mighty_zork#01HEN2R65468ZG657C4ZPHJ4EX/votes/1", - "inReplyTo": "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", - "name": "tissues", - "to": "http://fossbros-anonymous.io/users/foss_satan", - "type": "Note" - }, - { - "attributedTo": "http://localhost:8080/users/the_mighty_zork", - "id": "http://localhost:8080/users/the_mighty_zork#01HEN2R65468ZG657C4ZPHJ4EX/votes/2", - "inReplyTo": "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", - "name": "financial times", - "to": "http://fossbros-anonymous.io/users/foss_satan", - "type": "Note" - } - ], + "id": "http://localhost:8080/users/the_mighty_zork/activity#vote0/http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", + "object": { + "attributedTo": "http://localhost:8080/users/the_mighty_zork", + "id": "http://localhost:8080/users/the_mighty_zork#01HEN2R65468ZG657C4ZPHJ4EX/votes/1", + "inReplyTo": "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", + "name": "tissues", + "to": "http://fossbros-anonymous.io/users/foss_satan", + "type": "Note" + }, "published": "2021-09-11T11:45:37+02:00", "to": "http://fossbros-anonymous.io/users/foss_satan", "type": "Create" -}`, string(bytes)) +}`, string(bytes0)) + + suite.Equal(`{ + "@context": "https://www.w3.org/ns/activitystreams", + "actor": "http://localhost:8080/users/the_mighty_zork", + "id": "http://localhost:8080/users/the_mighty_zork/activity#vote1/http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", + "object": { + "attributedTo": "http://localhost:8080/users/the_mighty_zork", + "id": "http://localhost:8080/users/the_mighty_zork#01HEN2R65468ZG657C4ZPHJ4EX/votes/2", + "inReplyTo": "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", + "name": "financial times", + "to": "http://fossbros-anonymous.io/users/foss_satan", + "type": "Note" + }, + "published": "2021-09-11T11:45:37+02:00", + "to": "http://fossbros-anonymous.io/users/foss_satan", + "type": "Create" +}`, string(bytes1)) } func (suite *InternalToASTestSuite) TestInteractionReqToASAcceptAnnounce() { diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 5f919f014..fda59610b 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -1534,6 +1534,7 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins Title: i.Title, Description: i.Description, DescriptionText: i.DescriptionText, + CustomCSS: i.CustomCSS, ShortDescription: i.ShortDescription, ShortDescriptionText: i.ShortDescriptionText, Email: i.ContactEmail, @@ -1674,6 +1675,7 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins SourceURL: instanceSourceURL, Description: i.Description, DescriptionText: i.DescriptionText, + CustomCSS: i.CustomCSS, Usage: apimodel.InstanceV2Usage{}, // todo: not implemented Languages: config.GetInstanceLanguages().TagStrs(), Rules: c.InstanceRulesToAPIRules(i.Rules), @@ -1862,7 +1864,7 @@ func (c *Converter) NotificationToAPINotification( return &apimodel.Notification{ ID: n.ID, - Type: string(n.NotificationType), + Type: n.NotificationType.String(), CreatedAt: util.FormatISO8601(n.CreatedAt), Account: apiAccount, Status: apiStatus, diff --git a/internal/validate/formvalidation.go b/internal/validate/formvalidation.go index 207e8e05e..4de7636a5 100644 --- a/internal/validate/formvalidation.go +++ b/internal/validate/formvalidation.go @@ -189,6 +189,16 @@ func CustomCSS(customCSS string) error { return nil } +func InstanceCustomCSS(customCSS string) error { + + maximumCustomCSSLength := config.GetAccountsCustomCSSLength() + if length := len([]rune(customCSS)); length > maximumCustomCSSLength { + return fmt.Errorf("custom_css must be less than %d characters, but submitted custom_css was %d characters", maximumCustomCSSLength, length) + } + + return nil +} + // EmojiShortcode just runs the given shortcode through the regular expression // for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 1-30 characters, // a-zA-Z, numbers, and underscores. diff --git a/internal/web/about.go b/internal/web/about.go index 2bc558962..843dda652 100644 --- a/internal/web/about.go +++ b/internal/web/about.go @@ -54,7 +54,7 @@ func (m *Module) aboutGETHandler(c *gin.Context) { Template: "about.tmpl", Instance: instance, OGMeta: apiutil.OGBase(instance), - Stylesheets: []string{cssAbout}, + Stylesheets: []string{cssAbout, instanceCustomCSSPath}, Extra: map[string]any{ "showStrap": true, "blocklistExposed": config.GetInstanceExposeSuspendedWeb(), diff --git a/internal/web/confirmemail.go b/internal/web/confirmemail.go index e512761f4..21028c6c4 100644 --- a/internal/web/confirmemail.go +++ b/internal/web/confirmemail.go @@ -127,8 +127,9 @@ func (m *Module) confirmEmailPOSTHandler(c *gin.Context) { // Serve page informing user that their // email address is now confirmed. page := apiutil.WebPage{ - Template: "confirmed_email.tmpl", - Instance: instance, + Template: "confirmed_email.tmpl", + Instance: instance, + Stylesheets: []string{instanceCustomCSSPath}, Extra: map[string]any{ "email": user.Email, "username": user.Account.Username, diff --git a/internal/web/customcss.go b/internal/web/customcss.go index b4072f2a7..36ae9de55 100644 --- a/internal/web/customcss.go +++ b/internal/web/customcss.go @@ -55,3 +55,22 @@ func (m *Module) customCSSGETHandler(c *gin.Context) { c.Header(cacheControlHeader, cacheControlNoCache) c.Data(http.StatusOK, textCSSUTF8, []byte(customCSS)) } + +func (m *Module) instanceCustomCSSGETHandler(c *gin.Context) { + + if _, err := apiutil.NegotiateAccept(c, apiutil.TextCSS); err != nil { + apiutil.WebErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + instanceV1, errWithCode := m.processor.InstanceGetV1(c.Request.Context()) + if errWithCode != nil { + apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + instanceCustomCSS := instanceV1.CustomCSS + + c.Header(cacheControlHeader, cacheControlNoCache) + c.Data(http.StatusOK, textCSSUTF8, []byte(instanceCustomCSS)) +} diff --git a/internal/web/domain-blocklist.go b/internal/web/domain-blocklist.go index 5d631e0f7..7b6710049 100644 --- a/internal/web/domain-blocklist.go +++ b/internal/web/domain-blocklist.go @@ -67,7 +67,7 @@ func (m *Module) domainBlockListGETHandler(c *gin.Context) { Template: "domain-blocklist.tmpl", Instance: instance, OGMeta: apiutil.OGBase(instance), - Stylesheets: []string{cssFA}, + Stylesheets: []string{cssFA, instanceCustomCSSPath}, Javascript: []string{jsFrontend}, Extra: map[string]any{"blocklist": domainBlocks}, } diff --git a/internal/web/index.go b/internal/web/index.go index 25960cf7f..dd9d80561 100644 --- a/internal/web/index.go +++ b/internal/web/index.go @@ -59,7 +59,7 @@ func (m *Module) indexHandler(c *gin.Context) { Template: "index.tmpl", Instance: instance, OGMeta: apiutil.OGBase(instance), - Stylesheets: []string{cssAbout, cssIndex}, + Stylesheets: []string{cssAbout, cssIndex, instanceCustomCSSPath}, Extra: map[string]any{"showStrap": true}, } diff --git a/internal/web/profile.go b/internal/web/profile.go index 60157fd19..741dc2a83 100644 --- a/internal/web/profile.go +++ b/internal/web/profile.go @@ -132,7 +132,7 @@ func (m *Module) profileGETHandler(c *gin.Context) { } // Prepare stylesheets for profile. - stylesheets := make([]string, 0, 6) + stylesheets := make([]string, 0, 7) // Basic profile stylesheets. stylesheets = append( @@ -142,6 +142,7 @@ func (m *Module) profileGETHandler(c *gin.Context) { cssStatus, cssThread, cssProfile, + instanceCustomCSSPath, }..., ) diff --git a/internal/web/settings-panel.go b/internal/web/settings-panel.go index ec8166e95..41cd8666e 100644 --- a/internal/web/settings-panel.go +++ b/internal/web/settings-panel.go @@ -53,6 +53,7 @@ func (m *Module) SettingsPanelHandler(c *gin.Context) { cssProfile, // Used for rendering stub/fake profiles. cssStatus, // Used for rendering stub/fake statuses. cssSettings, + instanceCustomCSSPath, }, Javascript: []string{jsSettings}, } diff --git a/internal/web/signup.go b/internal/web/signup.go index a943f3680..64b9f4e2d 100644 --- a/internal/web/signup.go +++ b/internal/web/signup.go @@ -126,9 +126,10 @@ func (m *Module) signupPOSTHandler(c *gin.Context) { // Serve a page informing the // user that they've signed up. page := apiutil.WebPage{ - Template: "signed-up.tmpl", - Instance: instance, - OGMeta: apiutil.OGBase(instance), + Template: "signed-up.tmpl", + Instance: instance, + Stylesheets: []string{instanceCustomCSSPath}, + OGMeta: apiutil.OGBase(instance), Extra: map[string]any{ "email": user.UnconfirmedEmail, "username": user.Account.Username, diff --git a/internal/web/tag.go b/internal/web/tag.go index 5c3cd31a6..423000f99 100644 --- a/internal/web/tag.go +++ b/internal/web/tag.go @@ -59,7 +59,7 @@ func (m *Module) tagGETHandler(c *gin.Context) { Template: "tag.tmpl", Instance: instance, OGMeta: apiutil.OGBase(instance), - Stylesheets: []string{cssFA, cssThread, cssTag}, + Stylesheets: []string{cssFA, cssThread, cssTag, instanceCustomCSSPath}, Extra: map[string]any{"tagName": tagName}, } diff --git a/internal/web/thread.go b/internal/web/thread.go index d3ba6ea5e..557e00381 100644 --- a/internal/web/thread.go +++ b/internal/web/thread.go @@ -115,7 +115,7 @@ func (m *Module) threadGETHandler(c *gin.Context) { } // Prepare stylesheets for thread. - stylesheets := make([]string, 0, 5) + stylesheets := make([]string, 0, 6) // Basic thread stylesheets. stylesheets = append( @@ -124,6 +124,7 @@ func (m *Module) threadGETHandler(c *gin.Context) { cssFA, cssStatus, cssThread, + instanceCustomCSSPath, }..., ) diff --git a/internal/web/web.go b/internal/web/web.go index 185bf7120..35f8f21b0 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -36,20 +36,21 @@ import ( ) const ( - confirmEmailPath = "/" + uris.ConfirmEmailPath - profileGroupPath = "/@:username" - statusPath = "/statuses/:" + apiutil.WebStatusIDKey // leave out the '/@:username' prefix as this will be served within the profile group - tagsPath = "/tags/:" + apiutil.TagNameKey - customCSSPath = profileGroupPath + "/custom.css" - rssFeedPath = profileGroupPath + "/feed.rss" - assetsPathPrefix = "/assets" - distPathPrefix = assetsPathPrefix + "/dist" - themesPathPrefix = assetsPathPrefix + "/themes" - settingsPathPrefix = "/settings" - settingsPanelGlob = settingsPathPrefix + "/*panel" - userPanelPath = settingsPathPrefix + "/user" - adminPanelPath = settingsPathPrefix + "/admin" - signupPath = "/signup" + confirmEmailPath = "/" + uris.ConfirmEmailPath + profileGroupPath = "/@:username" + statusPath = "/statuses/:" + apiutil.WebStatusIDKey // leave out the '/@:username' prefix as this will be served within the profile group + tagsPath = "/tags/:" + apiutil.TagNameKey + customCSSPath = profileGroupPath + "/custom.css" + instanceCustomCSSPath = "/custom.css" + rssFeedPath = profileGroupPath + "/feed.rss" + assetsPathPrefix = "/assets" + distPathPrefix = assetsPathPrefix + "/dist" + themesPathPrefix = assetsPathPrefix + "/themes" + settingsPathPrefix = "/settings" + settingsPanelGlob = settingsPathPrefix + "/*panel" + userPanelPath = settingsPathPrefix + "/user" + adminPanelPath = settingsPathPrefix + "/admin" + signupPath = "/signup" cacheControlHeader = "Cache-Control" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control cacheControlNoCache = "no-cache" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives @@ -114,6 +115,7 @@ func (m *Module) Route(r *router.Router, mi ...gin.HandlerFunc) { r.AttachHandler(http.MethodGet, settingsPathPrefix, m.SettingsPanelHandler) r.AttachHandler(http.MethodGet, settingsPanelGlob, m.SettingsPanelHandler) r.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler) + r.AttachHandler(http.MethodGet, instanceCustomCSSPath, m.instanceCustomCSSGETHandler) r.AttachHandler(http.MethodGet, rssFeedPath, m.rssFeedGETHandler) r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler) r.AttachHandler(http.MethodPost, confirmEmailPath, m.confirmEmailPOSTHandler) diff --git a/test/swagger.sh b/test/swagger.sh index e8b4b5864..c7644c1af 100755 --- a/test/swagger.sh +++ b/test/swagger.sh @@ -5,7 +5,7 @@ set -eu swagger_cmd() { - go run github.com/go-swagger/go-swagger/cmd/swagger "$@" + go run ./vendor/github.com/go-swagger/go-swagger/cmd/swagger "$@" } swagger_spec='docs/api/swagger.yaml' diff --git a/vendor/codeberg.org/superseriousbusiness/exif-terminator/jpeg.go b/vendor/codeberg.org/superseriousbusiness/exif-terminator/jpeg.go index 3c8b7035f..01ca313ca 100644 --- a/vendor/codeberg.org/superseriousbusiness/exif-terminator/jpeg.go +++ b/vendor/codeberg.org/superseriousbusiness/exif-terminator/jpeg.go @@ -109,17 +109,17 @@ func (v *jpegVisitor) writeSegment(s *jpegstructure.Segment) error { sizeLen, found := markerLen[s.MarkerId] if !found || sizeLen == 2 { - sizeLen = 2 - l := uint16(len(s.Data) + sizeLen) - - if err := binary.Write(w, binary.BigEndian, &l); err != nil { + l := uint16(len(s.Data) + 2) + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, l) + if _, err := w.Write(b); err != nil { return err } - } else if sizeLen == 4 { - l := uint32(len(s.Data) + sizeLen) - - if err := binary.Write(w, binary.BigEndian, &l); err != nil { + l := uint32(len(s.Data) + 4) + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, l) + if _, err := w.Write(b); err != nil { return err } } else if sizeLen != 0 { diff --git a/vendor/codeberg.org/superseriousbusiness/exif-terminator/webp.go b/vendor/codeberg.org/superseriousbusiness/exif-terminator/webp.go index 392c4871d..b050f38fc 100644 --- a/vendor/codeberg.org/superseriousbusiness/exif-terminator/webp.go +++ b/vendor/codeberg.org/superseriousbusiness/exif-terminator/webp.go @@ -25,17 +25,16 @@ import ( ) const ( - riffHeaderSize = 4 * 3 + riffHeader = "RIFF" + webpHeader = "WEBP" + exifFourcc = "EXIF" + xmpFourcc = "XMP " ) var ( - riffHeader = [4]byte{'R', 'I', 'F', 'F'} - webpHeader = [4]byte{'W', 'E', 'B', 'P'} - exifFourcc = [4]byte{'E', 'X', 'I', 'F'} - xmpFourcc = [4]byte{'X', 'M', 'P', ' '} - errNoRiffHeader = errors.New("no RIFF header") errNoWebpHeader = errors.New("not a WEBP file") + errInvalidChunk = errors.New("invalid chunk") ) type webpVisitor struct { @@ -43,59 +42,68 @@ type webpVisitor struct { doneHeader bool } -func fourCC(b []byte) [4]byte { - return [4]byte{b[0], b[1], b[2], b[3]} -} - func (v *webpVisitor) split(data []byte, atEOF bool) (advance int, token []byte, err error) { // parse/write the header first if !v.doneHeader { - if len(data) < riffHeaderSize { - // need the full header + + // const rifHeaderSize = 12 + if len(data) < 12 { + if atEOF { + err = errNoRiffHeader + } return } - if fourCC(data) != riffHeader { + + if string(data[:4]) != riffHeader { err = errNoRiffHeader return } - if fourCC(data[8:]) != webpHeader { + + if string(data[8:12]) != webpHeader { err = errNoWebpHeader return } - if _, err = v.writer.Write(data[:riffHeaderSize]); err != nil { + + if _, err = v.writer.Write(data[:12]); err != nil { return } - advance += riffHeaderSize - data = data[riffHeaderSize:] + + advance += 12 + data = data[12:] v.doneHeader = true } - // need enough for fourcc and size - if len(data) < 8 { - return - } - size := int64(binary.LittleEndian.Uint32(data[4:])) - if (size & 1) != 0 { - // odd chunk size - extra padding byte - size++ - } - // wait until there is enough - if int64(len(data)-8) < size { - return - } - - fourcc := fourCC(data) - rawChunkData := data[8 : 8+size] - if fourcc == exifFourcc || fourcc == xmpFourcc { - // replace exif/xmp with blank - rawChunkData = make([]byte, size) - } - - if _, err = v.writer.Write(data[:8]); err == nil { - if _, err = v.writer.Write(rawChunkData); err == nil { - advance += 8 + int(size) + for { + // need enough for + // fourcc and size + if len(data) < 8 { + return } - } - return + size := int64(binary.LittleEndian.Uint32(data[4:])) + + if (size & 1) != 0 { + // odd chunk size: + // extra padding byte + size++ + } + + // wait until there is enough + if int64(len(data)) < 8+size { + return + } + + // replace exif/xmp with blank + switch string(data[:4]) { + case exifFourcc, xmpFourcc: + clear(data[8 : 8+size]) + } + + if _, err = v.writer.Write(data[:8+size]); err != nil { + return + } + + advance += 8 + int(size) + data = data[8+size:] + } } diff --git a/vendor/github.com/bahlo/generic-list-go/LICENSE b/vendor/github.com/bahlo/generic-list-go/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/github.com/bahlo/generic-list-go/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +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. diff --git a/vendor/github.com/bahlo/generic-list-go/README.md b/vendor/github.com/bahlo/generic-list-go/README.md new file mode 100644 index 000000000..68bbce9fb --- /dev/null +++ b/vendor/github.com/bahlo/generic-list-go/README.md @@ -0,0 +1,5 @@ +# generic-list-go [![CI](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml/badge.svg)](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml) + +Go [container/list](https://pkg.go.dev/container/list) but with generics. + +The code is based on `container/list` in `go1.18beta2`. diff --git a/vendor/github.com/bahlo/generic-list-go/list.go b/vendor/github.com/bahlo/generic-list-go/list.go new file mode 100644 index 000000000..a06a7c612 --- /dev/null +++ b/vendor/github.com/bahlo/generic-list-go/list.go @@ -0,0 +1,235 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package list implements a doubly linked list. +// +// To iterate over a list (where l is a *List): +// for e := l.Front(); e != nil; e = e.Next() { +// // do something with e.Value +// } +// +package list + +// Element is an element of a linked list. +type Element[T any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *Element[T] + + // The list to which this element belongs. + list *List[T] + + // The value stored with this element. + Value T +} + +// Next returns the next list element or nil. +func (e *Element[T]) Next() *Element[T] { + if p := e.next; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// Prev returns the previous list element or nil. +func (e *Element[T]) Prev() *Element[T] { + if p := e.prev; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// List represents a doubly linked list. +// The zero value for List is an empty list ready to use. +type List[T any] struct { + root Element[T] // sentinel list element, only &root, root.prev, and root.next are used + len int // current list length excluding (this) sentinel element +} + +// Init initializes or clears list l. +func (l *List[T]) Init() *List[T] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// New returns an initialized list. +func New[T any]() *List[T] { return new(List[T]).Init() } + +// Len returns the number of elements of list l. +// The complexity is O(1). +func (l *List[T]) Len() int { return l.len } + +// Front returns the first element of list l or nil if the list is empty. +func (l *List[T]) Front() *Element[T] { + if l.len == 0 { + return nil + } + return l.root.next +} + +// Back returns the last element of list l or nil if the list is empty. +func (l *List[T]) Back() *Element[T] { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List value. +func (l *List[T]) lazyInit() { + if l.root.next == nil { + l.Init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *List[T]) insert(e, at *Element[T]) *Element[T] { + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). +func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] { + return l.insert(&Element[T]{Value: v}, at) +} + +// remove removes e from its list, decrements l.len +func (l *List[T]) remove(e *Element[T]) { + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- +} + +// move moves e to next to at. +func (l *List[T]) move(e, at *Element[T]) { + if e == at { + return + } + e.prev.next = e.next + e.next.prev = e.prev + + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e +} + +// Remove removes e from l if e is an element of list l. +// It returns the element value e.Value. +// The element must not be nil. +func (l *List[T]) Remove(e *Element[T]) T { + if e.list == l { + // if e.list == l, l must have been initialized when e was inserted + // in l or l == nil (e is a zero Element) and l.remove will crash + l.remove(e) + } + return e.Value +} + +// PushFront inserts a new element e with value v at the front of list l and returns e. +func (l *List[T]) PushFront(v T) *Element[T] { + l.lazyInit() + return l.insertValue(v, &l.root) +} + +// PushBack inserts a new element e with value v at the back of list l and returns e. +func (l *List[T]) PushBack(v T) *Element[T] { + l.lazyInit() + return l.insertValue(v, l.root.prev) +} + +// InsertBefore inserts a new element e with value v immediately before mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark.prev) +} + +// InsertAfter inserts a new element e with value v immediately after mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark) +} + +// MoveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *List[T]) MoveToFront(e *Element[T]) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, &l.root) +} + +// MoveToBack moves element e to the back of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *List[T]) MoveToBack(e *Element[T]) { + if e.list != l || l.root.prev == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, l.root.prev) +} + +// MoveBefore moves element e to its new position before mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *List[T]) MoveBefore(e, mark *Element[T]) { + if e.list != l || e == mark || mark.list != l { + return + } + l.move(e, mark.prev) +} + +// MoveAfter moves element e to its new position after mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *List[T]) MoveAfter(e, mark *Element[T]) { + if e.list != l || e == mark || mark.list != l { + return + } + l.move(e, mark) +} + +// PushBackList inserts a copy of another list at the back of list l. +// The lists l and other may be the same. They must not be nil. +func (l *List[T]) PushBackList(other *List[T]) { + l.lazyInit() + for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { + l.insertValue(e.Value, l.root.prev) + } +} + +// PushFrontList inserts a copy of another list at the front of list l. +// The lists l and other may be the same. They must not be nil. +func (l *List[T]) PushFrontList(other *List[T]) { + l.lazyInit() + for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { + l.insertValue(e.Value, &l.root) + } +} diff --git a/vendor/github.com/buger/jsonparser/.gitignore b/vendor/github.com/buger/jsonparser/.gitignore new file mode 100644 index 000000000..5598d8a56 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/.gitignore @@ -0,0 +1,12 @@ + +*.test + +*.out + +*.mprof + +.idea + +vendor/github.com/buger/goterm/ +prof.cpu +prof.mem diff --git a/vendor/github.com/buger/jsonparser/.travis.yml b/vendor/github.com/buger/jsonparser/.travis.yml new file mode 100644 index 000000000..dbfb7cf98 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/.travis.yml @@ -0,0 +1,11 @@ +language: go +arch: + - amd64 + - ppc64le +go: + - 1.7.x + - 1.8.x + - 1.9.x + - 1.10.x + - 1.11.x +script: go test -v ./. diff --git a/vendor/github.com/buger/jsonparser/Dockerfile b/vendor/github.com/buger/jsonparser/Dockerfile new file mode 100644 index 000000000..37fc9fd0b --- /dev/null +++ b/vendor/github.com/buger/jsonparser/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.6 + +RUN go get github.com/Jeffail/gabs +RUN go get github.com/bitly/go-simplejson +RUN go get github.com/pquerna/ffjson +RUN go get github.com/antonholmquist/jason +RUN go get github.com/mreiferson/go-ujson +RUN go get -tags=unsafe -u github.com/ugorji/go/codec +RUN go get github.com/mailru/easyjson + +WORKDIR /go/src/github.com/buger/jsonparser +ADD . /go/src/github.com/buger/jsonparser \ No newline at end of file diff --git a/vendor/github.com/buger/jsonparser/LICENSE b/vendor/github.com/buger/jsonparser/LICENSE new file mode 100644 index 000000000..ac25aeb7d --- /dev/null +++ b/vendor/github.com/buger/jsonparser/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Leonid Bugaev + +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/buger/jsonparser/Makefile b/vendor/github.com/buger/jsonparser/Makefile new file mode 100644 index 000000000..e843368cf --- /dev/null +++ b/vendor/github.com/buger/jsonparser/Makefile @@ -0,0 +1,36 @@ +SOURCE = parser.go +CONTAINER = jsonparser +SOURCE_PATH = /go/src/github.com/buger/jsonparser +BENCHMARK = JsonParser +BENCHTIME = 5s +TEST = . +DRUN = docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) + +build: + docker build -t $(CONTAINER) . + +race: + $(DRUN) --env GORACE="halt_on_error=1" go test ./. $(ARGS) -v -race -timeout 15s + +bench: + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -benchtime $(BENCHTIME) -v + +bench_local: + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench . $(ARGS) -benchtime $(BENCHTIME) -v + +profile: + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -memprofile mem.mprof -v + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -cpuprofile cpu.out -v + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -c + +test: + $(DRUN) go test $(LDFLAGS) ./ -run $(TEST) -timeout 10s $(ARGS) -v + +fmt: + $(DRUN) go fmt ./... + +vet: + $(DRUN) go vet ./. + +bash: + $(DRUN) /bin/bash \ No newline at end of file diff --git a/vendor/github.com/buger/jsonparser/README.md b/vendor/github.com/buger/jsonparser/README.md new file mode 100644 index 000000000..d7e0ec397 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/README.md @@ -0,0 +1,365 @@ +[![Go Report Card](https://goreportcard.com/badge/github.com/buger/jsonparser)](https://goreportcard.com/report/github.com/buger/jsonparser) ![License](https://img.shields.io/dub/l/vibe-d.svg) +# Alternative JSON parser for Go (10x times faster standard library) + +It does not require you to know the structure of the payload (eg. create structs), and allows accessing fields by providing the path to them. It is up to **10 times faster** than standard `encoding/json` package (depending on payload size and usage), **allocates no memory**. See benchmarks below. + +## Rationale +Originally I made this for a project that relies on a lot of 3rd party APIs that can be unpredictable and complex. +I love simplicity and prefer to avoid external dependecies. `encoding/json` requires you to know exactly your data structures, or if you prefer to use `map[string]interface{}` instead, it will be very slow and hard to manage. +I investigated what's on the market and found that most libraries are just wrappers around `encoding/json`, there is few options with own parsers (`ffjson`, `easyjson`), but they still requires you to create data structures. + + +Goal of this project is to push JSON parser to the performance limits and not sacrifice with compliance and developer user experience. + +## Example +For the given JSON our goal is to extract the user's full name, number of github followers and avatar. + +```go +import "github.com/buger/jsonparser" + +... + +data := []byte(`{ + "person": { + "name": { + "first": "Leonid", + "last": "Bugaev", + "fullName": "Leonid Bugaev" + }, + "github": { + "handle": "buger", + "followers": 109 + }, + "avatars": [ + { "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", "type": "thumbnail" } + ] + }, + "company": { + "name": "Acme" + } +}`) + +// You can specify key path by providing arguments to Get function +jsonparser.Get(data, "person", "name", "fullName") + +// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type +jsonparser.GetInt(data, "person", "github", "followers") + +// When you try to get object, it will return you []byte slice pointer to data containing it +// In `company` it will be `{"name": "Acme"}` +jsonparser.Get(data, "company") + +// If the key doesn't exist it will throw an error +var size int64 +if value, err := jsonparser.GetInt(data, "company", "size"); err == nil { + size = value +} + +// You can use `ArrayEach` helper to iterate items [item1, item2 .... itemN] +jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { + fmt.Println(jsonparser.Get(value, "url")) +}, "person", "avatars") + +// Or use can access fields by index! +jsonparser.GetString(data, "person", "avatars", "[0]", "url") + +// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN } +jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error { + fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType) + return nil +}, "person", "name") + +// The most efficient way to extract multiple keys is `EachKey` + +paths := [][]string{ + []string{"person", "name", "fullName"}, + []string{"person", "avatars", "[0]", "url"}, + []string{"company", "url"}, +} +jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, err error){ + switch idx { + case 0: // []string{"person", "name", "fullName"} + ... + case 1: // []string{"person", "avatars", "[0]", "url"} + ... + case 2: // []string{"company", "url"}, + ... + } +}, paths...) + +// For more information see docs below +``` + +## Need to speedup your app? + +I'm available for consulting and can help you push your app performance to the limits. Ping me at: leonsbox@gmail.com. + +## Reference + +Library API is really simple. You just need the `Get` method to perform any operation. The rest is just helpers around it. + +You also can view API at [godoc.org](https://godoc.org/github.com/buger/jsonparser) + + +### **`Get`** +```go +func Get(data []byte, keys ...string) (value []byte, dataType jsonparser.ValueType, offset int, err error) +``` +Receives data structure, and key path to extract value from. + +Returns: +* `value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error +* `dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null` +* `offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper. +* `err` - If the key is not found or any other parsing issue, it should return error. If key not found it also sets `dataType` to `NotExist` + +Accepts multiple keys to specify path to JSON value (in case of quering nested structures). +If no keys are provided it will try to extract the closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation. + +Note that keys can be an array indexes: `jsonparser.GetInt("person", "avatars", "[0]", "url")`, pretty cool, yeah? + +### **`GetString`** +```go +func GetString(data []byte, keys ...string) (val string, err error) +``` +Returns strings properly handing escaped and unicode characters. Note that this will cause additional memory allocations. + +### **`GetUnsafeString`** +If you need string in your app, and ready to sacrifice with support of escaped symbols in favor of speed. It returns string mapped to existing byte slice memory, without any allocations: +```go +s, _, := jsonparser.GetUnsafeString(data, "person", "name", "title") +switch s { + case 'CEO': + ... + case 'Engineer' + ... + ... +} +``` +Note that `unsafe` here means that your string will exist until GC will free underlying byte slice, for most of cases it means that you can use this string only in current context, and should not pass it anywhere externally: through channels or any other way. + + +### **`GetBoolean`**, **`GetInt`** and **`GetFloat`** +```go +func GetBoolean(data []byte, keys ...string) (val bool, err error) + +func GetFloat(data []byte, keys ...string) (val float64, err error) + +func GetInt(data []byte, keys ...string) (val int64, err error) +``` +If you know the key type, you can use the helpers above. +If key data type do not match, it will return error. + +### **`ArrayEach`** +```go +func ArrayEach(data []byte, cb func(value []byte, dataType jsonparser.ValueType, offset int, err error), keys ...string) +``` +Needed for iterating arrays, accepts a callback function with the same return arguments as `Get`. + +### **`ObjectEach`** +```go +func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) +``` +Needed for iterating object, accepts a callback function. Example: +```go +var handler func([]byte, []byte, jsonparser.ValueType, int) error +handler = func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error { + //do stuff here +} +jsonparser.ObjectEach(myJson, handler) +``` + + +### **`EachKey`** +```go +func EachKey(data []byte, cb func(idx int, value []byte, dataType jsonparser.ValueType, err error), paths ...[]string) +``` +When you need to read multiple keys, and you do not afraid of low-level API `EachKey` is your friend. It read payload only single time, and calls callback function once path is found. For example when you call multiple times `Get`, it has to process payload multiple times, each time you call it. Depending on payload `EachKey` can be multiple times faster than `Get`. Path can use nested keys as well! + +```go +paths := [][]string{ + []string{"uuid"}, + []string{"tz"}, + []string{"ua"}, + []string{"st"}, +} +var data SmallPayload + +jsonparser.EachKey(smallFixture, func(idx int, value []byte, vt jsonparser.ValueType, err error){ + switch idx { + case 0: + data.Uuid, _ = value + case 1: + v, _ := jsonparser.ParseInt(value) + data.Tz = int(v) + case 2: + data.Ua, _ = value + case 3: + v, _ := jsonparser.ParseInt(value) + data.St = int(v) + } +}, paths...) +``` + +### **`Set`** +```go +func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) +``` +Receives existing data structure, key path to set, and value to set at that key. *This functionality is experimental.* + +Returns: +* `value` - Pointer to original data structure with updated or added key value. +* `err` - If any parsing issue, it should return error. + +Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures). + +Note that keys can be an array indexes: `jsonparser.Set(data, []byte("http://github.com"), "person", "avatars", "[0]", "url")` + +### **`Delete`** +```go +func Delete(data []byte, keys ...string) value []byte +``` +Receives existing data structure, and key path to delete. *This functionality is experimental.* + +Returns: +* `value` - Pointer to original data structure with key path deleted if it can be found. If there is no key path, then the whole data structure is deleted. + +Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures). + +Note that keys can be an array indexes: `jsonparser.Delete(data, "person", "avatars", "[0]", "url")` + + +## What makes it so fast? +* It does not rely on `encoding/json`, `reflection` or `interface{}`, the only real package dependency is `bytes`. +* Operates with JSON payload on byte level, providing you pointers to the original data structure: no memory allocation. +* No automatic type conversions, by default everything is a []byte, but it provides you value type, so you can convert by yourself (there is few helpers included). +* Does not parse full record, only keys you specified + + +## Benchmarks + +There are 3 benchmark types, trying to simulate real-life usage for small, medium and large JSON payloads. +For each metric, the lower value is better. Time/op is in nanoseconds. Values better than standard encoding/json marked as bold text. +Benchmarks run on standard Linode 1024 box. + +Compared libraries: +* https://golang.org/pkg/encoding/json +* https://github.com/Jeffail/gabs +* https://github.com/a8m/djson +* https://github.com/bitly/go-simplejson +* https://github.com/antonholmquist/jason +* https://github.com/mreiferson/go-ujson +* https://github.com/ugorji/go/codec +* https://github.com/pquerna/ffjson +* https://github.com/mailru/easyjson +* https://github.com/buger/jsonparser + +#### TLDR +If you want to skip next sections we have 2 winner: `jsonparser` and `easyjson`. +`jsonparser` is up to 10 times faster than standard `encoding/json` package (depending on payload size and usage), and almost infinitely (literally) better in memory consumption because it operates with data on byte level, and provide direct slice pointers. +`easyjson` wins in CPU in medium tests and frankly i'm impressed with this package: it is remarkable results considering that it is almost drop-in replacement for `encoding/json` (require some code generation). + +It's hard to fully compare `jsonparser` and `easyjson` (or `ffson`), they a true parsers and fully process record, unlike `jsonparser` which parse only keys you specified. + +If you searching for replacement of `encoding/json` while keeping structs, `easyjson` is an amazing choice. If you want to process dynamic JSON, have memory constrains, or more control over your data you should try `jsonparser`. + +`jsonparser` performance heavily depends on usage, and it works best when you do not need to process full record, only some keys. The more calls you need to make, the slower it will be, in contrast `easyjson` (or `ffjson`, `encoding/json`) parser record only 1 time, and then you can make as many calls as you want. + +With great power comes great responsibility! :) + + +#### Small payload + +Each test processes 190 bytes of http log as a JSON record. +It should read multiple fields. +https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_small_payload_test.go + +Library | time/op | bytes/op | allocs/op + ------ | ------- | -------- | ------- +encoding/json struct | 7879 | 880 | 18 +encoding/json interface{} | 8946 | 1521 | 38 +Jeffail/gabs | 10053 | 1649 | 46 +bitly/go-simplejson | 10128 | 2241 | 36 +antonholmquist/jason | 27152 | 7237 | 101 +github.com/ugorji/go/codec | 8806 | 2176 | 31 +mreiferson/go-ujson | **7008** | **1409** | 37 +a8m/djson | 3862 | 1249 | 30 +pquerna/ffjson | **3769** | **624** | **15** +mailru/easyjson | **2002** | **192** | **9** +buger/jsonparser | **1367** | **0** | **0** +buger/jsonparser (EachKey API) | **809** | **0** | **0** + +Winners are ffjson, easyjson and jsonparser, where jsonparser is up to 9.8x faster than encoding/json and 4.6x faster than ffjson, and slightly faster than easyjson. +If you look at memory allocation, jsonparser has no rivals, as it makes no data copy and operates with raw []byte structures and pointers to it. + +#### Medium payload + +Each test processes a 2.4kb JSON record (based on Clearbit API). +It should read multiple nested fields and 1 array. + +https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_medium_payload_test.go + +| Library | time/op | bytes/op | allocs/op | +| ------- | ------- | -------- | --------- | +| encoding/json struct | 57749 | 1336 | 29 | +| encoding/json interface{} | 79297 | 10627 | 215 | +| Jeffail/gabs | 83807 | 11202 | 235 | +| bitly/go-simplejson | 88187 | 17187 | 220 | +| antonholmquist/jason | 94099 | 19013 | 247 | +| github.com/ugorji/go/codec | 114719 | 6712 | 152 | +| mreiferson/go-ujson | **56972** | 11547 | 270 | +| a8m/djson | 28525 | 10196 | 198 | +| pquerna/ffjson | **20298** | **856** | **20** | +| mailru/easyjson | **10512** | **336** | **12** | +| buger/jsonparser | **15955** | **0** | **0** | +| buger/jsonparser (EachKey API) | **8916** | **0** | **0** | + +The difference between ffjson and jsonparser in CPU usage is smaller, while the memory consumption difference is growing. On the other hand `easyjson` shows remarkable performance for medium payload. + +`gabs`, `go-simplejson` and `jason` are based on encoding/json and map[string]interface{} and actually only helpers for unstructured JSON, their performance correlate with `encoding/json interface{}`, and they will skip next round. +`go-ujson` while have its own parser, shows same performance as `encoding/json`, also skips next round. Same situation with `ugorji/go/codec`, but it showed unexpectedly bad performance for complex payloads. + + +#### Large payload + +Each test processes a 24kb JSON record (based on Discourse API) +It should read 2 arrays, and for each item in array get a few fields. +Basically it means processing a full JSON file. + +https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_large_payload_test.go + +| Library | time/op | bytes/op | allocs/op | +| --- | --- | --- | --- | +| encoding/json struct | 748336 | 8272 | 307 | +| encoding/json interface{} | 1224271 | 215425 | 3395 | +| a8m/djson | 510082 | 213682 | 2845 | +| pquerna/ffjson | **312271** | **7792** | **298** | +| mailru/easyjson | **154186** | **6992** | **288** | +| buger/jsonparser | **85308** | **0** | **0** | + +`jsonparser` now is a winner, but do not forget that it is way more lightweight parser than `ffson` or `easyjson`, and they have to parser all the data, while `jsonparser` parse only what you need. All `ffjson`, `easysjon` and `jsonparser` have their own parsing code, and does not depend on `encoding/json` or `interface{}`, thats one of the reasons why they are so fast. `easyjson` also use a bit of `unsafe` package to reduce memory consuption (in theory it can lead to some unexpected GC issue, but i did not tested enough) + +Also last benchmark did not included `EachKey` test, because in this particular case we need to read lot of Array values, and using `ArrayEach` is more efficient. + +## Questions and support + +All bug-reports and suggestions should go though Github Issues. + +## Contributing + +1. Fork it +2. Create your feature branch (git checkout -b my-new-feature) +3. Commit your changes (git commit -am 'Added some feature') +4. Push to the branch (git push origin my-new-feature) +5. Create new Pull Request + +## Development + +All my development happens using Docker, and repo include some Make tasks to simplify development. + +* `make build` - builds docker image, usually can be called only once +* `make test` - run tests +* `make fmt` - run go fmt +* `make bench` - run benchmarks (if you need to run only single benchmark modify `BENCHMARK` variable in make file) +* `make profile` - runs benchmark and generate 3 files- `cpu.out`, `mem.mprof` and `benchmark.test` binary, which can be used for `go tool pprof` +* `make bash` - enter container (i use it for running `go tool pprof` above) diff --git a/vendor/github.com/buger/jsonparser/bytes.go b/vendor/github.com/buger/jsonparser/bytes.go new file mode 100644 index 000000000..0bb0ff395 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/bytes.go @@ -0,0 +1,47 @@ +package jsonparser + +import ( + bio "bytes" +) + +// minInt64 '-9223372036854775808' is the smallest representable number in int64 +const minInt64 = `9223372036854775808` + +// About 2x faster then strconv.ParseInt because it only supports base 10, which is enough for JSON +func parseInt(bytes []byte) (v int64, ok bool, overflow bool) { + if len(bytes) == 0 { + return 0, false, false + } + + var neg bool = false + if bytes[0] == '-' { + neg = true + bytes = bytes[1:] + } + + var b int64 = 0 + for _, c := range bytes { + if c >= '0' && c <= '9' { + b = (10 * v) + int64(c-'0') + } else { + return 0, false, false + } + if overflow = (b < v); overflow { + break + } + v = b + } + + if overflow { + if neg && bio.Equal(bytes, []byte(minInt64)) { + return b, true, false + } + return 0, false, true + } + + if neg { + return -v, true, false + } else { + return v, true, false + } +} diff --git a/vendor/github.com/buger/jsonparser/bytes_safe.go b/vendor/github.com/buger/jsonparser/bytes_safe.go new file mode 100644 index 000000000..ff16a4a19 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/bytes_safe.go @@ -0,0 +1,25 @@ +// +build appengine appenginevm + +package jsonparser + +import ( + "strconv" +) + +// See fastbytes_unsafe.go for explanation on why *[]byte is used (signatures must be consistent with those in that file) + +func equalStr(b *[]byte, s string) bool { + return string(*b) == s +} + +func parseFloat(b *[]byte) (float64, error) { + return strconv.ParseFloat(string(*b), 64) +} + +func bytesToString(b *[]byte) string { + return string(*b) +} + +func StringToBytes(s string) []byte { + return []byte(s) +} diff --git a/vendor/github.com/buger/jsonparser/bytes_unsafe.go b/vendor/github.com/buger/jsonparser/bytes_unsafe.go new file mode 100644 index 000000000..589fea87e --- /dev/null +++ b/vendor/github.com/buger/jsonparser/bytes_unsafe.go @@ -0,0 +1,44 @@ +// +build !appengine,!appenginevm + +package jsonparser + +import ( + "reflect" + "strconv" + "unsafe" + "runtime" +) + +// +// The reason for using *[]byte rather than []byte in parameters is an optimization. As of Go 1.6, +// the compiler cannot perfectly inline the function when using a non-pointer slice. That is, +// the non-pointer []byte parameter version is slower than if its function body is manually +// inlined, whereas the pointer []byte version is equally fast to the manually inlined +// version. Instruction count in assembly taken from "go tool compile" confirms this difference. +// +// TODO: Remove hack after Go 1.7 release +// +func equalStr(b *[]byte, s string) bool { + return *(*string)(unsafe.Pointer(b)) == s +} + +func parseFloat(b *[]byte) (float64, error) { + return strconv.ParseFloat(*(*string)(unsafe.Pointer(b)), 64) +} + +// A hack until issue golang/go#2632 is fixed. +// See: https://github.com/golang/go/issues/2632 +func bytesToString(b *[]byte) string { + return *(*string)(unsafe.Pointer(b)) +} + +func StringToBytes(s string) []byte { + b := make([]byte, 0, 0) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh.Data = sh.Data + bh.Cap = sh.Len + bh.Len = sh.Len + runtime.KeepAlive(s) + return b +} diff --git a/vendor/github.com/buger/jsonparser/escape.go b/vendor/github.com/buger/jsonparser/escape.go new file mode 100644 index 000000000..49669b942 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/escape.go @@ -0,0 +1,173 @@ +package jsonparser + +import ( + "bytes" + "unicode/utf8" +) + +// JSON Unicode stuff: see https://tools.ietf.org/html/rfc7159#section-7 + +const supplementalPlanesOffset = 0x10000 +const highSurrogateOffset = 0xD800 +const lowSurrogateOffset = 0xDC00 + +const basicMultilingualPlaneReservedOffset = 0xDFFF +const basicMultilingualPlaneOffset = 0xFFFF + +func combineUTF16Surrogates(high, low rune) rune { + return supplementalPlanesOffset + (high-highSurrogateOffset)<<10 + (low - lowSurrogateOffset) +} + +const badHex = -1 + +func h2I(c byte) int { + switch { + case c >= '0' && c <= '9': + return int(c - '0') + case c >= 'A' && c <= 'F': + return int(c - 'A' + 10) + case c >= 'a' && c <= 'f': + return int(c - 'a' + 10) + } + return badHex +} + +// decodeSingleUnicodeEscape decodes a single \uXXXX escape sequence. The prefix \u is assumed to be present and +// is not checked. +// In JSON, these escapes can either come alone or as part of "UTF16 surrogate pairs" that must be handled together. +// This function only handles one; decodeUnicodeEscape handles this more complex case. +func decodeSingleUnicodeEscape(in []byte) (rune, bool) { + // We need at least 6 characters total + if len(in) < 6 { + return utf8.RuneError, false + } + + // Convert hex to decimal + h1, h2, h3, h4 := h2I(in[2]), h2I(in[3]), h2I(in[4]), h2I(in[5]) + if h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex { + return utf8.RuneError, false + } + + // Compose the hex digits + return rune(h1<<12 + h2<<8 + h3<<4 + h4), true +} + +// isUTF16EncodedRune checks if a rune is in the range for non-BMP characters, +// which is used to describe UTF16 chars. +// Source: https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane +func isUTF16EncodedRune(r rune) bool { + return highSurrogateOffset <= r && r <= basicMultilingualPlaneReservedOffset +} + +func decodeUnicodeEscape(in []byte) (rune, int) { + if r, ok := decodeSingleUnicodeEscape(in); !ok { + // Invalid Unicode escape + return utf8.RuneError, -1 + } else if r <= basicMultilingualPlaneOffset && !isUTF16EncodedRune(r) { + // Valid Unicode escape in Basic Multilingual Plane + return r, 6 + } else if r2, ok := decodeSingleUnicodeEscape(in[6:]); !ok { // Note: previous decodeSingleUnicodeEscape success guarantees at least 6 bytes remain + // UTF16 "high surrogate" without manditory valid following Unicode escape for the "low surrogate" + return utf8.RuneError, -1 + } else if r2 < lowSurrogateOffset { + // Invalid UTF16 "low surrogate" + return utf8.RuneError, -1 + } else { + // Valid UTF16 surrogate pair + return combineUTF16Surrogates(r, r2), 12 + } +} + +// backslashCharEscapeTable: when '\X' is found for some byte X, it is to be replaced with backslashCharEscapeTable[X] +var backslashCharEscapeTable = [...]byte{ + '"': '"', + '\\': '\\', + '/': '/', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t', +} + +// unescapeToUTF8 unescapes the single escape sequence starting at 'in' into 'out' and returns +// how many characters were consumed from 'in' and emitted into 'out'. +// If a valid escape sequence does not appear as a prefix of 'in', (-1, -1) to signal the error. +func unescapeToUTF8(in, out []byte) (inLen int, outLen int) { + if len(in) < 2 || in[0] != '\\' { + // Invalid escape due to insufficient characters for any escape or no initial backslash + return -1, -1 + } + + // https://tools.ietf.org/html/rfc7159#section-7 + switch e := in[1]; e { + case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': + // Valid basic 2-character escapes (use lookup table) + out[0] = backslashCharEscapeTable[e] + return 2, 1 + case 'u': + // Unicode escape + if r, inLen := decodeUnicodeEscape(in); inLen == -1 { + // Invalid Unicode escape + return -1, -1 + } else { + // Valid Unicode escape; re-encode as UTF8 + outLen := utf8.EncodeRune(out, r) + return inLen, outLen + } + } + + return -1, -1 +} + +// unescape unescapes the string contained in 'in' and returns it as a slice. +// If 'in' contains no escaped characters: +// Returns 'in'. +// Else, if 'out' is of sufficient capacity (guaranteed if cap(out) >= len(in)): +// 'out' is used to build the unescaped string and is returned with no extra allocation +// Else: +// A new slice is allocated and returned. +func Unescape(in, out []byte) ([]byte, error) { + firstBackslash := bytes.IndexByte(in, '\\') + if firstBackslash == -1 { + return in, nil + } + + // Get a buffer of sufficient size (allocate if needed) + if cap(out) < len(in) { + out = make([]byte, len(in)) + } else { + out = out[0:len(in)] + } + + // Copy the first sequence of unescaped bytes to the output and obtain a buffer pointer (subslice) + copy(out, in[:firstBackslash]) + in = in[firstBackslash:] + buf := out[firstBackslash:] + + for len(in) > 0 { + // Unescape the next escaped character + inLen, bufLen := unescapeToUTF8(in, buf) + if inLen == -1 { + return nil, MalformedStringEscapeError + } + + in = in[inLen:] + buf = buf[bufLen:] + + // Copy everything up until the next backslash + nextBackslash := bytes.IndexByte(in, '\\') + if nextBackslash == -1 { + copy(buf, in) + buf = buf[len(in):] + break + } else { + copy(buf, in[:nextBackslash]) + buf = buf[nextBackslash:] + in = in[nextBackslash:] + } + } + + // Trim the out buffer to the amount that was actually emitted + return out[:len(out)-len(buf)], nil +} diff --git a/vendor/github.com/buger/jsonparser/fuzz.go b/vendor/github.com/buger/jsonparser/fuzz.go new file mode 100644 index 000000000..854bd11b2 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/fuzz.go @@ -0,0 +1,117 @@ +package jsonparser + +func FuzzParseString(data []byte) int { + r, err := ParseString(data) + if err != nil || r == "" { + return 0 + } + return 1 +} + +func FuzzEachKey(data []byte) int { + paths := [][]string{ + {"name"}, + {"order"}, + {"nested", "a"}, + {"nested", "b"}, + {"nested2", "a"}, + {"nested", "nested3", "b"}, + {"arr", "[1]", "b"}, + {"arrInt", "[3]"}, + {"arrInt", "[5]"}, + {"nested"}, + {"arr", "["}, + {"a\n", "b\n"}, + } + EachKey(data, func(idx int, value []byte, vt ValueType, err error) {}, paths...) + return 1 +} + +func FuzzDelete(data []byte) int { + Delete(data, "test") + return 1 +} + +func FuzzSet(data []byte) int { + _, err := Set(data, []byte(`"new value"`), "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzObjectEach(data []byte) int { + _ = ObjectEach(data, func(key, value []byte, valueType ValueType, off int) error { + return nil + }) + return 1 +} + +func FuzzParseFloat(data []byte) int { + _, err := ParseFloat(data) + if err != nil { + return 0 + } + return 1 +} + +func FuzzParseInt(data []byte) int { + _, err := ParseInt(data) + if err != nil { + return 0 + } + return 1 +} + +func FuzzParseBool(data []byte) int { + _, err := ParseBoolean(data) + if err != nil { + return 0 + } + return 1 +} + +func FuzzTokenStart(data []byte) int { + _ = tokenStart(data) + return 1 +} + +func FuzzGetString(data []byte) int { + _, err := GetString(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetFloat(data []byte) int { + _, err := GetFloat(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetInt(data []byte) int { + _, err := GetInt(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetBoolean(data []byte) int { + _, err := GetBoolean(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetUnsafeString(data []byte) int { + _, err := GetUnsafeString(data, "test") + if err != nil { + return 0 + } + return 1 +} diff --git a/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh b/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh new file mode 100644 index 000000000..c573b0e2d --- /dev/null +++ b/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh @@ -0,0 +1,47 @@ +#!/bin/bash -eu + +git clone https://github.com/dvyukov/go-fuzz-corpus +zip corpus.zip go-fuzz-corpus/json/corpus/* + +cp corpus.zip $OUT/fuzzparsestring_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseString fuzzparsestring + +cp corpus.zip $OUT/fuzzeachkey_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzEachKey fuzzeachkey + +cp corpus.zip $OUT/fuzzdelete_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzDelete fuzzdelete + +cp corpus.zip $OUT/fuzzset_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzSet fuzzset + +cp corpus.zip $OUT/fuzzobjecteach_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzObjectEach fuzzobjecteach + +cp corpus.zip $OUT/fuzzparsefloat_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseFloat fuzzparsefloat + +cp corpus.zip $OUT/fuzzparseint_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseInt fuzzparseint + +cp corpus.zip $OUT/fuzzparsebool_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseBool fuzzparsebool + +cp corpus.zip $OUT/fuzztokenstart_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzTokenStart fuzztokenstart + +cp corpus.zip $OUT/fuzzgetstring_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetString fuzzgetstring + +cp corpus.zip $OUT/fuzzgetfloat_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetFloat fuzzgetfloat + +cp corpus.zip $OUT/fuzzgetint_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetInt fuzzgetint + +cp corpus.zip $OUT/fuzzgetboolean_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetBoolean fuzzgetboolean + +cp corpus.zip $OUT/fuzzgetunsafestring_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetUnsafeString fuzzgetunsafestring + diff --git a/vendor/github.com/buger/jsonparser/parser.go b/vendor/github.com/buger/jsonparser/parser.go new file mode 100644 index 000000000..14b80bc48 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/parser.go @@ -0,0 +1,1283 @@ +package jsonparser + +import ( + "bytes" + "errors" + "fmt" + "strconv" +) + +// Errors +var ( + KeyPathNotFoundError = errors.New("Key path not found") + UnknownValueTypeError = errors.New("Unknown value type") + MalformedJsonError = errors.New("Malformed JSON error") + MalformedStringError = errors.New("Value is string, but can't find closing '\"' symbol") + MalformedArrayError = errors.New("Value is array, but can't find closing ']' symbol") + MalformedObjectError = errors.New("Value looks like object, but can't find closing '}' symbol") + MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol") + OverflowIntegerError = errors.New("Value is number, but overflowed while parsing") + MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string") +) + +// How much stack space to allocate for unescaping JSON strings; if a string longer +// than this needs to be escaped, it will result in a heap allocation +const unescapeStackBufSize = 64 + +func tokenEnd(data []byte) int { + for i, c := range data { + switch c { + case ' ', '\n', '\r', '\t', ',', '}', ']': + return i + } + } + + return len(data) +} + +func findTokenStart(data []byte, token byte) int { + for i := len(data) - 1; i >= 0; i-- { + switch data[i] { + case token: + return i + case '[', '{': + return 0 + } + } + + return 0 +} + +func findKeyStart(data []byte, key string) (int, error) { + i := 0 + ln := len(data) + if ln > 0 && (data[0] == '{' || data[0] == '[') { + i = 1 + } + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + + if ku, err := Unescape(StringToBytes(key), stackbuf[:]); err == nil { + key = bytesToString(&ku) + } + + for i < ln { + switch data[i] { + case '"': + i++ + keyBegin := i + + strEnd, keyEscaped := stringEnd(data[i:]) + if strEnd == -1 { + break + } + i += strEnd + keyEnd := i - 1 + + valueOffset := nextToken(data[i:]) + if valueOffset == -1 { + break + } + + i += valueOffset + + // if string is a key, and key level match + k := data[keyBegin:keyEnd] + // for unescape: if there are no escape sequences, this is cheap; if there are, it is a + // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize + if keyEscaped { + if ku, err := Unescape(k, stackbuf[:]); err != nil { + break + } else { + k = ku + } + } + + if data[i] == ':' && len(key) == len(k) && bytesToString(&k) == key { + return keyBegin - 1, nil + } + + case '[': + end := blockEnd(data[i:], data[i], ']') + if end != -1 { + i = i + end + } + case '{': + end := blockEnd(data[i:], data[i], '}') + if end != -1 { + i = i + end + } + } + i++ + } + + return -1, KeyPathNotFoundError +} + +func tokenStart(data []byte) int { + for i := len(data) - 1; i >= 0; i-- { + switch data[i] { + case '\n', '\r', '\t', ',', '{', '[': + return i + } + } + + return 0 +} + +// Find position of next character which is not whitespace +func nextToken(data []byte) int { + for i, c := range data { + switch c { + case ' ', '\n', '\r', '\t': + continue + default: + return i + } + } + + return -1 +} + +// Find position of last character which is not whitespace +func lastToken(data []byte) int { + for i := len(data) - 1; i >= 0; i-- { + switch data[i] { + case ' ', '\n', '\r', '\t': + continue + default: + return i + } + } + + return -1 +} + +// Tries to find the end of string +// Support if string contains escaped quote symbols. +func stringEnd(data []byte) (int, bool) { + escaped := false + for i, c := range data { + if c == '"' { + if !escaped { + return i + 1, false + } else { + j := i - 1 + for { + if j < 0 || data[j] != '\\' { + return i + 1, true // even number of backslashes + } + j-- + if j < 0 || data[j] != '\\' { + break // odd number of backslashes + } + j-- + + } + } + } else if c == '\\' { + escaped = true + } + } + + return -1, escaped +} + +// Find end of the data structure, array or object. +// For array openSym and closeSym will be '[' and ']', for object '{' and '}' +func blockEnd(data []byte, openSym byte, closeSym byte) int { + level := 0 + i := 0 + ln := len(data) + + for i < ln { + switch data[i] { + case '"': // If inside string, skip it + se, _ := stringEnd(data[i+1:]) + if se == -1 { + return -1 + } + i += se + case openSym: // If open symbol, increase level + level++ + case closeSym: // If close symbol, increase level + level-- + + // If we have returned to the original level, we're done + if level == 0 { + return i + 1 + } + } + i++ + } + + return -1 +} + +func searchKeys(data []byte, keys ...string) int { + keyLevel := 0 + level := 0 + i := 0 + ln := len(data) + lk := len(keys) + lastMatched := true + + if lk == 0 { + return 0 + } + + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + + for i < ln { + switch data[i] { + case '"': + i++ + keyBegin := i + + strEnd, keyEscaped := stringEnd(data[i:]) + if strEnd == -1 { + return -1 + } + i += strEnd + keyEnd := i - 1 + + valueOffset := nextToken(data[i:]) + if valueOffset == -1 { + return -1 + } + + i += valueOffset + + // if string is a key + if data[i] == ':' { + if level < 1 { + return -1 + } + + key := data[keyBegin:keyEnd] + + // for unescape: if there are no escape sequences, this is cheap; if there are, it is a + // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize + var keyUnesc []byte + if !keyEscaped { + keyUnesc = key + } else if ku, err := Unescape(key, stackbuf[:]); err != nil { + return -1 + } else { + keyUnesc = ku + } + + if level <= len(keys) { + if equalStr(&keyUnesc, keys[level-1]) { + lastMatched = true + + // if key level match + if keyLevel == level-1 { + keyLevel++ + // If we found all keys in path + if keyLevel == lk { + return i + 1 + } + } + } else { + lastMatched = false + } + } else { + return -1 + } + } else { + i-- + } + case '{': + + // in case parent key is matched then only we will increase the level otherwise can directly + // can move to the end of this block + if !lastMatched { + end := blockEnd(data[i:], '{', '}') + if end == -1 { + return -1 + } + i += end - 1 + } else { + level++ + } + case '}': + level-- + if level == keyLevel { + keyLevel-- + } + case '[': + // If we want to get array element by index + if keyLevel == level && keys[level][0] == '[' { + var keyLen = len(keys[level]) + if keyLen < 3 || keys[level][0] != '[' || keys[level][keyLen-1] != ']' { + return -1 + } + aIdx, err := strconv.Atoi(keys[level][1 : keyLen-1]) + if err != nil { + return -1 + } + var curIdx int + var valueFound []byte + var valueOffset int + var curI = i + ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) { + if curIdx == aIdx { + valueFound = value + valueOffset = offset + if dataType == String { + valueOffset = valueOffset - 2 + valueFound = data[curI+valueOffset : curI+valueOffset+len(value)+2] + } + } + curIdx += 1 + }) + + if valueFound == nil { + return -1 + } else { + subIndex := searchKeys(valueFound, keys[level+1:]...) + if subIndex < 0 { + return -1 + } + return i + valueOffset + subIndex + } + } else { + // Do not search for keys inside arrays + if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 { + return -1 + } else { + i += arraySkip - 1 + } + } + case ':': // If encountered, JSON data is malformed + return -1 + } + + i++ + } + + return -1 +} + +func sameTree(p1, p2 []string) bool { + minLen := len(p1) + if len(p2) < minLen { + minLen = len(p2) + } + + for pi_1, p_1 := range p1[:minLen] { + if p2[pi_1] != p_1 { + return false + } + } + + return true +} + +func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int { + var x struct{} + pathFlags := make([]bool, len(paths)) + var level, pathsMatched, i int + ln := len(data) + + var maxPath int + for _, p := range paths { + if len(p) > maxPath { + maxPath = len(p) + } + } + + pathsBuf := make([]string, maxPath) + + for i < ln { + switch data[i] { + case '"': + i++ + keyBegin := i + + strEnd, keyEscaped := stringEnd(data[i:]) + if strEnd == -1 { + return -1 + } + i += strEnd + + keyEnd := i - 1 + + valueOffset := nextToken(data[i:]) + if valueOffset == -1 { + return -1 + } + + i += valueOffset + + // if string is a key, and key level match + if data[i] == ':' { + match := -1 + key := data[keyBegin:keyEnd] + + // for unescape: if there are no escape sequences, this is cheap; if there are, it is a + // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize + var keyUnesc []byte + if !keyEscaped { + keyUnesc = key + } else { + var stackbuf [unescapeStackBufSize]byte + if ku, err := Unescape(key, stackbuf[:]); err != nil { + return -1 + } else { + keyUnesc = ku + } + } + + if maxPath >= level { + if level < 1 { + cb(-1, nil, Unknown, MalformedJsonError) + return -1 + } + + pathsBuf[level-1] = bytesToString(&keyUnesc) + for pi, p := range paths { + if len(p) != level || pathFlags[pi] || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) { + continue + } + + match = pi + + pathsMatched++ + pathFlags[pi] = true + + v, dt, _, e := Get(data[i+1:]) + cb(pi, v, dt, e) + + if pathsMatched == len(paths) { + break + } + } + if pathsMatched == len(paths) { + return i + } + } + + if match == -1 { + tokenOffset := nextToken(data[i+1:]) + i += tokenOffset + + if data[i] == '{' { + blockSkip := blockEnd(data[i:], '{', '}') + i += blockSkip + 1 + } + } + + if i < ln { + switch data[i] { + case '{', '}', '[', '"': + i-- + } + } + } else { + i-- + } + case '{': + level++ + case '}': + level-- + case '[': + var ok bool + arrIdxFlags := make(map[int]struct{}) + pIdxFlags := make([]bool, len(paths)) + + if level < 0 { + cb(-1, nil, Unknown, MalformedJsonError) + return -1 + } + + for pi, p := range paths { + if len(p) < level+1 || pathFlags[pi] || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) { + continue + } + if len(p[level]) >= 2 { + aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1]) + arrIdxFlags[aIdx] = x + pIdxFlags[pi] = true + } + } + + if len(arrIdxFlags) > 0 { + level++ + + var curIdx int + arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) { + if _, ok = arrIdxFlags[curIdx]; ok { + for pi, p := range paths { + if pIdxFlags[pi] { + aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1]) + + if curIdx == aIdx { + of := searchKeys(value, p[level:]...) + + pathsMatched++ + pathFlags[pi] = true + + if of != -1 { + v, dt, _, e := Get(value[of:]) + cb(pi, v, dt, e) + } + } + } + } + } + + curIdx += 1 + }) + + if pathsMatched == len(paths) { + return i + } + + i += arrOff - 1 + } else { + // Do not search for keys inside arrays + if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 { + return -1 + } else { + i += arraySkip - 1 + } + } + case ']': + level-- + } + + i++ + } + + return -1 +} + +// Data types available in valid JSON data. +type ValueType int + +const ( + NotExist = ValueType(iota) + String + Number + Object + Array + Boolean + Null + Unknown +) + +func (vt ValueType) String() string { + switch vt { + case NotExist: + return "non-existent" + case String: + return "string" + case Number: + return "number" + case Object: + return "object" + case Array: + return "array" + case Boolean: + return "boolean" + case Null: + return "null" + default: + return "unknown" + } +} + +var ( + trueLiteral = []byte("true") + falseLiteral = []byte("false") + nullLiteral = []byte("null") +) + +func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte { + isIndex := string(keys[0][0]) == "[" + offset := 0 + lk := calcAllocateSpace(keys, setValue, comma, object) + buffer := make([]byte, lk, lk) + if comma { + offset += WriteToBuffer(buffer[offset:], ",") + } + if isIndex && !comma { + offset += WriteToBuffer(buffer[offset:], "[") + } else { + if object { + offset += WriteToBuffer(buffer[offset:], "{") + } + if !isIndex { + offset += WriteToBuffer(buffer[offset:], "\"") + offset += WriteToBuffer(buffer[offset:], keys[0]) + offset += WriteToBuffer(buffer[offset:], "\":") + } + } + + for i := 1; i < len(keys); i++ { + if string(keys[i][0]) == "[" { + offset += WriteToBuffer(buffer[offset:], "[") + } else { + offset += WriteToBuffer(buffer[offset:], "{\"") + offset += WriteToBuffer(buffer[offset:], keys[i]) + offset += WriteToBuffer(buffer[offset:], "\":") + } + } + offset += WriteToBuffer(buffer[offset:], string(setValue)) + for i := len(keys) - 1; i > 0; i-- { + if string(keys[i][0]) == "[" { + offset += WriteToBuffer(buffer[offset:], "]") + } else { + offset += WriteToBuffer(buffer[offset:], "}") + } + } + if isIndex && !comma { + offset += WriteToBuffer(buffer[offset:], "]") + } + if object && !isIndex { + offset += WriteToBuffer(buffer[offset:], "}") + } + return buffer +} + +func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int { + isIndex := string(keys[0][0]) == "[" + lk := 0 + if comma { + // , + lk += 1 + } + if isIndex && !comma { + // [] + lk += 2 + } else { + if object { + // { + lk += 1 + } + if !isIndex { + // "keys[0]" + lk += len(keys[0]) + 3 + } + } + + + lk += len(setValue) + for i := 1; i < len(keys); i++ { + if string(keys[i][0]) == "[" { + // [] + lk += 2 + } else { + // {"keys[i]":setValue} + lk += len(keys[i]) + 5 + } + } + + if object && !isIndex { + // } + lk += 1 + } + + return lk +} + +func WriteToBuffer(buffer []byte, str string) int { + copy(buffer, str) + return len(str) +} + +/* + +Del - Receives existing data structure, path to delete. + +Returns: +`data` - return modified data + +*/ +func Delete(data []byte, keys ...string) []byte { + lk := len(keys) + if lk == 0 { + return data[:0] + } + + array := false + if len(keys[lk-1]) > 0 && string(keys[lk-1][0]) == "[" { + array = true + } + + var startOffset, keyOffset int + endOffset := len(data) + var err error + if !array { + if len(keys) > 1 { + _, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...) + if err == KeyPathNotFoundError { + // problem parsing the data + return data + } + } + + keyOffset, err = findKeyStart(data[startOffset:endOffset], keys[lk-1]) + if err == KeyPathNotFoundError { + // problem parsing the data + return data + } + keyOffset += startOffset + _, _, _, subEndOffset, _ := internalGet(data[startOffset:endOffset], keys[lk-1]) + endOffset = startOffset + subEndOffset + tokEnd := tokenEnd(data[endOffset:]) + tokStart := findTokenStart(data[:keyOffset], ","[0]) + + if data[endOffset+tokEnd] == ","[0] { + endOffset += tokEnd + 1 + } else if data[endOffset+tokEnd] == " "[0] && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ","[0] { + endOffset += tokEnd + 2 + } else if data[endOffset+tokEnd] == "}"[0] && data[tokStart] == ","[0] { + keyOffset = tokStart + } + } else { + _, _, keyOffset, endOffset, err = internalGet(data, keys...) + if err == KeyPathNotFoundError { + // problem parsing the data + return data + } + + tokEnd := tokenEnd(data[endOffset:]) + tokStart := findTokenStart(data[:keyOffset], ","[0]) + + if data[endOffset+tokEnd] == ","[0] { + endOffset += tokEnd + 1 + } else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] { + keyOffset = tokStart + } + } + + // We need to remove remaining trailing comma if we delete las element in the object + prevTok := lastToken(data[:keyOffset]) + remainedValue := data[endOffset:] + + var newOffset int + if nextToken(remainedValue) > -1 && remainedValue[nextToken(remainedValue)] == '}' && data[prevTok] == ',' { + newOffset = prevTok + } else { + newOffset = prevTok + 1 + } + + // We have to make a copy here if we don't want to mangle the original data, because byte slices are + // accessed by reference and not by value + dataCopy := make([]byte, len(data)) + copy(dataCopy, data) + data = append(dataCopy[:newOffset], dataCopy[endOffset:]...) + + return data +} + +/* + +Set - Receives existing data structure, path to set, and data to set at that key. + +Returns: +`value` - modified byte array +`err` - On any parsing error + +*/ +func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) { + // ensure keys are set + if len(keys) == 0 { + return nil, KeyPathNotFoundError + } + + _, _, startOffset, endOffset, err := internalGet(data, keys...) + if err != nil { + if err != KeyPathNotFoundError { + // problem parsing the data + return nil, err + } + // full path doesnt exist + // does any subpath exist? + var depth int + for i := range keys { + _, _, start, end, sErr := internalGet(data, keys[:i+1]...) + if sErr != nil { + break + } else { + endOffset = end + startOffset = start + depth++ + } + } + comma := true + object := false + if endOffset == -1 { + firstToken := nextToken(data) + // We can't set a top-level key if data isn't an object + if firstToken < 0 || data[firstToken] != '{' { + return nil, KeyPathNotFoundError + } + // Don't need a comma if the input is an empty object + secondToken := firstToken + 1 + nextToken(data[firstToken+1:]) + if data[secondToken] == '}' { + comma = false + } + // Set the top level key at the end (accounting for any trailing whitespace) + // This assumes last token is valid like '}', could check and return error + endOffset = lastToken(data) + } + depthOffset := endOffset + if depth != 0 { + // if subpath is a non-empty object, add to it + // or if subpath is a non-empty array, add to it + if (data[startOffset] == '{' && data[startOffset+1+nextToken(data[startOffset+1:])] != '}') || + (data[startOffset] == '[' && data[startOffset+1+nextToken(data[startOffset+1:])] == '{') && keys[depth:][0][0] == 91 { + depthOffset-- + startOffset = depthOffset + // otherwise, over-write it with a new object + } else { + comma = false + object = true + } + } else { + startOffset = depthOffset + } + value = append(data[:startOffset], append(createInsertComponent(keys[depth:], setValue, comma, object), data[depthOffset:]...)...) + } else { + // path currently exists + startComponent := data[:startOffset] + endComponent := data[endOffset:] + + value = make([]byte, len(startComponent)+len(endComponent)+len(setValue)) + newEndOffset := startOffset + len(setValue) + copy(value[0:startOffset], startComponent) + copy(value[startOffset:newEndOffset], setValue) + copy(value[newEndOffset:], endComponent) + } + return value, nil +} + +func getType(data []byte, offset int) ([]byte, ValueType, int, error) { + var dataType ValueType + endOffset := offset + + // if string value + if data[offset] == '"' { + dataType = String + if idx, _ := stringEnd(data[offset+1:]); idx != -1 { + endOffset += idx + 1 + } else { + return nil, dataType, offset, MalformedStringError + } + } else if data[offset] == '[' { // if array value + dataType = Array + // break label, for stopping nested loops + endOffset = blockEnd(data[offset:], '[', ']') + + if endOffset == -1 { + return nil, dataType, offset, MalformedArrayError + } + + endOffset += offset + } else if data[offset] == '{' { // if object value + dataType = Object + // break label, for stopping nested loops + endOffset = blockEnd(data[offset:], '{', '}') + + if endOffset == -1 { + return nil, dataType, offset, MalformedObjectError + } + + endOffset += offset + } else { + // Number, Boolean or None + end := tokenEnd(data[endOffset:]) + + if end == -1 { + return nil, dataType, offset, MalformedValueError + } + + value := data[offset : endOffset+end] + + switch data[offset] { + case 't', 'f': // true or false + if bytes.Equal(value, trueLiteral) || bytes.Equal(value, falseLiteral) { + dataType = Boolean + } else { + return nil, Unknown, offset, UnknownValueTypeError + } + case 'u', 'n': // undefined or null + if bytes.Equal(value, nullLiteral) { + dataType = Null + } else { + return nil, Unknown, offset, UnknownValueTypeError + } + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': + dataType = Number + default: + return nil, Unknown, offset, UnknownValueTypeError + } + + endOffset += end + } + return data[offset:endOffset], dataType, endOffset, nil +} + +/* +Get - Receives data structure, and key path to extract value from. + +Returns: +`value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error +`dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null` +`offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper. +`err` - If key not found or any other parsing issue it should return error. If key not found it also sets `dataType` to `NotExist` + +Accept multiple keys to specify path to JSON value (in case of quering nested structures). +If no keys provided it will try to extract closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation. +*/ +func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) { + a, b, _, d, e := internalGet(data, keys...) + return a, b, d, e +} + +func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) { + if len(keys) > 0 { + if offset = searchKeys(data, keys...); offset == -1 { + return nil, NotExist, -1, -1, KeyPathNotFoundError + } + } + + // Go to closest value + nO := nextToken(data[offset:]) + if nO == -1 { + return nil, NotExist, offset, -1, MalformedJsonError + } + + offset += nO + value, dataType, endOffset, err = getType(data, offset) + if err != nil { + return value, dataType, offset, endOffset, err + } + + // Strip quotes from string values + if dataType == String { + value = value[1 : len(value)-1] + } + + return value[:len(value):len(value)], dataType, offset, endOffset, nil +} + +// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`. +func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) { + if len(data) == 0 { + return -1, MalformedObjectError + } + + nT := nextToken(data) + if nT == -1 { + return -1, MalformedJsonError + } + + offset = nT + 1 + + if len(keys) > 0 { + if offset = searchKeys(data, keys...); offset == -1 { + return offset, KeyPathNotFoundError + } + + // Go to closest value + nO := nextToken(data[offset:]) + if nO == -1 { + return offset, MalformedJsonError + } + + offset += nO + + if data[offset] != '[' { + return offset, MalformedArrayError + } + + offset++ + } + + nO := nextToken(data[offset:]) + if nO == -1 { + return offset, MalformedJsonError + } + + offset += nO + + if data[offset] == ']' { + return offset, nil + } + + for true { + v, t, o, e := Get(data[offset:]) + + if e != nil { + return offset, e + } + + if o == 0 { + break + } + + if t != NotExist { + cb(v, t, offset+o-len(v), e) + } + + if e != nil { + break + } + + offset += o + + skipToToken := nextToken(data[offset:]) + if skipToToken == -1 { + return offset, MalformedArrayError + } + offset += skipToToken + + if data[offset] == ']' { + break + } + + if data[offset] != ',' { + return offset, MalformedArrayError + } + + offset++ + } + + return offset, nil +} + +// ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry +func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) { + offset := 0 + + // Descend to the desired key, if requested + if len(keys) > 0 { + if off := searchKeys(data, keys...); off == -1 { + return KeyPathNotFoundError + } else { + offset = off + } + } + + // Validate and skip past opening brace + if off := nextToken(data[offset:]); off == -1 { + return MalformedObjectError + } else if offset += off; data[offset] != '{' { + return MalformedObjectError + } else { + offset++ + } + + // Skip to the first token inside the object, or stop if we find the ending brace + if off := nextToken(data[offset:]); off == -1 { + return MalformedJsonError + } else if offset += off; data[offset] == '}' { + return nil + } + + // Loop pre-condition: data[offset] points to what should be either the next entry's key, or the closing brace (if it's anything else, the JSON is malformed) + for offset < len(data) { + // Step 1: find the next key + var key []byte + + // Check what the the next token is: start of string, end of object, or something else (error) + switch data[offset] { + case '"': + offset++ // accept as string and skip opening quote + case '}': + return nil // we found the end of the object; stop and return success + default: + return MalformedObjectError + } + + // Find the end of the key string + var keyEscaped bool + if off, esc := stringEnd(data[offset:]); off == -1 { + return MalformedJsonError + } else { + key, keyEscaped = data[offset:offset+off-1], esc + offset += off + } + + // Unescape the string if needed + if keyEscaped { + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + if keyUnescaped, err := Unescape(key, stackbuf[:]); err != nil { + return MalformedStringEscapeError + } else { + key = keyUnescaped + } + } + + // Step 2: skip the colon + if off := nextToken(data[offset:]); off == -1 { + return MalformedJsonError + } else if offset += off; data[offset] != ':' { + return MalformedJsonError + } else { + offset++ + } + + // Step 3: find the associated value, then invoke the callback + if value, valueType, off, err := Get(data[offset:]); err != nil { + return err + } else if err := callback(key, value, valueType, offset+off); err != nil { // Invoke the callback here! + return err + } else { + offset += off + } + + // Step 4: skip over the next comma to the following token, or stop if we hit the ending brace + if off := nextToken(data[offset:]); off == -1 { + return MalformedArrayError + } else { + offset += off + switch data[offset] { + case '}': + return nil // Stop if we hit the close brace + case ',': + offset++ // Ignore the comma + default: + return MalformedObjectError + } + } + + // Skip to the next token after the comma + if off := nextToken(data[offset:]); off == -1 { + return MalformedArrayError + } else { + offset += off + } + } + + return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace +} + +// GetUnsafeString returns the value retrieved by `Get`, use creates string without memory allocation by mapping string to slice memory. It does not handle escape symbols. +func GetUnsafeString(data []byte, keys ...string) (val string, err error) { + v, _, _, e := Get(data, keys...) + + if e != nil { + return "", e + } + + return bytesToString(&v), nil +} + +// GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols +// If key data type do not match, it will return an error. +func GetString(data []byte, keys ...string) (val string, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return "", e + } + + if t != String { + return "", fmt.Errorf("Value is not a string: %s", string(v)) + } + + // If no escapes return raw content + if bytes.IndexByte(v, '\\') == -1 { + return string(v), nil + } + + return ParseString(v) +} + +// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible. +// The offset is the same as in `Get`. +// If key data type do not match, it will return an error. +func GetFloat(data []byte, keys ...string) (val float64, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return 0, e + } + + if t != Number { + return 0, fmt.Errorf("Value is not a number: %s", string(v)) + } + + return ParseFloat(v) +} + +// GetInt returns the value retrieved by `Get`, cast to a int64 if possible. +// If key data type do not match, it will return an error. +func GetInt(data []byte, keys ...string) (val int64, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return 0, e + } + + if t != Number { + return 0, fmt.Errorf("Value is not a number: %s", string(v)) + } + + return ParseInt(v) +} + +// GetBoolean returns the value retrieved by `Get`, cast to a bool if possible. +// The offset is the same as in `Get`. +// If key data type do not match, it will return error. +func GetBoolean(data []byte, keys ...string) (val bool, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return false, e + } + + if t != Boolean { + return false, fmt.Errorf("Value is not a boolean: %s", string(v)) + } + + return ParseBoolean(v) +} + +// ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness) +func ParseBoolean(b []byte) (bool, error) { + switch { + case bytes.Equal(b, trueLiteral): + return true, nil + case bytes.Equal(b, falseLiteral): + return false, nil + default: + return false, MalformedValueError + } +} + +// ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string) +func ParseString(b []byte) (string, error) { + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + if bU, err := Unescape(b, stackbuf[:]); err != nil { + return "", MalformedValueError + } else { + return string(bU), nil + } +} + +// ParseNumber parses a Number ValueType into a Go float64 +func ParseFloat(b []byte) (float64, error) { + if v, err := parseFloat(&b); err != nil { + return 0, MalformedValueError + } else { + return v, nil + } +} + +// ParseInt parses a Number ValueType into a Go int64 +func ParseInt(b []byte) (int64, error) { + if v, ok, overflow := parseInt(b); !ok { + if overflow { + return 0, OverflowIntegerError + } + return 0, MalformedValueError + } else { + return v, nil + } +} diff --git a/vendor/github.com/go-swagger/go-swagger/codescan/schema.go b/vendor/github.com/go-swagger/go-swagger/codescan/schema.go index dce74fc30..98bdecba6 100644 --- a/vendor/github.com/go-swagger/go-swagger/codescan/schema.go +++ b/vendor/github.com/go-swagger/go-swagger/codescan/schema.go @@ -303,6 +303,8 @@ func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error } switch titpe := tpe.(type) { + case *types.Alias: + return nil // resolves panic case *types.Basic: return swaggerSchemaForType(titpe.String(), tgt) case *types.Pointer: diff --git a/vendor/github.com/gorilla/websocket/.editorconfig b/vendor/github.com/gorilla/websocket/.editorconfig deleted file mode 100644 index 2940ec92a..000000000 --- a/vendor/github.com/gorilla/websocket/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -; https://editorconfig.org/ - -root = true - -[*] -insert_final_newline = true -charset = utf-8 -trim_trailing_whitespace = true -indent_style = space -indent_size = 2 - -[{Makefile,go.mod,go.sum,*.go,.gitmodules}] -indent_style = tab -indent_size = 4 - -[*.md] -indent_size = 4 -trim_trailing_whitespace = false - -eclint_indent_style = unset diff --git a/vendor/github.com/gorilla/websocket/.gitignore b/vendor/github.com/gorilla/websocket/.gitignore index 84039fec6..cd3fcd1ef 100644 --- a/vendor/github.com/gorilla/websocket/.gitignore +++ b/vendor/github.com/gorilla/websocket/.gitignore @@ -1 +1,25 @@ -coverage.coverprofile +# 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/ +*.iml diff --git a/vendor/github.com/gorilla/websocket/.golangci.yml b/vendor/github.com/gorilla/websocket/.golangci.yml deleted file mode 100644 index 44cf86a06..000000000 --- a/vendor/github.com/gorilla/websocket/.golangci.yml +++ /dev/null @@ -1,13 +0,0 @@ -run: - timeout: "5m" - # will not run golangci-lint against *_test.go - tests: false -issues: - exclude-dirs: - - examples/*.go - exclude-rules: - # excluding error checks from all the .go files - - path: ./*.go - linters: - - errcheck - diff --git a/vendor/github.com/gorilla/websocket/AUTHORS b/vendor/github.com/gorilla/websocket/AUTHORS new file mode 100644 index 000000000..1931f4006 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/AUTHORS @@ -0,0 +1,9 @@ +# This is the official list of Gorilla WebSocket authors for copyright +# purposes. +# +# Please keep the list sorted. + +Gary Burd +Google LLC (https://opensource.google.com/) +Joachim Bauch + diff --git a/vendor/github.com/gorilla/websocket/LICENSE b/vendor/github.com/gorilla/websocket/LICENSE index 8692af650..9171c9722 100644 --- a/vendor/github.com/gorilla/websocket/LICENSE +++ b/vendor/github.com/gorilla/websocket/LICENSE @@ -1,27 +1,22 @@ -Copyright (c) 2023 The Gorilla Authors. All rights reserved. +Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: +modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * 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. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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. \ No newline at end of file + 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 HOLDER 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. diff --git a/vendor/github.com/gorilla/websocket/Makefile b/vendor/github.com/gorilla/websocket/Makefile deleted file mode 100644 index 603a63f50..000000000 --- a/vendor/github.com/gorilla/websocket/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') -GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest - -GO_SEC=$(shell which gosec 2> /dev/null || echo '') -GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest - -GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') -GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest - -.PHONY: golangci-lint -golangci-lint: - $(if $(GO_LINT), ,go install $(GO_LINT_URI)) - @echo "##### Running golangci-lint" - golangci-lint run -v - -.PHONY: gosec -gosec: - $(if $(GO_SEC), ,go install $(GO_SEC_URI)) - @echo "##### Running gosec" - gosec -exclude-dir examples ./... - -.PHONY: govulncheck -govulncheck: - $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) - @echo "##### Running govulncheck" - govulncheck ./... - -.PHONY: verify -verify: golangci-lint gosec govulncheck - -.PHONY: test -test: - @echo "##### Running tests" - go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... diff --git a/vendor/github.com/gorilla/websocket/README.md b/vendor/github.com/gorilla/websocket/README.md index 525a62a92..d33ed7fdd 100644 --- a/vendor/github.com/gorilla/websocket/README.md +++ b/vendor/github.com/gorilla/websocket/README.md @@ -1,23 +1,19 @@ -# gorilla/websocket +# Gorilla WebSocket -![testing](https://github.com/gorilla/websocket/actions/workflows/test.yml/badge.svg) -[![codecov](https://codecov.io/github/gorilla/websocket/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/websocket) -[![godoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) -[![sourcegraph](https://sourcegraph.com/github.com/gorilla/websocket/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/websocket?badge) +[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) +[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket) -Gorilla WebSocket is a [Go](http://golang.org/) implementation of the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. - -![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) +Gorilla WebSocket is a [Go](http://golang.org/) implementation of the +[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. ### Documentation * [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc) -* [Chat example](https://github.com/gorilla/websocket/tree/main/examples/chat) -* [Command example](https://github.com/gorilla/websocket/tree/main/examples/command) -* [Client and server example](https://github.com/gorilla/websocket/tree/main/examples/echo) -* [File watch example](https://github.com/gorilla/websocket/tree/main/examples/filewatch) -* [Write buffer pool example](https://github.com/gorilla/websocket/tree/main/examples/bufferpool) +* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) +* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) +* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) +* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) ### Status @@ -33,4 +29,5 @@ package API is stable. The Gorilla WebSocket package passes the server tests in the [Autobahn Test Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn -subdirectory](https://github.com/gorilla/websocket/tree/main/examples/autobahn). +subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). + diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go index 7023e1176..04fdafee1 100644 --- a/vendor/github.com/gorilla/websocket/client.go +++ b/vendor/github.com/gorilla/websocket/client.go @@ -11,14 +11,13 @@ import ( "errors" "fmt" "io" + "io/ioutil" "net" "net/http" "net/http/httptrace" "net/url" "strings" "time" - - "golang.org/x/net/proxy" ) // ErrBadHandshake is returned when the server response to opening handshake is @@ -305,7 +304,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h return nil, nil, err } if proxyURL != nil { - dialer, err := proxy.FromURL(proxyURL, netDialerFunc(netDial)) + dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial)) if err != nil { return nil, nil, err } @@ -392,7 +391,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h } } - if resp.StatusCode != http.StatusSwitchingProtocols || + if resp.StatusCode != 101 || !tokenListContainsValue(resp.Header, "Upgrade", "websocket") || !tokenListContainsValue(resp.Header, "Connection", "upgrade") || resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { @@ -401,7 +400,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h // debugging. buf := make([]byte, 1024) n, _ := io.ReadFull(resp.Body, buf) - resp.Body = io.NopCloser(bytes.NewReader(buf[:n])) + resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) return nil, resp, ErrBadHandshake } @@ -419,7 +418,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h break } - resp.Body = io.NopCloser(bytes.NewReader([]byte{})) + resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") netConn.SetDeadline(time.Time{}) @@ -429,7 +428,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h func cloneTLSConfig(cfg *tls.Config) *tls.Config { if cfg == nil { - return &tls.Config{MinVersion: tls.VersionTLS12} + return &tls.Config{} } return cfg.Clone() } diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go index 49399b120..5161ef81f 100644 --- a/vendor/github.com/gorilla/websocket/conn.go +++ b/vendor/github.com/gorilla/websocket/conn.go @@ -6,10 +6,11 @@ package websocket import ( "bufio" - "crypto/rand" "encoding/binary" "errors" "io" + "io/ioutil" + "math/rand" "net" "strconv" "strings" @@ -180,16 +181,16 @@ var ( errInvalidControlFrame = errors.New("websocket: invalid control frame") ) -// maskRand is an io.Reader for generating mask bytes. The reader is initialized -// to crypto/rand Reader. Tests swap the reader to a math/rand reader for -// reproducible results. -var maskRand = rand.Reader - -// newMaskKey returns a new 32 bit value for masking client frames. func newMaskKey() [4]byte { - var k [4]byte - _, _ = io.ReadFull(maskRand, k[:]) - return k + n := rand.Uint32() + return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} +} + +func hideTempErr(err error) error { + if e, ok := err.(net.Error); ok && e.Temporary() { + err = &netError{msg: e.Error(), timeout: e.Timeout()} + } + return err } func isControl(frameType int) bool { @@ -357,6 +358,7 @@ func (c *Conn) RemoteAddr() net.Addr { // Write methods func (c *Conn) writeFatal(err error) error { + err = hideTempErr(err) c.writeErrMu.Lock() if c.writeErr == nil { c.writeErr = err @@ -434,27 +436,21 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er maskBytes(key, 0, buf[6:]) } - if deadline.IsZero() { - // No timeout for zero time. - <-c.mu - } else { - d := time.Until(deadline) + d := 1000 * time.Hour + if !deadline.IsZero() { + d = deadline.Sub(time.Now()) if d < 0 { return errWriteTimeout } - select { - case <-c.mu: - default: - timer := time.NewTimer(d) - select { - case <-c.mu: - timer.Stop() - case <-timer.C: - return errWriteTimeout - } - } } + timer := time.NewTimer(d) + select { + case <-c.mu: + timer.Stop() + case <-timer.C: + return errWriteTimeout + } defer func() { c.mu <- struct{}{} }() c.writeErrMu.Lock() @@ -799,7 +795,7 @@ func (c *Conn) advanceFrame() (int, error) { // 1. Skip remainder of previous frame. if c.readRemaining > 0 { - if _, err := io.CopyN(io.Discard, c.br, c.readRemaining); err != nil { + if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { return noFrame, err } } @@ -1012,7 +1008,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { for c.readErr == nil { frameType, err := c.advanceFrame() if err != nil { - c.readErr = err + c.readErr = hideTempErr(err) break } @@ -1052,7 +1048,7 @@ func (r *messageReader) Read(b []byte) (int, error) { b = b[:c.readRemaining] } n, err := c.br.Read(b) - c.readErr = err + c.readErr = hideTempErr(err) if c.isServer { c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) } @@ -1073,7 +1069,7 @@ func (r *messageReader) Read(b []byte) (int, error) { frameType, err := c.advanceFrame() switch { case err != nil: - c.readErr = err + c.readErr = hideTempErr(err) case frameType == TextMessage || frameType == BinaryMessage: c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") } @@ -1098,7 +1094,7 @@ func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { if err != nil { return messageType, nil, err } - p, err = io.ReadAll(r) + p, err = ioutil.ReadAll(r) return messageType, p, err } @@ -1165,7 +1161,7 @@ func (c *Conn) SetPingHandler(h func(appData string) error) { err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) if err == ErrCloseSent { return nil - } else if _, ok := err.(net.Error); ok { + } else if e, ok := err.(net.Error); ok && e.Temporary() { return nil } return err @@ -1240,15 +1236,3 @@ func FormatCloseMessage(closeCode int, text string) []byte { copy(buf[2:], text) return buf } - -var messageTypes = map[int]string{ - TextMessage: "TextMessage", - BinaryMessage: "BinaryMessage", - CloseMessage: "CloseMessage", - PingMessage: "PingMessage", - PongMessage: "PongMessage", -} - -func FormatMessageType(mt int) string { - return messageTypes[mt] -} diff --git a/vendor/github.com/gorilla/websocket/proxy.go b/vendor/github.com/gorilla/websocket/proxy.go index 3c570c26f..e0f466b72 100644 --- a/vendor/github.com/gorilla/websocket/proxy.go +++ b/vendor/github.com/gorilla/websocket/proxy.go @@ -12,8 +12,6 @@ import ( "net/http" "net/url" "strings" - - "golang.org/x/net/proxy" ) type netDialerFunc func(network, addr string) (net.Conn, error) @@ -23,7 +21,7 @@ func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { } func init() { - proxy.RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy.Dialer) (proxy.Dialer, error) { + proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil }) } @@ -70,7 +68,7 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) return nil, err } - if resp.StatusCode != http.StatusOK { + if resp.StatusCode != 200 { conn.Close() f := strings.SplitN(resp.Status, " ", 2) return nil, errors.New(f[1]) diff --git a/vendor/github.com/gorilla/websocket/server.go b/vendor/github.com/gorilla/websocket/server.go index fda75ff0d..bb3359743 100644 --- a/vendor/github.com/gorilla/websocket/server.go +++ b/vendor/github.com/gorilla/websocket/server.go @@ -33,7 +33,6 @@ type Upgrader struct { // size is zero, then buffers allocated by the HTTP server are used. The // I/O buffer sizes do not limit the size of the messages that can be sent // or received. - // The default value is 4096 bytes, 4kb. ReadBufferSize, WriteBufferSize int // WriteBufferPool is a pool of buffers for write operations. If the value @@ -102,8 +101,8 @@ func checkSameOrigin(r *http.Request) bool { func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { if u.Subprotocols != nil { clientProtocols := Subprotocols(r) - for _, clientProtocol := range clientProtocols { - for _, serverProtocol := range u.Subprotocols { + for _, serverProtocol := range u.Subprotocols { + for _, clientProtocol := range clientProtocols { if clientProtocol == serverProtocol { return clientProtocol } @@ -173,7 +172,12 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade } } - netConn, brw, err := http.NewResponseController(w).Hijack() + h, ok := w.(http.Hijacker) + if !ok { + return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") + } + var brw *bufio.ReadWriter + netConn, brw, err := h.Hijack() if err != nil { return u.returnError(w, r, http.StatusInternalServerError, err.Error()) } diff --git a/vendor/github.com/gorilla/websocket/tls_handshake.go b/vendor/github.com/gorilla/websocket/tls_handshake.go index 7f3864534..a62b68ccb 100644 --- a/vendor/github.com/gorilla/websocket/tls_handshake.go +++ b/vendor/github.com/gorilla/websocket/tls_handshake.go @@ -1,3 +1,6 @@ +//go:build go1.17 +// +build go1.17 + package websocket import ( diff --git a/vendor/github.com/minio/minio-go/v7/api-prompt-object.go b/vendor/github.com/minio/minio-go/v7/api-prompt-object.go new file mode 100644 index 000000000..dac062a75 --- /dev/null +++ b/vendor/github.com/minio/minio-go/v7/api-prompt-object.go @@ -0,0 +1,78 @@ +/* + * MinIO Go Library for Amazon S3 Compatible Cloud Storage + * Copyright 2015-2024 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "bytes" + "context" + "io" + "net/http" + + "github.com/goccy/go-json" + "github.com/minio/minio-go/v7/pkg/s3utils" +) + +// PromptObject performs language model inference with the prompt and referenced object as context. +// Inference is performed using a Lambda handler that can process the prompt and object. +// Currently, this functionality is limited to certain MinIO servers. +func (c *Client) PromptObject(ctx context.Context, bucketName, objectName, prompt string, opts PromptObjectOptions) (io.ReadCloser, error) { + // Input validation. + if err := s3utils.CheckValidBucketName(bucketName); err != nil { + return nil, ErrorResponse{ + StatusCode: http.StatusBadRequest, + Code: "InvalidBucketName", + Message: err.Error(), + } + } + if err := s3utils.CheckValidObjectName(objectName); err != nil { + return nil, ErrorResponse{ + StatusCode: http.StatusBadRequest, + Code: "XMinioInvalidObjectName", + Message: err.Error(), + } + } + + opts.AddLambdaArnToReqParams(opts.LambdaArn) + opts.SetHeader("Content-Type", "application/json") + opts.AddPromptArg("prompt", prompt) + promptReqBytes, err := json.Marshal(opts.PromptArgs) + if err != nil { + return nil, err + } + + // Execute POST on bucket/object. + resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{ + bucketName: bucketName, + objectName: objectName, + queryValues: opts.toQueryValues(), + customHeader: opts.Header(), + contentSHA256Hex: sum256Hex(promptReqBytes), + contentBody: bytes.NewReader(promptReqBytes), + contentLength: int64(len(promptReqBytes)), + }) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + defer closeResponse(resp) + return nil, httpRespToErrorResponse(resp, bucketName, objectName) + } + + return resp.Body, nil +} diff --git a/vendor/github.com/minio/minio-go/v7/api-prompt-options.go b/vendor/github.com/minio/minio-go/v7/api-prompt-options.go new file mode 100644 index 000000000..4493a75d4 --- /dev/null +++ b/vendor/github.com/minio/minio-go/v7/api-prompt-options.go @@ -0,0 +1,84 @@ +/* + * MinIO Go Library for Amazon S3 Compatible Cloud Storage + * Copyright 2015-2024 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "net/http" + "net/url" +) + +// PromptObjectOptions provides options to PromptObject call. +// LambdaArn is the ARN of the Prompt Lambda to be invoked. +// PromptArgs is a map of key-value pairs to be passed to the inference action on the Prompt Lambda. +// "prompt" is a reserved key and should not be used as a key in PromptArgs. +type PromptObjectOptions struct { + LambdaArn string + PromptArgs map[string]any + headers map[string]string + reqParams url.Values +} + +// Header returns the http.Header representation of the POST options. +func (o PromptObjectOptions) Header() http.Header { + headers := make(http.Header, len(o.headers)) + for k, v := range o.headers { + headers.Set(k, v) + } + return headers +} + +// AddPromptArg Add a key value pair to the prompt arguments where the key is a string and +// the value is a JSON serializable. +func (o *PromptObjectOptions) AddPromptArg(key string, value any) { + if o.PromptArgs == nil { + o.PromptArgs = make(map[string]any) + } + o.PromptArgs[key] = value +} + +// AddLambdaArnToReqParams adds the lambdaArn to the request query string parameters. +func (o *PromptObjectOptions) AddLambdaArnToReqParams(lambdaArn string) { + if o.reqParams == nil { + o.reqParams = make(url.Values) + } + o.reqParams.Add("lambdaArn", lambdaArn) +} + +// SetHeader adds a key value pair to the options. The +// key-value pair will be part of the HTTP POST request +// headers. +func (o *PromptObjectOptions) SetHeader(key, value string) { + if o.headers == nil { + o.headers = make(map[string]string) + } + o.headers[http.CanonicalHeaderKey(key)] = value +} + +// toQueryValues - Convert the reqParams in Options to query string parameters. +func (o *PromptObjectOptions) toQueryValues() url.Values { + urlValues := make(url.Values) + if o.reqParams != nil { + for key, values := range o.reqParams { + for _, value := range values { + urlValues.Add(key, value) + } + } + } + + return urlValues +} diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-fan-out.go b/vendor/github.com/minio/minio-go/v7/api-put-object-fan-out.go index 0ae9142e1..3023b949c 100644 --- a/vendor/github.com/minio/minio-go/v7/api-put-object-fan-out.go +++ b/vendor/github.com/minio/minio-go/v7/api-put-object-fan-out.go @@ -85,7 +85,10 @@ func (c *Client) PutObjectFanOut(ctx context.Context, bucket string, fanOutData policy.SetEncryption(fanOutReq.SSE) // Set checksum headers if any. - policy.SetChecksum(fanOutReq.Checksum) + err := policy.SetChecksum(fanOutReq.Checksum) + if err != nil { + return nil, err + } url, formData, err := c.PresignedPostPolicy(ctx, policy) if err != nil { diff --git a/vendor/github.com/minio/minio-go/v7/api.go b/vendor/github.com/minio/minio-go/v7/api.go index 380ec4fde..88e8d4347 100644 --- a/vendor/github.com/minio/minio-go/v7/api.go +++ b/vendor/github.com/minio/minio-go/v7/api.go @@ -133,7 +133,7 @@ type Options struct { // Global constants. const ( libraryName = "minio-go" - libraryVersion = "v7.0.80" + libraryVersion = "v7.0.81" ) // User Agent should always following the below style. diff --git a/vendor/github.com/minio/minio-go/v7/functional_tests.go b/vendor/github.com/minio/minio-go/v7/functional_tests.go index c0180b36b..43383d134 100644 --- a/vendor/github.com/minio/minio-go/v7/functional_tests.go +++ b/vendor/github.com/minio/minio-go/v7/functional_tests.go @@ -160,7 +160,7 @@ func logError(testName, function string, args map[string]interface{}, startTime } else { logFailure(testName, function, args, startTime, alert, message, err) if !isRunOnFail() { - panic(err) + panic(fmt.Sprintf("Test failed with message: %s, err: %v", message, err)) } } } @@ -393,6 +393,42 @@ func getFuncNameLoc(caller int) string { return strings.TrimPrefix(runtime.FuncForPC(pc).Name(), "main.") } +type ClientConfig struct { + // MinIO client configuration + TraceOn bool // Turn on tracing of HTTP requests and responses to stderr + CredsV2 bool // Use V2 credentials if true, otherwise use v4 + TrailingHeaders bool // Send trailing headers in requests +} + +func NewClient(config ClientConfig) (*minio.Client, error) { + // Instantiate new MinIO client + var creds *credentials.Credentials + if config.CredsV2 { + creds = credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), "") + } else { + creds = credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), "") + } + opts := &minio.Options{ + Creds: creds, + Transport: createHTTPTransport(), + Secure: mustParseBool(os.Getenv(enableHTTPS)), + TrailingHeaders: config.TrailingHeaders, + } + client, err := minio.New(os.Getenv(serverEndpoint), opts) + if err != nil { + return nil, err + } + + if config.TraceOn { + client.TraceOn(os.Stderr) + } + + // Set user agent. + client.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + + return client, nil +} + // Tests bucket re-create errors. func testMakeBucketError() { region := "eu-central-1" @@ -407,27 +443,12 @@ func testMakeBucketError() { "region": region, } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - Transport: createHTTPTransport(), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -462,20 +483,12 @@ func testMetadataSizeLimit() { "objectName": "", "opts.UserMetadata": "", } - rand.Seed(startTime.Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - Transport: createHTTPTransport(), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client creation failed", err) return } - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -531,27 +544,12 @@ func testMakeBucketRegions() { "region": region, } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -598,27 +596,12 @@ func testPutObjectReadAt() { "opts": "objectContentType", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -697,27 +680,12 @@ func testListObjectVersions() { "recursive": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -817,27 +785,12 @@ func testStatObjectWithVersioning() { function := "StatObject" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -935,27 +888,12 @@ func testGetObjectWithVersioning() { function := "GetObject()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -1075,27 +1013,12 @@ func testPutObjectWithVersioning() { function := "GetObject()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -1223,28 +1146,12 @@ func testListMultipartUpload() { function := "GetObject()" args := map[string]interface{}{} - // Instantiate new minio client object. - opts := &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - } - c, err := minio.New(os.Getenv(serverEndpoint), opts) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - core, err := minio.NewCore(os.Getenv(serverEndpoint), opts) - if err != nil { - logError(testName, function, args, startTime, "", "MinIO core client object creation failed", err) - return - } - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + core := minio.Core{Client: c} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") @@ -1347,27 +1254,12 @@ func testCopyObjectWithVersioning() { function := "CopyObject()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -1485,27 +1377,12 @@ func testConcurrentCopyObjectWithVersioning() { function := "CopyObject()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -1646,27 +1523,12 @@ func testComposeObjectWithVersioning() { function := "ComposeObject()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -1787,27 +1649,12 @@ func testRemoveObjectWithVersioning() { function := "DeleteObject()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -1900,27 +1747,12 @@ func testRemoveObjectsWithVersioning() { function := "DeleteObjects()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -1996,27 +1828,12 @@ func testObjectTaggingWithVersioning() { function := "{Get,Set,Remove}ObjectTagging()" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -2164,27 +1981,12 @@ func testPutObjectWithChecksums() { return } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -2230,7 +2032,7 @@ func testPutObjectWithChecksums() { h := test.cs.Hasher() h.Reset() - // Test with Wrong CRC. + // Test with a bad CRC - we haven't called h.Write(b), so this is a checksum of empty data meta[test.cs.Key()] = base64.StdEncoding.EncodeToString(h.Sum(nil)) args["metadata"] = meta args["range"] = "false" @@ -2350,28 +2152,12 @@ func testPutObjectWithTrailingChecksums() { return } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - TrailingHeaders: true, - }) + c, err := NewClient(ClientConfig{TrailingHeaders: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -2541,28 +2327,12 @@ func testPutMultipartObjectWithChecksums(trailing bool) { return } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - TrailingHeaders: trailing, - }) + c, err := NewClient(ClientConfig{TrailingHeaders: trailing}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -2620,7 +2390,7 @@ func testPutMultipartObjectWithChecksums(trailing bool) { cmpChecksum := func(got, want string) { if want != got { logError(testName, function, args, startTime, "", "checksum mismatch", fmt.Errorf("want %s, got %s", want, got)) - //fmt.Printf("want %s, got %s\n", want, got) + // fmt.Printf("want %s, got %s\n", want, got) return } } @@ -2741,25 +2511,12 @@ func testTrailingChecksums() { return } - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - TrailingHeaders: true, - }) + c, err := NewClient(ClientConfig{TrailingHeaders: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -2881,7 +2638,6 @@ func testTrailingChecksums() { test.ChecksumCRC32C = hashMultiPart(b, int(test.PO.PartSize), test.hasher) // Set correct CRC. - // c.TraceOn(os.Stderr) resp, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(b), int64(bufSize), test.PO) if err != nil { logError(testName, function, args, startTime, "", "PutObject failed", err) @@ -2933,6 +2689,8 @@ func testTrailingChecksums() { delete(args, "metadata") } + + logSuccess(testName, function, args, startTime) } // Test PutObject with custom checksums. @@ -2952,25 +2710,12 @@ func testPutObjectWithAutomaticChecksums() { return } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - TrailingHeaders: true, - }) + c, err := NewClient(ClientConfig{TrailingHeaders: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -2997,8 +2742,6 @@ func testPutObjectWithAutomaticChecksums() { {header: "x-amz-checksum-crc32c", hasher: crc32.New(crc32.MakeTable(crc32.Castagnoli))}, } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) // defer c.TraceOff() for i, test := range tests { @@ -3108,20 +2851,12 @@ func testGetObjectAttributes() { return } - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - TrailingHeaders: true, - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{TrailingHeaders: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName err = c.MakeBucket( @@ -3315,19 +3050,12 @@ func testGetObjectAttributesSSECEncryption() { return } - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - TrailingHeaders: true, - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - Transport: createHTTPTransport(), - }) + c, err := NewClient(ClientConfig{TrailingHeaders: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName err = c.MakeBucket( @@ -3401,19 +3129,12 @@ func testGetObjectAttributesErrorCases() { return } - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - TrailingHeaders: true, - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{TrailingHeaders: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) unknownBucket := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-bucket-") unknownObject := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-object-") @@ -3657,27 +3378,12 @@ func testPutObjectWithMetadata() { return } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -3764,27 +3470,12 @@ func testPutObjectWithContentLanguage() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -3834,27 +3525,12 @@ func testPutObjectStreaming() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -3906,27 +3582,12 @@ func testGetObjectSeekEnd() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4029,27 +3690,12 @@ func testGetObjectClosedTwice() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4120,26 +3766,13 @@ func testRemoveObjectsContext() { "bucketName": "", } - // Seed random based on current tie. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Enable tracing, write to stdout. - // c.TraceOn(os.Stderr) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4217,27 +3850,12 @@ func testRemoveMultipleObjects() { "bucketName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - - // Enable tracing, write to stdout. - // c.TraceOn(os.Stderr) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4301,27 +3919,12 @@ func testRemoveMultipleObjectsWithResult() { "bucketName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - - // Enable tracing, write to stdout. - // c.TraceOn(os.Stderr) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4437,27 +4040,12 @@ func testFPutObjectMultipart() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4543,27 +4131,12 @@ func testFPutObject() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") location := "us-east-1" @@ -4713,27 +4286,13 @@ func testFPutObjectContext() { "fileName": "", "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4814,27 +4373,13 @@ func testFPutObjectContextV2() { "objectName": "", "opts": "minio.PutObjectOptions{ContentType:objectContentType}", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4919,24 +4464,12 @@ func testPutObjectContext() { "opts": "", } - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Make a new bucket. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -4989,27 +4522,12 @@ func testGetObjectS3Zip() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{"x-minio-extract": true} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -5173,27 +4691,12 @@ func testGetObjectReadSeekFunctional() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -5343,27 +4846,12 @@ func testGetObjectReadAtFunctional() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -5521,27 +5009,12 @@ func testGetObjectReadAtWhenEOFWasReached() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -5641,27 +5114,12 @@ func testPresignedPostPolicy() { "policy": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") @@ -5689,50 +5147,22 @@ func testPresignedPostPolicy() { return } - // Save the data - _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"}) - if err != nil { - logError(testName, function, args, startTime, "", "PutObject failed", err) - return - } - policy := minio.NewPostPolicy() - - if err := policy.SetBucket(""); err == nil { - logError(testName, function, args, startTime, "", "SetBucket did not fail for invalid conditions", err) - return - } - if err := policy.SetKey(""); err == nil { - logError(testName, function, args, startTime, "", "SetKey did not fail for invalid conditions", err) - return - } - if err := policy.SetExpires(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)); err == nil { - logError(testName, function, args, startTime, "", "SetExpires did not fail for invalid conditions", err) - return - } - if err := policy.SetContentType(""); err == nil { - logError(testName, function, args, startTime, "", "SetContentType did not fail for invalid conditions", err) - return - } - if err := policy.SetContentLengthRange(1024*1024, 1024); err == nil { - logError(testName, function, args, startTime, "", "SetContentLengthRange did not fail for invalid conditions", err) - return - } - if err := policy.SetUserMetadata("", ""); err == nil { - logError(testName, function, args, startTime, "", "SetUserMetadata did not fail for invalid conditions", err) - return - } - policy.SetBucket(bucketName) policy.SetKey(objectName) policy.SetExpires(time.Now().UTC().AddDate(0, 0, 10)) // expires in 10 days policy.SetContentType("binary/octet-stream") policy.SetContentLengthRange(10, 1024*1024) policy.SetUserMetadata(metadataKey, metadataValue) + policy.SetContentEncoding("gzip") // Add CRC32C checksum := minio.ChecksumCRC32C.ChecksumBytes(buf) - policy.SetChecksum(checksum) + err = policy.SetChecksum(checksum) + if err != nil { + logError(testName, function, args, startTime, "", "SetChecksum failed", err) + return + } args["policy"] = policy.String() @@ -5828,7 +5258,7 @@ func testPresignedPostPolicy() { expectedLocation := scheme + os.Getenv(serverEndpoint) + "/" + bucketName + "/" + objectName expectedLocationBucketDNS := scheme + bucketName + "." + os.Getenv(serverEndpoint) + "/" + objectName - if !strings.Contains(expectedLocation, "s3.amazonaws.com/") { + if !strings.Contains(expectedLocation, ".amazonaws.com/") { // Test when not against AWS S3. if val, ok := res.Header["Location"]; ok { if val[0] != expectedLocation && val[0] != expectedLocationBucketDNS { @@ -5840,9 +5270,194 @@ func testPresignedPostPolicy() { return } } - want := checksum.Encoded() - if got := res.Header.Get("X-Amz-Checksum-Crc32c"); got != want { - logError(testName, function, args, startTime, "", fmt.Sprintf("Want checksum %q, got %q", want, got), nil) + wantChecksumCrc32c := checksum.Encoded() + if got := res.Header.Get("X-Amz-Checksum-Crc32c"); got != wantChecksumCrc32c { + logError(testName, function, args, startTime, "", fmt.Sprintf("Want checksum %q, got %q", wantChecksumCrc32c, got), nil) + return + } + + // Ensure that when we subsequently GetObject, the checksum is returned + gopts := minio.GetObjectOptions{Checksum: true} + r, err := c.GetObject(context.Background(), bucketName, objectName, gopts) + if err != nil { + logError(testName, function, args, startTime, "", "GetObject failed", err) + return + } + st, err := r.Stat() + if err != nil { + logError(testName, function, args, startTime, "", "Stat failed", err) + return + } + if st.ChecksumCRC32C != wantChecksumCrc32c { + logError(testName, function, args, startTime, "", fmt.Sprintf("Want checksum %s, got %s", wantChecksumCrc32c, st.ChecksumCRC32C), nil) + return + } + + logSuccess(testName, function, args, startTime) +} + +// testPresignedPostPolicyWrongFile tests that when we have a policy with a checksum, we cannot POST the wrong file +func testPresignedPostPolicyWrongFile() { + // initialize logging params + startTime := time.Now() + testName := getFuncName() + function := "PresignedPostPolicy(policy)" + args := map[string]interface{}{ + "policy": "", + } + + c, err := NewClient(ClientConfig{}) + if err != nil { + logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) + return + } + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") + + // Make a new bucket in 'us-east-1' (source bucket). + err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"}) + if err != nil { + logError(testName, function, args, startTime, "", "MakeBucket failed", err) + return + } + + defer cleanupBucket(bucketName, c) + + // Generate 33K of data. + reader := getDataReader("datafile-33-kB") + defer reader.Close() + + objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") + // Azure requires the key to not start with a number + metadataKey := randString(60, rand.NewSource(time.Now().UnixNano()), "user") + metadataValue := randString(60, rand.NewSource(time.Now().UnixNano()), "") + + buf, err := io.ReadAll(reader) + if err != nil { + logError(testName, function, args, startTime, "", "ReadAll failed", err) + return + } + + policy := minio.NewPostPolicy() + policy.SetBucket(bucketName) + policy.SetKey(objectName) + policy.SetExpires(time.Now().UTC().AddDate(0, 0, 10)) // expires in 10 days + policy.SetContentType("binary/octet-stream") + policy.SetContentLengthRange(10, 1024*1024) + policy.SetUserMetadata(metadataKey, metadataValue) + + // Add CRC32C of the 33kB file that the policy will explicitly allow. + checksum := minio.ChecksumCRC32C.ChecksumBytes(buf) + err = policy.SetChecksum(checksum) + if err != nil { + logError(testName, function, args, startTime, "", "SetChecksum failed", err) + return + } + + args["policy"] = policy.String() + + presignedPostPolicyURL, formData, err := c.PresignedPostPolicy(context.Background(), policy) + if err != nil { + logError(testName, function, args, startTime, "", "PresignedPostPolicy failed", err) + return + } + + // At this stage, we have a policy that allows us to upload datafile-33-kB. + // Test that uploading datafile-10-kB, with a different checksum, fails as expected + filePath := getMintDataDirFilePath("datafile-10-kB") + if filePath == "" { + // Make a temp file with 10 KB data. + file, err := os.CreateTemp(os.TempDir(), "PresignedPostPolicyTest") + if err != nil { + logError(testName, function, args, startTime, "", "TempFile creation failed", err) + return + } + if _, err = io.Copy(file, getDataReader("datafile-10-kB")); err != nil { + logError(testName, function, args, startTime, "", "Copy failed", err) + return + } + if err = file.Close(); err != nil { + logError(testName, function, args, startTime, "", "File Close failed", err) + return + } + filePath = file.Name() + } + fileReader := getDataReader("datafile-10-kB") + defer fileReader.Close() + buf10k, err := io.ReadAll(fileReader) + if err != nil { + logError(testName, function, args, startTime, "", "ReadAll failed", err) + return + } + otherChecksum := minio.ChecksumCRC32C.ChecksumBytes(buf10k) + + var formBuf bytes.Buffer + writer := multipart.NewWriter(&formBuf) + for k, v := range formData { + if k == "x-amz-checksum-crc32c" { + v = otherChecksum.Encoded() + } + writer.WriteField(k, v) + } + + // Add file to post request + f, err := os.Open(filePath) + defer f.Close() + if err != nil { + logError(testName, function, args, startTime, "", "File open failed", err) + return + } + w, err := writer.CreateFormFile("file", filePath) + if err != nil { + logError(testName, function, args, startTime, "", "CreateFormFile failed", err) + return + } + _, err = io.Copy(w, f) + if err != nil { + logError(testName, function, args, startTime, "", "Copy failed", err) + return + } + writer.Close() + + httpClient := &http.Client{ + Timeout: 30 * time.Second, + Transport: createHTTPTransport(), + } + args["url"] = presignedPostPolicyURL.String() + + req, err := http.NewRequest(http.MethodPost, presignedPostPolicyURL.String(), bytes.NewReader(formBuf.Bytes())) + if err != nil { + logError(testName, function, args, startTime, "", "HTTP request failed", err) + return + } + + req.Header.Set("Content-Type", writer.FormDataContentType()) + + // Make the POST request with the form data. + res, err := httpClient.Do(req) + if err != nil { + logError(testName, function, args, startTime, "", "HTTP request failed", err) + return + } + defer res.Body.Close() + if res.StatusCode != http.StatusForbidden { + logError(testName, function, args, startTime, "", "HTTP request unexpected status", errors.New(res.Status)) + return + } + + // Read the response body, ensure it has checksum failure message + resBody, err := io.ReadAll(res.Body) + if err != nil { + logError(testName, function, args, startTime, "", "ReadAll failed", err) + return + } + + // Normalize the response body, because S3 uses quotes around the policy condition components + // in the error message, MinIO does not. + resBodyStr := strings.ReplaceAll(string(resBody), `"`, "") + if !strings.Contains(resBodyStr, "Policy Condition failed: [eq, $x-amz-checksum-crc32c, aHnJMw==]") { + logError(testName, function, args, startTime, "", "Unexpected response body", errors.New(resBodyStr)) return } @@ -5857,27 +5472,12 @@ func testCopyObject() { function := "CopyObject(dst, src)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") @@ -6052,27 +5652,12 @@ func testSSECEncryptedGetObjectReadSeekFunctional() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -6235,27 +5820,12 @@ func testSSES3EncryptedGetObjectReadSeekFunctional() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -6416,27 +5986,12 @@ func testSSECEncryptedGetObjectReadAtFunctional() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -6600,27 +6155,12 @@ func testSSES3EncryptedGetObjectReadAtFunctional() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -6785,27 +6325,13 @@ func testSSECEncryptionPutGet() { "objectName": "", "sse": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -6895,27 +6421,13 @@ func testSSECEncryptionFPut() { "contentType": "", "sse": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -7018,27 +6530,13 @@ func testSSES3EncryptionPutGet() { "objectName": "", "sse": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -7126,27 +6624,13 @@ func testSSES3EncryptionFPut() { "contentType": "", "sse": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -7255,26 +6739,12 @@ func testBucketNotification() { return } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable to debug - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - bucketName := os.Getenv("NOTIFY_BUCKET") args["bucketName"] = bucketName @@ -7350,26 +6820,12 @@ func testFunctional() { functionAll := "" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, nil, startTime, "", "MinIO client object creation failed", err) return } - // Enable to debug - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") @@ -8029,24 +7485,12 @@ func testGetObjectModified() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Make a new bucket. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -8125,24 +7569,12 @@ func testPutObjectUploadSeekedObject() { "contentType": "binary/octet-stream", } - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Make a new bucket. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -8245,27 +7677,12 @@ func testMakeBucketErrorV2() { "region": "eu-west-1", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") region := "eu-west-1" @@ -8305,27 +7722,12 @@ func testGetObjectClosedTwiceV2() { "region": "eu-west-1", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -8396,27 +7798,12 @@ func testFPutObjectV2() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -8557,27 +7944,12 @@ func testMakeBucketRegionsV2() { "region": "eu-west-1", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -8620,27 +7992,12 @@ func testGetObjectReadSeekFunctionalV2() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -8775,27 +8132,12 @@ func testGetObjectReadAtFunctionalV2() { function := "GetObject(bucketName, objectName)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -8937,27 +8279,12 @@ func testCopyObjectV2() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") @@ -9156,13 +8483,7 @@ func testComposeObjectErrorCasesV2() { function := "ComposeObject(destination, sourceList)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9254,13 +8575,7 @@ func testCompose10KSourcesV2() { function := "ComposeObject(destination, sourceList)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9276,13 +8591,7 @@ func testEncryptedEmptyObject() { function := "PutObject(bucketName, objectName, reader, objectSize, opts)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return @@ -9430,7 +8739,7 @@ func testEncryptedCopyObjectWrapper(c *minio.Client, bucketName string, sseSrc, dstEncryption = sseDst } // 3. get copied object and check if content is equal - coreClient := minio.Core{c} + coreClient := minio.Core{Client: c} reader, _, _, err := coreClient.GetObject(context.Background(), bucketName, "dstObject", minio.GetObjectOptions{ServerSideEncryption: dstEncryption}) if err != nil { logError(testName, function, args, startTime, "", "GetObject failed", err) @@ -9537,13 +8846,7 @@ func testUnencryptedToSSECCopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9552,7 +8855,6 @@ func testUnencryptedToSSECCopyObject() { bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject")) - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, nil, sseDst) } @@ -9564,13 +8866,7 @@ func testUnencryptedToSSES3CopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9580,7 +8876,6 @@ func testUnencryptedToSSES3CopyObject() { var sseSrc encrypt.ServerSide sseDst := encrypt.NewSSE() - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9592,13 +8887,7 @@ func testUnencryptedToUnencryptedCopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9607,7 +8896,6 @@ func testUnencryptedToUnencryptedCopyObject() { bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") var sseSrc, sseDst encrypt.ServerSide - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9619,13 +8907,7 @@ func testEncryptedSSECToSSECCopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9635,7 +8917,6 @@ func testEncryptedSSECToSSECCopyObject() { sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject")) sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject")) - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9647,13 +8928,7 @@ func testEncryptedSSECToSSES3CopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9663,7 +8938,6 @@ func testEncryptedSSECToSSES3CopyObject() { sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject")) sseDst := encrypt.NewSSE() - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9675,13 +8949,7 @@ func testEncryptedSSECToUnencryptedCopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9691,7 +8959,6 @@ func testEncryptedSSECToUnencryptedCopyObject() { sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject")) var sseDst encrypt.ServerSide - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9703,13 +8970,7 @@ func testEncryptedSSES3ToSSECCopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9719,7 +8980,6 @@ func testEncryptedSSES3ToSSECCopyObject() { sseSrc := encrypt.NewSSE() sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject")) - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9731,13 +8991,7 @@ func testEncryptedSSES3ToSSES3CopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9747,7 +9001,6 @@ func testEncryptedSSES3ToSSES3CopyObject() { sseSrc := encrypt.NewSSE() sseDst := encrypt.NewSSE() - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9759,13 +9012,7 @@ func testEncryptedSSES3ToUnencryptedCopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9775,7 +9022,6 @@ func testEncryptedSSES3ToUnencryptedCopyObject() { sseSrc := encrypt.NewSSE() var sseDst encrypt.ServerSide - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9787,13 +9033,7 @@ func testEncryptedCopyObjectV2() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9803,7 +9043,6 @@ func testEncryptedCopyObjectV2() { sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject")) sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject")) - // c.TraceOn(os.Stderr) testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst) } @@ -9814,13 +9053,7 @@ func testDecryptedCopyObject() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return @@ -9874,26 +9107,14 @@ func testSSECMultipartEncryptedToSSECCopyObjectPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -10072,26 +9293,14 @@ func testSSECEncryptedToSSECCopyObjectPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -10250,26 +9459,14 @@ func testSSECEncryptedToUnencryptedCopyPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -10427,26 +9624,14 @@ func testSSECEncryptedToSSES3CopyObjectPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -10607,26 +9792,14 @@ func testUnencryptedToSSECCopyObjectPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -10782,26 +9955,14 @@ func testUnencryptedToUnencryptedCopyPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -10953,26 +10114,14 @@ func testUnencryptedToSSES3CopyObjectPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -11126,26 +10275,14 @@ func testSSES3EncryptedToSSECCopyObjectPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -11302,26 +10439,14 @@ func testSSES3EncryptedToUnencryptedCopyPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -11474,26 +10599,14 @@ func testSSES3EncryptedToSSES3CopyObjectPart() { function := "CopyObjectPart(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - client, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + client, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return } // Instantiate new core client object. - c := minio.Core{client} - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + c := minio.Core{Client: client} // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test") @@ -11648,19 +10761,12 @@ func testUserMetadataCopying() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // c.TraceOn(os.Stderr) testUserMetadataCopyingWrapper(c) } @@ -11825,19 +10931,12 @@ func testUserMetadataCopyingV2() { function := "CopyObject(destination, source)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) return } - // c.TraceOn(os.Stderr) testUserMetadataCopyingWrapper(c) } @@ -11848,13 +10947,7 @@ func testStorageClassMetadataPutObject() { args := map[string]interface{}{} testName := getFuncName() - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return @@ -11936,13 +11029,7 @@ func testStorageClassInvalidMetadataPutObject() { args := map[string]interface{}{} testName := getFuncName() - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return @@ -11979,13 +11066,7 @@ func testStorageClassMetadataCopyObject() { args := map[string]interface{}{} testName := getFuncName() - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - Transport: createHTTPTransport(), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err) return @@ -12106,27 +11187,12 @@ func testPutObjectNoLengthV2() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { - logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -12182,27 +11248,12 @@ func testPutObjectsUnknownV2() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { - logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -12273,27 +11324,12 @@ func testPutObject0ByteV2() { "opts": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { - logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -12338,13 +11374,7 @@ func testComposeObjectErrorCases() { function := "ComposeObject(destination, sourceList)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return @@ -12361,13 +11391,7 @@ func testCompose10KSources() { function := "ComposeObject(destination, sourceList)" args := map[string]interface{}{} - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return @@ -12385,26 +11409,12 @@ func testFunctionalV2() { functionAll := "" args := map[string]interface{}{} - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - Transport: createHTTPTransport(), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) return } - // Enable to debug - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") location := "us-east-1" @@ -12838,27 +11848,13 @@ func testGetObjectContext() { "bucketName": "", "objectName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -12941,27 +11937,13 @@ func testFGetObjectContext() { "objectName": "", "fileName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -13033,24 +12015,12 @@ func testGetObjectRanges() { defer cancel() rng := rand.NewSource(time.Now().UnixNano()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rng, "minio-go-test-") args["bucketName"] = bucketName @@ -13140,27 +12110,13 @@ func testGetObjectACLContext() { "bucketName": "", "objectName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -13318,24 +12274,12 @@ func testPutObjectContextV2() { "size": "", "opts": "", } - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { - logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Make a new bucket. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -13390,27 +12334,13 @@ func testGetObjectContextV2() { "bucketName": "", "objectName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { - logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -13491,27 +12421,13 @@ func testFGetObjectContextV2() { "objectName": "", "fileName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{CredsV2: true}) if err != nil { - logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -13580,27 +12496,13 @@ func testListObjects() { "objectPrefix": "", "recursive": "true", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -13684,24 +12586,12 @@ func testCors() { "cors": "", } - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Create or reuse a bucket that will get cors settings applied to it and deleted when done bucketName := os.Getenv("MINIO_GO_TEST_BUCKET_CORS") if bucketName == "" { @@ -14420,24 +13310,12 @@ func testCorsSetGetDelete() { "cors": "", } - // Instantiate new minio client object - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -14519,27 +13397,13 @@ func testRemoveObjects() { "objectPrefix": "", "recursive": "true", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -14653,27 +13517,13 @@ func testGetBucketTagging() { args := map[string]interface{}{ "bucketName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -14709,27 +13559,13 @@ func testSetBucketTagging() { "bucketName": "", "tags": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -14795,27 +13631,13 @@ func testRemoveBucketTagging() { args := map[string]interface{}{ "bucketName": "", } - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - // Instantiate new minio client object. - c, err := minio.New(os.Getenv(serverEndpoint), - &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), - }) + c, err := NewClient(ClientConfig{}) if err != nil { logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) return } - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - // Generate a new random bucket name. bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") args["bucketName"] = bucketName @@ -14961,6 +13783,7 @@ func main() { testGetObjectReadAtFunctional() testGetObjectReadAtWhenEOFWasReached() testPresignedPostPolicy() + testPresignedPostPolicyWrongFile() testCopyObject() testComposeObjectErrorCases() testCompose10KSources() diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go index f1c76c78e..787f0a38d 100644 --- a/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go +++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go @@ -58,9 +58,10 @@ type WebIdentityResult struct { // WebIdentityToken - web identity token with expiry. type WebIdentityToken struct { - Token string - AccessToken string - Expiry int + Token string + AccessToken string + RefreshToken string + Expiry int } // A STSWebIdentity retrieves credentials from MinIO service, and keeps track if diff --git a/vendor/github.com/minio/minio-go/v7/post-policy.go b/vendor/github.com/minio/minio-go/v7/post-policy.go index 19687e027..26bf441b5 100644 --- a/vendor/github.com/minio/minio-go/v7/post-policy.go +++ b/vendor/github.com/minio/minio-go/v7/post-policy.go @@ -85,7 +85,7 @@ func (p *PostPolicy) SetExpires(t time.Time) error { // SetKey - Sets an object name for the policy based upload. func (p *PostPolicy) SetKey(key string) error { - if strings.TrimSpace(key) == "" || key == "" { + if strings.TrimSpace(key) == "" { return errInvalidArgument("Object name is empty.") } policyCond := policyCondition{ @@ -118,7 +118,7 @@ func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error { // SetBucket - Sets bucket at which objects will be uploaded to. func (p *PostPolicy) SetBucket(bucketName string) error { - if strings.TrimSpace(bucketName) == "" || bucketName == "" { + if strings.TrimSpace(bucketName) == "" { return errInvalidArgument("Bucket name is empty.") } policyCond := policyCondition{ @@ -135,7 +135,7 @@ func (p *PostPolicy) SetBucket(bucketName string) error { // SetCondition - Sets condition for credentials, date and algorithm func (p *PostPolicy) SetCondition(matchType, condition, value string) error { - if strings.TrimSpace(value) == "" || value == "" { + if strings.TrimSpace(value) == "" { return errInvalidArgument("No value specified for condition") } @@ -156,7 +156,7 @@ func (p *PostPolicy) SetCondition(matchType, condition, value string) error { // SetTagging - Sets tagging for the object for this policy based upload. func (p *PostPolicy) SetTagging(tagging string) error { - if strings.TrimSpace(tagging) == "" || tagging == "" { + if strings.TrimSpace(tagging) == "" { return errInvalidArgument("No tagging specified.") } _, err := tags.ParseObjectXML(strings.NewReader(tagging)) @@ -178,7 +178,7 @@ func (p *PostPolicy) SetTagging(tagging string) error { // SetContentType - Sets content-type of the object for this policy // based upload. func (p *PostPolicy) SetContentType(contentType string) error { - if strings.TrimSpace(contentType) == "" || contentType == "" { + if strings.TrimSpace(contentType) == "" { return errInvalidArgument("No content type specified.") } policyCond := policyCondition{ @@ -211,7 +211,7 @@ func (p *PostPolicy) SetContentTypeStartsWith(contentTypeStartsWith string) erro // SetContentDisposition - Sets content-disposition of the object for this policy func (p *PostPolicy) SetContentDisposition(contentDisposition string) error { - if strings.TrimSpace(contentDisposition) == "" || contentDisposition == "" { + if strings.TrimSpace(contentDisposition) == "" { return errInvalidArgument("No content disposition specified.") } policyCond := policyCondition{ @@ -226,27 +226,44 @@ func (p *PostPolicy) SetContentDisposition(contentDisposition string) error { return nil } +// SetContentEncoding - Sets content-encoding of the object for this policy +func (p *PostPolicy) SetContentEncoding(contentEncoding string) error { + if strings.TrimSpace(contentEncoding) == "" { + return errInvalidArgument("No content encoding specified.") + } + policyCond := policyCondition{ + matchType: "eq", + condition: "$Content-Encoding", + value: contentEncoding, + } + if err := p.addNewPolicy(policyCond); err != nil { + return err + } + p.formData["Content-Encoding"] = contentEncoding + return nil +} + // SetContentLengthRange - Set new min and max content length // condition for all incoming uploads. -func (p *PostPolicy) SetContentLengthRange(min, max int64) error { - if min > max { +func (p *PostPolicy) SetContentLengthRange(minLen, maxLen int64) error { + if minLen > maxLen { return errInvalidArgument("Minimum limit is larger than maximum limit.") } - if min < 0 { + if minLen < 0 { return errInvalidArgument("Minimum limit cannot be negative.") } - if max <= 0 { + if maxLen <= 0 { return errInvalidArgument("Maximum limit cannot be non-positive.") } - p.contentLengthRange.min = min - p.contentLengthRange.max = max + p.contentLengthRange.min = minLen + p.contentLengthRange.max = maxLen return nil } // SetSuccessActionRedirect - Sets the redirect success url of the object for this policy // based upload. func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error { - if strings.TrimSpace(redirect) == "" || redirect == "" { + if strings.TrimSpace(redirect) == "" { return errInvalidArgument("Redirect is empty") } policyCond := policyCondition{ @@ -264,7 +281,7 @@ func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error { // SetSuccessStatusAction - Sets the status success code of the object for this policy // based upload. func (p *PostPolicy) SetSuccessStatusAction(status string) error { - if strings.TrimSpace(status) == "" || status == "" { + if strings.TrimSpace(status) == "" { return errInvalidArgument("Status is empty") } policyCond := policyCondition{ @@ -282,10 +299,10 @@ func (p *PostPolicy) SetSuccessStatusAction(status string) error { // SetUserMetadata - Set user metadata as a key/value couple. // Can be retrieved through a HEAD request or an event. func (p *PostPolicy) SetUserMetadata(key, value string) error { - if strings.TrimSpace(key) == "" || key == "" { + if strings.TrimSpace(key) == "" { return errInvalidArgument("Key is empty") } - if strings.TrimSpace(value) == "" || value == "" { + if strings.TrimSpace(value) == "" { return errInvalidArgument("Value is empty") } headerName := fmt.Sprintf("x-amz-meta-%s", key) @@ -304,7 +321,7 @@ func (p *PostPolicy) SetUserMetadata(key, value string) error { // SetUserMetadataStartsWith - Set how an user metadata should starts with. // Can be retrieved through a HEAD request or an event. func (p *PostPolicy) SetUserMetadataStartsWith(key, value string) error { - if strings.TrimSpace(key) == "" || key == "" { + if strings.TrimSpace(key) == "" { return errInvalidArgument("Key is empty") } headerName := fmt.Sprintf("x-amz-meta-%s", key) @@ -321,11 +338,29 @@ func (p *PostPolicy) SetUserMetadataStartsWith(key, value string) error { } // SetChecksum sets the checksum of the request. -func (p *PostPolicy) SetChecksum(c Checksum) { +func (p *PostPolicy) SetChecksum(c Checksum) error { if c.IsSet() { p.formData[amzChecksumAlgo] = c.Type.String() p.formData[c.Type.Key()] = c.Encoded() + + policyCond := policyCondition{ + matchType: "eq", + condition: fmt.Sprintf("$%s", amzChecksumAlgo), + value: c.Type.String(), + } + if err := p.addNewPolicy(policyCond); err != nil { + return err + } + policyCond = policyCondition{ + matchType: "eq", + condition: fmt.Sprintf("$%s", c.Type.Key()), + value: c.Encoded(), + } + if err := p.addNewPolicy(policyCond); err != nil { + return err + } } + return nil } // SetEncryption - sets encryption headers for POST API diff --git a/vendor/github.com/minio/minio-go/v7/retry-continous.go b/vendor/github.com/minio/minio-go/v7/retry-continous.go index bfeea95f3..81fcf16f1 100644 --- a/vendor/github.com/minio/minio-go/v7/retry-continous.go +++ b/vendor/github.com/minio/minio-go/v7/retry-continous.go @@ -20,7 +20,7 @@ package minio import "time" // newRetryTimerContinous creates a timer with exponentially increasing delays forever. -func (c *Client) newRetryTimerContinous(unit, cap time.Duration, jitter float64, doneCh chan struct{}) <-chan int { +func (c *Client) newRetryTimerContinous(baseSleep, maxSleep time.Duration, jitter float64, doneCh chan struct{}) <-chan int { attemptCh := make(chan int) // normalize jitter to the range [0, 1.0] @@ -39,10 +39,10 @@ func (c *Client) newRetryTimerContinous(unit, cap time.Duration, jitter float64, if attempt > maxAttempt { attempt = maxAttempt } - // sleep = random_between(0, min(cap, base * 2 ** attempt)) - sleep := unit * time.Duration(1< cap { - sleep = cap + // sleep = random_between(0, min(maxSleep, base * 2 ** attempt)) + sleep := baseSleep * time.Duration(1< maxSleep { + sleep = maxSleep } if jitter != NoJitter { sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter) diff --git a/vendor/github.com/minio/minio-go/v7/retry.go b/vendor/github.com/minio/minio-go/v7/retry.go index d15eb5901..4cc45920c 100644 --- a/vendor/github.com/minio/minio-go/v7/retry.go +++ b/vendor/github.com/minio/minio-go/v7/retry.go @@ -45,7 +45,7 @@ var DefaultRetryCap = time.Second // newRetryTimer creates a timer with exponentially increasing // delays until the maximum retry attempts are reached. -func (c *Client) newRetryTimer(ctx context.Context, maxRetry int, unit, cap time.Duration, jitter float64) <-chan int { +func (c *Client) newRetryTimer(ctx context.Context, maxRetry int, baseSleep, maxSleep time.Duration, jitter float64) <-chan int { attemptCh := make(chan int) // computes the exponential backoff duration according to @@ -59,10 +59,10 @@ func (c *Client) newRetryTimer(ctx context.Context, maxRetry int, unit, cap time jitter = MaxJitter } - // sleep = random_between(0, min(cap, base * 2 ** attempt)) - sleep := unit * time.Duration(1< cap { - sleep = cap + // sleep = random_between(0, min(maxSleep, base * 2 ** attempt)) + sleep := baseSleep * time.Duration(1< maxSleep { + sleep = maxSleep } if jitter != NoJitter { sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter) diff --git a/vendor/github.com/ncruces/go-sqlite3/README.md b/vendor/github.com/ncruces/go-sqlite3/README.md index 935b9f254..f5394ab22 100644 --- a/vendor/github.com/ncruces/go-sqlite3/README.md +++ b/vendor/github.com/ncruces/go-sqlite3/README.md @@ -77,7 +77,7 @@ It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (amd64/arm64), Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64), -illumos (amd64), and Solaris (amd64). +DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64). The Go VFS is tested by running SQLite's [mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c). @@ -90,9 +90,20 @@ Perfomance of the [`database/sql`](https://pkg.go.dev/database/sql) driver is The Wasm and VFS layers are also tested by running SQLite's [speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c). +### FAQ, issues, new features + +For questions, please see [Discussions](https://github.com/ncruces/go-sqlite3/discussions/categories/q-a). + +Also, post there if you used this driver for something interesting +([_"Show and tell"_](https://github.com/ncruces/go-sqlite3/discussions/categories/show-and-tell)), +have an [idea](https://github.com/ncruces/go-sqlite3/discussions/categories/ideas)… + +The [Issue](https://github.com/ncruces/go-sqlite3/issues) tracker is for bugs we want fixed, +and features we're working on, planning to work on, or asking for help with. + ### Alternatives - [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) - [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite) - [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3) -- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite) \ No newline at end of file +- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite) diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/README.md b/vendor/github.com/ncruces/go-sqlite3/embed/README.md index b7b25c461..7a7a52a49 100644 --- a/vendor/github.com/ncruces/go-sqlite3/embed/README.md +++ b/vendor/github.com/ncruces/go-sqlite3/embed/README.md @@ -1,6 +1,6 @@ # Embeddable Wasm build of SQLite -This folder includes an embeddable Wasm build of SQLite 3.47.0 for use with +This folder includes an embeddable Wasm build of SQLite 3.47.1 for use with [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3). The following optional features are compiled in: diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm index 173ad0e08..2e86b6f5d 100644 Binary files a/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm and b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm differ diff --git a/vendor/github.com/ncruces/go-sqlite3/go.work.sum b/vendor/github.com/ncruces/go-sqlite3/go.work.sum index 52265b555..c3936965c 100644 --- a/vendor/github.com/ncruces/go-sqlite3/go.work.sum +++ b/vendor/github.com/ncruces/go-sqlite3/go.work.sum @@ -9,5 +9,6 @@ golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/README.md b/vendor/github.com/ncruces/go-sqlite3/vfs/README.md index cf0e3c30f..08777972e 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/README.md +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/README.md @@ -30,7 +30,6 @@ like SQLite. You can also opt into a cross-platform locking implementation with the `sqlite3_dotlk` build tag. -The only requirement is an atomic `os.Mkdir`. Otherwise, file locking is not supported, and you must use [`nolock=1`](https://sqlite.org/uri.html#urinolock) diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go b/vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go index 900fa0952..42d7468f5 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go @@ -101,6 +101,14 @@ func (c cksmFile) Pragma(name string, value string) (string, error) { return "", _NOTFOUND } +func (c cksmFile) DeviceCharacteristics() DeviceCharacteristic { + res := c.File.DeviceCharacteristics() + if c.verifyCksm { + res &^= IOCAP_SUBPAGE_READ + } + return res +} + func (c cksmFile) fileControl(ctx context.Context, mod api.Module, op _FcntlOpcode, pArg uint32) _ErrorCode { switch op { case _FCNTL_CKPT_START: diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/const.go b/vendor/github.com/ncruces/go-sqlite3/vfs/const.go index 0a8fee621..896cdaca4 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/const.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/const.go @@ -177,6 +177,7 @@ const ( IOCAP_POWERSAFE_OVERWRITE DeviceCharacteristic = 0x00001000 IOCAP_IMMUTABLE DeviceCharacteristic = 0x00002000 IOCAP_BATCH_ATOMIC DeviceCharacteristic = 0x00004000 + IOCAP_SUBPAGE_READ DeviceCharacteristic = 0x00008000 ) // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/file.go b/vendor/github.com/ncruces/go-sqlite3/vfs/file.go index ba70aa14f..b5d285375 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/file.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/file.go @@ -187,7 +187,7 @@ func (f *vfsFile) SectorSize() int { } func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic { - var res DeviceCharacteristic + res := IOCAP_SUBPAGE_READ if osBatchAtomic(f.File) { res |= IOCAP_BATCH_ATOMIC } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go index 56713e359..cc5da7cab 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go @@ -15,9 +15,15 @@ func osGetSharedLock(file *os.File) _ErrorCode { func osGetReservedLock(file *os.File) _ErrorCode { rc := osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK) if rc == _BUSY { - // The documentation states the lock is upgraded by releasing the previous lock, - // then acquiring the new lock. - // This is a race, so return BUSY_SNAPSHOT to ensure the transaction is aborted. + // The documentation states that a lock is upgraded by + // releasing the previous lock, then acquiring the new lock. + // Going over the source code of various BSDs, though, + // with LOCK_NB, the lock is not released, + // and EAGAIN is returned holding the shared lock. + // Still, if we're already in a transaction, we want to abort it, + // so return BUSY_SNAPSHOT here. If there's no transaction active, + // SQLite will change this back to SQLITE_BUSY, + // and invoke the busy handler if appropriate. return _BUSY_SNAPSHOT } return rc @@ -33,9 +39,11 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode { rc := osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) if rc == _BUSY { - // The documentation states the lock is upgraded by releasing the previous lock, - // then acquiring the new lock. - // This is a race, so return IOERR_RDLOCK to ensure the transaction is aborted. + // The documentation states that a lock is downgraded by + // releasing the previous lock then acquiring the new lock. + // Going over the source code of various BSDs, though, + // with LOCK_SH|LOCK_NB this should never happen. + // Return IOERR_RDLOCK, as BUSY would cause an assert to fail. return _IOERR_RDLOCK } return _OK @@ -50,7 +58,10 @@ func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode { } func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { - // Test the RESERVED lock. + // Test the RESERVED lock with fcntl(F_GETLK). + // This only works on systems where fcntl and flock are compatible. + // However, SQLite only calls this while holding a shared lock, + // so the difference is immaterial. lock, rc := osTestLock(file, _RESERVED_BYTE, 1) return lock == unix.F_WRLCK, rc } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_dotlk.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_dotlk.go index 1c1a49c11..b00a1865b 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_dotlk.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_dotlk.go @@ -28,7 +28,8 @@ func osGetSharedLock(file *os.File) _ErrorCode { name := file.Name() locker := vfsDotLocks[name] if locker == nil { - err := os.Mkdir(name+".lock", 0777) + f, err := os.OpenFile(name+".lock", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + f.Close() if errors.Is(err, fs.ErrExist) { return _BUSY // Another process has the lock. } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go index b901f98aa..0b6e5d342 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go @@ -50,14 +50,17 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { if rc != _OK { // Reacquire the SHARED lock. - osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) + if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { + // notest // this should never happen + return _IOERR_RDLOCK + } } return rc } func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { if state >= LOCK_EXCLUSIVE { - // Release the EXCLUSIVE lock. + // Release the EXCLUSIVE lock while holding the PENDING lock. osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) // Reacquire the SHARED lock. @@ -78,7 +81,7 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { } func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { - // Release all locks. + // Release all locks, PENDING must be last. if state >= LOCK_RESERVED { osUnlock(file, _RESERVED_BYTE, 1) } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_bsd.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_bsd.go index d4e046369..07cabf7b5 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_bsd.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_bsd.go @@ -14,52 +14,52 @@ import ( "github.com/ncruces/go-sqlite3/internal/util" ) -type vfsShmFile struct { +type vfsShmParent struct { *os.File info os.FileInfo - refs int // +checklocks:vfsShmFilesMtx + refs int // +checklocks:vfsShmListMtx lock [_SHM_NLOCK]int16 // +checklocks:Mutex sync.Mutex } var ( - // +checklocks:vfsShmFilesMtx - vfsShmFiles []*vfsShmFile - vfsShmFilesMtx sync.Mutex + // +checklocks:vfsShmListMtx + vfsShmList []*vfsShmParent + vfsShmListMtx sync.Mutex ) type vfsShm struct { - *vfsShmFile + *vfsShmParent path string lock [_SHM_NLOCK]bool regions []*util.MappedRegion } func (s *vfsShm) Close() error { - if s.vfsShmFile == nil { + if s.vfsShmParent == nil { return nil } - vfsShmFilesMtx.Lock() - defer vfsShmFilesMtx.Unlock() + vfsShmListMtx.Lock() + defer vfsShmListMtx.Unlock() // Unlock everything. s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK) // Decrease reference count. - if s.vfsShmFile.refs > 0 { - s.vfsShmFile.refs-- - s.vfsShmFile = nil + if s.vfsShmParent.refs > 0 { + s.vfsShmParent.refs-- + s.vfsShmParent = nil return nil } err := s.File.Close() - for i, g := range vfsShmFiles { - if g == s.vfsShmFile { - vfsShmFiles[i] = nil - s.vfsShmFile = nil + for i, g := range vfsShmList { + if g == s.vfsShmParent { + vfsShmList[i] = nil + s.vfsShmParent = nil return err } } @@ -67,7 +67,7 @@ func (s *vfsShm) Close() error { } func (s *vfsShm) shmOpen() _ErrorCode { - if s.vfsShmFile != nil { + if s.vfsShmParent != nil { return _OK } @@ -85,13 +85,13 @@ func (s *vfsShm) shmOpen() _ErrorCode { return _IOERR_FSTAT } - vfsShmFilesMtx.Lock() - defer vfsShmFilesMtx.Unlock() + vfsShmListMtx.Lock() + defer vfsShmListMtx.Unlock() // Find a shared file, increase the reference count. - for _, g := range vfsShmFiles { + for _, g := range vfsShmList { if g != nil && os.SameFile(fi, g.info) { - s.vfsShmFile = g + s.vfsShmParent = g g.refs++ return _OK } @@ -107,18 +107,18 @@ func (s *vfsShm) shmOpen() _ErrorCode { } // Add the new shared file. - s.vfsShmFile = &vfsShmFile{ + s.vfsShmParent = &vfsShmParent{ File: f, info: fi, } f = nil // Don't close the file. - for i, g := range vfsShmFiles { + for i, g := range vfsShmList { if g == nil { - vfsShmFiles[i] = s.vfsShmFile + vfsShmList[i] = s.vfsShmParent return _OK } } - vfsShmFiles = append(vfsShmFiles, s.vfsShmFile) + vfsShmList = append(vfsShmList, s.vfsShmParent) return _OK } @@ -157,57 +157,11 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { s.Lock() defer s.Unlock() - - switch { - case flags&_SHM_UNLOCK != 0: - for i := offset; i < offset+n; i++ { - if s.lock[i] { - if s.vfsShmFile.lock[i] == 0 { - panic(util.AssertErr()) - } - if s.vfsShmFile.lock[i] <= 0 { - s.vfsShmFile.lock[i] = 0 - } else { - s.vfsShmFile.lock[i]-- - } - s.lock[i] = false - } - } - case flags&_SHM_SHARED != 0: - for i := offset; i < offset+n; i++ { - if s.lock[i] { - panic(util.AssertErr()) - } - if s.vfsShmFile.lock[i]+1 <= 0 { - return _BUSY - } - } - for i := offset; i < offset+n; i++ { - s.vfsShmFile.lock[i]++ - s.lock[i] = true - } - case flags&_SHM_EXCLUSIVE != 0: - for i := offset; i < offset+n; i++ { - if s.lock[i] { - panic(util.AssertErr()) - } - if s.vfsShmFile.lock[i] != 0 { - return _BUSY - } - } - for i := offset; i < offset+n; i++ { - s.vfsShmFile.lock[i] = -1 - s.lock[i] = true - } - default: - panic(util.AssertErr()) - } - - return _OK + return s.shmMemLock(offset, n, flags) } func (s *vfsShm) shmUnmap(delete bool) { - if s.vfsShmFile == nil { + if s.vfsShmParent == nil { return } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_copy.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_copy.go index 7a250523e..e6007aa1c 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_copy.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_copy.go @@ -31,7 +31,10 @@ const ( // // https://sqlite.org/walformat.html#the_wal_index_file_format -func (s *vfsShm) shmAcquire() { +func (s *vfsShm) shmAcquire(ptr *_ErrorCode) { + if ptr != nil && *ptr != _OK { + return + } if len(s.ptrs) == 0 || shmUnmodified(s.shadow[0][:], s.shared[0][:]) { return } @@ -69,7 +72,7 @@ func (s *vfsShm) shmRelease() { func (s *vfsShm) shmBarrier() { s.Lock() - s.shmAcquire() + s.shmAcquire(nil) s.shmRelease() s.Unlock() } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_dotlk.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_dotlk.go index 36e00a1cd..4c7f47dec 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_dotlk.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_dotlk.go @@ -13,22 +13,22 @@ import ( "github.com/tetratelabs/wazero/api" ) -type vfsShmBuffer struct { +type vfsShmParent struct { shared [][_WALINDEX_PGSZ]byte - refs int // +checklocks:vfsShmBuffersMtx + refs int // +checklocks:vfsShmListMtx lock [_SHM_NLOCK]int16 // +checklocks:Mutex sync.Mutex } var ( - // +checklocks:vfsShmBuffersMtx - vfsShmBuffers = map[string]*vfsShmBuffer{} - vfsShmBuffersMtx sync.Mutex + // +checklocks:vfsShmListMtx + vfsShmList = map[string]*vfsShmParent{} + vfsShmListMtx sync.Mutex ) type vfsShm struct { - *vfsShmBuffer + *vfsShmParent mod api.Module alloc api.Function free api.Function @@ -40,20 +40,20 @@ type vfsShm struct { } func (s *vfsShm) Close() error { - if s.vfsShmBuffer == nil { + if s.vfsShmParent == nil { return nil } - vfsShmBuffersMtx.Lock() - defer vfsShmBuffersMtx.Unlock() + vfsShmListMtx.Lock() + defer vfsShmListMtx.Unlock() // Unlock everything. s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK) // Decrease reference count. - if s.vfsShmBuffer.refs > 0 { - s.vfsShmBuffer.refs-- - s.vfsShmBuffer = nil + if s.vfsShmParent.refs > 0 { + s.vfsShmParent.refs-- + s.vfsShmParent = nil return nil } @@ -61,22 +61,22 @@ func (s *vfsShm) Close() error { if err != nil && !errors.Is(err, fs.ErrNotExist) { return _IOERR_UNLOCK } - delete(vfsShmBuffers, s.path) - s.vfsShmBuffer = nil + delete(vfsShmList, s.path) + s.vfsShmParent = nil return nil } func (s *vfsShm) shmOpen() _ErrorCode { - if s.vfsShmBuffer != nil { + if s.vfsShmParent != nil { return _OK } - vfsShmBuffersMtx.Lock() - defer vfsShmBuffersMtx.Unlock() + vfsShmListMtx.Lock() + defer vfsShmListMtx.Unlock() // Find a shared buffer, increase the reference count. - if g, ok := vfsShmBuffers[s.path]; ok { - s.vfsShmBuffer = g + if g, ok := vfsShmList[s.path]; ok { + s.vfsShmParent = g g.refs++ return _OK } @@ -92,8 +92,8 @@ func (s *vfsShm) shmOpen() _ErrorCode { } // Add the new shared buffer. - s.vfsShmBuffer = &vfsShmBuffer{} - vfsShmBuffers[s.path] = s.vfsShmBuffer + s.vfsShmParent = &vfsShmParent{} + vfsShmList[s.path] = s.vfsShmParent return _OK } @@ -112,7 +112,7 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext s.Lock() defer s.Unlock() - defer s.shmAcquire() + defer s.shmAcquire(nil) // Extend shared memory. if int(id) >= len(s.shared) { @@ -125,7 +125,6 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext // Allocate shadow memory. if int(id) >= len(s.shadow) { s.shadow = append(s.shadow, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shadow)+1)...) - s.shadow[0][4] = 1 // force invalidation } // Allocate local memory. @@ -141,70 +140,26 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext s.ptrs = append(s.ptrs, uint32(s.stack[0])) } + s.shadow[0][4] = 1 return s.ptrs[id], _OK } -func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) { s.Lock() defer s.Unlock() switch { case flags&_SHM_LOCK != 0: - defer s.shmAcquire() + defer s.shmAcquire(&rc) case flags&_SHM_EXCLUSIVE != 0: s.shmRelease() } - switch { - case flags&_SHM_UNLOCK != 0: - for i := offset; i < offset+n; i++ { - if s.lock[i] { - if s.vfsShmBuffer.lock[i] == 0 { - panic(util.AssertErr()) - } - if s.vfsShmBuffer.lock[i] <= 0 { - s.vfsShmBuffer.lock[i] = 0 - } else { - s.vfsShmBuffer.lock[i]-- - } - s.lock[i] = false - } - } - case flags&_SHM_SHARED != 0: - for i := offset; i < offset+n; i++ { - if s.lock[i] { - panic(util.AssertErr()) - } - if s.vfsShmBuffer.lock[i]+1 <= 0 { - return _BUSY - } - } - for i := offset; i < offset+n; i++ { - s.vfsShmBuffer.lock[i]++ - s.lock[i] = true - } - case flags&_SHM_EXCLUSIVE != 0: - for i := offset; i < offset+n; i++ { - if s.lock[i] { - panic(util.AssertErr()) - } - if s.vfsShmBuffer.lock[i] != 0 { - return _BUSY - } - } - for i := offset; i < offset+n; i++ { - s.vfsShmBuffer.lock[i] = -1 - s.lock[i] = true - } - default: - panic(util.AssertErr()) - } - - return _OK + return s.shmMemLock(offset, n, flags) } func (s *vfsShm) shmUnmap(delete bool) { - if s.vfsShmBuffer == nil { + if s.vfsShmParent == nil { return } defer s.Close() diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_memlk.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_memlk.go new file mode 100644 index 000000000..dc7b91350 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_memlk.go @@ -0,0 +1,55 @@ +//go:build ((freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk + +package vfs + +import "github.com/ncruces/go-sqlite3/internal/util" + +// +checklocks:s.Mutex +func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) _ErrorCode { + switch { + case flags&_SHM_UNLOCK != 0: + for i := offset; i < offset+n; i++ { + if s.lock[i] { + if s.vfsShmParent.lock[i] == 0 { + panic(util.AssertErr()) + } + if s.vfsShmParent.lock[i] <= 0 { + s.vfsShmParent.lock[i] = 0 + } else { + s.vfsShmParent.lock[i]-- + } + s.lock[i] = false + } + } + case flags&_SHM_SHARED != 0: + for i := offset; i < offset+n; i++ { + if s.lock[i] { + panic(util.AssertErr()) + } + if s.vfsShmParent.lock[i]+1 <= 0 { + return _BUSY + } + } + for i := offset; i < offset+n; i++ { + s.vfsShmParent.lock[i]++ + s.lock[i] = true + } + case flags&_SHM_EXCLUSIVE != 0: + for i := offset; i < offset+n; i++ { + if s.lock[i] { + panic(util.AssertErr()) + } + if s.vfsShmParent.lock[i] != 0 { + return _BUSY + } + } + for i := offset; i < offset+n; i++ { + s.vfsShmParent.lock[i] = -1 + s.lock[i] = true + } + default: + panic(util.AssertErr()) + } + + return _OK +} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_windows.go b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_windows.go index 218d8e2c7..374d491ac 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/shm_windows.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/shm_windows.go @@ -64,7 +64,7 @@ func (s *vfsShm) shmOpen() _ErrorCode { return osReadLock(s.File, _SHM_DMS, 1, time.Millisecond) } -func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) { +func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ uint32, rc _ErrorCode) { // Ensure size is a multiple of the OS page size. if size != _WALINDEX_PGSZ || (windows.Getpagesize()-1)&_WALINDEX_PGSZ != 0 { return 0, _IOERR_SHMMAP @@ -78,7 +78,7 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext return 0, rc } - defer s.shmAcquire() + defer s.shmAcquire(&rc) // Check if file is big enough. o, err := s.Seek(0, io.SeekEnd) @@ -107,7 +107,6 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext // Allocate shadow memory. if int(id) >= len(s.shadow) { s.shadow = append(s.shadow, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shadow)+1)...) - s.shadow[0][4] = 1 // force invalidation } // Allocate local memory. @@ -123,22 +122,23 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext s.ptrs = append(s.ptrs, uint32(s.stack[0])) } + s.shadow[0][4] = 1 return s.ptrs[id], _OK } -func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { - switch { - case flags&_SHM_LOCK != 0: - defer s.shmAcquire() - case flags&_SHM_EXCLUSIVE != 0: - s.shmRelease() - } - +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) { var timeout time.Duration if s.blocking { timeout = time.Millisecond } + switch { + case flags&_SHM_LOCK != 0: + defer s.shmAcquire(&rc) + case flags&_SHM_EXCLUSIVE != 0: + s.shmRelease() + } + switch { case flags&_SHM_UNLOCK != 0: return osUnlock(s.File, _SHM_BASE+uint32(offset), uint32(n)) diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go index 4d4b4aad6..7e19eba09 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -7,10 +7,13 @@ import ( "time" ) -type CompareType int +// Deprecated: CompareType has only ever been for internal use and has accidentally been published since v1.6.0. Do not use it. +type CompareType = compareResult + +type compareResult int const ( - compareLess CompareType = iota - 1 + compareLess compareResult = iota - 1 compareEqual compareGreater ) @@ -39,7 +42,7 @@ var ( bytesType = reflect.TypeOf([]byte{}) ) -func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { +func compare(obj1, obj2 interface{}, kind reflect.Kind) (compareResult, bool) { obj1Value := reflect.ValueOf(obj1) obj2Value := reflect.ValueOf(obj2) @@ -325,7 +328,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time) } - return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64) + if timeObj1.Before(timeObj2) { + return compareLess, true + } + if timeObj1.Equal(timeObj2) { + return compareEqual, true + } + return compareGreater, true } case reflect.Slice: { @@ -345,7 +354,7 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte) } - return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true + return compareResult(bytes.Compare(bytesObj1, bytesObj2)), true } case reflect.Uintptr: { @@ -381,7 +390,7 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } // GreaterOrEqual asserts that the first element is greater than or equal to the second @@ -394,7 +403,7 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } // Less asserts that the first element is less than the second @@ -406,7 +415,7 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } // LessOrEqual asserts that the first element is less than or equal to the second @@ -419,7 +428,7 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter if h, ok := t.(tHelper); ok { h.Helper() } - return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) + return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } // Positive asserts that the specified element is positive @@ -431,7 +440,7 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { h.Helper() } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs...) + return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, "\"%v\" is not positive", msgAndArgs...) } // Negative asserts that the specified element is negative @@ -443,10 +452,10 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { h.Helper() } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs...) + return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, "\"%v\" is not negative", msgAndArgs...) } -func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { +func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } @@ -469,7 +478,7 @@ func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedCompare return true } -func containsValue(values []CompareType, value CompareType) bool { +func containsValue(values []compareResult, value compareResult) bool { for _, v := range values { if v == value { return true diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 3ddab109a..190634165 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -104,8 +104,8 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { @@ -186,7 +186,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick // assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -568,6 +568,23 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a return NotContains(t, s, contains, append([]interface{}{msg}, args...)...) } +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// assert.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) +} + // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // @@ -604,7 +621,16 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotErrorAs(t, err, target, append([]interface{}{msg}, args...)...) +} + +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index a84e09bd4..21629087b 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -186,8 +186,8 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface return EqualExportedValuesf(a.t, expected, actual, msg, args...) } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { @@ -197,8 +197,8 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn return EqualValues(a.t, expected, actual, msgAndArgs...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { @@ -336,7 +336,7 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti // a.EventuallyWithT(func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -361,7 +361,7 @@ func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor // a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithTf(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1128,6 +1128,40 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin return NotContainsf(a.t, s, contains, msg, args...) } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 2, 3]) -> true +// +// a.NotElementsMatch([1, 2, 3], [1, 2, 4]) -> true +func (a *Assertions) NotElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// a.NotElementsMatchf([1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotElementsMatchf(a.t, listA, listB, msg, args...) +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // @@ -1200,7 +1234,25 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str return NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorAs(a.t, err, target, msgAndArgs...) +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorAsf(a.t, err, target, msg, args...) +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { @@ -1209,7 +1261,7 @@ func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface return NotErrorIs(a.t, err, target, msgAndArgs...) } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go index 00df62a05..1d2f71824 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_order.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -6,7 +6,7 @@ import ( ) // isOrdered checks that collection contains orderable elements. -func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { +func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool { objKind := reflect.TypeOf(object).Kind() if objKind != reflect.Slice && objKind != reflect.Array { return false @@ -50,7 +50,7 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareT // assert.IsIncreasing(t, []float{1, 2}) // assert.IsIncreasing(t, []string{"a", "b"}) func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } // IsNonIncreasing asserts that the collection is not increasing @@ -59,7 +59,7 @@ func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo // assert.IsNonIncreasing(t, []float{2, 1}) // assert.IsNonIncreasing(t, []string{"b", "a"}) func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } // IsDecreasing asserts that the collection is decreasing @@ -68,7 +68,7 @@ func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // assert.IsDecreasing(t, []float{2, 1}) // assert.IsDecreasing(t, []string{"b", "a"}) func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } // IsNonDecreasing asserts that the collection is not decreasing @@ -77,5 +77,5 @@ func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo // assert.IsNonDecreasing(t, []float{1, 2}) // assert.IsNonDecreasing(t, []string{"a", "b"}) func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) + return isOrdered(t, object, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 0b7570f21..4e91332bb 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -19,7 +19,9 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" - "gopkg.in/yaml.v3" + + // Wrapper around gopkg.in/yaml.v3 + "github.com/stretchr/testify/assert/yaml" ) //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl" @@ -45,6 +47,10 @@ type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool // for table driven tests. type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool +// PanicAssertionFunc is a common function prototype when validating a panic value. Can be useful +// for table driven tests. +type PanicAssertionFunc = func(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool + // Comparison is a custom function that returns true on success and false on failure type Comparison func() (success bool) @@ -496,7 +502,13 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b h.Helper() } - if !samePointers(expected, actual) { + same, ok := samePointers(expected, actual) + if !ok { + return Fail(t, "Both arguments must be pointers", msgAndArgs...) + } + + if !same { + // both are pointers but not the same type & pointing to the same address return Fail(t, fmt.Sprintf("Not same: \n"+ "expected: %p %#v\n"+ "actual : %p %#v", expected, expected, actual, actual), msgAndArgs...) @@ -516,7 +528,13 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} h.Helper() } - if samePointers(expected, actual) { + same, ok := samePointers(expected, actual) + if !ok { + //fails when the arguments are not pointers + return !(Fail(t, "Both arguments must be pointers", msgAndArgs...)) + } + + if same { return Fail(t, fmt.Sprintf( "Expected and actual point to the same object: %p %#v", expected, expected), msgAndArgs...) @@ -524,21 +542,23 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} return true } -// samePointers compares two generic interface objects and returns whether -// they point to the same object -func samePointers(first, second interface{}) bool { +// samePointers checks if two generic interface objects are pointers of the same +// type pointing to the same object. It returns two values: same indicating if +// they are the same type and point to the same object, and ok indicating that +// both inputs are pointers. +func samePointers(first, second interface{}) (same bool, ok bool) { firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second) if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr { - return false + return false, false //not both are pointers } firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second) if firstType != secondType { - return false + return false, true // both are pointers, but of different types } // compare pointer addresses - return first == second + return first == second, true } // formatUnequalValues takes two values of arbitrary types and returns string @@ -572,8 +592,8 @@ func truncatingFormat(data interface{}) string { return value } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { @@ -615,21 +635,6 @@ func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs .. return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...) } - if aType.Kind() == reflect.Ptr { - aType = aType.Elem() - } - if bType.Kind() == reflect.Ptr { - bType = bType.Elem() - } - - if aType.Kind() != reflect.Struct { - return Fail(t, fmt.Sprintf("Types expected to both be struct or pointer to struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...) - } - - if bType.Kind() != reflect.Struct { - return Fail(t, fmt.Sprintf("Types expected to both be struct or pointer to struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...) - } - expected = copyExportedFields(expected) actual = copyExportedFields(actual) @@ -1170,6 +1175,39 @@ func formatListDiff(listA, listB interface{}, extraA, extraB []interface{}) stri return msg.String() } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true +// +// assert.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true +func NotElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if isEmpty(listA) && isEmpty(listB) { + return Fail(t, "listA and listB contain the same elements", msgAndArgs) + } + + if !isList(t, listA, msgAndArgs...) { + return Fail(t, "listA is not a list type", msgAndArgs...) + } + if !isList(t, listB, msgAndArgs...) { + return Fail(t, "listB is not a list type", msgAndArgs...) + } + + extraA, extraB := diffLists(listA, listB) + if len(extraA) == 0 && len(extraB) == 0 { + return Fail(t, "listA and listB contain the same elements", msgAndArgs) + } + + return true +} + // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { @@ -1488,6 +1526,9 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd if err != nil { return Fail(t, err.Error(), msgAndArgs...) } + if math.IsNaN(actualEpsilon) { + return Fail(t, "relative error is NaN", msgAndArgs...) + } if actualEpsilon > epsilon { return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ " < %#v (actual)", epsilon, actualEpsilon), msgAndArgs...) @@ -1611,7 +1652,6 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in // matchRegexp return true if a specified regexp matches a string. func matchRegexp(rx interface{}, str interface{}) bool { - var r *regexp.Regexp if rr, ok := rx.(*regexp.Regexp); ok { r = rr @@ -1619,7 +1659,14 @@ func matchRegexp(rx interface{}, str interface{}) bool { r = regexp.MustCompile(fmt.Sprint(rx)) } - return (r.FindStringIndex(fmt.Sprint(str)) != nil) + switch v := str.(type) { + case []byte: + return r.Match(v) + case string: + return r.MatchString(v) + default: + return r.MatchString(fmt.Sprint(v)) + } } @@ -1872,7 +1919,7 @@ var spewConfigStringerEnabled = spew.ConfigState{ MaxDepth: 10, } -type tHelper interface { +type tHelper = interface { Helper() } @@ -1911,6 +1958,9 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t // CollectT implements the TestingT interface and collects all errors. type CollectT struct { + // A slice of errors. Non-nil slice denotes a failure. + // If it's non-nil but len(c.errors) == 0, this is also a failure + // obtained by direct c.FailNow() call. errors []error } @@ -1919,9 +1969,10 @@ func (c *CollectT) Errorf(format string, args ...interface{}) { c.errors = append(c.errors, fmt.Errorf(format, args...)) } -// FailNow panics. -func (*CollectT) FailNow() { - panic("Assertion failed") +// FailNow stops execution by calling runtime.Goexit. +func (c *CollectT) FailNow() { + c.fail() + runtime.Goexit() } // Deprecated: That was a method for internal usage that should not have been published. Now just panics. @@ -1934,6 +1985,16 @@ func (*CollectT) Copy(TestingT) { panic("Copy() is deprecated") } +func (c *CollectT) fail() { + if !c.failed() { + c.errors = []error{} // Make it non-nil to mark a failure. + } +} + +func (c *CollectT) failed() bool { + return c.errors != nil +} + // EventuallyWithT asserts that given condition will be met in waitFor time, // periodically checking target function each tick. In contrast to Eventually, // it supplies a CollectT to the condition function, so that the condition @@ -1951,14 +2012,14 @@ func (*CollectT) Copy(TestingT) { // assert.EventuallyWithT(t, func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } var lastFinishedTickErrs []error - ch := make(chan []error, 1) + ch := make(chan *CollectT, 1) timer := time.NewTimer(waitFor) defer timer.Stop() @@ -1978,16 +2039,16 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time go func() { collect := new(CollectT) defer func() { - ch <- collect.errors + ch <- collect }() condition(collect) }() - case errs := <-ch: - if len(errs) == 0 { + case collect := <-ch: + if !collect.failed() { return true } // Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached. - lastFinishedTickErrs = errs + lastFinishedTickErrs = collect.errors tick = ticker.C } } @@ -2049,7 +2110,7 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { ), msgAndArgs...) } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { @@ -2090,6 +2151,24 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{ ), msgAndArgs...) } +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !errors.As(err, target) { + return true + } + + chain := buildErrorChainString(err) + + return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ + "found: %q\n"+ + "in chain: %s", target, chain, + ), msgAndArgs...) +} + func buildErrorChainString(err error) string { if err == nil { return "" diff --git a/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go b/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go new file mode 100644 index 000000000..baa0cc7d7 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go @@ -0,0 +1,25 @@ +//go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default +// +build testify_yaml_custom,!testify_yaml_fail,!testify_yaml_default + +// Package yaml is an implementation of YAML functions that calls a pluggable implementation. +// +// This implementation is selected with the testify_yaml_custom build tag. +// +// go test -tags testify_yaml_custom +// +// This implementation can be used at build time to replace the default implementation +// to avoid linking with [gopkg.in/yaml.v3]. +// +// In your test package: +// +// import assertYaml "github.com/stretchr/testify/assert/yaml" +// +// func init() { +// assertYaml.Unmarshal = func (in []byte, out interface{}) error { +// // ... +// return nil +// } +// } +package yaml + +var Unmarshal func(in []byte, out interface{}) error diff --git a/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go b/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go new file mode 100644 index 000000000..b83c6cf64 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go @@ -0,0 +1,37 @@ +//go:build !testify_yaml_fail && !testify_yaml_custom +// +build !testify_yaml_fail,!testify_yaml_custom + +// Package yaml is just an indirection to handle YAML deserialization. +// +// This package is just an indirection that allows the builder to override the +// indirection with an alternative implementation of this package that uses +// another implementation of YAML deserialization. This allows to not either not +// use YAML deserialization at all, or to use another implementation than +// [gopkg.in/yaml.v3] (for example for license compatibility reasons, see [PR #1120]). +// +// Alternative implementations are selected using build tags: +// +// - testify_yaml_fail: [Unmarshal] always fails with an error +// - testify_yaml_custom: [Unmarshal] is a variable. Caller must initialize it +// before calling any of [github.com/stretchr/testify/assert.YAMLEq] or +// [github.com/stretchr/testify/assert.YAMLEqf]. +// +// Usage: +// +// go test -tags testify_yaml_fail +// +// You can check with "go list" which implementation is linked: +// +// go list -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml +// go list -tags testify_yaml_fail -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml +// go list -tags testify_yaml_custom -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml +// +// [PR #1120]: https://github.com/stretchr/testify/pull/1120 +package yaml + +import goyaml "gopkg.in/yaml.v3" + +// Unmarshal is just a wrapper of [gopkg.in/yaml.v3.Unmarshal]. +func Unmarshal(in []byte, out interface{}) error { + return goyaml.Unmarshal(in, out) +} diff --git a/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go b/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go new file mode 100644 index 000000000..e78f7dfe6 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go @@ -0,0 +1,18 @@ +//go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default +// +build testify_yaml_fail,!testify_yaml_custom,!testify_yaml_default + +// Package yaml is an implementation of YAML functions that always fail. +// +// This implementation can be used at build time to replace the default implementation +// to avoid linking with [gopkg.in/yaml.v3]: +// +// go test -tags testify_yaml_fail +package yaml + +import "errors" + +var errNotImplemented = errors.New("YAML functions are not available (see https://pkg.go.dev/github.com/stretchr/testify/assert/yaml)") + +func Unmarshal([]byte, interface{}) error { + return errNotImplemented +} diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go index 506a82f80..d8921950d 100644 --- a/vendor/github.com/stretchr/testify/require/require.go +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -34,9 +34,9 @@ func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interfac // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Contains(t, "Hello World", "World") -// assert.Contains(t, ["Hello", "World"], "World") -// assert.Contains(t, {"Hello": "World"}, "Hello") +// require.Contains(t, "Hello World", "World") +// require.Contains(t, ["Hello", "World"], "World") +// require.Contains(t, {"Hello": "World"}, "Hello") func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -50,9 +50,9 @@ func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...int // Containsf asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") -// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") -// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +// require.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// require.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// require.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -91,7 +91,7 @@ func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { // listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, // the number of appearances of each of them in both lists should match. // -// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +// require.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -106,7 +106,7 @@ func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs // listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, // the number of appearances of each of them in both lists should match. // -// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +// require.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -120,7 +120,7 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Empty(t, obj) +// require.Empty(t, obj) func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -134,7 +134,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { // Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // -// assert.Emptyf(t, obj, "error message %s", "formatted") +// require.Emptyf(t, obj, "error message %s", "formatted") func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -147,7 +147,7 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { // Equal asserts that two objects are equal. // -// assert.Equal(t, 123, 123) +// require.Equal(t, 123, 123) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -166,7 +166,7 @@ func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...i // and that it is equal to the provided error. // // actualObj, err := SomeFunction() -// assert.EqualError(t, err, expectedErrorString) +// require.EqualError(t, err, expectedErrorString) func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -181,7 +181,7 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte // and that it is equal to the provided error. // // actualObj, err := SomeFunction() -// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +// require.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -200,8 +200,8 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args // Exported int // notExported int // } -// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true -// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false +// require.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true +// require.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -220,8 +220,8 @@ func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, m // Exported int // notExported int // } -// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true -// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +// require.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// require.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -232,10 +232,10 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, t.FailNow() } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // -// assert.EqualValues(t, uint32(123), int32(123)) +// require.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -246,10 +246,10 @@ func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArg t.FailNow() } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // -// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +// require.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -262,7 +262,7 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri // Equalf asserts that two objects are equal. // -// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// require.Equalf(t, 123, 123, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). Function equality @@ -280,8 +280,8 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar // Error asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() -// if assert.Error(t, err) { -// assert.Equal(t, expectedError, err) +// if require.Error(t, err) { +// require.Equal(t, expectedError, err) // } func Error(t TestingT, err error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -321,7 +321,7 @@ func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...int // and that the error contains the specified substring. // // actualObj, err := SomeFunction() -// assert.ErrorContains(t, err, expectedErrorSubString) +// require.ErrorContains(t, err, expectedErrorSubString) func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -336,7 +336,7 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in // and that the error contains the specified substring. // // actualObj, err := SomeFunction() -// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +// require.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -374,8 +374,8 @@ func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface // Errorf asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() -// if assert.Errorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedErrorf, err) +// if require.Errorf(t, err, "error message %s", "formatted") { +// require.Equal(t, expectedErrorf, err) // } func Errorf(t TestingT, err error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -390,7 +390,7 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) { // Eventually asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +// require.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -415,10 +415,10 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t // time.Sleep(8*time.Second) // externalValue = true // }() -// assert.EventuallyWithT(t, func(c *assert.CollectT) { +// require.EventuallyWithT(t, func(c *require.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick -// assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// require.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -443,10 +443,10 @@ func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitF // time.Sleep(8*time.Second) // externalValue = true // }() -// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { +// require.EventuallyWithTf(t, func(c *require.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick -// assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// require.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -460,7 +460,7 @@ func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), wait // Eventuallyf asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // -// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// require.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -473,7 +473,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick // Exactly asserts that two objects are equal in value and type. // -// assert.Exactly(t, int32(123), int64(123)) +// require.Exactly(t, int32(123), int64(123)) func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -486,7 +486,7 @@ func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs .. // Exactlyf asserts that two objects are equal in value and type. // -// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +// require.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -543,7 +543,7 @@ func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { // False asserts that the specified value is false. // -// assert.False(t, myBool) +// require.False(t, myBool) func False(t TestingT, value bool, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -556,7 +556,7 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) { // Falsef asserts that the specified value is false. // -// assert.Falsef(t, myBool, "error message %s", "formatted") +// require.Falsef(t, myBool, "error message %s", "formatted") func Falsef(t TestingT, value bool, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -593,9 +593,9 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { // Greater asserts that the first element is greater than the second // -// assert.Greater(t, 2, 1) -// assert.Greater(t, float64(2), float64(1)) -// assert.Greater(t, "b", "a") +// require.Greater(t, 2, 1) +// require.Greater(t, float64(2), float64(1)) +// require.Greater(t, "b", "a") func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -608,10 +608,10 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface // GreaterOrEqual asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqual(t, 2, 1) -// assert.GreaterOrEqual(t, 2, 2) -// assert.GreaterOrEqual(t, "b", "a") -// assert.GreaterOrEqual(t, "b", "b") +// require.GreaterOrEqual(t, 2, 1) +// require.GreaterOrEqual(t, 2, 2) +// require.GreaterOrEqual(t, "b", "a") +// require.GreaterOrEqual(t, "b", "b") func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -624,10 +624,10 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in // GreaterOrEqualf asserts that the first element is greater than or equal to the second // -// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") -// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +// require.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// require.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// require.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// require.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -640,9 +640,9 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg // Greaterf asserts that the first element is greater than the second // -// assert.Greaterf(t, 2, 1, "error message %s", "formatted") -// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") -// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +// require.Greaterf(t, 2, 1, "error message %s", "formatted") +// require.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// require.Greaterf(t, "b", "a", "error message %s", "formatted") func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -656,7 +656,7 @@ func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...in // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// require.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -672,7 +672,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url s // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // -// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// require.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -688,7 +688,7 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// require.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { @@ -704,7 +704,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, ur // HTTPBodyNotContainsf asserts that a specified handler returns a // body that does not contain a string. // -// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// require.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { @@ -719,7 +719,7 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u // HTTPError asserts that a specified handler returns an error status code. // -// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -734,7 +734,7 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, // HTTPErrorf asserts that a specified handler returns an error status code. // -// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -749,7 +749,7 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, // HTTPRedirect asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -764,7 +764,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url strin // HTTPRedirectf asserts that a specified handler returns a redirect status code. // -// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// require.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -779,7 +779,7 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri // HTTPStatusCode asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// require.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { @@ -794,7 +794,7 @@ func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url str // HTTPStatusCodef asserts that a specified handler returns a specified status code. // -// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// require.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { @@ -809,7 +809,7 @@ func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url st // HTTPSuccess asserts that a specified handler returns a success status code. // -// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// require.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { @@ -824,7 +824,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string // HTTPSuccessf asserts that a specified handler returns a success status code. // -// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// require.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") // // Returns whether the assertion was successful (true) or not (false). func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { @@ -839,7 +839,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin // Implements asserts that an object is implemented by the specified interface. // -// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +// require.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -852,7 +852,7 @@ func Implements(t TestingT, interfaceObject interface{}, object interface{}, msg // Implementsf asserts that an object is implemented by the specified interface. // -// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// require.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -865,7 +865,7 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms // InDelta asserts that the two numerals are within delta of each other. // -// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +// require.InDelta(t, math.Pi, 22/7.0, 0.01) func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -922,7 +922,7 @@ func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta f // InDeltaf asserts that the two numerals are within delta of each other. // -// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +// require.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -979,9 +979,9 @@ func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon fl // IsDecreasing asserts that the collection is decreasing // -// assert.IsDecreasing(t, []int{2, 1, 0}) -// assert.IsDecreasing(t, []float{2, 1}) -// assert.IsDecreasing(t, []string{"b", "a"}) +// require.IsDecreasing(t, []int{2, 1, 0}) +// require.IsDecreasing(t, []float{2, 1}) +// require.IsDecreasing(t, []string{"b", "a"}) func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -994,9 +994,9 @@ func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { // IsDecreasingf asserts that the collection is decreasing // -// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// require.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// require.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// require.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1009,9 +1009,9 @@ func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface // IsIncreasing asserts that the collection is increasing // -// assert.IsIncreasing(t, []int{1, 2, 3}) -// assert.IsIncreasing(t, []float{1, 2}) -// assert.IsIncreasing(t, []string{"a", "b"}) +// require.IsIncreasing(t, []int{1, 2, 3}) +// require.IsIncreasing(t, []float{1, 2}) +// require.IsIncreasing(t, []string{"a", "b"}) func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1024,9 +1024,9 @@ func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { // IsIncreasingf asserts that the collection is increasing // -// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// require.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// require.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// require.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1039,9 +1039,9 @@ func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface // IsNonDecreasing asserts that the collection is not decreasing // -// assert.IsNonDecreasing(t, []int{1, 1, 2}) -// assert.IsNonDecreasing(t, []float{1, 2}) -// assert.IsNonDecreasing(t, []string{"a", "b"}) +// require.IsNonDecreasing(t, []int{1, 1, 2}) +// require.IsNonDecreasing(t, []float{1, 2}) +// require.IsNonDecreasing(t, []string{"a", "b"}) func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1054,9 +1054,9 @@ func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // IsNonDecreasingf asserts that the collection is not decreasing // -// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") -// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1069,9 +1069,9 @@ func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interf // IsNonIncreasing asserts that the collection is not increasing // -// assert.IsNonIncreasing(t, []int{2, 1, 1}) -// assert.IsNonIncreasing(t, []float{2, 1}) -// assert.IsNonIncreasing(t, []string{"b", "a"}) +// require.IsNonIncreasing(t, []int{2, 1, 1}) +// require.IsNonIncreasing(t, []float{2, 1}) +// require.IsNonIncreasing(t, []string{"b", "a"}) func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1084,9 +1084,9 @@ func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // IsNonIncreasingf asserts that the collection is not increasing // -// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") -// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1121,7 +1121,7 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin // JSONEq asserts that two JSON strings are equivalent. // -// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// require.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1134,7 +1134,7 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{ // JSONEqf asserts that two JSON strings are equivalent. // -// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +// require.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1148,7 +1148,7 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// assert.Len(t, mySlice, 3) +// require.Len(t, mySlice, 3) func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1162,7 +1162,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // -// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +// require.Lenf(t, mySlice, 3, "error message %s", "formatted") func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1175,9 +1175,9 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf // Less asserts that the first element is less than the second // -// assert.Less(t, 1, 2) -// assert.Less(t, float64(1), float64(2)) -// assert.Less(t, "a", "b") +// require.Less(t, 1, 2) +// require.Less(t, float64(1), float64(2)) +// require.Less(t, "a", "b") func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1190,10 +1190,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) // LessOrEqual asserts that the first element is less than or equal to the second // -// assert.LessOrEqual(t, 1, 2) -// assert.LessOrEqual(t, 2, 2) -// assert.LessOrEqual(t, "a", "b") -// assert.LessOrEqual(t, "b", "b") +// require.LessOrEqual(t, 1, 2) +// require.LessOrEqual(t, 2, 2) +// require.LessOrEqual(t, "a", "b") +// require.LessOrEqual(t, "b", "b") func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1206,10 +1206,10 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter // LessOrEqualf asserts that the first element is less than or equal to the second // -// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") -// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") -// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +// require.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// require.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// require.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// require.LessOrEqualf(t, "b", "b", "error message %s", "formatted") func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1222,9 +1222,9 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args . // Lessf asserts that the first element is less than the second // -// assert.Lessf(t, 1, 2, "error message %s", "formatted") -// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") -// assert.Lessf(t, "a", "b", "error message %s", "formatted") +// require.Lessf(t, 1, 2, "error message %s", "formatted") +// require.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// require.Lessf(t, "a", "b", "error message %s", "formatted") func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1237,8 +1237,8 @@ func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...inter // Negative asserts that the specified element is negative // -// assert.Negative(t, -1) -// assert.Negative(t, -1.23) +// require.Negative(t, -1) +// require.Negative(t, -1.23) func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1251,8 +1251,8 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { // Negativef asserts that the specified element is negative // -// assert.Negativef(t, -1, "error message %s", "formatted") -// assert.Negativef(t, -1.23, "error message %s", "formatted") +// require.Negativef(t, -1, "error message %s", "formatted") +// require.Negativef(t, -1.23, "error message %s", "formatted") func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1266,7 +1266,7 @@ func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +// require.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1280,7 +1280,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D // Neverf asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // -// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +// require.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1293,7 +1293,7 @@ func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time. // Nil asserts that the specified object is nil. // -// assert.Nil(t, err) +// require.Nil(t, err) func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1306,7 +1306,7 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { // Nilf asserts that the specified object is nil. // -// assert.Nilf(t, err, "error message %s", "formatted") +// require.Nilf(t, err, "error message %s", "formatted") func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1344,8 +1344,8 @@ func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) { // NoError asserts that a function returned no error (i.e. `nil`). // // actualObj, err := SomeFunction() -// if assert.NoError(t, err) { -// assert.Equal(t, expectedObj, actualObj) +// if require.NoError(t, err) { +// require.Equal(t, expectedObj, actualObj) // } func NoError(t TestingT, err error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1360,8 +1360,8 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) { // NoErrorf asserts that a function returned no error (i.e. `nil`). // // actualObj, err := SomeFunction() -// if assert.NoErrorf(t, err, "error message %s", "formatted") { -// assert.Equal(t, expectedObj, actualObj) +// if require.NoErrorf(t, err, "error message %s", "formatted") { +// require.Equal(t, expectedObj, actualObj) // } func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1400,9 +1400,9 @@ func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) { // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContains(t, "Hello World", "Earth") -// assert.NotContains(t, ["Hello", "World"], "Earth") -// assert.NotContains(t, {"Hello": "World"}, "Earth") +// require.NotContains(t, "Hello World", "Earth") +// require.NotContains(t, ["Hello", "World"], "Earth") +// require.NotContains(t, {"Hello": "World"}, "Earth") func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1416,9 +1416,9 @@ func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ... // NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") -// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +// require.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// require.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// require.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1429,11 +1429,51 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a t.FailNow() } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// require.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// require.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true +// +// require.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true +func NotElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// require.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// require.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// require.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmpty(t, obj) { -// assert.Equal(t, "two", obj[1]) +// if require.NotEmpty(t, obj) { +// require.Equal(t, "two", obj[1]) // } func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1448,8 +1488,8 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { // NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // -// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { -// assert.Equal(t, "two", obj[1]) +// if require.NotEmptyf(t, obj, "error message %s", "formatted") { +// require.Equal(t, "two", obj[1]) // } func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1463,7 +1503,7 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) // NotEqual asserts that the specified values are NOT equal. // -// assert.NotEqual(t, obj1, obj2) +// require.NotEqual(t, obj1, obj2) // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1479,7 +1519,7 @@ func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs . // NotEqualValues asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValues(t, obj1, obj2) +// require.NotEqualValues(t, obj1, obj2) func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1492,7 +1532,7 @@ func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAnd // NotEqualValuesf asserts that two objects are not equal even when converted to the same type // -// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +// require.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1505,7 +1545,7 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s // NotEqualf asserts that the specified values are NOT equal. // -// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// require.NotEqualf(t, obj1, obj2, "error message %s", "formatted") // // Pointer variable equality is determined based on the equality of the // referenced values (as opposed to the memory addresses). @@ -1519,7 +1559,31 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, t.FailNow() } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1531,7 +1595,7 @@ func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) t.FailNow() } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { @@ -1545,7 +1609,7 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf // NotImplements asserts that an object does not implement the specified interface. // -// assert.NotImplements(t, (*MyInterface)(nil), new(MyObject)) +// require.NotImplements(t, (*MyInterface)(nil), new(MyObject)) func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1558,7 +1622,7 @@ func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, // NotImplementsf asserts that an object does not implement the specified interface. // -// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +// require.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1571,7 +1635,7 @@ func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, // NotNil asserts that the specified object is not nil. // -// assert.NotNil(t, err) +// require.NotNil(t, err) func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1584,7 +1648,7 @@ func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { // NotNilf asserts that the specified object is not nil. // -// assert.NotNilf(t, err, "error message %s", "formatted") +// require.NotNilf(t, err, "error message %s", "formatted") func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1597,7 +1661,7 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanics(t, func(){ RemainCalm() }) +// require.NotPanics(t, func(){ RemainCalm() }) func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1610,7 +1674,7 @@ func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { // NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +// require.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1623,8 +1687,8 @@ func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interfac // NotRegexp asserts that a specified regexp does not match a string. // -// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") +// require.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// require.NotRegexp(t, "^start", "it's not starting") func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1637,8 +1701,8 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf // NotRegexpf asserts that a specified regexp does not match a string. // -// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +// require.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// require.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1651,7 +1715,7 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args .. // NotSame asserts that two pointers do not reference the same object. // -// assert.NotSame(t, ptr1, ptr2) +// require.NotSame(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1667,7 +1731,7 @@ func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs .. // NotSamef asserts that two pointers do not reference the same object. // -// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// require.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1685,8 +1749,8 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, // contain all elements given in the specified subset list(array, slice...) or // map. // -// assert.NotSubset(t, [1, 3, 4], [1, 2]) -// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) +// require.NotSubset(t, [1, 3, 4], [1, 2]) +// require.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1701,8 +1765,8 @@ func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...i // contain all elements given in the specified subset list(array, slice...) or // map. // -// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") -// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +// require.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") +// require.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1737,7 +1801,7 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panics(t, func(){ GoCrazy() }) +// require.Panics(t, func(){ GoCrazy() }) func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1752,7 +1816,7 @@ func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +// require.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1767,7 +1831,7 @@ func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAn // panics, and that the recovered panic value is an error that satisfies the // EqualError comparison. // -// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// require.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1781,7 +1845,7 @@ func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg // PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +// require.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1795,7 +1859,7 @@ func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, m // PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that // the recovered panic value equals the expected panic value. // -// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +// require.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1808,7 +1872,7 @@ func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, // Panicsf asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +// require.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1821,8 +1885,8 @@ func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{} // Positive asserts that the specified element is positive // -// assert.Positive(t, 1) -// assert.Positive(t, 1.23) +// require.Positive(t, 1) +// require.Positive(t, 1.23) func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1835,8 +1899,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { // Positivef asserts that the specified element is positive // -// assert.Positivef(t, 1, "error message %s", "formatted") -// assert.Positivef(t, 1.23, "error message %s", "formatted") +// require.Positivef(t, 1, "error message %s", "formatted") +// require.Positivef(t, 1.23, "error message %s", "formatted") func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1849,8 +1913,8 @@ func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { // Regexp asserts that a specified regexp matches a string. // -// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") +// require.Regexp(t, regexp.MustCompile("start"), "it's starting") +// require.Regexp(t, "start...$", "it's not starting") func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1863,8 +1927,8 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // Regexpf asserts that a specified regexp matches a string. // -// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +// require.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// require.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1877,7 +1941,7 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in // Same asserts that two pointers reference the same object. // -// assert.Same(t, ptr1, ptr2) +// require.Same(t, ptr1, ptr2) // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1893,7 +1957,7 @@ func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...in // Samef asserts that two pointers reference the same object. // -// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// require.Samef(t, ptr1, ptr2, "error message %s", "formatted") // // Both arguments must be pointer variables. Pointer variable sameness is // determined based on the equality of both type and value. @@ -1910,8 +1974,8 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg // Subset asserts that the specified list(array, slice...) or map contains all // elements given in the specified subset list(array, slice...) or map. // -// assert.Subset(t, [1, 2, 3], [1, 2]) -// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1}) +// require.Subset(t, [1, 2, 3], [1, 2]) +// require.Subset(t, {"x": 1, "y": 2}, {"x": 1}) func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1925,8 +1989,8 @@ func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...inte // Subsetf asserts that the specified list(array, slice...) or map contains all // elements given in the specified subset list(array, slice...) or map. // -// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") -// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +// require.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") +// require.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1939,7 +2003,7 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args // True asserts that the specified value is true. // -// assert.True(t, myBool) +// require.True(t, myBool) func True(t TestingT, value bool, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1952,7 +2016,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) { // Truef asserts that the specified value is true. // -// assert.Truef(t, myBool, "error message %s", "formatted") +// require.Truef(t, myBool, "error message %s", "formatted") func Truef(t TestingT, value bool, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1965,7 +2029,7 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) { // WithinDuration asserts that the two times are within duration delta of each other. // -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +// require.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1978,7 +2042,7 @@ func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time // WithinDurationf asserts that the two times are within duration delta of each other. // -// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +// require.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1991,7 +2055,7 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim // WithinRange asserts that a time is within a time range (inclusive). // -// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +// require.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -2004,7 +2068,7 @@ func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, m // WithinRangef asserts that a time is within a time range (inclusive). // -// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +// require.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl index 55e42ddeb..8b3283685 100644 --- a/vendor/github.com/stretchr/testify/require/require.go.tmpl +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -1,4 +1,4 @@ -{{.Comment}} +{{ replace .Comment "assert." "require."}} func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { if h, ok := t.(tHelper); ok { h.Helper() } if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go index eee8310a5..1bd87304f 100644 --- a/vendor/github.com/stretchr/testify/require/require_forward.go +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -187,8 +187,8 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface EqualExportedValuesf(a.t, expected, actual, msg, args...) } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { @@ -198,8 +198,8 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn EqualValues(a.t, expected, actual, msgAndArgs...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { @@ -337,7 +337,7 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti // a.EventuallyWithT(func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -362,7 +362,7 @@ func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), w // a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1129,6 +1129,40 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin NotContainsf(a.t, s, contains, msg, args...) } +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 2, 3]) -> true +// +// a.NotElementsMatch([1, 2, 3], [1, 2, 4]) -> true +func (a *Assertions) NotElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// a.NotElementsMatchf([1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatchf(a.t, listA, listB, msg, args...) +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // @@ -1201,7 +1235,25 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str NotEqualf(a.t, expected, actual, msg, args...) } -// NotErrorIs asserts that at none of the errors in err's chain matches target. +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAs(a.t, err, target, msgAndArgs...) +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAsf(a.t, err, target, msg, args...) +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { @@ -1210,7 +1262,7 @@ func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface NotErrorIs(a.t, err, target, msgAndArgs...) } -// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// NotErrorIsf asserts that none of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go index 91772dfeb..6b7ce929e 100644 --- a/vendor/github.com/stretchr/testify/require/requirements.go +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -6,7 +6,7 @@ type TestingT interface { FailNow() } -type tHelper interface { +type tHelper = interface { Helper() } diff --git a/vendor/github.com/stretchr/testify/suite/doc.go b/vendor/github.com/stretchr/testify/suite/doc.go index 8d55a3aa8..05a562f72 100644 --- a/vendor/github.com/stretchr/testify/suite/doc.go +++ b/vendor/github.com/stretchr/testify/suite/doc.go @@ -5,6 +5,8 @@ // or individual tests (depending on which interface(s) you // implement). // +// The suite package does not support parallel tests. See [issue 934]. +// // A testing suite is usually built by first extending the built-in // suite functionality from suite.Suite in testify. Alternatively, // you could reproduce that logic on your own if you wanted (you @@ -63,4 +65,6 @@ // func TestExampleTestSuite(t *testing.T) { // suite.Run(t, new(ExampleTestSuite)) // } +// +// [issue 934]: https://github.com/stretchr/testify/issues/934 package suite diff --git a/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go b/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go index 46e6bdafd..3c9da22d3 100644 --- a/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go +++ b/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go @@ -2,7 +2,6 @@ package buffer import ( "io" - "io/ioutil" ) var nullBuffer = []byte{0} @@ -18,7 +17,7 @@ type Lexer struct { restore func() } -// NewLexer returns a new Lexer for a given io.Reader, and uses ioutil.ReadAll to read it into a byte slice. +// NewLexer returns a new Lexer for a given io.Reader, and uses io.ReadAll to read it into a byte slice. // If the io.Reader implements Bytes, that is used instead. // It will append a NULL at the end of the buffer. func NewLexer(r io.Reader) *Lexer { @@ -30,7 +29,7 @@ func NewLexer(r io.Reader) *Lexer { b = buffer.Bytes() } else { var err error - b, err = ioutil.ReadAll(r) + b, err = io.ReadAll(r) if err != nil { return &Lexer{ buf: nullBuffer, diff --git a/vendor/github.com/tdewolff/parse/v2/common.go b/vendor/github.com/tdewolff/parse/v2/common.go index e0795304c..1883d1bd4 100644 --- a/vendor/github.com/tdewolff/parse/v2/common.go +++ b/vendor/github.com/tdewolff/parse/v2/common.go @@ -317,9 +317,13 @@ func replaceEntities(b []byte, i int, entitiesMap map[string][]byte, revEntities } } else { for ; j < len(b) && j-i-1 <= MaxEntityLength && b[j] != ';'; j++ { + if !(b[j] >= '0' && b[j] <= '9' || b[j] >= 'a' && b[j] <= 'z' || b[j] >= 'A' && b[j] <= 'Z') { + // invalid character reference character + break + } } - if j <= i+1 || len(b) <= j { - return b, j - 1 + if len(b) <= j || j == i+1 || b[j] != ';' { + return b, i } var ok bool @@ -399,7 +403,7 @@ func ReplaceMultipleWhitespaceAndEntities(b []byte, entitiesMap map[string][]byt if j == 0 { return b } else if j == 1 { // only if starts with whitespace - b[k-1] = b[0] + b[k-1] = b[0] // move newline to end of whitespace return b[k-1:] } else if k < len(b) { j += copy(b[j:], b[k:]) diff --git a/vendor/github.com/tdewolff/parse/v2/html/lex.go b/vendor/github.com/tdewolff/parse/v2/html/lex.go index b24d4dcd2..8774ea264 100644 --- a/vendor/github.com/tdewolff/parse/v2/html/lex.go +++ b/vendor/github.com/tdewolff/parse/v2/html/lex.go @@ -362,7 +362,8 @@ func (l *Lexer) shiftBogusComment() []byte { func (l *Lexer) shiftStartTag() (TokenType, []byte) { for { - if c := l.r.Peek(0); (c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c) && (c < '0' || '9' < c) && c != '-' { + // spec says only a-zA-Z0-9, but we're lenient here + if c := l.r.Peek(0); c == ' ' || c == '>' || c == '/' && l.r.Peek(1) == '>' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == 0 && l.r.Err() != nil || 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) { break } l.r.Move(1) diff --git a/vendor/github.com/tdewolff/parse/v2/input.go b/vendor/github.com/tdewolff/parse/v2/input.go index 924f14f0c..586ad7306 100644 --- a/vendor/github.com/tdewolff/parse/v2/input.go +++ b/vendor/github.com/tdewolff/parse/v2/input.go @@ -2,7 +2,6 @@ package parse import ( "io" - "io/ioutil" ) var nullBuffer = []byte{0} @@ -18,7 +17,7 @@ type Input struct { restore func() } -// NewInput returns a new Input for a given io.Input and uses ioutil.ReadAll to read it into a byte slice. +// NewInput returns a new Input for a given io.Input and uses io.ReadAll to read it into a byte slice. // If the io.Input implements Bytes, that is used instead. It will append a NULL at the end of the buffer. func NewInput(r io.Reader) *Input { var b []byte @@ -29,7 +28,7 @@ func NewInput(r io.Reader) *Input { b = buffer.Bytes() } else { var err error - b, err = ioutil.ReadAll(r) + b, err = io.ReadAll(r) if err != nil { return &Input{ buf: nullBuffer, diff --git a/vendor/github.com/tetratelabs/wazero/README.md b/vendor/github.com/tetratelabs/wazero/README.md index f020be99a..e49fcb8a8 100644 --- a/vendor/github.com/tetratelabs/wazero/README.md +++ b/vendor/github.com/tetratelabs/wazero/README.md @@ -96,14 +96,21 @@ systems are ones we test, but that doesn't necessarily mean other operating system versions won't work. We currently test Linux (Ubuntu and scratch), MacOS and Windows as packaged by -[GitHub Actions][11], as well compilation of 32-bit Linux and 64-bit FreeBSD. +[GitHub Actions][11], as well as nested VMs running on Linux for FreeBSD, NetBSD, +OpenBSD, DragonFly BSD, illumos and Solaris. + +We also test cross compilation for many `GOOS` and `GOARCH` combinations. * Interpreter * Linux is tested on amd64 (native) as well arm64 and riscv64 via emulation. - * MacOS and Windows are only tested on amd64. + * Windows, FreeBSD, NetBSD, OpenBSD, DragonFly BSD, illumos and Solaris are + tested only on amd64. + * macOS is tested only on arm64. * Compiler * Linux is tested on amd64 (native) as well arm64 via emulation. - * MacOS and Windows are only tested on amd64. + * Windows, FreeBSD, NetBSD, DragonFly BSD, illumos and Solaris are + tested only on amd64. + * macOS is tested only on arm64. wazero has no dependencies and doesn't require CGO. This means it can also be embedded in an application that doesn't use an operating system. This is a main diff --git a/vendor/github.com/tetratelabs/wazero/config_supported.go b/vendor/github.com/tetratelabs/wazero/config_supported.go index eb31ab935..214c2bb8c 100644 --- a/vendor/github.com/tetratelabs/wazero/config_supported.go +++ b/vendor/github.com/tetratelabs/wazero/config_supported.go @@ -5,10 +5,15 @@ // // Meanwhile, users who know their runtime.GOOS can operate with the compiler // may choose to use NewRuntimeConfigCompiler explicitly. -//go:build (amd64 || arm64) && (darwin || linux || freebsd || windows) +//go:build (amd64 || arm64) && (linux || darwin || freebsd || netbsd || dragonfly || solaris || windows) package wazero +import "github.com/tetratelabs/wazero/internal/platform" + func newRuntimeConfig() RuntimeConfig { - return NewRuntimeConfigCompiler() + if platform.CompilerSupported() { + return NewRuntimeConfigCompiler() + } + return NewRuntimeConfigInterpreter() } diff --git a/vendor/github.com/tetratelabs/wazero/config_unsupported.go b/vendor/github.com/tetratelabs/wazero/config_unsupported.go index 3e5a53cda..be56a4bc2 100644 --- a/vendor/github.com/tetratelabs/wazero/config_unsupported.go +++ b/vendor/github.com/tetratelabs/wazero/config_unsupported.go @@ -1,5 +1,5 @@ // This is the opposite constraint of config_supported.go -//go:build !(amd64 || arm64) || !(darwin || linux || freebsd || windows) +//go:build !(amd64 || arm64) || !(linux || darwin || freebsd || netbsd || dragonfly || solaris || windows) package wazero diff --git a/vendor/github.com/tetratelabs/wazero/experimental/memory.go b/vendor/github.com/tetratelabs/wazero/experimental/memory.go index 26540648b..8bf3aa35f 100644 --- a/vendor/github.com/tetratelabs/wazero/experimental/memory.go +++ b/vendor/github.com/tetratelabs/wazero/experimental/memory.go @@ -43,7 +43,7 @@ type LinearMemory interface { } // WithMemoryAllocator registers the given MemoryAllocator into the given -// context.Context. +// context.Context. The context must be passed when initializing a module. func WithMemoryAllocator(ctx context.Context, allocator MemoryAllocator) context.Context { if allocator != nil { return context.WithValue(ctx, expctxkeys.MemoryAllocatorKey{}, allocator) diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go index aeeb6b645..7c27c92af 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go @@ -2196,7 +2196,7 @@ func (m *machine) Encode(ctx context.Context) (err error) { } // ResolveRelocations implements backend.Machine. -func (m *machine) ResolveRelocations(refToBinaryOffset []int, binary []byte, relocations []backend.RelocationInfo, _ []int) { +func (m *machine) ResolveRelocations(refToBinaryOffset []int, _ int, binary []byte, relocations []backend.RelocationInfo, _ []int) { for _, r := range relocations { offset := r.Offset calleeFnOffset := refToBinaryOffset[r.FuncRef] diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go index 83902d927..932fe842b 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go @@ -21,7 +21,7 @@ const ( // trampolineIslandInterval is the range of the trampoline island. // Half of the range is used for the trampoline island, and the other half is used for the function. - trampolineIslandInterval = maxUnconditionalBranchOffset / 2 + trampolineIslandInterval = (maxUnconditionalBranchOffset - 1) / 2 // maxNumFunctions explicitly specifies the maximum number of functions that can be allowed in a single executable. maxNumFunctions = trampolineIslandInterval >> 6 @@ -42,12 +42,13 @@ func (m *machine) CallTrampolineIslandInfo(numFunctions int) (interval, size int // ResolveRelocations implements backend.Machine ResolveRelocations. func (m *machine) ResolveRelocations( refToBinaryOffset []int, + importedFns int, executable []byte, relocations []backend.RelocationInfo, callTrampolineIslandOffsets []int, ) { for _, islandOffset := range callTrampolineIslandOffsets { - encodeCallTrampolineIsland(refToBinaryOffset, islandOffset, executable) + encodeCallTrampolineIsland(refToBinaryOffset, importedFns, islandOffset, executable) } for _, r := range relocations { @@ -71,11 +72,15 @@ func (m *machine) ResolveRelocations( // encodeCallTrampolineIsland encodes a trampoline island for the given functions. // Each island consists of a trampoline instruction sequence for each function. // Each trampoline instruction sequence consists of 4 instructions + 32-bit immediate. -func encodeCallTrampolineIsland(refToBinaryOffset []int, islandOffset int, executable []byte) { - for i := 0; i < len(refToBinaryOffset); i++ { +func encodeCallTrampolineIsland(refToBinaryOffset []int, importedFns int, islandOffset int, executable []byte) { + // We skip the imported functions: they don't need trampolines + // and are not accounted for. + binaryOffsets := refToBinaryOffset[importedFns:] + + for i := 0; i < len(binaryOffsets); i++ { trampolineOffset := islandOffset + trampolineCallSize*i - fnOffset := refToBinaryOffset[i] + fnOffset := binaryOffsets[i] diff := fnOffset - (trampolineOffset + 16) if diff > math.MaxInt32 || diff < math.MinInt32 { // This case even amd64 can't handle. 4GB is too big. diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go index 9044a9e4b..3a29e7cd6 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go @@ -77,11 +77,13 @@ type ( // ResolveRelocations resolves the relocations after emitting machine code. // * refToBinaryOffset: the map from the function reference (ssa.FuncRef) to the executable offset. + // * importedFns: the max index of the imported functions at the beginning of refToBinaryOffset // * executable: the binary to resolve the relocations. // * relocations: the relocations to resolve. // * callTrampolineIslandOffsets: the offsets of the trampoline islands in the executable. ResolveRelocations( refToBinaryOffset []int, + importedFns int, executable []byte, relocations []RelocationInfo, callTrampolineIslandOffsets []int, diff --git a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go index f02b905fc..a6df3e7e7 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go +++ b/vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go @@ -314,7 +314,7 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene // Resolve relocations for local function calls. if len(rels) > 0 { - machine.ResolveRelocations(refToBinaryOffset, executable, rels, callTrampolineIslandOffsets) + machine.ResolveRelocations(refToBinaryOffset, importedFns, executable, rels, callTrampolineIslandOffsets) } if runtime.GOARCH == "arm64" { diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go index 0dc6ec19c..0220d56fd 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go @@ -28,3 +28,8 @@ const ( CpuExtraFeatureAmd64ABM CpuFeature = 1 << 5 // Note: when adding new features, ensure that the feature is included in CpuFeatureFlags.Raw. ) + +const ( + // CpuFeatureArm64Atomic is the flag to query CpuFeatureFlags.Has for Large System Extensions capabilities on arm64 + CpuFeatureArm64Atomic CpuFeature = 1 << 21 +) diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go index fbdb53936..a0c7734a0 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go @@ -1,4 +1,4 @@ -//go:build amd64 && !tinygo +//go:build gc package platform @@ -12,7 +12,7 @@ type cpuFeatureFlags struct { } // cpuid exposes the CPUID instruction to the Go layer (https://www.amd.com/system/files/TechDocs/25481.pdf) -// implemented in impl_amd64.s +// implemented in cpuid_amd64.s func cpuid(arg1, arg2 uint32) (eax, ebx, ecx, edx uint32) // cpuidAsBitmap combines the result of invoking cpuid to uint64 bitmap. @@ -60,8 +60,9 @@ func (f *cpuFeatureFlags) HasExtra(cpuFeature CpuFeature) bool { // Raw implements the same method on the CpuFeatureFlags interface. func (f *cpuFeatureFlags) Raw() uint64 { - // Below, we only set the first 4 bits for the features we care about, - // instead of setting all the unnecessary bits obtained from the CPUID instruction. + // Below, we only set bits for the features we care about, + // instead of setting all the unnecessary bits obtained from the + // CPUID instruction. var ret uint64 if f.Has(CpuFeatureAmd64SSE3) { ret = 1 << 0 diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.s b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.s index 8d483f3a6..4950ee629 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.s +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.s @@ -1,6 +1,9 @@ +//go:build gc + #include "textflag.h" // lifted from github.com/intel-go/cpuid and src/internal/cpu/cpu_x86.s + // func cpuid(arg1, arg2 uint32) (eax, ebx, ecx, edx uint32) TEXT ·cpuid(SB), NOSPLIT, $0-24 MOVL arg1+0(FP), AX @@ -11,4 +14,3 @@ TEXT ·cpuid(SB), NOSPLIT, $0-24 MOVL CX, ecx+16(FP) MOVL DX, edx+20(FP) RET - diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.go new file mode 100644 index 000000000..5430353fd --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.go @@ -0,0 +1,71 @@ +//go:build gc + +package platform + +import "runtime" + +// CpuFeatures exposes the capabilities for this CPU, queried via the Has, HasExtra methods. +var CpuFeatures = loadCpuFeatureFlags() + +// cpuFeatureFlags implements CpuFeatureFlags interface. +type cpuFeatureFlags struct { + isar0 uint64 + isar1 uint64 +} + +// implemented in cpuid_arm64.s +func getisar0() uint64 + +// implemented in cpuid_arm64.s +func getisar1() uint64 + +func loadCpuFeatureFlags() CpuFeatureFlags { + switch runtime.GOOS { + case "darwin", "windows": + // These OSes do not allow userland to read the instruction set attribute registers, + // but basically require atomic instructions: + // - "darwin" is the desktop version (mobile version is "ios"), + // and the M1 is a ARMv8.4. + // - "windows" requires them from Windows 11, see page 12 + // https://download.microsoft.com/download/7/8/8/788bf5ab-0751-4928-a22c-dffdc23c27f2/Minimum%20Hardware%20Requirements%20for%20Windows%2011.pdf + return &cpuFeatureFlags{ + isar0: uint64(CpuFeatureArm64Atomic), + isar1: 0, + } + case "linux", "freebsd": + // These OSes allow userland to read the instruction set attribute registers, + // which is otherwise restricted to EL0: + // https://kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt + // See these for contents of the registers: + // https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/ID-AA64ISAR0-EL1--AArch64-Instruction-Set-Attribute-Register-0 + // https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/ID-AA64ISAR1-EL1--AArch64-Instruction-Set-Attribute-Register-1 + return &cpuFeatureFlags{ + isar0: getisar0(), + isar1: getisar1(), + } + default: + return &cpuFeatureFlags{} + } +} + +// Has implements the same method on the CpuFeatureFlags interface. +func (f *cpuFeatureFlags) Has(cpuFeature CpuFeature) bool { + return (f.isar0 & uint64(cpuFeature)) != 0 +} + +// HasExtra implements the same method on the CpuFeatureFlags interface. +func (f *cpuFeatureFlags) HasExtra(cpuFeature CpuFeature) bool { + return (f.isar1 & uint64(cpuFeature)) != 0 +} + +// Raw implements the same method on the CpuFeatureFlags interface. +func (f *cpuFeatureFlags) Raw() uint64 { + // Below, we only set bits for the features we care about, + // instead of setting all the unnecessary bits obtained from the + // instruction set attribute registers. + var ret uint64 + if f.Has(CpuFeatureArm64Atomic) { + ret = 1 << 0 + } + return ret +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.s b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.s new file mode 100644 index 000000000..98305ad47 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.s @@ -0,0 +1,21 @@ +//go:build gc + +#include "textflag.h" + +// lifted from github.com/golang/sys and cpu/cpu_arm64.s + +// func getisar0() uint64 +TEXT ·getisar0(SB), NOSPLIT, $0-8 + // get Instruction Set Attributes 0 into x0 + // mrs x0, ID_AA64ISAR0_EL1 = d5380600 + WORD $0xd5380600 + MOVD R0, ret+0(FP) + RET + +// func getisar1() uint64 +TEXT ·getisar1(SB), NOSPLIT, $0-8 + // get Instruction Set Attributes 1 into x0 + // mrs x0, ID_AA64ISAR1_EL1 = d5380620 + WORD $0xd5380620 + MOVD R0, ret+0(FP) + RET diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go index 291bcea65..50a178f52 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go @@ -1,4 +1,4 @@ -//go:build !amd64 || tinygo +//go:build !(amd64 || arm64) || !gc package platform diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go index ed5c40a4d..9f0610f27 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go @@ -1,5 +1,5 @@ // Separated from linux which has support for huge pages. -//go:build darwin || freebsd +//go:build darwin || freebsd || netbsd || dragonfly || solaris package platform diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go index b0519003b..8d0baa712 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go @@ -1,10 +1,9 @@ -//go:build (darwin || linux || freebsd) && !tinygo +//go:build (linux || darwin || freebsd || netbsd || dragonfly || solaris) && !tinygo package platform import ( "syscall" - "unsafe" ) const ( @@ -31,17 +30,3 @@ func mmapCodeSegmentARM64(size int) ([]byte, error) { // The region must be RW: RW for writing native codes. return mmapCodeSegment(size, mmapProtARM64) } - -// MprotectRX is like syscall.Mprotect with RX permission, defined locally so that freebsd compiles. -func MprotectRX(b []byte) (err error) { - var _p0 unsafe.Pointer - if len(b) > 0 { - _p0 = unsafe.Pointer(&b[0]) - } - const prot = syscall.PROT_READ | syscall.PROT_EXEC - _, _, e1 := syscall.Syscall(syscall.SYS_MPROTECT, uintptr(_p0), uintptr(len(b)), uintptr(prot)) - if e1 != 0 { - err = syscall.Errno(e1) - } - return -} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go index 079aa643f..f3fa0911a 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go @@ -1,4 +1,4 @@ -//go:build !(darwin || linux || freebsd || windows) || tinygo +//go:build !(linux || darwin || freebsd || netbsd || dragonfly || solaris || windows) || tinygo package platform diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_bsd.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_bsd.go new file mode 100644 index 000000000..f8f40cabe --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_bsd.go @@ -0,0 +1,22 @@ +//go:build (freebsd || netbsd || dragonfly) && !tinygo + +package platform + +import ( + "syscall" + "unsafe" +) + +// MprotectRX is like syscall.Mprotect with RX permission, defined locally so that BSD compiles. +func MprotectRX(b []byte) (err error) { + var _p0 unsafe.Pointer + if len(b) > 0 { + _p0 = unsafe.Pointer(&b[0]) + } + const prot = syscall.PROT_READ | syscall.PROT_EXEC + _, _, e1 := syscall.Syscall(syscall.SYS_MPROTECT, uintptr(_p0), uintptr(len(b)), uintptr(prot)) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_syscall.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_syscall.go new file mode 100644 index 000000000..6fe96d6f6 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_syscall.go @@ -0,0 +1,10 @@ +//go:build (linux || darwin) && !tinygo + +package platform + +import "syscall" + +// MprotectRX is like syscall.Mprotect with RX permission. +func MprotectRX(b []byte) (err error) { + return syscall.Mprotect(b, syscall.PROT_READ|syscall.PROT_EXEC) +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_unsupported.go b/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_unsupported.go new file mode 100644 index 000000000..84719ab08 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_unsupported.go @@ -0,0 +1,9 @@ +//go:build solaris && !tinygo + +package platform + +import "syscall" + +func MprotectRX(b []byte) error { + return syscall.ENOTSUP +} diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/platform.go b/vendor/github.com/tetratelabs/wazero/internal/platform/platform.go index a27556240..b9af094c1 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/platform.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/platform.go @@ -11,15 +11,16 @@ import ( // archRequirementsVerified is set by platform-specific init to true if the platform is supported var archRequirementsVerified bool -// CompilerSupported is exported for tests and includes constraints here and also the assembler. +// CompilerSupported includes constraints here and also the assembler. func CompilerSupported() bool { switch runtime.GOOS { - case "darwin", "windows", "linux", "freebsd": + case "linux", "darwin", "freebsd", "netbsd", "dragonfly", "windows": + return archRequirementsVerified + case "solaris", "illumos": + return runtime.GOARCH == "amd64" && archRequirementsVerified default: return false } - - return archRequirementsVerified } // MmapCodeSegment copies the code into the executable region and returns the byte slice of the region. diff --git a/vendor/github.com/tetratelabs/wazero/internal/platform/platform_arm64.go b/vendor/github.com/tetratelabs/wazero/internal/platform/platform_arm64.go index caac58a3d..a8df707c7 100644 --- a/vendor/github.com/tetratelabs/wazero/internal/platform/platform_arm64.go +++ b/vendor/github.com/tetratelabs/wazero/internal/platform/platform_arm64.go @@ -2,6 +2,6 @@ package platform // init verifies that the current CPU supports the required ARM64 features func init() { - // No further checks currently needed. - archRequirementsVerified = true + // Ensure atomic instructions are supported. + archRequirementsVerified = CpuFeatures.Has(CpuFeatureArm64Atomic) } diff --git a/vendor/github.com/uptrace/bun/CHANGELOG.md b/vendor/github.com/uptrace/bun/CHANGELOG.md index ded6f1f40..82ee95a83 100644 --- a/vendor/github.com/uptrace/bun/CHANGELOG.md +++ b/vendor/github.com/uptrace/bun/CHANGELOG.md @@ -1,3 +1,52 @@ +## [1.2.6](https://github.com/uptrace/bun/compare/v1.2.5...v1.2.6) (2024-11-20) + + +### Bug Fixes + +* append IDENTITY to ADD COLUMN statement if needed ([694f873](https://github.com/uptrace/bun/commit/694f873d61ed8d2f09032ae0c0dbec4b71c3719e)) +* **ci:** prune stale should be executed at 3 AM every day ([0cedcb0](https://github.com/uptrace/bun/commit/0cedcb068229b63041a4f48de12bb767c8454048)) +* cleanup after testUniqueRenamedTable ([b1ae32e](https://github.com/uptrace/bun/commit/b1ae32e9e9f45ff2a66e50bfd13bedcf6653d874)) +* fix go.mod of oracledialect ([89e21ea](https://github.com/uptrace/bun/commit/89e21eab362c60511cca00890ae29551a2ba7c46)) +* has many relationship with multiple columns ([1664b2c](https://github.com/uptrace/bun/commit/1664b2c07a5f6cfd3b6730e5005373686e9830a6)) +* ignore case for type equivalence ([c3253a5](https://github.com/uptrace/bun/commit/c3253a5c59b078607db9e216ddc11afdef546e05)) +* implement DefaultSchema for Oracle dialect ([d08fa40](https://github.com/uptrace/bun/commit/d08fa40cc87d67296a83a77448ea511531fc8cdd)) +* **oracledialect:** add go.mod file so the dialect is released properly ([#1043](https://github.com/uptrace/bun/issues/1043)) ([1bb5597](https://github.com/uptrace/bun/commit/1bb5597f1a32f5d693101ef4a62e25d99f5b9db5)) +* **oracledialect:** update go.mod by go mod tidy to fix tests ([7f90a15](https://github.com/uptrace/bun/commit/7f90a15c51a2482dda94226dd13b913d6b470a29)) +* **pgdialect:** array value quoting ([892c416](https://github.com/uptrace/bun/commit/892c416272a8428c592896d65d3ad51a6f2356d8)) +* remove schema name from t.Name during bun-schema inspection ([31ed582](https://github.com/uptrace/bun/commit/31ed58254ad08143d88684672acd33ce044ea5a9)) +* rename column only if the name does not exist in 'target' ([fed6012](https://github.com/uptrace/bun/commit/fed6012d177e55b8320b31ef37fc02a0cbf0b9f5)) +* support embed with tag Unique ([3acd6dd](https://github.com/uptrace/bun/commit/3acd6dd8546118d7b867ca796a5e56311edad070)) +* update oracledialect/version.go in release.sh ([bcd070f](https://github.com/uptrace/bun/commit/bcd070f48a75d0092a5620261658c9c5994f0bf6)) +* update schema.Field names ([9b810de](https://github.com/uptrace/bun/commit/9b810dee4b1a721efb82c913099f39f52c44eb57)) + + +### Features + +* add and drop columns ([3fdd5b8](https://github.com/uptrace/bun/commit/3fdd5b8f635f849a74e78c665274609f75245b19)) +* add and drop IDENTITY ([dd83779](https://github.com/uptrace/bun/commit/dd837795c31490fd8816eec0e9833e79fafdda32)) +* add support type for net/netip.addr and net/netip.prefix ([#1028](https://github.com/uptrace/bun/issues/1028)) ([95c4a8e](https://github.com/uptrace/bun/commit/95c4a8ebd634e1e99114727a7b157eeeb9297ee9)) +* **automigrate:** detect renamed tables ([c03938f](https://github.com/uptrace/bun/commit/c03938ff5e9fa2f653e4c60668b1368357d2de10)) +* change column type ([3cfd8c6](https://github.com/uptrace/bun/commit/3cfd8c62125786aaf6f493acc5b39f4d3db3d628)) +* **ci:** support release on osx ([435510b](https://github.com/uptrace/bun/commit/435510b0a73b0d9e6d06e3e3c3f0fa4379e9ed8c)) +* create sql migrations and apply them ([1bf7cfd](https://github.com/uptrace/bun/commit/1bf7cfd067e0e26ae212b0f7421e5abc6f67fb4f)) +* create transactional migration files ([c3320f6](https://github.com/uptrace/bun/commit/c3320f624830dc2fe99af2c7cbe492b2a83f9e4a)) +* detect Create/Drop table ([408859f](https://github.com/uptrace/bun/commit/408859f07be38236b39a00909cdce55d49f6f824)) +* detect modified relations ([a918dc4](https://github.com/uptrace/bun/commit/a918dc472a33dd24c5fffd4d048bcf49f2e07a42)) +* detect renamed columns ([886d0a5](https://github.com/uptrace/bun/commit/886d0a5b18aba272f1c86af2a2cf68ce4c8879f2)) +* detect renamed tables ([8857bab](https://github.com/uptrace/bun/commit/8857bab54b94170d218633f3b210d379e4e51a21)) +* enhance Apply method to accept multiple functions ([7823f2f](https://github.com/uptrace/bun/commit/7823f2f24c814e104dc59475156255c7b3b26144)) +* implement fmt.Stringer queries ([5060e47](https://github.com/uptrace/bun/commit/5060e47db13451a982e48d0f14055a58ba60b472)) +* improve FK handling ([a822fc5](https://github.com/uptrace/bun/commit/a822fc5f8ae547b7cd41e1ca35609d519d78598b)) +* include target schema name in migration name ([ac8d221](https://github.com/uptrace/bun/commit/ac8d221e6443b469e794314c5fc189250fa542d5)) +* **mariadb:** support RETURNING clause in DELETE statement ([b8dec9d](https://github.com/uptrace/bun/commit/b8dec9d9a06124696bd5ee2abbf33f19087174b6)) +* migrate FKs ([4c1dfdb](https://github.com/uptrace/bun/commit/4c1dfdbe99c73d0c0f2d7b1f8b11adf30c6a41f7)) +* **mysql:** support ORDER BY and LIMIT clauses in UPDATE and DELETE statements ([de71bed](https://github.com/uptrace/bun/commit/de71bed9252980648269af85b7a51cbc464ce710)) +* support modifying primary keys ([a734629](https://github.com/uptrace/bun/commit/a734629fa285406038cbe4a50798626b5ac08539)) +* support UNIQUE constraints ([3c4d5d2](https://github.com/uptrace/bun/commit/3c4d5d2c47be4652fb9b5cf1c6bd7b6c0a437287)) +* use *bun.DB in MigratorDialect ([a8788bf](https://github.com/uptrace/bun/commit/a8788bf62cbcc954a08532c299c774262de7a81d)) + + + ## [1.2.5](https://github.com/uptrace/bun/compare/v1.2.3...v1.2.5) (2024-10-26) diff --git a/vendor/github.com/uptrace/bun/Makefile b/vendor/github.com/uptrace/bun/Makefile index 50a1903e7..255d0f7ee 100644 --- a/vendor/github.com/uptrace/bun/Makefile +++ b/vendor/github.com/uptrace/bun/Makefile @@ -6,7 +6,7 @@ test: echo "go test in $${dir}"; \ (cd "$${dir}" && \ go test && \ - env GOOS=linux GOARCH=386 go test && \ + env GOOS=linux GOARCH=386 TZ= go test && \ go vet); \ done diff --git a/vendor/github.com/uptrace/bun/README.md b/vendor/github.com/uptrace/bun/README.md index dbe5bc0b4..eb2d98bc5 100644 --- a/vendor/github.com/uptrace/bun/README.md +++ b/vendor/github.com/uptrace/bun/README.md @@ -4,6 +4,7 @@ [![PkgGoDev](https://pkg.go.dev/badge/github.com/uptrace/bun)](https://pkg.go.dev/github.com/uptrace/bun) [![Documentation](https://img.shields.io/badge/bun-documentation-informational)](https://bun.uptrace.dev/) [![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj) +[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Bun%20Guru-006BFF)](https://gurubase.io/g/bun) > Bun is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace). Uptrace > is an open-source APM tool that supports distributed tracing, metrics, and logs. You can use it to diff --git a/vendor/github.com/uptrace/bun/db.go b/vendor/github.com/uptrace/bun/db.go index 106dfe905..2f52a2248 100644 --- a/vendor/github.com/uptrace/bun/db.go +++ b/vendor/github.com/uptrace/bun/db.go @@ -703,6 +703,5 @@ func (tx Tx) NewDropColumn() *DropColumnQuery { //------------------------------------------------------------------------------ func (db *DB) makeQueryBytes() []byte { - // TODO: make this configurable? - return make([]byte, 0, 4096) + return internal.MakeQueryBytes() } diff --git a/vendor/github.com/uptrace/bun/dialect/append.go b/vendor/github.com/uptrace/bun/dialect/append.go index 48f092284..8f5485fe3 100644 --- a/vendor/github.com/uptrace/bun/dialect/append.go +++ b/vendor/github.com/uptrace/bun/dialect/append.go @@ -25,24 +25,24 @@ func AppendBool(b []byte, v bool) []byte { return append(b, "FALSE"...) } -func AppendFloat32(b []byte, v float32) []byte { - return appendFloat(b, float64(v), 32) +func AppendFloat32(b []byte, num float32) []byte { + return appendFloat(b, float64(num), 32) } -func AppendFloat64(b []byte, v float64) []byte { - return appendFloat(b, v, 64) +func AppendFloat64(b []byte, num float64) []byte { + return appendFloat(b, num, 64) } -func appendFloat(b []byte, v float64, bitSize int) []byte { +func appendFloat(b []byte, num float64, bitSize int) []byte { switch { - case math.IsNaN(v): + case math.IsNaN(num): return append(b, "'NaN'"...) - case math.IsInf(v, 1): + case math.IsInf(num, 1): return append(b, "'Infinity'"...) - case math.IsInf(v, -1): + case math.IsInf(num, -1): return append(b, "'-Infinity'"...) default: - return strconv.AppendFloat(b, v, 'f', -1, bitSize) + return strconv.AppendFloat(b, num, 'f', -1, bitSize) } } diff --git a/vendor/github.com/uptrace/bun/dialect/feature/feature.go b/vendor/github.com/uptrace/bun/dialect/feature/feature.go index e311394d5..0707c6f88 100644 --- a/vendor/github.com/uptrace/bun/dialect/feature/feature.go +++ b/vendor/github.com/uptrace/bun/dialect/feature/feature.go @@ -31,5 +31,8 @@ const ( UpdateFromTable MSSavepoint GeneratedIdentity - CompositeIn // ... WHERE (A,B) IN ((N, NN), (N, NN)...) + CompositeIn // ... WHERE (A,B) IN ((N, NN), (N, NN)...) + UpdateOrderLimit // UPDATE ... ORDER BY ... LIMIT ... + DeleteOrderLimit // DELETE ... ORDER BY ... LIMIT ... + DeleteReturning ) diff --git a/vendor/github.com/uptrace/bun/dialect/pgdialect/alter_table.go b/vendor/github.com/uptrace/bun/dialect/pgdialect/alter_table.go new file mode 100644 index 000000000..d20f8c069 --- /dev/null +++ b/vendor/github.com/uptrace/bun/dialect/pgdialect/alter_table.go @@ -0,0 +1,245 @@ +package pgdialect + +import ( + "fmt" + "strings" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" + "github.com/uptrace/bun/migrate/sqlschema" + "github.com/uptrace/bun/schema" +) + +func (d *Dialect) NewMigrator(db *bun.DB, schemaName string) sqlschema.Migrator { + return &migrator{db: db, schemaName: schemaName, BaseMigrator: sqlschema.NewBaseMigrator(db)} +} + +type migrator struct { + *sqlschema.BaseMigrator + + db *bun.DB + schemaName string +} + +var _ sqlschema.Migrator = (*migrator)(nil) + +func (m *migrator) AppendSQL(b []byte, operation interface{}) (_ []byte, err error) { + fmter := m.db.Formatter() + + // Append ALTER TABLE statement to the enclosed query bytes []byte. + appendAlterTable := func(query []byte, tableName string) []byte { + query = append(query, "ALTER TABLE "...) + query = m.appendFQN(fmter, query, tableName) + return append(query, " "...) + } + + switch change := operation.(type) { + case *migrate.CreateTableOp: + return m.AppendCreateTable(b, change.Model) + case *migrate.DropTableOp: + return m.AppendDropTable(b, m.schemaName, change.TableName) + case *migrate.RenameTableOp: + b, err = m.renameTable(fmter, appendAlterTable(b, change.TableName), change) + case *migrate.RenameColumnOp: + b, err = m.renameColumn(fmter, appendAlterTable(b, change.TableName), change) + case *migrate.AddColumnOp: + b, err = m.addColumn(fmter, appendAlterTable(b, change.TableName), change) + case *migrate.DropColumnOp: + b, err = m.dropColumn(fmter, appendAlterTable(b, change.TableName), change) + case *migrate.AddPrimaryKeyOp: + b, err = m.addPrimaryKey(fmter, appendAlterTable(b, change.TableName), change.PrimaryKey) + case *migrate.ChangePrimaryKeyOp: + b, err = m.changePrimaryKey(fmter, appendAlterTable(b, change.TableName), change) + case *migrate.DropPrimaryKeyOp: + b, err = m.dropConstraint(fmter, appendAlterTable(b, change.TableName), change.PrimaryKey.Name) + case *migrate.AddUniqueConstraintOp: + b, err = m.addUnique(fmter, appendAlterTable(b, change.TableName), change) + case *migrate.DropUniqueConstraintOp: + b, err = m.dropConstraint(fmter, appendAlterTable(b, change.TableName), change.Unique.Name) + case *migrate.ChangeColumnTypeOp: + b, err = m.changeColumnType(fmter, appendAlterTable(b, change.TableName), change) + case *migrate.AddForeignKeyOp: + b, err = m.addForeignKey(fmter, appendAlterTable(b, change.TableName()), change) + case *migrate.DropForeignKeyOp: + b, err = m.dropConstraint(fmter, appendAlterTable(b, change.TableName()), change.ConstraintName) + default: + return nil, fmt.Errorf("append sql: unknown operation %T", change) + } + if err != nil { + return nil, fmt.Errorf("append sql: %w", err) + } + return b, nil +} + +func (m *migrator) appendFQN(fmter schema.Formatter, b []byte, tableName string) []byte { + return fmter.AppendQuery(b, "?.?", bun.Ident(m.schemaName), bun.Ident(tableName)) +} + +func (m *migrator) renameTable(fmter schema.Formatter, b []byte, rename *migrate.RenameTableOp) (_ []byte, err error) { + b = append(b, "RENAME TO "...) + b = fmter.AppendName(b, rename.NewName) + return b, nil +} + +func (m *migrator) renameColumn(fmter schema.Formatter, b []byte, rename *migrate.RenameColumnOp) (_ []byte, err error) { + b = append(b, "RENAME COLUMN "...) + b = fmter.AppendName(b, rename.OldName) + + b = append(b, " TO "...) + b = fmter.AppendName(b, rename.NewName) + + return b, nil +} + +func (m *migrator) addColumn(fmter schema.Formatter, b []byte, add *migrate.AddColumnOp) (_ []byte, err error) { + b = append(b, "ADD COLUMN "...) + b = fmter.AppendName(b, add.ColumnName) + b = append(b, " "...) + + b, err = add.Column.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + + if add.Column.GetDefaultValue() != "" { + b = append(b, " DEFAULT "...) + b = append(b, add.Column.GetDefaultValue()...) + b = append(b, " "...) + } + + if add.Column.GetIsIdentity() { + b = appendGeneratedAsIdentity(b) + } + + return b, nil +} + +func (m *migrator) dropColumn(fmter schema.Formatter, b []byte, drop *migrate.DropColumnOp) (_ []byte, err error) { + b = append(b, "DROP COLUMN "...) + b = fmter.AppendName(b, drop.ColumnName) + + return b, nil +} + +func (m *migrator) addPrimaryKey(fmter schema.Formatter, b []byte, pk sqlschema.PrimaryKey) (_ []byte, err error) { + b = append(b, "ADD PRIMARY KEY ("...) + b, _ = pk.Columns.AppendQuery(fmter, b) + b = append(b, ")"...) + + return b, nil +} + +func (m *migrator) changePrimaryKey(fmter schema.Formatter, b []byte, change *migrate.ChangePrimaryKeyOp) (_ []byte, err error) { + b, _ = m.dropConstraint(fmter, b, change.Old.Name) + b = append(b, ", "...) + b, _ = m.addPrimaryKey(fmter, b, change.New) + return b, nil +} + +func (m *migrator) addUnique(fmter schema.Formatter, b []byte, change *migrate.AddUniqueConstraintOp) (_ []byte, err error) { + b = append(b, "ADD CONSTRAINT "...) + if change.Unique.Name != "" { + b = fmter.AppendName(b, change.Unique.Name) + } else { + // Default naming scheme for unique constraints in Postgres is __key + b = fmter.AppendName(b, fmt.Sprintf("%s_%s_key", change.TableName, change.Unique.Columns)) + } + b = append(b, " UNIQUE ("...) + b, _ = change.Unique.Columns.AppendQuery(fmter, b) + b = append(b, ")"...) + + return b, nil +} + +func (m *migrator) dropConstraint(fmter schema.Formatter, b []byte, name string) (_ []byte, err error) { + b = append(b, "DROP CONSTRAINT "...) + b = fmter.AppendName(b, name) + + return b, nil +} + +func (m *migrator) addForeignKey(fmter schema.Formatter, b []byte, add *migrate.AddForeignKeyOp) (_ []byte, err error) { + b = append(b, "ADD CONSTRAINT "...) + + name := add.ConstraintName + if name == "" { + colRef := add.ForeignKey.From + columns := strings.Join(colRef.Column.Split(), "_") + name = fmt.Sprintf("%s_%s_fkey", colRef.TableName, columns) + } + b = fmter.AppendName(b, name) + + b = append(b, " FOREIGN KEY ("...) + if b, err = add.ForeignKey.From.Column.AppendQuery(fmter, b); err != nil { + return b, err + } + b = append(b, ")"...) + + b = append(b, " REFERENCES "...) + b = m.appendFQN(fmter, b, add.ForeignKey.To.TableName) + + b = append(b, " ("...) + if b, err = add.ForeignKey.To.Column.AppendQuery(fmter, b); err != nil { + return b, err + } + b = append(b, ")"...) + + return b, nil +} + +func (m *migrator) changeColumnType(fmter schema.Formatter, b []byte, colDef *migrate.ChangeColumnTypeOp) (_ []byte, err error) { + // alterColumn never re-assigns err, so there is no need to check for err != nil after calling it + var i int + appendAlterColumn := func() { + if i > 0 { + b = append(b, ", "...) + } + b = append(b, "ALTER COLUMN "...) + b = fmter.AppendName(b, colDef.Column) + i++ + } + + got, want := colDef.From, colDef.To + + inspector := m.db.Dialect().(sqlschema.InspectorDialect) + if !inspector.CompareType(want, got) { + appendAlterColumn() + b = append(b, " SET DATA TYPE "...) + if b, err = want.AppendQuery(fmter, b); err != nil { + return b, err + } + } + + // Column must be declared NOT NULL before identity can be added. + // Although PG can resolve the order of operations itself, we make this explicit in the query. + if want.GetIsNullable() != got.GetIsNullable() { + appendAlterColumn() + if !want.GetIsNullable() { + b = append(b, " SET NOT NULL"...) + } else { + b = append(b, " DROP NOT NULL"...) + } + } + + if want.GetIsIdentity() != got.GetIsIdentity() { + appendAlterColumn() + if !want.GetIsIdentity() { + b = append(b, " DROP IDENTITY"...) + } else { + b = append(b, " ADD"...) + b = appendGeneratedAsIdentity(b) + } + } + + if want.GetDefaultValue() != got.GetDefaultValue() { + appendAlterColumn() + if want.GetDefaultValue() == "" { + b = append(b, " DROP DEFAULT"...) + } else { + b = append(b, " SET DEFAULT "...) + b = append(b, want.GetDefaultValue()...) + } + } + + return b, nil +} diff --git a/vendor/github.com/uptrace/bun/dialect/pgdialect/array.go b/vendor/github.com/uptrace/bun/dialect/pgdialect/array.go index 46b55659b..b0d9a2331 100644 --- a/vendor/github.com/uptrace/bun/dialect/pgdialect/array.go +++ b/vendor/github.com/uptrace/bun/dialect/pgdialect/array.go @@ -5,6 +5,7 @@ import ( "database/sql/driver" "encoding/hex" "fmt" + "math" "reflect" "strconv" "time" @@ -159,7 +160,7 @@ func arrayAppend(fmter schema.Formatter, b []byte, v interface{}) []byte { case int64: return strconv.AppendInt(b, v, 10) case float64: - return dialect.AppendFloat64(b, v) + return arrayAppendFloat64(b, v) case bool: return dialect.AppendBool(b, v) case []byte: @@ -167,7 +168,10 @@ func arrayAppend(fmter schema.Formatter, b []byte, v interface{}) []byte { case string: return arrayAppendString(b, v) case time.Time: - return fmter.Dialect().AppendTime(b, v) + b = append(b, '"') + b = appendTime(b, v) + b = append(b, '"') + return b default: err := fmt.Errorf("pgdialect: can't append %T", v) return dialect.AppendError(b, err) @@ -288,7 +292,7 @@ func appendFloat64Slice(b []byte, floats []float64) []byte { b = append(b, '{') for _, n := range floats { - b = dialect.AppendFloat64(b, n) + b = arrayAppendFloat64(b, n) b = append(b, ',') } if len(floats) > 0 { @@ -302,6 +306,19 @@ func appendFloat64Slice(b []byte, floats []float64) []byte { return b } +func arrayAppendFloat64(b []byte, num float64) []byte { + switch { + case math.IsNaN(num): + return append(b, "NaN"...) + case math.IsInf(num, 1): + return append(b, "Infinity"...) + case math.IsInf(num, -1): + return append(b, "-Infinity"...) + default: + return strconv.AppendFloat(b, num, 'f', -1, 64) + } +} + func appendTimeSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte { ts := v.Convert(sliceTimeType).Interface().([]time.Time) return appendTimeSlice(fmter, b, ts) @@ -383,6 +400,10 @@ func arrayScanner(typ reflect.Type) schema.ScannerFunc { } } + if src == nil { + return nil + } + b, err := toBytes(src) if err != nil { return err @@ -553,7 +574,7 @@ func scanFloat64SliceValue(dest reflect.Value, src interface{}) error { } func scanFloat64Slice(src interface{}) ([]float64, error) { - if src == -1 { + if src == nil { return nil, nil } @@ -593,7 +614,7 @@ func toBytes(src interface{}) ([]byte, error) { case []byte: return src, nil default: - return nil, fmt.Errorf("bun: got %T, wanted []byte or string", src) + return nil, fmt.Errorf("pgdialect: got %T, wanted []byte or string", src) } } diff --git a/vendor/github.com/uptrace/bun/dialect/pgdialect/dialect.go b/vendor/github.com/uptrace/bun/dialect/pgdialect/dialect.go index 358971f61..040163f98 100644 --- a/vendor/github.com/uptrace/bun/dialect/pgdialect/dialect.go +++ b/vendor/github.com/uptrace/bun/dialect/pgdialect/dialect.go @@ -10,6 +10,7 @@ import ( "github.com/uptrace/bun/dialect" "github.com/uptrace/bun/dialect/feature" "github.com/uptrace/bun/dialect/sqltype" + "github.com/uptrace/bun/migrate/sqlschema" "github.com/uptrace/bun/schema" ) @@ -29,6 +30,10 @@ type Dialect struct { features feature.Feature } +var _ schema.Dialect = (*Dialect)(nil) +var _ sqlschema.InspectorDialect = (*Dialect)(nil) +var _ sqlschema.MigratorDialect = (*Dialect)(nil) + func New() *Dialect { d := new(Dialect) d.tables = schema.NewTables(d) @@ -48,7 +53,8 @@ func New() *Dialect { feature.InsertOnConflict | feature.SelectExists | feature.GeneratedIdentity | - feature.CompositeIn + feature.CompositeIn | + feature.DeleteReturning return d } @@ -118,5 +124,10 @@ func (d *Dialect) AppendUint64(b []byte, n uint64) []byte { } func (d *Dialect) AppendSequence(b []byte, _ *schema.Table, _ *schema.Field) []byte { + return appendGeneratedAsIdentity(b) +} + +// appendGeneratedAsIdentity appends GENERATED BY DEFAULT AS IDENTITY to the column definition. +func appendGeneratedAsIdentity(b []byte) []byte { return append(b, " GENERATED BY DEFAULT AS IDENTITY"...) } diff --git a/vendor/github.com/uptrace/bun/dialect/pgdialect/inspector.go b/vendor/github.com/uptrace/bun/dialect/pgdialect/inspector.go new file mode 100644 index 000000000..42bbbe84f --- /dev/null +++ b/vendor/github.com/uptrace/bun/dialect/pgdialect/inspector.go @@ -0,0 +1,297 @@ +package pgdialect + +import ( + "context" + "strings" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate/sqlschema" + orderedmap "github.com/wk8/go-ordered-map/v2" +) + +type ( + Schema = sqlschema.BaseDatabase + Table = sqlschema.BaseTable + Column = sqlschema.BaseColumn +) + +func (d *Dialect) NewInspector(db *bun.DB, options ...sqlschema.InspectorOption) sqlschema.Inspector { + return newInspector(db, options...) +} + +type Inspector struct { + sqlschema.InspectorConfig + db *bun.DB +} + +var _ sqlschema.Inspector = (*Inspector)(nil) + +func newInspector(db *bun.DB, options ...sqlschema.InspectorOption) *Inspector { + i := &Inspector{db: db} + sqlschema.ApplyInspectorOptions(&i.InspectorConfig, options...) + return i +} + +func (in *Inspector) Inspect(ctx context.Context) (sqlschema.Database, error) { + dbSchema := Schema{ + Tables: orderedmap.New[string, sqlschema.Table](), + ForeignKeys: make(map[sqlschema.ForeignKey]string), + } + + exclude := in.ExcludeTables + if len(exclude) == 0 { + // Avoid getting NOT IN (NULL) if bun.In() is called with an empty slice. + exclude = []string{""} + } + + var tables []*InformationSchemaTable + if err := in.db.NewRaw(sqlInspectTables, in.SchemaName, bun.In(exclude)).Scan(ctx, &tables); err != nil { + return dbSchema, err + } + + var fks []*ForeignKey + if err := in.db.NewRaw(sqlInspectForeignKeys, in.SchemaName, bun.In(exclude), bun.In(exclude)).Scan(ctx, &fks); err != nil { + return dbSchema, err + } + dbSchema.ForeignKeys = make(map[sqlschema.ForeignKey]string, len(fks)) + + for _, table := range tables { + var columns []*InformationSchemaColumn + if err := in.db.NewRaw(sqlInspectColumnsQuery, table.Schema, table.Name).Scan(ctx, &columns); err != nil { + return dbSchema, err + } + + colDefs := orderedmap.New[string, sqlschema.Column]() + uniqueGroups := make(map[string][]string) + + for _, c := range columns { + def := c.Default + if c.IsSerial || c.IsIdentity { + def = "" + } else if !c.IsDefaultLiteral { + def = strings.ToLower(def) + } + + colDefs.Set(c.Name, &Column{ + Name: c.Name, + SQLType: c.DataType, + VarcharLen: c.VarcharLen, + DefaultValue: def, + IsNullable: c.IsNullable, + IsAutoIncrement: c.IsSerial, + IsIdentity: c.IsIdentity, + }) + + for _, group := range c.UniqueGroups { + uniqueGroups[group] = append(uniqueGroups[group], c.Name) + } + } + + var unique []sqlschema.Unique + for name, columns := range uniqueGroups { + unique = append(unique, sqlschema.Unique{ + Name: name, + Columns: sqlschema.NewColumns(columns...), + }) + } + + var pk *sqlschema.PrimaryKey + if len(table.PrimaryKey.Columns) > 0 { + pk = &sqlschema.PrimaryKey{ + Name: table.PrimaryKey.ConstraintName, + Columns: sqlschema.NewColumns(table.PrimaryKey.Columns...), + } + } + + dbSchema.Tables.Set(table.Name, &Table{ + Schema: table.Schema, + Name: table.Name, + Columns: colDefs, + PrimaryKey: pk, + UniqueConstraints: unique, + }) + } + + for _, fk := range fks { + dbSchema.ForeignKeys[sqlschema.ForeignKey{ + From: sqlschema.NewColumnReference(fk.SourceTable, fk.SourceColumns...), + To: sqlschema.NewColumnReference(fk.TargetTable, fk.TargetColumns...), + }] = fk.ConstraintName + } + return dbSchema, nil +} + +type InformationSchemaTable struct { + Schema string `bun:"table_schema,pk"` + Name string `bun:"table_name,pk"` + PrimaryKey PrimaryKey `bun:"embed:primary_key_"` + + Columns []*InformationSchemaColumn `bun:"rel:has-many,join:table_schema=table_schema,join:table_name=table_name"` +} + +type InformationSchemaColumn struct { + Schema string `bun:"table_schema"` + Table string `bun:"table_name"` + Name string `bun:"column_name"` + DataType string `bun:"data_type"` + VarcharLen int `bun:"varchar_len"` + IsArray bool `bun:"is_array"` + ArrayDims int `bun:"array_dims"` + Default string `bun:"default"` + IsDefaultLiteral bool `bun:"default_is_literal_expr"` + IsIdentity bool `bun:"is_identity"` + IndentityType string `bun:"identity_type"` + IsSerial bool `bun:"is_serial"` + IsNullable bool `bun:"is_nullable"` + UniqueGroups []string `bun:"unique_groups,array"` +} + +type ForeignKey struct { + ConstraintName string `bun:"constraint_name"` + SourceSchema string `bun:"schema_name"` + SourceTable string `bun:"table_name"` + SourceColumns []string `bun:"columns,array"` + TargetSchema string `bun:"target_schema"` + TargetTable string `bun:"target_table"` + TargetColumns []string `bun:"target_columns,array"` +} + +type PrimaryKey struct { + ConstraintName string `bun:"name"` + Columns []string `bun:"columns,array"` +} + +const ( + // sqlInspectTables retrieves all user-defined tables in the selected schema. + // Pass bun.In([]string{...}) to exclude tables from this inspection or bun.In([]string{''}) to include all results. + sqlInspectTables = ` +SELECT + "t".table_schema, + "t".table_name, + pk.name AS primary_key_name, + pk.columns AS primary_key_columns +FROM information_schema.tables "t" + LEFT JOIN ( + SELECT i.indrelid, "idx".relname AS "name", ARRAY_AGG("a".attname) AS "columns" + FROM pg_index i + JOIN pg_attribute "a" + ON "a".attrelid = i.indrelid + AND "a".attnum = ANY("i".indkey) + AND i.indisprimary + JOIN pg_class "idx" ON i.indexrelid = "idx".oid + GROUP BY 1, 2 + ) pk + ON ("t".table_schema || '.' || "t".table_name)::regclass = pk.indrelid +WHERE table_type = 'BASE TABLE' + AND "t".table_schema = ? + AND "t".table_schema NOT LIKE 'pg_%' + AND "table_name" NOT IN (?) +ORDER BY "t".table_schema, "t".table_name +` + + // sqlInspectColumnsQuery retrieves column definitions for the specified table. + // Unlike sqlInspectTables and sqlInspectSchema, it should be passed to bun.NewRaw + // with additional args for table_schema and table_name. + sqlInspectColumnsQuery = ` +SELECT + "c".table_schema, + "c".table_name, + "c".column_name, + "c".data_type, + "c".character_maximum_length::integer AS varchar_len, + "c".data_type = 'ARRAY' AS is_array, + COALESCE("c".array_dims, 0) AS array_dims, + CASE + WHEN "c".column_default ~ '^''.*''::.*$' THEN substring("c".column_default FROM '^''(.*)''::.*$') + ELSE "c".column_default + END AS "default", + "c".column_default ~ '^''.*''::.*$' OR "c".column_default ~ '^[0-9\.]+$' AS default_is_literal_expr, + "c".is_identity = 'YES' AS is_identity, + "c".column_default = format('nextval(''%s_%s_seq''::regclass)', "c".table_name, "c".column_name) AS is_serial, + COALESCE("c".identity_type, '') AS identity_type, + "c".is_nullable = 'YES' AS is_nullable, + "c"."unique_groups" AS unique_groups +FROM ( + SELECT + "table_schema", + "table_name", + "column_name", + "c".data_type, + "c".character_maximum_length, + "c".column_default, + "c".is_identity, + "c".is_nullable, + att.array_dims, + att.identity_type, + att."unique_groups", + att."constraint_type" + FROM information_schema.columns "c" + LEFT JOIN ( + SELECT + s.nspname AS "table_schema", + "t".relname AS "table_name", + "c".attname AS "column_name", + "c".attndims AS array_dims, + "c".attidentity AS identity_type, + ARRAY_AGG(con.conname) FILTER (WHERE con.contype = 'u') AS "unique_groups", + ARRAY_AGG(con.contype) AS "constraint_type" + FROM ( + SELECT + conname, + contype, + connamespace, + conrelid, + conrelid AS attrelid, + UNNEST(conkey) AS attnum + FROM pg_constraint + ) con + LEFT JOIN pg_attribute "c" USING (attrelid, attnum) + LEFT JOIN pg_namespace s ON s.oid = con.connamespace + LEFT JOIN pg_class "t" ON "t".oid = con.conrelid + GROUP BY 1, 2, 3, 4, 5 + ) att USING ("table_schema", "table_name", "column_name") + ) "c" +WHERE "table_schema" = ? AND "table_name" = ? +ORDER BY "table_schema", "table_name", "column_name" +` + + // sqlInspectForeignKeys get FK definitions for user-defined tables. + // Pass bun.In([]string{...}) to exclude tables from this inspection or bun.In([]string{''}) to include all results. + sqlInspectForeignKeys = ` +WITH + "schemas" AS ( + SELECT oid, nspname + FROM pg_namespace + ), + "tables" AS ( + SELECT oid, relnamespace, relname, relkind + FROM pg_class + ), + "columns" AS ( + SELECT attrelid, attname, attnum + FROM pg_attribute + WHERE attisdropped = false + ) +SELECT DISTINCT + co.conname AS "constraint_name", + ss.nspname AS schema_name, + s.relname AS "table_name", + ARRAY_AGG(sc.attname) AS "columns", + ts.nspname AS target_schema, + "t".relname AS target_table, + ARRAY_AGG(tc.attname) AS target_columns +FROM pg_constraint co + LEFT JOIN "tables" s ON s.oid = co.conrelid + LEFT JOIN "schemas" ss ON ss.oid = s.relnamespace + LEFT JOIN "columns" sc ON sc.attrelid = s.oid AND sc.attnum = ANY(co.conkey) + LEFT JOIN "tables" t ON t.oid = co.confrelid + LEFT JOIN "schemas" ts ON ts.oid = "t".relnamespace + LEFT JOIN "columns" tc ON tc.attrelid = "t".oid AND tc.attnum = ANY(co.confkey) +WHERE co.contype = 'f' + AND co.conrelid IN (SELECT oid FROM pg_class WHERE relkind = 'r') + AND ARRAY_POSITION(co.conkey, sc.attnum) = ARRAY_POSITION(co.confkey, tc.attnum) + AND ss.nspname = ? + AND s.relname NOT IN (?) AND "t".relname NOT IN (?) +GROUP BY "constraint_name", "schema_name", "table_name", target_schema, target_table +` +) diff --git a/vendor/github.com/uptrace/bun/dialect/pgdialect/sqltype.go b/vendor/github.com/uptrace/bun/dialect/pgdialect/sqltype.go index fad84209d..bacc00e86 100644 --- a/vendor/github.com/uptrace/bun/dialect/pgdialect/sqltype.go +++ b/vendor/github.com/uptrace/bun/dialect/pgdialect/sqltype.go @@ -5,18 +5,22 @@ import ( "encoding/json" "net" "reflect" + "strings" "github.com/uptrace/bun/dialect/sqltype" + "github.com/uptrace/bun/migrate/sqlschema" "github.com/uptrace/bun/schema" ) const ( // Date / Time - pgTypeTimestampTz = "TIMESTAMPTZ" // Timestamp with a time zone - pgTypeDate = "DATE" // Date - pgTypeTime = "TIME" // Time without a time zone - pgTypeTimeTz = "TIME WITH TIME ZONE" // Time with a time zone - pgTypeInterval = "INTERVAL" // Time Interval + pgTypeTimestamp = "TIMESTAMP" // Timestamp + pgTypeTimestampWithTz = "TIMESTAMP WITH TIME ZONE" // Timestamp with a time zone + pgTypeTimestampTz = "TIMESTAMPTZ" // Timestamp with a time zone (alias) + pgTypeDate = "DATE" // Date + pgTypeTime = "TIME" // Time without a time zone + pgTypeTimeTz = "TIME WITH TIME ZONE" // Time with a time zone + pgTypeInterval = "INTERVAL" // Time interval // Network Addresses pgTypeInet = "INET" // IPv4 or IPv6 hosts and networks @@ -28,6 +32,13 @@ const ( pgTypeSerial = "SERIAL" // 4 byte autoincrementing integer pgTypeBigSerial = "BIGSERIAL" // 8 byte autoincrementing integer + // Character Types + pgTypeChar = "CHAR" // fixed length string (blank padded) + pgTypeCharacter = "CHARACTER" // alias for CHAR + pgTypeText = "TEXT" // variable length string without limit + pgTypeVarchar = "VARCHAR" // variable length string with optional limit + pgTypeCharacterVarying = "CHARACTER VARYING" // alias for VARCHAR + // Binary Data Types pgTypeBytea = "BYTEA" // binary string ) @@ -43,6 +54,10 @@ func (d *Dialect) DefaultVarcharLen() int { return 0 } +func (d *Dialect) DefaultSchema() string { + return "public" +} + func fieldSQLType(field *schema.Field) string { if field.UserSQLType != "" { return field.UserSQLType @@ -103,3 +118,62 @@ func sqlType(typ reflect.Type) string { return sqlType } + +var ( + char = newAliases(pgTypeChar, pgTypeCharacter) + varchar = newAliases(pgTypeVarchar, pgTypeCharacterVarying) + timestampTz = newAliases(sqltype.Timestamp, pgTypeTimestampTz, pgTypeTimestampWithTz) +) + +func (d *Dialect) CompareType(col1, col2 sqlschema.Column) bool { + typ1, typ2 := strings.ToUpper(col1.GetSQLType()), strings.ToUpper(col2.GetSQLType()) + + if typ1 == typ2 { + return checkVarcharLen(col1, col2, d.DefaultVarcharLen()) + } + + switch { + case char.IsAlias(typ1) && char.IsAlias(typ2): + return checkVarcharLen(col1, col2, d.DefaultVarcharLen()) + case varchar.IsAlias(typ1) && varchar.IsAlias(typ2): + return checkVarcharLen(col1, col2, d.DefaultVarcharLen()) + case timestampTz.IsAlias(typ1) && timestampTz.IsAlias(typ2): + return true + } + return false +} + +// checkVarcharLen returns true if columns have the same VarcharLen, or, +// if one specifies no VarcharLen and the other one has the default lenght for pgdialect. +// We assume that the types are otherwise equivalent and that any non-character column +// would have VarcharLen == 0; +func checkVarcharLen(col1, col2 sqlschema.Column, defaultLen int) bool { + vl1, vl2 := col1.GetVarcharLen(), col2.GetVarcharLen() + + if vl1 == vl2 { + return true + } + + if (vl1 == 0 && vl2 == defaultLen) || (vl1 == defaultLen && vl2 == 0) { + return true + } + return false +} + +// typeAlias defines aliases for common data types. It is a lightweight string set implementation. +type typeAlias map[string]struct{} + +// IsAlias checks if typ1 and typ2 are aliases of the same data type. +func (t typeAlias) IsAlias(typ string) bool { + _, ok := t[typ] + return ok +} + +// newAliases creates a set of aliases. +func newAliases(aliases ...string) typeAlias { + types := make(typeAlias) + for _, a := range aliases { + types[a] = struct{}{} + } + return types +} diff --git a/vendor/github.com/uptrace/bun/dialect/pgdialect/version.go b/vendor/github.com/uptrace/bun/dialect/pgdialect/version.go index c06043647..a4a6a760a 100644 --- a/vendor/github.com/uptrace/bun/dialect/pgdialect/version.go +++ b/vendor/github.com/uptrace/bun/dialect/pgdialect/version.go @@ -2,5 +2,5 @@ package pgdialect // Version is the current release version. func Version() string { - return "1.2.5" + return "1.2.6" } diff --git a/vendor/github.com/uptrace/bun/dialect/sqlitedialect/dialect.go b/vendor/github.com/uptrace/bun/dialect/sqlitedialect/dialect.go index 3bfe500ff..92959482e 100644 --- a/vendor/github.com/uptrace/bun/dialect/sqlitedialect/dialect.go +++ b/vendor/github.com/uptrace/bun/dialect/sqlitedialect/dialect.go @@ -40,7 +40,8 @@ func New() *Dialect { feature.TableNotExists | feature.SelectExists | feature.AutoIncrement | - feature.CompositeIn + feature.CompositeIn | + feature.DeleteReturning return d } @@ -96,9 +97,13 @@ func (d *Dialect) DefaultVarcharLen() int { // AUTOINCREMENT is only valid for INTEGER PRIMARY KEY, and this method will be a noop for other columns. // // Because this is a valid construct: +// // CREATE TABLE ("id" INTEGER PRIMARY KEY AUTOINCREMENT); +// // and this is not: +// // CREATE TABLE ("id" INTEGER AUTOINCREMENT, PRIMARY KEY ("id")); +// // AppendSequence adds a primary key constraint as a *side-effect*. Callers should expect it to avoid building invalid SQL. // SQLite also [does not support] AUTOINCREMENT column in composite primary keys. // @@ -111,6 +116,13 @@ func (d *Dialect) AppendSequence(b []byte, table *schema.Table, field *schema.Fi return b } +// DefaultSchemaName is the "schema-name" of the main database. +// The details might differ from other dialects, but for all means and purposes +// "main" is the default schema in an SQLite database. +func (d *Dialect) DefaultSchema() string { + return "main" +} + func fieldSQLType(field *schema.Field) string { switch field.DiscoveredSQLType { case sqltype.SmallInt, sqltype.BigInt: diff --git a/vendor/github.com/uptrace/bun/dialect/sqlitedialect/version.go b/vendor/github.com/uptrace/bun/dialect/sqlitedialect/version.go index e3cceaa77..18c6f7857 100644 --- a/vendor/github.com/uptrace/bun/dialect/sqlitedialect/version.go +++ b/vendor/github.com/uptrace/bun/dialect/sqlitedialect/version.go @@ -2,5 +2,5 @@ package sqlitedialect // Version is the current release version. func Version() string { - return "1.2.5" + return "1.2.6" } diff --git a/vendor/github.com/uptrace/bun/internal/util.go b/vendor/github.com/uptrace/bun/internal/util.go index 66b92b3c5..ba1341e61 100644 --- a/vendor/github.com/uptrace/bun/internal/util.go +++ b/vendor/github.com/uptrace/bun/internal/util.go @@ -79,3 +79,9 @@ func indirectNil(v reflect.Value) reflect.Value { } return v } + +// MakeQueryBytes returns zero-length byte slice with capacity of 4096. +func MakeQueryBytes() []byte { + // TODO: make this configurable? + return make([]byte, 0, 4096) +} diff --git a/vendor/github.com/uptrace/bun/migrate/auto.go b/vendor/github.com/uptrace/bun/migrate/auto.go new file mode 100644 index 000000000..e56fa23a0 --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/auto.go @@ -0,0 +1,429 @@ +package migrate + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/internal" + "github.com/uptrace/bun/migrate/sqlschema" + "github.com/uptrace/bun/schema" +) + +type AutoMigratorOption func(m *AutoMigrator) + +// WithModel adds a bun.Model to the scope of migrations. +func WithModel(models ...interface{}) AutoMigratorOption { + return func(m *AutoMigrator) { + m.includeModels = append(m.includeModels, models...) + } +} + +// WithExcludeTable tells the AutoMigrator to ignore a table in the database. +// This prevents AutoMigrator from dropping tables which may exist in the schema +// but which are not used by the application. +// +// Do not exclude tables included via WithModel, as BunModelInspector ignores this setting. +func WithExcludeTable(tables ...string) AutoMigratorOption { + return func(m *AutoMigrator) { + m.excludeTables = append(m.excludeTables, tables...) + } +} + +// WithSchemaName changes the default database schema to migrate objects in. +func WithSchemaName(schemaName string) AutoMigratorOption { + return func(m *AutoMigrator) { + m.schemaName = schemaName + } +} + +// WithTableNameAuto overrides default migrations table name. +func WithTableNameAuto(table string) AutoMigratorOption { + return func(m *AutoMigrator) { + m.table = table + m.migratorOpts = append(m.migratorOpts, WithTableName(table)) + } +} + +// WithLocksTableNameAuto overrides default migration locks table name. +func WithLocksTableNameAuto(table string) AutoMigratorOption { + return func(m *AutoMigrator) { + m.locksTable = table + m.migratorOpts = append(m.migratorOpts, WithLocksTableName(table)) + } +} + +// WithMarkAppliedOnSuccessAuto sets the migrator to only mark migrations as applied/unapplied +// when their up/down is successful. +func WithMarkAppliedOnSuccessAuto(enabled bool) AutoMigratorOption { + return func(m *AutoMigrator) { + m.migratorOpts = append(m.migratorOpts, WithMarkAppliedOnSuccess(enabled)) + } +} + +// WithMigrationsDirectoryAuto overrides the default directory for migration files. +func WithMigrationsDirectoryAuto(directory string) AutoMigratorOption { + return func(m *AutoMigrator) { + m.migrationsOpts = append(m.migrationsOpts, WithMigrationsDirectory(directory)) + } +} + +// AutoMigrator performs automated schema migrations. +// +// It is designed to be a drop-in replacement for some Migrator functionality and supports all existing +// configuration options. +// Similarly to Migrator, it has methods to create SQL migrations, write them to a file, and apply them. +// Unlike Migrator, it detects the differences between the state defined by bun models and the current +// database schema automatically. +// +// Usage: +// 1. Generate migrations and apply them au once with AutoMigrator.Migrate(). +// 2. Create up- and down-SQL migration files and apply migrations using Migrator.Migrate(). +// +// While both methods produce complete, reversible migrations (with entries in the database +// and SQL migration files), prefer creating migrations and applying them separately for +// any non-trivial cases to ensure AutoMigrator detects expected changes correctly. +// +// Limitations: +// - AutoMigrator only supports a subset of the possible ALTER TABLE modifications. +// - Some changes are not automatically reversible. For example, you would need to manually +// add a CREATE TABLE query to the .down migration file to revert a DROP TABLE migration. +// - Does not validate most dialect-specific constraints. For example, when changing column +// data type, make sure the data con be auto-casted to the new type. +// - Due to how the schema-state diff is calculated, it is not possible to rename a table and +// modify any of its columns' _data type_ in a single run. This will cause the AutoMigrator +// to drop and re-create the table under a different name; it is better to apply this change in 2 steps. +// Renaming a table and renaming its columns at the same time is possible. +// - Renaming table/column to an existing name, i.e. like this [A->B] [B->C], is not possible due to how +// AutoMigrator distinguishes "rename" and "unchanged" columns. +// +// Dialect must implement both sqlschema.Inspector and sqlschema.Migrator to be used with AutoMigrator. +type AutoMigrator struct { + db *bun.DB + + // dbInspector creates the current state for the target database. + dbInspector sqlschema.Inspector + + // modelInspector creates the desired state based on the model definitions. + modelInspector sqlschema.Inspector + + // dbMigrator executes ALTER TABLE queries. + dbMigrator sqlschema.Migrator + + table string // Migrations table (excluded from database inspection) + locksTable string // Migration locks table (excluded from database inspection) + + // schemaName is the database schema considered for migration. + schemaName string + + // includeModels define the migration scope. + includeModels []interface{} + + // excludeTables are excluded from database inspection. + excludeTables []string + + // diffOpts are passed to detector constructor. + diffOpts []diffOption + + // migratorOpts are passed to Migrator constructor. + migratorOpts []MigratorOption + + // migrationsOpts are passed to Migrations constructor. + migrationsOpts []MigrationsOption +} + +func NewAutoMigrator(db *bun.DB, opts ...AutoMigratorOption) (*AutoMigrator, error) { + am := &AutoMigrator{ + db: db, + table: defaultTable, + locksTable: defaultLocksTable, + schemaName: db.Dialect().DefaultSchema(), + } + + for _, opt := range opts { + opt(am) + } + am.excludeTables = append(am.excludeTables, am.table, am.locksTable) + + dbInspector, err := sqlschema.NewInspector(db, sqlschema.WithSchemaName(am.schemaName), sqlschema.WithExcludeTables(am.excludeTables...)) + if err != nil { + return nil, err + } + am.dbInspector = dbInspector + am.diffOpts = append(am.diffOpts, withCompareTypeFunc(db.Dialect().(sqlschema.InspectorDialect).CompareType)) + + dbMigrator, err := sqlschema.NewMigrator(db, am.schemaName) + if err != nil { + return nil, err + } + am.dbMigrator = dbMigrator + + tables := schema.NewTables(db.Dialect()) + tables.Register(am.includeModels...) + am.modelInspector = sqlschema.NewBunModelInspector(tables, sqlschema.WithSchemaName(am.schemaName)) + + return am, nil +} + +func (am *AutoMigrator) plan(ctx context.Context) (*changeset, error) { + var err error + + got, err := am.dbInspector.Inspect(ctx) + if err != nil { + return nil, err + } + + want, err := am.modelInspector.Inspect(ctx) + if err != nil { + return nil, err + } + + changes := diff(got, want, am.diffOpts...) + if err := changes.ResolveDependencies(); err != nil { + return nil, fmt.Errorf("plan migrations: %w", err) + } + return changes, nil +} + +// Migrate writes required changes to a new migration file and runs the migration. +// This will create and entry in the migrations table, making it possible to revert +// the changes with Migrator.Rollback(). MigrationOptions are passed on to Migrator.Migrate(). +func (am *AutoMigrator) Migrate(ctx context.Context, opts ...MigrationOption) (*MigrationGroup, error) { + migrations, _, err := am.createSQLMigrations(ctx, false) + if err != nil { + return nil, fmt.Errorf("auto migrate: %w", err) + } + + migrator := NewMigrator(am.db, migrations, am.migratorOpts...) + if err := migrator.Init(ctx); err != nil { + return nil, fmt.Errorf("auto migrate: %w", err) + } + + group, err := migrator.Migrate(ctx, opts...) + if err != nil { + return nil, fmt.Errorf("auto migrate: %w", err) + } + return group, nil +} + +// CreateSQLMigration writes required changes to a new migration file. +// Use migrate.Migrator to apply the generated migrations. +func (am *AutoMigrator) CreateSQLMigrations(ctx context.Context) ([]*MigrationFile, error) { + _, files, err := am.createSQLMigrations(ctx, true) + return files, err +} + +// CreateTxSQLMigration writes required changes to a new migration file making sure they will be executed +// in a transaction when applied. Use migrate.Migrator to apply the generated migrations. +func (am *AutoMigrator) CreateTxSQLMigrations(ctx context.Context) ([]*MigrationFile, error) { + _, files, err := am.createSQLMigrations(ctx, false) + return files, err +} + +func (am *AutoMigrator) createSQLMigrations(ctx context.Context, transactional bool) (*Migrations, []*MigrationFile, error) { + changes, err := am.plan(ctx) + if err != nil { + return nil, nil, fmt.Errorf("create sql migrations: %w", err) + } + + name, _ := genMigrationName(am.schemaName + "_auto") + migrations := NewMigrations(am.migrationsOpts...) + migrations.Add(Migration{ + Name: name, + Up: changes.Up(am.dbMigrator), + Down: changes.Down(am.dbMigrator), + Comment: "Changes detected by bun.AutoMigrator", + }) + + // Append .tx.up.sql or .up.sql to migration name, dependin if it should be transactional. + fname := func(direction string) string { + return name + map[bool]string{true: ".tx.", false: "."}[transactional] + direction + ".sql" + } + + up, err := am.createSQL(ctx, migrations, fname("up"), changes, transactional) + if err != nil { + return nil, nil, fmt.Errorf("create sql migration up: %w", err) + } + + down, err := am.createSQL(ctx, migrations, fname("down"), changes.GetReverse(), transactional) + if err != nil { + return nil, nil, fmt.Errorf("create sql migration down: %w", err) + } + return migrations, []*MigrationFile{up, down}, nil +} + +func (am *AutoMigrator) createSQL(_ context.Context, migrations *Migrations, fname string, changes *changeset, transactional bool) (*MigrationFile, error) { + var buf bytes.Buffer + + if transactional { + buf.WriteString("SET statement_timeout = 0;") + } + + if err := changes.WriteTo(&buf, am.dbMigrator); err != nil { + return nil, err + } + content := buf.Bytes() + + fpath := filepath.Join(migrations.getDirectory(), fname) + if err := os.WriteFile(fpath, content, 0o644); err != nil { + return nil, err + } + + mf := &MigrationFile{ + Name: fname, + Path: fpath, + Content: string(content), + } + return mf, nil +} + +// Func creates a MigrationFunc that applies all operations all the changeset. +func (c *changeset) Func(m sqlschema.Migrator) MigrationFunc { + return func(ctx context.Context, db *bun.DB) error { + return c.apply(ctx, db, m) + } +} + +// GetReverse returns a new changeset with each operation in it "reversed" and in reverse order. +func (c *changeset) GetReverse() *changeset { + var reverse changeset + for i := len(c.operations) - 1; i >= 0; i-- { + reverse.Add(c.operations[i].GetReverse()) + } + return &reverse +} + +// Up is syntactic sugar. +func (c *changeset) Up(m sqlschema.Migrator) MigrationFunc { + return c.Func(m) +} + +// Down is syntactic sugar. +func (c *changeset) Down(m sqlschema.Migrator) MigrationFunc { + return c.GetReverse().Func(m) +} + +// apply generates SQL for each operation and executes it. +func (c *changeset) apply(ctx context.Context, db *bun.DB, m sqlschema.Migrator) error { + if len(c.operations) == 0 { + return nil + } + + for _, op := range c.operations { + if _, isComment := op.(*comment); isComment { + continue + } + + b := internal.MakeQueryBytes() + b, err := m.AppendSQL(b, op) + if err != nil { + return fmt.Errorf("apply changes: %w", err) + } + + query := internal.String(b) + if _, err = db.ExecContext(ctx, query); err != nil { + return fmt.Errorf("apply changes: %w", err) + } + } + return nil +} + +func (c *changeset) WriteTo(w io.Writer, m sqlschema.Migrator) error { + var err error + + b := internal.MakeQueryBytes() + for _, op := range c.operations { + if c, isComment := op.(*comment); isComment { + b = append(b, "/*\n"...) + b = append(b, *c...) + b = append(b, "\n*/"...) + continue + } + + b, err = m.AppendSQL(b, op) + if err != nil { + return fmt.Errorf("write changeset: %w", err) + } + b = append(b, ";\n"...) + } + if _, err := w.Write(b); err != nil { + return fmt.Errorf("write changeset: %w", err) + } + return nil +} + +func (c *changeset) ResolveDependencies() error { + if len(c.operations) <= 1 { + return nil + } + + const ( + unvisited = iota + current + visited + ) + + status := make(map[Operation]int, len(c.operations)) + for _, op := range c.operations { + status[op] = unvisited + } + + var resolved []Operation + var nextOp Operation + var visit func(op Operation) error + + next := func() bool { + for op, s := range status { + if s == unvisited { + nextOp = op + return true + } + } + return false + } + + // visit iterates over c.operations until it finds all operations that depend on the current one + // or runs into cirtular dependency, in which case it will return an error. + visit = func(op Operation) error { + switch status[op] { + case visited: + return nil + case current: + // TODO: add details (circle) to the error message + return errors.New("detected circular dependency") + } + + status[op] = current + + for _, another := range c.operations { + if dop, hasDeps := another.(interface { + DependsOn(Operation) bool + }); another == op || !hasDeps || !dop.DependsOn(op) { + continue + } + if err := visit(another); err != nil { + return err + } + } + + status[op] = visited + + // Any dependent nodes would've already been added to the list by now, so we prepend. + resolved = append([]Operation{op}, resolved...) + return nil + } + + for next() { + if err := visit(nextOp); err != nil { + return err + } + } + + c.operations = resolved + return nil +} diff --git a/vendor/github.com/uptrace/bun/migrate/diff.go b/vendor/github.com/uptrace/bun/migrate/diff.go new file mode 100644 index 000000000..42e55dcde --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/diff.go @@ -0,0 +1,411 @@ +package migrate + +import ( + "github.com/uptrace/bun/migrate/sqlschema" +) + +// changeset is a set of changes to the database schema definition. +type changeset struct { + operations []Operation +} + +// Add new operations to the changeset. +func (c *changeset) Add(op ...Operation) { + c.operations = append(c.operations, op...) +} + +// diff calculates the diff between the current database schema and the target state. +// The changeset is not sorted -- the caller should resolve dependencies before applying the changes. +func diff(got, want sqlschema.Database, opts ...diffOption) *changeset { + d := newDetector(got, want, opts...) + return d.detectChanges() +} + +func (d *detector) detectChanges() *changeset { + currentTables := d.current.GetTables() + targetTables := d.target.GetTables() + +RenameCreate: + for wantName, wantTable := range targetTables.FromOldest() { + + // A table with this name exists in the database. We assume that schema objects won't + // be renamed to an already existing name, nor do we support such cases. + // Simply check if the table definition has changed. + if haveTable, ok := currentTables.Get(wantName); ok { + d.detectColumnChanges(haveTable, wantTable, true) + d.detectConstraintChanges(haveTable, wantTable) + continue + } + + // Find all renamed tables. We assume that renamed tables have the same signature. + for haveName, haveTable := range currentTables.FromOldest() { + if _, exists := targetTables.Get(haveName); !exists && d.canRename(haveTable, wantTable) { + d.changes.Add(&RenameTableOp{ + TableName: haveTable.GetName(), + NewName: wantName, + }) + d.refMap.RenameTable(haveTable.GetName(), wantName) + + // Find renamed columns, if any, and check if constraints (PK, UNIQUE) have been updated. + // We need not check wantTable any further. + d.detectColumnChanges(haveTable, wantTable, false) + d.detectConstraintChanges(haveTable, wantTable) + currentTables.Delete(haveName) + continue RenameCreate + } + } + + // If wantTable does not exist in the database and was not renamed + // then we need to create this table in the database. + additional := wantTable.(*sqlschema.BunTable) + d.changes.Add(&CreateTableOp{ + TableName: wantTable.GetName(), + Model: additional.Model, + }) + } + + // Drop any remaining "current" tables which do not have a model. + for name, table := range currentTables.FromOldest() { + if _, keep := targetTables.Get(name); !keep { + d.changes.Add(&DropTableOp{ + TableName: table.GetName(), + }) + } + } + + targetFKs := d.target.GetForeignKeys() + currentFKs := d.refMap.Deref() + + for fk := range targetFKs { + if _, ok := currentFKs[fk]; !ok { + d.changes.Add(&AddForeignKeyOp{ + ForeignKey: fk, + ConstraintName: "", // leave empty to let each dialect apply their convention + }) + } + } + + for fk, name := range currentFKs { + if _, ok := targetFKs[fk]; !ok { + d.changes.Add(&DropForeignKeyOp{ + ConstraintName: name, + ForeignKey: fk, + }) + } + } + + return &d.changes +} + +// detechColumnChanges finds renamed columns and, if checkType == true, columns with changed type. +func (d *detector) detectColumnChanges(current, target sqlschema.Table, checkType bool) { + currentColumns := current.GetColumns() + targetColumns := target.GetColumns() + +ChangeRename: + for tName, tCol := range targetColumns.FromOldest() { + + // This column exists in the database, so it hasn't been renamed, dropped, or added. + // Still, we should not delete(columns, thisColumn), because later we will need to + // check that we do not try to rename a column to an already a name that already exists. + if cCol, ok := currentColumns.Get(tName); ok { + if checkType && !d.equalColumns(cCol, tCol) { + d.changes.Add(&ChangeColumnTypeOp{ + TableName: target.GetName(), + Column: tName, + From: cCol, + To: d.makeTargetColDef(cCol, tCol), + }) + } + continue + } + + // Column tName does not exist in the database -- it's been either renamed or added. + // Find renamed columns first. + for cName, cCol := range currentColumns.FromOldest() { + // Cannot rename if a column with this name already exists or the types differ. + if _, exists := targetColumns.Get(cName); exists || !d.equalColumns(tCol, cCol) { + continue + } + d.changes.Add(&RenameColumnOp{ + TableName: target.GetName(), + OldName: cName, + NewName: tName, + }) + d.refMap.RenameColumn(target.GetName(), cName, tName) + currentColumns.Delete(cName) // no need to check this column again + + // Update primary key definition to avoid superficially recreating the constraint. + current.GetPrimaryKey().Columns.Replace(cName, tName) + + continue ChangeRename + } + + d.changes.Add(&AddColumnOp{ + TableName: target.GetName(), + ColumnName: tName, + Column: tCol, + }) + } + + // Drop columns which do not exist in the target schema and were not renamed. + for cName, cCol := range currentColumns.FromOldest() { + if _, keep := targetColumns.Get(cName); !keep { + d.changes.Add(&DropColumnOp{ + TableName: target.GetName(), + ColumnName: cName, + Column: cCol, + }) + } + } +} + +func (d *detector) detectConstraintChanges(current, target sqlschema.Table) { +Add: + for _, want := range target.GetUniqueConstraints() { + for _, got := range current.GetUniqueConstraints() { + if got.Equals(want) { + continue Add + } + } + d.changes.Add(&AddUniqueConstraintOp{ + TableName: target.GetName(), + Unique: want, + }) + } + +Drop: + for _, got := range current.GetUniqueConstraints() { + for _, want := range target.GetUniqueConstraints() { + if got.Equals(want) { + continue Drop + } + } + + d.changes.Add(&DropUniqueConstraintOp{ + TableName: target.GetName(), + Unique: got, + }) + } + + targetPK := target.GetPrimaryKey() + currentPK := current.GetPrimaryKey() + + // Detect primary key changes + if targetPK == nil && currentPK == nil { + return + } + switch { + case targetPK == nil && currentPK != nil: + d.changes.Add(&DropPrimaryKeyOp{ + TableName: target.GetName(), + PrimaryKey: *currentPK, + }) + case currentPK == nil && targetPK != nil: + d.changes.Add(&AddPrimaryKeyOp{ + TableName: target.GetName(), + PrimaryKey: *targetPK, + }) + case targetPK.Columns != currentPK.Columns: + d.changes.Add(&ChangePrimaryKeyOp{ + TableName: target.GetName(), + Old: *currentPK, + New: *targetPK, + }) + } +} + +func newDetector(got, want sqlschema.Database, opts ...diffOption) *detector { + cfg := &detectorConfig{ + cmpType: func(c1, c2 sqlschema.Column) bool { + return c1.GetSQLType() == c2.GetSQLType() && c1.GetVarcharLen() == c2.GetVarcharLen() + }, + } + for _, opt := range opts { + opt(cfg) + } + + return &detector{ + current: got, + target: want, + refMap: newRefMap(got.GetForeignKeys()), + cmpType: cfg.cmpType, + } +} + +type diffOption func(*detectorConfig) + +func withCompareTypeFunc(f CompareTypeFunc) diffOption { + return func(cfg *detectorConfig) { + cfg.cmpType = f + } +} + +// detectorConfig controls how differences in the model states are resolved. +type detectorConfig struct { + cmpType CompareTypeFunc +} + +// detector may modify the passed database schemas, so it isn't safe to re-use them. +type detector struct { + // current state represents the existing database schema. + current sqlschema.Database + + // target state represents the database schema defined in bun models. + target sqlschema.Database + + changes changeset + refMap refMap + + // cmpType determines column type equivalence. + // Default is direct comparison with '==' operator, which is inaccurate + // due to the existence of dialect-specific type aliases. The caller + // should pass a concrete InspectorDialect.EquuivalentType for robust comparison. + cmpType CompareTypeFunc +} + +// canRename checks if t1 can be renamed to t2. +func (d detector) canRename(t1, t2 sqlschema.Table) bool { + return t1.GetSchema() == t2.GetSchema() && equalSignatures(t1, t2, d.equalColumns) +} + +func (d detector) equalColumns(col1, col2 sqlschema.Column) bool { + return d.cmpType(col1, col2) && + col1.GetDefaultValue() == col2.GetDefaultValue() && + col1.GetIsNullable() == col2.GetIsNullable() && + col1.GetIsAutoIncrement() == col2.GetIsAutoIncrement() && + col1.GetIsIdentity() == col2.GetIsIdentity() +} + +func (d detector) makeTargetColDef(current, target sqlschema.Column) sqlschema.Column { + // Avoid unneccessary type-change migrations if the types are equivalent. + if d.cmpType(current, target) { + target = &sqlschema.BaseColumn{ + Name: target.GetName(), + DefaultValue: target.GetDefaultValue(), + IsNullable: target.GetIsNullable(), + IsAutoIncrement: target.GetIsAutoIncrement(), + IsIdentity: target.GetIsIdentity(), + + SQLType: current.GetSQLType(), + VarcharLen: current.GetVarcharLen(), + } + } + return target +} + +type CompareTypeFunc func(sqlschema.Column, sqlschema.Column) bool + +// equalSignatures determines if two tables have the same "signature". +func equalSignatures(t1, t2 sqlschema.Table, eq CompareTypeFunc) bool { + sig1 := newSignature(t1, eq) + sig2 := newSignature(t2, eq) + return sig1.Equals(sig2) +} + +// signature is a set of column definitions, which allows "relation/name-agnostic" comparison between them; +// meaning that two columns are considered equal if their types are the same. +type signature struct { + + // underlying stores the number of occurences for each unique column type. + // It helps to account for the fact that a table might have multiple columns that have the same type. + underlying map[sqlschema.BaseColumn]int + + eq CompareTypeFunc +} + +func newSignature(t sqlschema.Table, eq CompareTypeFunc) signature { + s := signature{ + underlying: make(map[sqlschema.BaseColumn]int), + eq: eq, + } + s.scan(t) + return s +} + +// scan iterates over table's field and counts occurrences of each unique column definition. +func (s *signature) scan(t sqlschema.Table) { + for _, icol := range t.GetColumns().FromOldest() { + scanCol := icol.(*sqlschema.BaseColumn) + // This is slightly more expensive than if the columns could be compared directly + // and we always did s.underlying[col]++, but we get type-equivalence in return. + col, count := s.getCount(*scanCol) + if count == 0 { + s.underlying[*scanCol] = 1 + } else { + s.underlying[col]++ + } + } +} + +// getCount uses CompareTypeFunc to find a column with the same (equivalent) SQL type +// and returns its count. Count 0 means there are no columns with of this type. +func (s *signature) getCount(keyCol sqlschema.BaseColumn) (key sqlschema.BaseColumn, count int) { + for col, cnt := range s.underlying { + if s.eq(&col, &keyCol) { + return col, cnt + } + } + return keyCol, 0 +} + +// Equals returns true if 2 signatures share an identical set of columns. +func (s *signature) Equals(other signature) bool { + if len(s.underlying) != len(other.underlying) { + return false + } + for col, count := range s.underlying { + if _, countOther := other.getCount(col); countOther != count { + return false + } + } + return true +} + +// refMap is a utility for tracking superficial changes in foreign keys, +// which do not require any modificiation in the database. +// Modern SQL dialects automatically updated foreign key constraints whenever +// a column or a table is renamed. Detector can use refMap to ignore any +// differences in foreign keys which were caused by renamed column/table. +type refMap map[*sqlschema.ForeignKey]string + +func newRefMap(fks map[sqlschema.ForeignKey]string) refMap { + rm := make(map[*sqlschema.ForeignKey]string) + for fk, name := range fks { + rm[&fk] = name + } + return rm +} + +// RenameT updates table name in all foreign key definions which depend on it. +func (rm refMap) RenameTable(tableName string, newName string) { + for fk := range rm { + switch tableName { + case fk.From.TableName: + fk.From.TableName = newName + case fk.To.TableName: + fk.To.TableName = newName + } + } +} + +// RenameColumn updates column name in all foreign key definions which depend on it. +func (rm refMap) RenameColumn(tableName string, column, newName string) { + for fk := range rm { + if tableName == fk.From.TableName { + fk.From.Column.Replace(column, newName) + } + if tableName == fk.To.TableName { + fk.To.Column.Replace(column, newName) + } + } +} + +// Deref returns copies of ForeignKey values to a map. +func (rm refMap) Deref() map[sqlschema.ForeignKey]string { + out := make(map[sqlschema.ForeignKey]string) + for fk, name := range rm { + out[*fk] = name + } + return out +} diff --git a/vendor/github.com/uptrace/bun/migrate/migrator.go b/vendor/github.com/uptrace/bun/migrate/migrator.go index e6d70e39f..d5a72aec0 100644 --- a/vendor/github.com/uptrace/bun/migrate/migrator.go +++ b/vendor/github.com/uptrace/bun/migrate/migrator.go @@ -12,14 +12,21 @@ import ( "github.com/uptrace/bun" ) +const ( + defaultTable = "bun_migrations" + defaultLocksTable = "bun_migration_locks" +) + type MigratorOption func(m *Migrator) +// WithTableName overrides default migrations table name. func WithTableName(table string) MigratorOption { return func(m *Migrator) { m.table = table } } +// WithLocksTableName overrides default migration locks table name. func WithLocksTableName(table string) MigratorOption { return func(m *Migrator) { m.locksTable = table @@ -27,7 +34,7 @@ func WithLocksTableName(table string) MigratorOption { } // WithMarkAppliedOnSuccess sets the migrator to only mark migrations as applied/unapplied -// when their up/down is successful +// when their up/down is successful. func WithMarkAppliedOnSuccess(enabled bool) MigratorOption { return func(m *Migrator) { m.markAppliedOnSuccess = enabled @@ -52,8 +59,8 @@ func NewMigrator(db *bun.DB, migrations *Migrations, opts ...MigratorOption) *Mi ms: migrations.ms, - table: "bun_migrations", - locksTable: "bun_migration_locks", + table: defaultTable, + locksTable: defaultLocksTable, } for _, opt := range opts { opt(m) @@ -246,7 +253,7 @@ func (m *Migrator) CreateGoMigration( opt(cfg) } - name, err := m.genMigrationName(name) + name, err := genMigrationName(name) if err != nil { return nil, err } @@ -269,7 +276,7 @@ func (m *Migrator) CreateGoMigration( // CreateTxSQLMigration creates transactional up and down SQL migration files. func (m *Migrator) CreateTxSQLMigrations(ctx context.Context, name string) ([]*MigrationFile, error) { - name, err := m.genMigrationName(name) + name, err := genMigrationName(name) if err != nil { return nil, err } @@ -289,7 +296,7 @@ func (m *Migrator) CreateTxSQLMigrations(ctx context.Context, name string) ([]*M // CreateSQLMigrations creates up and down SQL migration files. func (m *Migrator) CreateSQLMigrations(ctx context.Context, name string) ([]*MigrationFile, error) { - name, err := m.genMigrationName(name) + name, err := genMigrationName(name) if err != nil { return nil, err } @@ -307,7 +314,7 @@ func (m *Migrator) CreateSQLMigrations(ctx context.Context, name string) ([]*Mig return []*MigrationFile{up, down}, nil } -func (m *Migrator) createSQL(ctx context.Context, fname string, transactional bool) (*MigrationFile, error) { +func (m *Migrator) createSQL(_ context.Context, fname string, transactional bool) (*MigrationFile, error) { fpath := filepath.Join(m.migrations.getDirectory(), fname) template := sqlTemplate @@ -329,7 +336,7 @@ func (m *Migrator) createSQL(ctx context.Context, fname string, transactional bo var nameRE = regexp.MustCompile(`^[0-9a-z_\-]+$`) -func (m *Migrator) genMigrationName(name string) (string, error) { +func genMigrationName(name string) (string, error) { const timeFormat = "20060102150405" if name == "" { diff --git a/vendor/github.com/uptrace/bun/migrate/operations.go b/vendor/github.com/uptrace/bun/migrate/operations.go new file mode 100644 index 000000000..7b749c5a0 --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/operations.go @@ -0,0 +1,340 @@ +package migrate + +import ( + "fmt" + + "github.com/uptrace/bun/migrate/sqlschema" +) + +// Operation encapsulates the request to change a database definition +// and knowns which operation can revert it. +// +// It is useful to define "monolith" Operations whenever possible, +// even though they a dialect may require several distinct steps to apply them. +// For example, changing a primary key involves first dropping the old constraint +// before generating the new one. Yet, this is only an implementation detail and +// passing a higher-level ChangePrimaryKeyOp will give the dialect more information +// about the applied change. +// +// Some operations might be irreversible due to technical limitations. Returning +// a *comment from GetReverse() will add an explanatory note to the generate migation file. +// +// To declare dependency on another Operation, operations should implement +// { DependsOn(Operation) bool } interface, which Changeset will use to resolve dependencies. +type Operation interface { + GetReverse() Operation +} + +// CreateTableOp creates a new table in the schema. +// +// It does not report dependency on any other migration and may be executed first. +// Make sure the dialect does not include FOREIGN KEY constraints in the CREATE TABLE +// statement, as those may potentially reference not-yet-existing columns/tables. +type CreateTableOp struct { + TableName string + Model interface{} +} + +var _ Operation = (*CreateTableOp)(nil) + +func (op *CreateTableOp) GetReverse() Operation { + return &DropTableOp{TableName: op.TableName} +} + +// DropTableOp drops a database table. This operation is not reversible. +type DropTableOp struct { + TableName string +} + +var _ Operation = (*DropTableOp)(nil) + +func (op *DropTableOp) DependsOn(another Operation) bool { + drop, ok := another.(*DropForeignKeyOp) + return ok && drop.ForeignKey.DependsOnTable(op.TableName) +} + +// GetReverse for a DropTable returns a no-op migration. Logically, CreateTable is the reverse, +// but DropTable does not have the table's definition to create one. +func (op *DropTableOp) GetReverse() Operation { + c := comment(fmt.Sprintf("WARNING: \"DROP TABLE %s\" cannot be reversed automatically because table definition is not available", op.TableName)) + return &c +} + +// RenameTableOp renames the table. Changing the "schema" part of the table's FQN (moving tables between schemas) is not allowed. +type RenameTableOp struct { + TableName string + NewName string +} + +var _ Operation = (*RenameTableOp)(nil) + +func (op *RenameTableOp) GetReverse() Operation { + return &RenameTableOp{ + TableName: op.NewName, + NewName: op.TableName, + } +} + +// RenameColumnOp renames a column in the table. If the changeset includes a rename operation +// for the column's table, it should be executed first. +type RenameColumnOp struct { + TableName string + OldName string + NewName string +} + +var _ Operation = (*RenameColumnOp)(nil) + +func (op *RenameColumnOp) GetReverse() Operation { + return &RenameColumnOp{ + TableName: op.TableName, + OldName: op.NewName, + NewName: op.OldName, + } +} + +func (op *RenameColumnOp) DependsOn(another Operation) bool { + rename, ok := another.(*RenameTableOp) + return ok && op.TableName == rename.NewName +} + +// AddColumnOp adds a new column to the table. +type AddColumnOp struct { + TableName string + ColumnName string + Column sqlschema.Column +} + +var _ Operation = (*AddColumnOp)(nil) + +func (op *AddColumnOp) GetReverse() Operation { + return &DropColumnOp{ + TableName: op.TableName, + ColumnName: op.ColumnName, + Column: op.Column, + } +} + +// DropColumnOp drop a column from the table. +// +// While some dialects allow DROP CASCADE to drop dependent constraints, +// explicit handling on constraints is preferred for transparency and debugging. +// DropColumnOp depends on DropForeignKeyOp, DropPrimaryKeyOp, and ChangePrimaryKeyOp +// if any of the constraints is defined on this table. +type DropColumnOp struct { + TableName string + ColumnName string + Column sqlschema.Column +} + +var _ Operation = (*DropColumnOp)(nil) + +func (op *DropColumnOp) GetReverse() Operation { + return &AddColumnOp{ + TableName: op.TableName, + ColumnName: op.ColumnName, + Column: op.Column, + } +} + +func (op *DropColumnOp) DependsOn(another Operation) bool { + switch drop := another.(type) { + case *DropForeignKeyOp: + return drop.ForeignKey.DependsOnColumn(op.TableName, op.ColumnName) + case *DropPrimaryKeyOp: + return op.TableName == drop.TableName && drop.PrimaryKey.Columns.Contains(op.ColumnName) + case *ChangePrimaryKeyOp: + return op.TableName == drop.TableName && drop.Old.Columns.Contains(op.ColumnName) + } + return false +} + +// AddForeignKey adds a new FOREIGN KEY constraint. +type AddForeignKeyOp struct { + ForeignKey sqlschema.ForeignKey + ConstraintName string +} + +var _ Operation = (*AddForeignKeyOp)(nil) + +func (op *AddForeignKeyOp) TableName() string { + return op.ForeignKey.From.TableName +} + +func (op *AddForeignKeyOp) DependsOn(another Operation) bool { + switch another := another.(type) { + case *RenameTableOp: + return op.ForeignKey.DependsOnTable(another.TableName) || op.ForeignKey.DependsOnTable(another.NewName) + case *CreateTableOp: + return op.ForeignKey.DependsOnTable(another.TableName) + } + return false +} + +func (op *AddForeignKeyOp) GetReverse() Operation { + return &DropForeignKeyOp{ + ForeignKey: op.ForeignKey, + ConstraintName: op.ConstraintName, + } +} + +// DropForeignKeyOp drops a FOREIGN KEY constraint. +type DropForeignKeyOp struct { + ForeignKey sqlschema.ForeignKey + ConstraintName string +} + +var _ Operation = (*DropForeignKeyOp)(nil) + +func (op *DropForeignKeyOp) TableName() string { + return op.ForeignKey.From.TableName +} + +func (op *DropForeignKeyOp) GetReverse() Operation { + return &AddForeignKeyOp{ + ForeignKey: op.ForeignKey, + ConstraintName: op.ConstraintName, + } +} + +// AddUniqueConstraintOp adds new UNIQUE constraint to the table. +type AddUniqueConstraintOp struct { + TableName string + Unique sqlschema.Unique +} + +var _ Operation = (*AddUniqueConstraintOp)(nil) + +func (op *AddUniqueConstraintOp) GetReverse() Operation { + return &DropUniqueConstraintOp{ + TableName: op.TableName, + Unique: op.Unique, + } +} + +func (op *AddUniqueConstraintOp) DependsOn(another Operation) bool { + switch another := another.(type) { + case *AddColumnOp: + return op.TableName == another.TableName && op.Unique.Columns.Contains(another.ColumnName) + case *RenameTableOp: + return op.TableName == another.NewName + case *DropUniqueConstraintOp: + // We want to drop the constraint with the same name before adding this one. + return op.TableName == another.TableName && op.Unique.Name == another.Unique.Name + default: + return false + } + +} + +// DropUniqueConstraintOp drops a UNIQUE constraint. +type DropUniqueConstraintOp struct { + TableName string + Unique sqlschema.Unique +} + +var _ Operation = (*DropUniqueConstraintOp)(nil) + +func (op *DropUniqueConstraintOp) DependsOn(another Operation) bool { + if rename, ok := another.(*RenameTableOp); ok { + return op.TableName == rename.NewName + } + return false +} + +func (op *DropUniqueConstraintOp) GetReverse() Operation { + return &AddUniqueConstraintOp{ + TableName: op.TableName, + Unique: op.Unique, + } +} + +// ChangeColumnTypeOp set a new data type for the column. +// The two types should be such that the data can be auto-casted from one to another. +// E.g. reducing VARCHAR lenght is not possible in most dialects. +// AutoMigrator does not enforce or validate these rules. +type ChangeColumnTypeOp struct { + TableName string + Column string + From sqlschema.Column + To sqlschema.Column +} + +var _ Operation = (*ChangeColumnTypeOp)(nil) + +func (op *ChangeColumnTypeOp) GetReverse() Operation { + return &ChangeColumnTypeOp{ + TableName: op.TableName, + Column: op.Column, + From: op.To, + To: op.From, + } +} + +// DropPrimaryKeyOp drops the table's PRIMARY KEY. +type DropPrimaryKeyOp struct { + TableName string + PrimaryKey sqlschema.PrimaryKey +} + +var _ Operation = (*DropPrimaryKeyOp)(nil) + +func (op *DropPrimaryKeyOp) GetReverse() Operation { + return &AddPrimaryKeyOp{ + TableName: op.TableName, + PrimaryKey: op.PrimaryKey, + } +} + +// AddPrimaryKeyOp adds a new PRIMARY KEY to the table. +type AddPrimaryKeyOp struct { + TableName string + PrimaryKey sqlschema.PrimaryKey +} + +var _ Operation = (*AddPrimaryKeyOp)(nil) + +func (op *AddPrimaryKeyOp) GetReverse() Operation { + return &DropPrimaryKeyOp{ + TableName: op.TableName, + PrimaryKey: op.PrimaryKey, + } +} + +func (op *AddPrimaryKeyOp) DependsOn(another Operation) bool { + switch another := another.(type) { + case *AddColumnOp: + return op.TableName == another.TableName && op.PrimaryKey.Columns.Contains(another.ColumnName) + } + return false +} + +// ChangePrimaryKeyOp changes the PRIMARY KEY of the table. +type ChangePrimaryKeyOp struct { + TableName string + Old sqlschema.PrimaryKey + New sqlschema.PrimaryKey +} + +var _ Operation = (*AddPrimaryKeyOp)(nil) + +func (op *ChangePrimaryKeyOp) GetReverse() Operation { + return &ChangePrimaryKeyOp{ + TableName: op.TableName, + Old: op.New, + New: op.Old, + } +} + +// comment denotes an Operation that cannot be executed. +// +// Operations, which cannot be reversed due to current technical limitations, +// may return &comment with a helpful message from their GetReverse() method. +// +// Chnagelog should skip it when applying operations or output as log message, +// and write it as an SQL comment when creating migration files. +type comment string + +var _ Operation = (*comment)(nil) + +func (c *comment) GetReverse() Operation { return c } diff --git a/vendor/github.com/uptrace/bun/migrate/sqlschema/column.go b/vendor/github.com/uptrace/bun/migrate/sqlschema/column.go new file mode 100644 index 000000000..60f7ea8a6 --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/sqlschema/column.go @@ -0,0 +1,75 @@ +package sqlschema + +import ( + "fmt" + + "github.com/uptrace/bun/schema" +) + +type Column interface { + GetName() string + GetSQLType() string + GetVarcharLen() int + GetDefaultValue() string + GetIsNullable() bool + GetIsAutoIncrement() bool + GetIsIdentity() bool + AppendQuery(schema.Formatter, []byte) ([]byte, error) +} + +var _ Column = (*BaseColumn)(nil) + +// BaseColumn is a base column definition that stores various attributes of a column. +// +// Dialects and only dialects can use it to implement the Column interface. +// Other packages must use the Column interface. +type BaseColumn struct { + Name string + SQLType string + VarcharLen int + DefaultValue string + IsNullable bool + IsAutoIncrement bool + IsIdentity bool + // TODO: add Precision and Cardinality for timestamps/bit-strings/floats and arrays respectively. +} + +func (cd BaseColumn) GetName() string { + return cd.Name +} + +func (cd BaseColumn) GetSQLType() string { + return cd.SQLType +} + +func (cd BaseColumn) GetVarcharLen() int { + return cd.VarcharLen +} + +func (cd BaseColumn) GetDefaultValue() string { + return cd.DefaultValue +} + +func (cd BaseColumn) GetIsNullable() bool { + return cd.IsNullable +} + +func (cd BaseColumn) GetIsAutoIncrement() bool { + return cd.IsAutoIncrement +} + +func (cd BaseColumn) GetIsIdentity() bool { + return cd.IsIdentity +} + +// AppendQuery appends full SQL data type. +func (c *BaseColumn) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, err error) { + b = append(b, c.SQLType...) + if c.VarcharLen == 0 { + return b, nil + } + b = append(b, "("...) + b = append(b, fmt.Sprint(c.VarcharLen)...) + b = append(b, ")"...) + return b, nil +} diff --git a/vendor/github.com/uptrace/bun/migrate/sqlschema/database.go b/vendor/github.com/uptrace/bun/migrate/sqlschema/database.go new file mode 100644 index 000000000..cdc5b2d50 --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/sqlschema/database.go @@ -0,0 +1,127 @@ +package sqlschema + +import ( + "slices" + "strings" + + "github.com/uptrace/bun/schema" + orderedmap "github.com/wk8/go-ordered-map/v2" +) + +type Database interface { + GetTables() *orderedmap.OrderedMap[string, Table] + GetForeignKeys() map[ForeignKey]string +} + +var _ Database = (*BaseDatabase)(nil) + +// BaseDatabase is a base database definition. +// +// Dialects and only dialects can use it to implement the Database interface. +// Other packages must use the Database interface. +type BaseDatabase struct { + Tables *orderedmap.OrderedMap[string, Table] + ForeignKeys map[ForeignKey]string +} + +func (ds BaseDatabase) GetTables() *orderedmap.OrderedMap[string, Table] { + return ds.Tables +} + +func (ds BaseDatabase) GetForeignKeys() map[ForeignKey]string { + return ds.ForeignKeys +} + +type ForeignKey struct { + From ColumnReference + To ColumnReference +} + +func NewColumnReference(tableName string, columns ...string) ColumnReference { + return ColumnReference{ + TableName: tableName, + Column: NewColumns(columns...), + } +} + +func (fk ForeignKey) DependsOnTable(tableName string) bool { + return fk.From.TableName == tableName || fk.To.TableName == tableName +} + +func (fk ForeignKey) DependsOnColumn(tableName string, column string) bool { + return fk.DependsOnTable(tableName) && + (fk.From.Column.Contains(column) || fk.To.Column.Contains(column)) +} + +// Columns is a hashable representation of []string used to define schema constraints that depend on multiple columns. +// Although having duplicated column references in these constraints is illegal, Columns neither validates nor enforces this constraint on the caller. +type Columns string + +// NewColumns creates a composite column from a slice of column names. +func NewColumns(columns ...string) Columns { + slices.Sort(columns) + return Columns(strings.Join(columns, ",")) +} + +func (c *Columns) String() string { + return string(*c) +} + +func (c *Columns) AppendQuery(fmter schema.Formatter, b []byte) ([]byte, error) { + return schema.Safe(*c).AppendQuery(fmter, b) +} + +// Split returns a slice of column names that make up the composite. +func (c *Columns) Split() []string { + return strings.Split(c.String(), ",") +} + +// ContainsColumns checks that columns in "other" are a subset of current colums. +func (c *Columns) ContainsColumns(other Columns) bool { + columns := c.Split() +Outer: + for _, check := range other.Split() { + for _, column := range columns { + if check == column { + continue Outer + } + } + return false + } + return true +} + +// Contains checks that a composite column contains the current column. +func (c *Columns) Contains(other string) bool { + return c.ContainsColumns(Columns(other)) +} + +// Replace renames a column if it is part of the composite. +// If a composite consists of multiple columns, only one column will be renamed. +func (c *Columns) Replace(oldColumn, newColumn string) bool { + columns := c.Split() + for i, column := range columns { + if column == oldColumn { + columns[i] = newColumn + *c = NewColumns(columns...) + return true + } + } + return false +} + +// Unique represents a unique constraint defined on 1 or more columns. +type Unique struct { + Name string + Columns Columns +} + +// Equals checks that two unique constraint are the same, assuming both are defined for the same table. +func (u Unique) Equals(other Unique) bool { + return u.Columns == other.Columns +} + +type ColumnReference struct { + TableName string + Column Columns +} diff --git a/vendor/github.com/uptrace/bun/migrate/sqlschema/inspector.go b/vendor/github.com/uptrace/bun/migrate/sqlschema/inspector.go new file mode 100644 index 000000000..fc9af06fc --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/sqlschema/inspector.go @@ -0,0 +1,241 @@ +package sqlschema + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/schema" + orderedmap "github.com/wk8/go-ordered-map/v2" +) + +type InspectorDialect interface { + schema.Dialect + + // Inspector returns a new instance of Inspector for the dialect. + // Dialects MAY set their default InspectorConfig values in constructor + // but MUST apply InspectorOptions to ensure they can be overriden. + // + // Use ApplyInspectorOptions to reduce boilerplate. + NewInspector(db *bun.DB, options ...InspectorOption) Inspector + + // CompareType returns true if col1 and co2 SQL types are equivalent, + // i.e. they might use dialect-specifc type aliases (SERIAL ~ SMALLINT) + // or specify the same VARCHAR length differently (VARCHAR(255) ~ VARCHAR). + CompareType(Column, Column) bool +} + +// InspectorConfig controls the scope of migration by limiting the objects Inspector should return. +// Inspectors SHOULD use the configuration directly instead of copying it, or MAY choose to embed it, +// to make sure options are always applied correctly. +type InspectorConfig struct { + // SchemaName limits inspection to tables in a particular schema. + SchemaName string + + // ExcludeTables from inspection. + ExcludeTables []string +} + +// Inspector reads schema state. +type Inspector interface { + Inspect(ctx context.Context) (Database, error) +} + +func WithSchemaName(schemaName string) InspectorOption { + return func(cfg *InspectorConfig) { + cfg.SchemaName = schemaName + } +} + +// WithExcludeTables works in append-only mode, i.e. tables cannot be re-included. +func WithExcludeTables(tables ...string) InspectorOption { + return func(cfg *InspectorConfig) { + cfg.ExcludeTables = append(cfg.ExcludeTables, tables...) + } +} + +// NewInspector creates a new database inspector, if the dialect supports it. +func NewInspector(db *bun.DB, options ...InspectorOption) (Inspector, error) { + dialect, ok := (db.Dialect()).(InspectorDialect) + if !ok { + return nil, fmt.Errorf("%s does not implement sqlschema.Inspector", db.Dialect().Name()) + } + return &inspector{ + Inspector: dialect.NewInspector(db, options...), + }, nil +} + +func NewBunModelInspector(tables *schema.Tables, options ...InspectorOption) *BunModelInspector { + bmi := &BunModelInspector{ + tables: tables, + } + ApplyInspectorOptions(&bmi.InspectorConfig, options...) + return bmi +} + +type InspectorOption func(*InspectorConfig) + +func ApplyInspectorOptions(cfg *InspectorConfig, options ...InspectorOption) { + for _, opt := range options { + opt(cfg) + } +} + +// inspector is opaque pointer to a database inspector. +type inspector struct { + Inspector +} + +// BunModelInspector creates the current project state from the passed bun.Models. +// Do not recycle BunModelInspector for different sets of models, as older models will not be de-registerred before the next run. +type BunModelInspector struct { + InspectorConfig + tables *schema.Tables +} + +var _ Inspector = (*BunModelInspector)(nil) + +func (bmi *BunModelInspector) Inspect(ctx context.Context) (Database, error) { + state := BunModelSchema{ + BaseDatabase: BaseDatabase{ + ForeignKeys: make(map[ForeignKey]string), + }, + Tables: orderedmap.New[string, Table](), + } + for _, t := range bmi.tables.All() { + if t.Schema != bmi.SchemaName { + continue + } + + columns := orderedmap.New[string, Column]() + for _, f := range t.Fields { + + sqlType, length, err := parseLen(f.CreateTableSQLType) + if err != nil { + return nil, fmt.Errorf("parse length in %q: %w", f.CreateTableSQLType, err) + } + columns.Set(f.Name, &BaseColumn{ + Name: f.Name, + SQLType: strings.ToLower(sqlType), // TODO(dyma): maybe this is not necessary after Column.Eq() + VarcharLen: length, + DefaultValue: exprToLower(f.SQLDefault), + IsNullable: !f.NotNull, + IsAutoIncrement: f.AutoIncrement, + IsIdentity: f.Identity, + }) + } + + var unique []Unique + for name, group := range t.Unique { + // Create a separate unique index for single-column unique constraints + // let each dialect apply the default naming convention. + if name == "" { + for _, f := range group { + unique = append(unique, Unique{Columns: NewColumns(f.Name)}) + } + continue + } + + // Set the name if it is a "unique group", in which case the user has provided the name. + var columns []string + for _, f := range group { + columns = append(columns, f.Name) + } + unique = append(unique, Unique{Name: name, Columns: NewColumns(columns...)}) + } + + var pk *PrimaryKey + if len(t.PKs) > 0 { + var columns []string + for _, f := range t.PKs { + columns = append(columns, f.Name) + } + pk = &PrimaryKey{Columns: NewColumns(columns...)} + } + + // In cases where a table is defined in a non-default schema in the `bun:table` tag, + // schema.Table only extracts the name of the schema, but passes the entire tag value to t.Name + // for backwads-compatibility. For example, a bun model like this: + // type Model struct { bun.BaseModel `bun:"table:favourite.books` } + // produces + // schema.Table{ Schema: "favourite", Name: "favourite.books" } + tableName := strings.TrimPrefix(t.Name, t.Schema+".") + state.Tables.Set(tableName, &BunTable{ + BaseTable: BaseTable{ + Schema: t.Schema, + Name: tableName, + Columns: columns, + UniqueConstraints: unique, + PrimaryKey: pk, + }, + Model: t.ZeroIface, + }) + + for _, rel := range t.Relations { + // These relations are nominal and do not need a foreign key to be declared in the current table. + // They will be either expressed as N:1 relations in an m2m mapping table, or will be referenced by the other table if it's a 1:N. + if rel.Type == schema.ManyToManyRelation || + rel.Type == schema.HasManyRelation { + continue + } + + var fromCols, toCols []string + for _, f := range rel.BasePKs { + fromCols = append(fromCols, f.Name) + } + for _, f := range rel.JoinPKs { + toCols = append(toCols, f.Name) + } + + target := rel.JoinTable + state.ForeignKeys[ForeignKey{ + From: NewColumnReference(t.Name, fromCols...), + To: NewColumnReference(target.Name, toCols...), + }] = "" + } + } + return state, nil +} + +func parseLen(typ string) (string, int, error) { + paren := strings.Index(typ, "(") + if paren == -1 { + return typ, 0, nil + } + length, err := strconv.Atoi(typ[paren+1 : len(typ)-1]) + if err != nil { + return typ, 0, err + } + return typ[:paren], length, nil +} + +// exprToLower converts string to lowercase, if it does not contain a string literal 'lit'. +// Use it to ensure that user-defined default values in the models are always comparable +// to those returned by the database inspector, regardless of the case convention in individual drivers. +func exprToLower(s string) string { + if strings.HasPrefix(s, "'") && strings.HasSuffix(s, "'") { + return s + } + return strings.ToLower(s) +} + +// BunModelSchema is the schema state derived from bun table models. +type BunModelSchema struct { + BaseDatabase + + Tables *orderedmap.OrderedMap[string, Table] +} + +func (ms BunModelSchema) GetTables() *orderedmap.OrderedMap[string, Table] { + return ms.Tables +} + +// BunTable provides additional table metadata that is only accessible from scanning bun models. +type BunTable struct { + BaseTable + + // Model stores the zero interface to the underlying Go struct. + Model interface{} +} diff --git a/vendor/github.com/uptrace/bun/migrate/sqlschema/migrator.go b/vendor/github.com/uptrace/bun/migrate/sqlschema/migrator.go new file mode 100644 index 000000000..00500061b --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/sqlschema/migrator.go @@ -0,0 +1,49 @@ +package sqlschema + +import ( + "fmt" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/schema" +) + +type MigratorDialect interface { + schema.Dialect + NewMigrator(db *bun.DB, schemaName string) Migrator +} + +type Migrator interface { + AppendSQL(b []byte, operation interface{}) ([]byte, error) +} + +// migrator is a dialect-agnostic wrapper for sqlschema.MigratorDialect. +type migrator struct { + Migrator +} + +func NewMigrator(db *bun.DB, schemaName string) (Migrator, error) { + md, ok := db.Dialect().(MigratorDialect) + if !ok { + return nil, fmt.Errorf("%q dialect does not implement sqlschema.Migrator", db.Dialect().Name()) + } + return &migrator{ + Migrator: md.NewMigrator(db, schemaName), + }, nil +} + +// BaseMigrator can be embeded by dialect's Migrator implementations to re-use some of the existing bun queries. +type BaseMigrator struct { + db *bun.DB +} + +func NewBaseMigrator(db *bun.DB) *BaseMigrator { + return &BaseMigrator{db: db} +} + +func (m *BaseMigrator) AppendCreateTable(b []byte, model interface{}) ([]byte, error) { + return m.db.NewCreateTable().Model(model).AppendQuery(m.db.Formatter(), b) +} + +func (m *BaseMigrator) AppendDropTable(b []byte, schemaName, tableName string) ([]byte, error) { + return m.db.NewDropTable().TableExpr("?.?", bun.Ident(schemaName), bun.Ident(tableName)).AppendQuery(m.db.Formatter(), b) +} diff --git a/vendor/github.com/uptrace/bun/migrate/sqlschema/table.go b/vendor/github.com/uptrace/bun/migrate/sqlschema/table.go new file mode 100644 index 000000000..a805ba780 --- /dev/null +++ b/vendor/github.com/uptrace/bun/migrate/sqlschema/table.go @@ -0,0 +1,60 @@ +package sqlschema + +import ( + orderedmap "github.com/wk8/go-ordered-map/v2" +) + +type Table interface { + GetSchema() string + GetName() string + GetColumns() *orderedmap.OrderedMap[string, Column] + GetPrimaryKey() *PrimaryKey + GetUniqueConstraints() []Unique +} + +var _ Table = (*BaseTable)(nil) + +// BaseTable is a base table definition. +// +// Dialects and only dialects can use it to implement the Table interface. +// Other packages must use the Table interface. +type BaseTable struct { + Schema string + Name string + + // ColumnDefinitions map each column name to the column definition. + Columns *orderedmap.OrderedMap[string, Column] + + // PrimaryKey holds the primary key definition. + // A nil value means that no primary key is defined for the table. + PrimaryKey *PrimaryKey + + // UniqueConstraints defined on the table. + UniqueConstraints []Unique +} + +// PrimaryKey represents a primary key constraint defined on 1 or more columns. +type PrimaryKey struct { + Name string + Columns Columns +} + +func (td *BaseTable) GetSchema() string { + return td.Schema +} + +func (td *BaseTable) GetName() string { + return td.Name +} + +func (td *BaseTable) GetColumns() *orderedmap.OrderedMap[string, Column] { + return td.Columns +} + +func (td *BaseTable) GetPrimaryKey() *PrimaryKey { + return td.PrimaryKey +} + +func (td *BaseTable) GetUniqueConstraints() []Unique { + return td.UniqueConstraints +} diff --git a/vendor/github.com/uptrace/bun/model_table_has_many.go b/vendor/github.com/uptrace/bun/model_table_has_many.go index 544cdf5d6..cd721a1b2 100644 --- a/vendor/github.com/uptrace/bun/model_table_has_many.go +++ b/vendor/github.com/uptrace/bun/model_table_has_many.go @@ -51,7 +51,7 @@ func (m *hasManyModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error dest := makeDest(m, len(columns)) var n int - + m.structKey = make([]interface{}, len(m.rel.JoinPKs)) for rows.Next() { if m.sliceOfPtr { m.strct = reflect.New(m.table.Type).Elem() @@ -59,9 +59,8 @@ func (m *hasManyModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error m.strct.Set(m.table.ZeroValue) } m.structInited = false - m.scanIndex = 0 - m.structKey = m.structKey[:0] + if err := rows.Scan(dest...); err != nil { return 0, err } @@ -92,9 +91,9 @@ func (m *hasManyModel) Scan(src interface{}) error { return err } - for _, f := range m.rel.JoinPKs { - if f.Name == field.Name { - m.structKey = append(m.structKey, indirectFieldValue(field.Value(m.strct))) + for i, f := range m.rel.JoinPKs { + if f.Name == column { + m.structKey[i] = indirectFieldValue(field.Value(m.strct)) break } } diff --git a/vendor/github.com/uptrace/bun/package.json b/vendor/github.com/uptrace/bun/package.json index 6a8d7082e..15c6fd47b 100644 --- a/vendor/github.com/uptrace/bun/package.json +++ b/vendor/github.com/uptrace/bun/package.json @@ -1,6 +1,6 @@ { "name": "gobun", - "version": "1.2.5", + "version": "1.2.6", "main": "index.js", "repository": "git@github.com:uptrace/bun.git", "author": "Vladimir Mihailenco ", diff --git a/vendor/github.com/uptrace/bun/query_base.go b/vendor/github.com/uptrace/bun/query_base.go index 8a26a4c8a..52b0c1e22 100644 --- a/vendor/github.com/uptrace/bun/query_base.go +++ b/vendor/github.com/uptrace/bun/query_base.go @@ -6,6 +6,8 @@ import ( "database/sql/driver" "errors" "fmt" + "strconv" + "strings" "time" "github.com/uptrace/bun/dialect" @@ -1352,3 +1354,113 @@ func (ih *idxHintsQuery) bufIndexHint( b = append(b, ")"...) return b, nil } + +//------------------------------------------------------------------------------ + +type orderLimitOffsetQuery struct { + order []schema.QueryWithArgs + + limit int32 + offset int32 +} + +func (q *orderLimitOffsetQuery) addOrder(orders ...string) { + for _, order := range orders { + if order == "" { + continue + } + + index := strings.IndexByte(order, ' ') + if index == -1 { + q.order = append(q.order, schema.UnsafeIdent(order)) + continue + } + + field := order[:index] + sort := order[index+1:] + + switch strings.ToUpper(sort) { + case "ASC", "DESC", "ASC NULLS FIRST", "DESC NULLS FIRST", + "ASC NULLS LAST", "DESC NULLS LAST": + q.order = append(q.order, schema.SafeQuery("? ?", []interface{}{ + Ident(field), + Safe(sort), + })) + default: + q.order = append(q.order, schema.UnsafeIdent(order)) + } + } + +} + +func (q *orderLimitOffsetQuery) addOrderExpr(query string, args ...interface{}) { + q.order = append(q.order, schema.SafeQuery(query, args)) +} + +func (q *orderLimitOffsetQuery) appendOrder(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if len(q.order) > 0 { + b = append(b, " ORDER BY "...) + + for i, f := range q.order { + if i > 0 { + b = append(b, ", "...) + } + b, err = f.AppendQuery(fmter, b) + if err != nil { + return nil, err + } + } + + return b, nil + } + + // MSSQL: allows Limit() without Order() as per https://stackoverflow.com/a/36156953 + if q.limit > 0 && fmter.Dialect().Name() == dialect.MSSQL { + return append(b, " ORDER BY _temp_sort"...), nil + } + + return b, nil +} + +func (q *orderLimitOffsetQuery) setLimit(n int) { + q.limit = int32(n) +} + +func (q *orderLimitOffsetQuery) setOffset(n int) { + q.offset = int32(n) +} + +func (q *orderLimitOffsetQuery) appendLimitOffset(fmter schema.Formatter, b []byte) (_ []byte, err error) { + if fmter.Dialect().Features().Has(feature.OffsetFetch) { + if q.limit > 0 && q.offset > 0 { + b = append(b, " OFFSET "...) + b = strconv.AppendInt(b, int64(q.offset), 10) + b = append(b, " ROWS"...) + + b = append(b, " FETCH NEXT "...) + b = strconv.AppendInt(b, int64(q.limit), 10) + b = append(b, " ROWS ONLY"...) + } else if q.limit > 0 { + b = append(b, " OFFSET 0 ROWS"...) + + b = append(b, " FETCH NEXT "...) + b = strconv.AppendInt(b, int64(q.limit), 10) + b = append(b, " ROWS ONLY"...) + } else if q.offset > 0 { + b = append(b, " OFFSET "...) + b = strconv.AppendInt(b, int64(q.offset), 10) + b = append(b, " ROWS"...) + } + } else { + if q.limit > 0 { + b = append(b, " LIMIT "...) + b = strconv.AppendInt(b, int64(q.limit), 10) + } + if q.offset > 0 { + b = append(b, " OFFSET "...) + b = strconv.AppendInt(b, int64(q.offset), 10) + } + } + + return b, nil +} diff --git a/vendor/github.com/uptrace/bun/query_column_add.go b/vendor/github.com/uptrace/bun/query_column_add.go index 32a21338e..50576873c 100644 --- a/vendor/github.com/uptrace/bun/query_column_add.go +++ b/vendor/github.com/uptrace/bun/query_column_add.go @@ -42,9 +42,12 @@ func (q *AddColumnQuery) Err(err error) *AddColumnQuery { return q } -func (q *AddColumnQuery) Apply(fn func(*AddColumnQuery) *AddColumnQuery) *AddColumnQuery { - if fn != nil { - return fn(q) +// Apply calls each function in fns, passing the AddColumnQuery as an argument. +func (q *AddColumnQuery) Apply(fns ...func(*AddColumnQuery) *AddColumnQuery) *AddColumnQuery { + for _, fn := range fns { + if fn != nil { + q = fn(q) + } } return q } diff --git a/vendor/github.com/uptrace/bun/query_column_drop.go b/vendor/github.com/uptrace/bun/query_column_drop.go index 1439ed9b9..24fc93cfd 100644 --- a/vendor/github.com/uptrace/bun/query_column_drop.go +++ b/vendor/github.com/uptrace/bun/query_column_drop.go @@ -40,9 +40,12 @@ func (q *DropColumnQuery) Err(err error) *DropColumnQuery { return q } -func (q *DropColumnQuery) Apply(fn func(*DropColumnQuery) *DropColumnQuery) *DropColumnQuery { - if fn != nil { - return fn(q) +// Apply calls each function in fns, passing the DropColumnQuery as an argument. +func (q *DropColumnQuery) Apply(fns ...func(*DropColumnQuery) *DropColumnQuery) *DropColumnQuery { + for _, fn := range fns { + if fn != nil { + q = fn(q) + } } return q } diff --git a/vendor/github.com/uptrace/bun/query_delete.go b/vendor/github.com/uptrace/bun/query_delete.go index 49a750cc8..ccfeb1997 100644 --- a/vendor/github.com/uptrace/bun/query_delete.go +++ b/vendor/github.com/uptrace/bun/query_delete.go @@ -3,6 +3,7 @@ package bun import ( "context" "database/sql" + "errors" "time" "github.com/uptrace/bun/dialect/feature" @@ -12,6 +13,7 @@ import ( type DeleteQuery struct { whereBaseQuery + orderLimitOffsetQuery returningQuery } @@ -44,10 +46,12 @@ func (q *DeleteQuery) Err(err error) *DeleteQuery { return q } -// Apply calls the fn passing the DeleteQuery as an argument. -func (q *DeleteQuery) Apply(fn func(*DeleteQuery) *DeleteQuery) *DeleteQuery { - if fn != nil { - return fn(q) +// Apply calls each function in fns, passing the DeleteQuery as an argument. +func (q *DeleteQuery) Apply(fns ...func(*DeleteQuery) *DeleteQuery) *DeleteQuery { + for _, fn := range fns { + if fn != nil { + q = fn(q) + } } return q } @@ -120,17 +124,50 @@ func (q *DeleteQuery) WhereAllWithDeleted() *DeleteQuery { return q } +func (q *DeleteQuery) Order(orders ...string) *DeleteQuery { + if !q.hasFeature(feature.DeleteOrderLimit) { + q.err = errors.New("bun: order is not supported for current dialect") + return q + } + q.addOrder(orders...) + return q +} + +func (q *DeleteQuery) OrderExpr(query string, args ...interface{}) *DeleteQuery { + if !q.hasFeature(feature.DeleteOrderLimit) { + q.err = errors.New("bun: order is not supported for current dialect") + return q + } + q.addOrderExpr(query, args...) + return q +} + func (q *DeleteQuery) ForceDelete() *DeleteQuery { q.flags = q.flags.Set(forceDeleteFlag) return q } +// ------------------------------------------------------------------------------ +func (q *DeleteQuery) Limit(n int) *DeleteQuery { + if !q.hasFeature(feature.DeleteOrderLimit) { + q.err = errors.New("bun: limit is not supported for current dialect") + return q + } + q.setLimit(n) + return q +} + //------------------------------------------------------------------------------ // Returning adds a RETURNING clause to the query. // // To suppress the auto-generated RETURNING clause, use `Returning("NULL")`. func (q *DeleteQuery) Returning(query string, args ...interface{}) *DeleteQuery { + if !q.hasFeature(feature.DeleteReturning) { + q.err = errors.New("bun: returning is not supported for current dialect") + return q + } + q.addReturning(schema.SafeQuery(query, args)) return q } @@ -203,7 +240,21 @@ func (q *DeleteQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, e return nil, err } - if q.hasFeature(feature.Returning) && q.hasReturning() { + if q.hasMultiTables() && (len(q.order) > 0 || q.limit > 0) { + return nil, errors.New("bun: can't use ORDER or LIMIT with multiple tables") + } + + b, err = q.appendOrder(fmter, b) + if err != nil { + return nil, err + } + + b, err = q.appendLimitOffset(fmter, b) + if err != nil { + return nil, err + } + + if q.hasFeature(feature.DeleteReturning) && q.hasReturning() { b = append(b, " RETURNING "...) b, err = q.appendReturning(fmter, b) if err != nil { @@ -265,7 +316,7 @@ func (q *DeleteQuery) scanOrExec( return nil, err } - useScan := hasDest || (q.hasReturning() && q.hasFeature(feature.Returning|feature.Output)) + useScan := hasDest || (q.hasReturning() && q.hasFeature(feature.DeleteReturning|feature.Output)) var model Model if useScan { diff --git a/vendor/github.com/uptrace/bun/query_insert.go b/vendor/github.com/uptrace/bun/query_insert.go index 6d38a4efe..b6747cd65 100644 --- a/vendor/github.com/uptrace/bun/query_insert.go +++ b/vendor/github.com/uptrace/bun/query_insert.go @@ -53,10 +53,12 @@ func (q *InsertQuery) Err(err error) *InsertQuery { return q } -// Apply calls the fn passing the SelectQuery as an argument. -func (q *InsertQuery) Apply(fn func(*InsertQuery) *InsertQuery) *InsertQuery { - if fn != nil { - return fn(q) +// Apply calls each function in fns, passing the InsertQuery as an argument. +func (q *InsertQuery) Apply(fns ...func(*InsertQuery) *InsertQuery) *InsertQuery { + for _, fn := range fns { + if fn != nil { + q = fn(q) + } } return q } diff --git a/vendor/github.com/uptrace/bun/query_merge.go b/vendor/github.com/uptrace/bun/query_merge.go index 626752b8a..3c3f4f7f8 100644 --- a/vendor/github.com/uptrace/bun/query_merge.go +++ b/vendor/github.com/uptrace/bun/query_merge.go @@ -50,10 +50,12 @@ func (q *MergeQuery) Err(err error) *MergeQuery { return q } -// Apply calls the fn passing the MergeQuery as an argument. -func (q *MergeQuery) Apply(fn func(*MergeQuery) *MergeQuery) *MergeQuery { - if fn != nil { - return fn(q) +// Apply calls each function in fns, passing the MergeQuery as an argument. +func (q *MergeQuery) Apply(fns ...func(*MergeQuery) *MergeQuery) *MergeQuery { + for _, fn := range fns { + if fn != nil { + q = fn(q) + } } return q } diff --git a/vendor/github.com/uptrace/bun/query_raw.go b/vendor/github.com/uptrace/bun/query_raw.go index fda088a7c..1634d0e5b 100644 --- a/vendor/github.com/uptrace/bun/query_raw.go +++ b/vendor/github.com/uptrace/bun/query_raw.go @@ -96,3 +96,12 @@ func (q *RawQuery) AppendQuery(fmter schema.Formatter, b []byte) ([]byte, error) func (q *RawQuery) Operation() string { return "SELECT" } + +func (q *RawQuery) String() string { + buf, err := q.AppendQuery(q.db.Formatter(), nil) + if err != nil { + panic(err) + } + + return string(buf) +} diff --git a/vendor/github.com/uptrace/bun/query_select.go b/vendor/github.com/uptrace/bun/query_select.go index 5bb329143..2b0872ae0 100644 --- a/vendor/github.com/uptrace/bun/query_select.go +++ b/vendor/github.com/uptrace/bun/query_select.go @@ -6,8 +6,6 @@ import ( "database/sql" "errors" "fmt" - "strconv" - "strings" "sync" "github.com/uptrace/bun/dialect" @@ -25,14 +23,12 @@ type union struct { type SelectQuery struct { whereBaseQuery idxHintsQuery + orderLimitOffsetQuery distinctOn []schema.QueryWithArgs joins []joinQuery group []schema.QueryWithArgs having []schema.QueryWithArgs - order []schema.QueryWithArgs - limit int32 - offset int32 selFor schema.QueryWithArgs union []union @@ -66,10 +62,12 @@ func (q *SelectQuery) Err(err error) *SelectQuery { return q } -// Apply calls the fn passing the SelectQuery as an argument. -func (q *SelectQuery) Apply(fn func(*SelectQuery) *SelectQuery) *SelectQuery { - if fn != nil { - return fn(q) +// Apply calls each function in fns, passing the SelectQuery as an argument. +func (q *SelectQuery) Apply(fns ...func(*SelectQuery) *SelectQuery) *SelectQuery { + for _, fn := range fns { + if fn != nil { + q = fn(q) + } } return q } @@ -279,46 +277,22 @@ func (q *SelectQuery) Having(having string, args ...interface{}) *SelectQuery { } func (q *SelectQuery) Order(orders ...string) *SelectQuery { - for _, order := range orders { - if order == "" { - continue - } - - index := strings.IndexByte(order, ' ') - if index == -1 { - q.order = append(q.order, schema.UnsafeIdent(order)) - continue - } - - field := order[:index] - sort := order[index+1:] - - switch strings.ToUpper(sort) { - case "ASC", "DESC", "ASC NULLS FIRST", "DESC NULLS FIRST", - "ASC NULLS LAST", "DESC NULLS LAST": - q.order = append(q.order, schema.SafeQuery("? ?", []interface{}{ - Ident(field), - Safe(sort), - })) - default: - q.order = append(q.order, schema.UnsafeIdent(order)) - } - } + q.addOrder(orders...) return q } func (q *SelectQuery) OrderExpr(query string, args ...interface{}) *SelectQuery { - q.order = append(q.order, schema.SafeQuery(query, args)) + q.addOrderExpr(query, args...) return q } func (q *SelectQuery) Limit(n int) *SelectQuery { - q.limit = int32(n) + q.setLimit(n) return q } func (q *SelectQuery) Offset(n int) *SelectQuery { - q.offset = int32(n) + q.setOffset(n) return q } @@ -615,35 +589,9 @@ func (q *SelectQuery) appendQuery( return nil, err } - if fmter.Dialect().Features().Has(feature.OffsetFetch) { - if q.limit > 0 && q.offset > 0 { - b = append(b, " OFFSET "...) - b = strconv.AppendInt(b, int64(q.offset), 10) - b = append(b, " ROWS"...) - - b = append(b, " FETCH NEXT "...) - b = strconv.AppendInt(b, int64(q.limit), 10) - b = append(b, " ROWS ONLY"...) - } else if q.limit > 0 { - b = append(b, " OFFSET 0 ROWS"...) - - b = append(b, " FETCH NEXT "...) - b = strconv.AppendInt(b, int64(q.limit), 10) - b = append(b, " ROWS ONLY"...) - } else if q.offset > 0 { - b = append(b, " OFFSET "...) - b = strconv.AppendInt(b, int64(q.offset), 10) - b = append(b, " ROWS"...) - } - } else { - if q.limit > 0 { - b = append(b, " LIMIT "...) - b = strconv.AppendInt(b, int64(q.limit), 10) - } - if q.offset > 0 { - b = append(b, " OFFSET "...) - b = strconv.AppendInt(b, int64(q.offset), 10) - } + b, err = q.appendLimitOffset(fmter, b) + if err != nil { + return nil, err } if !q.selFor.IsZero() { @@ -782,31 +730,6 @@ func (q *SelectQuery) appendTables(fmter schema.Formatter, b []byte) (_ []byte, return q.appendTablesWithAlias(fmter, b) } -func (q *SelectQuery) appendOrder(fmter schema.Formatter, b []byte) (_ []byte, err error) { - if len(q.order) > 0 { - b = append(b, " ORDER BY "...) - - for i, f := range q.order { - if i > 0 { - b = append(b, ", "...) - } - b, err = f.AppendQuery(fmter, b) - if err != nil { - return nil, err - } - } - - return b, nil - } - - // MSSQL: allows Limit() without Order() as per https://stackoverflow.com/a/36156953 - if q.limit > 0 && fmter.Dialect().Name() == dialect.MSSQL { - return append(b, " ORDER BY _temp_sort"...), nil - } - - return b, nil -} - //------------------------------------------------------------------------------ func (q *SelectQuery) Rows(ctx context.Context) (*sql.Rows, error) { diff --git a/vendor/github.com/uptrace/bun/query_table_drop.go b/vendor/github.com/uptrace/bun/query_table_drop.go index e4447a8d2..a92014515 100644 --- a/vendor/github.com/uptrace/bun/query_table_drop.go +++ b/vendor/github.com/uptrace/bun/query_table_drop.go @@ -151,3 +151,12 @@ func (q *DropTableQuery) afterDropTableHook(ctx context.Context) error { } return nil } + +func (q *DropTableQuery) String() string { + buf, err := q.AppendQuery(q.db.Formatter(), nil) + if err != nil { + panic(err) + } + + return string(buf) +} diff --git a/vendor/github.com/uptrace/bun/query_update.go b/vendor/github.com/uptrace/bun/query_update.go index e56ba20d1..bb9264084 100644 --- a/vendor/github.com/uptrace/bun/query_update.go +++ b/vendor/github.com/uptrace/bun/query_update.go @@ -15,6 +15,7 @@ import ( type UpdateQuery struct { whereBaseQuery + orderLimitOffsetQuery returningQuery customValueQuery setQuery @@ -53,10 +54,12 @@ func (q *UpdateQuery) Err(err error) *UpdateQuery { return q } -// Apply calls the fn passing the SelectQuery as an argument. -func (q *UpdateQuery) Apply(fn func(*UpdateQuery) *UpdateQuery) *UpdateQuery { - if fn != nil { - return fn(q) +// Apply calls each function in fns, passing the UpdateQuery as an argument. +func (q *UpdateQuery) Apply(fns ...func(*UpdateQuery) *UpdateQuery) *UpdateQuery { + for _, fn := range fns { + if fn != nil { + q = fn(q) + } } return q } @@ -200,6 +203,34 @@ func (q *UpdateQuery) WhereAllWithDeleted() *UpdateQuery { return q } +// ------------------------------------------------------------------------------ +func (q *UpdateQuery) Order(orders ...string) *UpdateQuery { + if !q.hasFeature(feature.UpdateOrderLimit) { + q.err = errors.New("bun: order is not supported for current dialect") + return q + } + q.addOrder(orders...) + return q +} + +func (q *UpdateQuery) OrderExpr(query string, args ...interface{}) *UpdateQuery { + if !q.hasFeature(feature.UpdateOrderLimit) { + q.err = errors.New("bun: order is not supported for current dialect") + return q + } + q.addOrderExpr(query, args...) + return q +} + +func (q *UpdateQuery) Limit(n int) *UpdateQuery { + if !q.hasFeature(feature.UpdateOrderLimit) { + q.err = errors.New("bun: limit is not supported for current dialect") + return q + } + q.setLimit(n) + return q +} + //------------------------------------------------------------------------------ // Returning adds a RETURNING clause to the query. @@ -278,6 +309,16 @@ func (q *UpdateQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, e return nil, err } + b, err = q.appendOrder(fmter, b) + if err != nil { + return nil, err + } + + b, err = q.appendLimitOffset(fmter, b) + if err != nil { + return nil, err + } + if q.hasFeature(feature.Returning) && q.hasReturning() { b = append(b, " RETURNING "...) b, err = q.appendReturning(fmter, b) diff --git a/vendor/github.com/uptrace/bun/schema/dialect.go b/vendor/github.com/uptrace/bun/schema/dialect.go index 330293444..bb40af62b 100644 --- a/vendor/github.com/uptrace/bun/schema/dialect.go +++ b/vendor/github.com/uptrace/bun/schema/dialect.go @@ -39,6 +39,9 @@ type Dialect interface { // is mandatory in queries that modify the schema (CREATE TABLE / ADD COLUMN, etc). // Dialects that do not have such requirement may return 0, which should be interpreted so by the caller. DefaultVarcharLen() int + + // DefaultSchema should returns the name of the default database schema. + DefaultSchema() string } // ------------------------------------------------------------------------------ @@ -185,3 +188,7 @@ func (d *nopDialect) DefaultVarcharLen() int { func (d *nopDialect) AppendSequence(b []byte, _ *Table, _ *Field) []byte { return b } + +func (d *nopDialect) DefaultSchema() string { + return "nop" +} diff --git a/vendor/github.com/uptrace/bun/schema/table.go b/vendor/github.com/uptrace/bun/schema/table.go index c8e71e38f..82132c4f1 100644 --- a/vendor/github.com/uptrace/bun/schema/table.go +++ b/vendor/github.com/uptrace/bun/schema/table.go @@ -45,6 +45,7 @@ type Table struct { TypeName string ModelName string + Schema string Name string SQLName Safe SQLNameForSelects Safe @@ -85,6 +86,7 @@ func (table *Table) init(dialect Dialect, typ reflect.Type, canAddr bool) { table.setName(tableName) table.Alias = table.ModelName table.SQLAlias = table.quoteIdent(table.ModelName) + table.Schema = dialect.DefaultSchema() table.Fields = make([]*Field, 0, typ.NumField()) table.FieldMap = make(map[string]*Field, typ.NumField()) @@ -244,6 +246,31 @@ func (t *Table) processFields(typ reflect.Type, canAddr bool) { subfield.SQLName = t.quoteIdent(subfield.Name) } t.addField(subfield) + if v, ok := subfield.Tag.Options["unique"]; ok { + t.addUnique(subfield, embfield.prefix, v) + } + } +} + +func (t *Table) addUnique(field *Field, prefix string, tagOptions []string) { + var names []string + if len(tagOptions) == 1 { + // Split the value by comma, this will allow multiple names to be specified. + // We can use this to create multiple named unique constraints where a single column + // might be included in multiple constraints. + names = strings.Split(tagOptions[0], ",") + } else { + names = tagOptions + } + + for _, uname := range names { + if t.Unique == nil { + t.Unique = make(map[string][]*Field) + } + if uname != "" && prefix != "" { + uname = prefix + uname + } + t.Unique[uname] = append(t.Unique[uname], field) } } @@ -371,10 +398,18 @@ func (t *Table) processBaseModelField(f reflect.StructField) { } if tag.Name != "" { + schema, _ := t.schemaFromTagName(tag.Name) + t.Schema = schema + + // Eventually, we should only assign the "table" portion as the table name, + // which will also require a change in how the table name is appended to queries. + // Until that is done, set table name to tag.Name. t.setName(tag.Name) } if s, ok := tag.Option("table"); ok { + schema, _ := t.schemaFromTagName(s) + t.Schema = schema t.setName(s) } @@ -388,6 +423,17 @@ func (t *Table) processBaseModelField(f reflect.StructField) { } } +// schemaFromTagName splits the bun.BaseModel tag name into schema and table name +// in case it is specified in the "schema"."table" format. +// Assume default schema if one isn't explicitly specified. +func (t *Table) schemaFromTagName(name string) (string, string) { + schema, table := t.dialect.DefaultSchema(), name + if schemaTable := strings.Split(name, "."); len(schemaTable) == 2 { + schema, table = schemaTable[0], schemaTable[1] + } + return schema, table +} + // nolint func (t *Table) newField(sf reflect.StructField, tag tagparser.Tag) *Field { sqlName := internal.Underscore(sf.Name) @@ -439,22 +485,7 @@ func (t *Table) newField(sf reflect.StructField, tag tagparser.Tag) *Field { } if v, ok := tag.Options["unique"]; ok { - var names []string - if len(v) == 1 { - // Split the value by comma, this will allow multiple names to be specified. - // We can use this to create multiple named unique constraints where a single column - // might be included in multiple constraints. - names = strings.Split(v[0], ",") - } else { - names = v - } - - for _, uniqueName := range names { - if t.Unique == nil { - t.Unique = make(map[string][]*Field) - } - t.Unique[uniqueName] = append(t.Unique[uniqueName], field) - } + t.addUnique(field, "", v) } if s, ok := tag.Option("default"); ok { field.SQLDefault = s diff --git a/vendor/github.com/uptrace/bun/schema/tables.go b/vendor/github.com/uptrace/bun/schema/tables.go index 985093421..58c45cbee 100644 --- a/vendor/github.com/uptrace/bun/schema/tables.go +++ b/vendor/github.com/uptrace/bun/schema/tables.go @@ -77,6 +77,7 @@ func (t *Tables) InProgress(typ reflect.Type) *Table { return table } +// ByModel gets the table by its Go name. func (t *Tables) ByModel(name string) *Table { var found *Table t.tables.Range(func(typ reflect.Type, table *Table) bool { @@ -89,6 +90,7 @@ func (t *Tables) ByModel(name string) *Table { return found } +// ByName gets the table by its SQL name. func (t *Tables) ByName(name string) *Table { var found *Table t.tables.Range(func(typ reflect.Type, table *Table) bool { @@ -100,3 +102,13 @@ func (t *Tables) ByName(name string) *Table { }) return found } + +// All returns all registered tables. +func (t *Tables) All() []*Table { + var found []*Table + t.tables.Range(func(typ reflect.Type, table *Table) bool { + found = append(found, table) + return true + }) + return found +} diff --git a/vendor/github.com/uptrace/bun/version.go b/vendor/github.com/uptrace/bun/version.go index 7f23c12c3..244d63465 100644 --- a/vendor/github.com/uptrace/bun/version.go +++ b/vendor/github.com/uptrace/bun/version.go @@ -2,5 +2,5 @@ package bun // Version is the current release version. func Version() string { - return "1.2.5" + return "1.2.6" } diff --git a/vendor/github.com/wk8/go-ordered-map/v2/.gitignore b/vendor/github.com/wk8/go-ordered-map/v2/.gitignore new file mode 100644 index 000000000..57872d0f1 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/vendor/github.com/wk8/go-ordered-map/v2/.golangci.yml b/vendor/github.com/wk8/go-ordered-map/v2/.golangci.yml new file mode 100644 index 000000000..f305c9db1 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/.golangci.yml @@ -0,0 +1,78 @@ +run: + tests: false + +linters: + disable-all: true + enable: + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - decorder + # Disabling depguard as there is no guarded list of imports + # - depguard + - dogsled + - dupl + - durationcheck + - errcheck + - errchkjson + - errname + - errorlint + - exportloopref + - forbidigo + - funlen + # Don't need gci and goimports + # - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godox + - gofmt + - gofumpt + - goheader + - goimports + - mnd + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - govet + - grouper + - importas + - ineffassign + - lll + - maintidx + - makezero + - misspell + - nakedret + - nilerr + - nilnil + - noctx + - nolintlint + - paralleltest + - prealloc + - predeclared + - promlinter + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tagliatelle + - tenv + - testpackage + - thelper + - tparallel + # FIXME: doesn't support 1.23 yet + # - typecheck + - unconvert + - unparam + - unused + - varnamelen + - wastedassign + - whitespace diff --git a/vendor/github.com/wk8/go-ordered-map/v2/CHANGELOG.md b/vendor/github.com/wk8/go-ordered-map/v2/CHANGELOG.md new file mode 100644 index 000000000..f27126f84 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +[comment]: # (Changes since last release go here) + +## 2.1.8 - Jun 27th 2023 + +* Added support for YAML serialization/deserialization + +## 2.1.7 - Apr 13th 2023 + +* Renamed test_utils.go to utils_test.go + +## 2.1.6 - Feb 15th 2023 + +* Added `GetAndMoveToBack()` and `GetAndMoveToFront()` methods + +## 2.1.5 - Dec 13th 2022 + +* Added `Value()` method + +## 2.1.4 - Dec 12th 2022 + +* Fixed a bug with UTF-8 special characters in JSON keys + +## 2.1.3 - Dec 11th 2022 + +* Added support for JSON marshalling/unmarshalling of wrapper of primitive types + +## 2.1.2 - Dec 10th 2022 +* Allowing to pass options to `New`, to give a capacity hint, or initial data +* Allowing to deserialize nested ordered maps from JSON without having to explicitly instantiate them +* Added the `AddPairs` method + +## 2.1.1 - Dec 9th 2022 +* Fixing a bug with JSON marshalling + +## 2.1.0 - Dec 7th 2022 +* Added support for JSON serialization/deserialization diff --git a/vendor/github.com/wk8/go-ordered-map/v2/LICENSE b/vendor/github.com/wk8/go-ordered-map/v2/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/wk8/go-ordered-map/v2/Makefile b/vendor/github.com/wk8/go-ordered-map/v2/Makefile new file mode 100644 index 000000000..6e0e18a1b --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/Makefile @@ -0,0 +1,32 @@ +.DEFAULT_GOAL := all + +.PHONY: all +all: test_with_fuzz lint + +# the TEST_FLAGS env var can be set to eg run only specific tests +TEST_COMMAND = go test -v -count=1 -race -cover $(TEST_FLAGS) + +.PHONY: test +test: + $(TEST_COMMAND) + +.PHONY: bench +bench: + go test -bench=. + +FUZZ_TIME ?= 10s + +# see https://github.com/golang/go/issues/46312 +# and https://stackoverflow.com/a/72673487/4867444 +# if we end up having more fuzz tests +.PHONY: test_with_fuzz +test_with_fuzz: + $(TEST_COMMAND) -fuzz=FuzzRoundTripJSON -fuzztime=$(FUZZ_TIME) + $(TEST_COMMAND) -fuzz=FuzzRoundTripYAML -fuzztime=$(FUZZ_TIME) + +.PHONY: fuzz +fuzz: test_with_fuzz + +.PHONY: lint +lint: + golangci-lint run diff --git a/vendor/github.com/wk8/go-ordered-map/v2/README.md b/vendor/github.com/wk8/go-ordered-map/v2/README.md new file mode 100644 index 000000000..ebd8a4d37 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/README.md @@ -0,0 +1,207 @@ +[![Go Reference](https://pkg.go.dev/badge/github.com/wk8/go-ordered-map/v2.svg)](https://pkg.go.dev/github.com/wk8/go-ordered-map/v2) +[![Build Status](https://circleci.com/gh/wk8/go-ordered-map.svg?style=svg)](https://app.circleci.com/pipelines/github/wk8/go-ordered-map) + +# Golang Ordered Maps + +Same as regular maps, but also remembers the order in which keys were inserted, akin to [Python's `collections.OrderedDict`s](https://docs.python.org/3.7/library/collections.html#ordereddict-objects). + +It offers the following features: +* optimal runtime performance (all operations are constant time) +* optimal memory usage (only one copy of values, no unnecessary memory allocation) +* allows iterating from newest or oldest keys indifferently, without memory copy, allowing to `break` the iteration, and in time linear to the number of keys iterated over rather than the total length of the ordered map +* supports any generic types for both keys and values. If you're running go < 1.18, you can use [version 1](https://github.com/wk8/go-ordered-map/tree/v1) that takes and returns generic `interface{}`s instead of using generics +* idiomatic API, akin to that of [`container/list`](https://golang.org/pkg/container/list) +* support for JSON and YAML marshalling + +## Documentation + +[The full documentation is available on pkg.go.dev](https://pkg.go.dev/github.com/wk8/go-ordered-map/v2). + +## Installation +```bash +go get -u github.com/wk8/go-ordered-map/v2 +``` + +Or use your favorite golang vendoring tool! + +## Supported go versions + +Go >= 1.23 is required to use version >= 2.2.0 of this library, as it uses generics and iterators. + +if you're running go < 1.23, you can use [version 2.1.8](https://github.com/wk8/go-ordered-map/tree/v2.1.8) instead. + +If you're running go < 1.18, you can use [version 1](https://github.com/wk8/go-ordered-map/tree/v1) instead. + +## Example / usage + +```go +package main + +import ( + "fmt" + + "github.com/wk8/go-ordered-map/v2" +) + +func main() { + om := orderedmap.New[string, string]() + + om.Set("foo", "bar") + om.Set("bar", "baz") + om.Set("coucou", "toi") + + fmt.Println(om.Get("foo")) // => "bar", true + fmt.Println(om.Get("i dont exist")) // => "", false + + // iterating pairs from oldest to newest: + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + fmt.Printf("%s => %s\n", pair.Key, pair.Value) + } // prints: + // foo => bar + // bar => baz + // coucou => toi + + // iterating over the 2 newest pairs: + i := 0 + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + fmt.Printf("%s => %s\n", pair.Key, pair.Value) + i++ + if i >= 2 { + break + } + } // prints: + // coucou => toi + // bar => baz +} +``` + +An `OrderedMap`'s keys must implement `comparable`, and its values can be anything, for example: + +```go +type myStruct struct { + payload string +} + +func main() { + om := orderedmap.New[int, *myStruct]() + + om.Set(12, &myStruct{"foo"}) + om.Set(1, &myStruct{"bar"}) + + value, present := om.Get(12) + if !present { + panic("should be there!") + } + fmt.Println(value.payload) // => foo + + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + fmt.Printf("%d => %s\n", pair.Key, pair.Value.payload) + } // prints: + // 12 => foo + // 1 => bar +} +``` + +Also worth noting that you can provision ordered maps with a capacity hint, as you would do by passing an optional hint to `make(map[K]V, capacity`): +```go +om := orderedmap.New[int, *myStruct](28) +``` + +You can also pass in some initial data to store in the map: +```go +om := orderedmap.New[int, string](orderedmap.WithInitialData[int, string]( + orderedmap.Pair[int, string]{ + Key: 12, + Value: "foo", + }, + orderedmap.Pair[int, string]{ + Key: 28, + Value: "bar", + }, +)) +``` + +`OrderedMap`s also support JSON serialization/deserialization, and preserves order: + +```go +// serialization +data, err := json.Marshal(om) +... + +// deserialization +om := orderedmap.New[string, string]() // or orderedmap.New[int, any](), or any type you expect +err := json.Unmarshal(data, &om) +... +``` + +Similarly, it also supports YAML serialization/deserialization using the yaml.v3 package, which also preserves order: + +```go +// serialization +data, err := yaml.Marshal(om) +... + +// deserialization +om := orderedmap.New[string, string]() // or orderedmap.New[int, any](), or any type you expect +err := yaml.Unmarshal(data, &om) +... +``` + +## Iterator support (go >= 1.23) + +The `FromOldest`, `FromNewest`, `KeysFromOldest`, `KeysFromNewest`, `ValuesFromOldest` and `ValuesFromNewest` methods return iterators over the map's pairs, starting from the oldest or newest pair, respectively. + +For example: + +```go +om := orderedmap.New[int, string]() +om.Set(1, "foo") +om.Set(2, "bar") +om.Set(3, "baz") + +for k, v := range om.FromOldest() { + fmt.Printf("%d => %s\n", k, v) +} + +// prints: +// 1 => foo +// 2 => bar +// 3 => baz + +for k := range om.KeysNewest() { + fmt.Printf("%d\n", k) +} + +// prints: +// 3 +// 2 +// 1 +``` + +`From` is a convenience function that creates a new `OrderedMap` from an iterator over key-value pairs. + +```go +om := orderedmap.New[int, string]() +om.Set(1, "foo") +om.Set(2, "bar") +om.Set(3, "baz") + +om2 := orderedmap.From(om.FromOldest()) + +for k, v := range om2.FromOldest() { + fmt.Printf("%d => %s\n", k, v) +} + +// prints: +// 1 => foo +// 2 => bar +// 3 => baz +``` + +## Alternatives + +There are several other ordered map golang implementations out there, but I believe that at the time of writing none of them offer the same functionality as this library; more specifically: +* [iancoleman/orderedmap](https://github.com/iancoleman/orderedmap) only accepts `string` keys, its `Delete` operations are linear +* [cevaris/ordered_map](https://github.com/cevaris/ordered_map) uses a channel for iterations, and leaks goroutines if the iteration is interrupted before fully traversing the map +* [mantyr/iterator](https://github.com/mantyr/iterator) also uses a channel for iterations, and its `Delete` operations are linear +* [samdolan/go-ordered-map](https://github.com/samdolan/go-ordered-map) adds unnecessary locking (users should add their own locking instead if they need it), its `Delete` and `Get` operations are linear, iterations trigger a linear memory allocation diff --git a/vendor/github.com/wk8/go-ordered-map/v2/json.go b/vendor/github.com/wk8/go-ordered-map/v2/json.go new file mode 100644 index 000000000..53f176a05 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/json.go @@ -0,0 +1,182 @@ +package orderedmap + +import ( + "bytes" + "encoding" + "encoding/json" + "fmt" + "reflect" + "unicode/utf8" + + "github.com/buger/jsonparser" + "github.com/mailru/easyjson/jwriter" +) + +var ( + _ json.Marshaler = &OrderedMap[int, any]{} + _ json.Unmarshaler = &OrderedMap[int, any]{} +) + +// MarshalJSON implements the json.Marshaler interface. +func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { //nolint:funlen + if om == nil || om.list == nil { + return []byte("null"), nil + } + + writer := jwriter.Writer{} + writer.RawByte('{') + + for pair, firstIteration := om.Oldest(), true; pair != nil; pair = pair.Next() { + if firstIteration { + firstIteration = false + } else { + writer.RawByte(',') + } + + switch key := any(pair.Key).(type) { + case string: + writer.String(key) + case encoding.TextMarshaler: + writer.RawByte('"') + writer.Raw(key.MarshalText()) + writer.RawByte('"') + case int: + writer.IntStr(key) + case int8: + writer.Int8Str(key) + case int16: + writer.Int16Str(key) + case int32: + writer.Int32Str(key) + case int64: + writer.Int64Str(key) + case uint: + writer.UintStr(key) + case uint8: + writer.Uint8Str(key) + case uint16: + writer.Uint16Str(key) + case uint32: + writer.Uint32Str(key) + case uint64: + writer.Uint64Str(key) + default: + + // this switch takes care of wrapper types around primitive types, such as + // type myType string + switch keyValue := reflect.ValueOf(key); keyValue.Type().Kind() { + case reflect.String: + writer.String(keyValue.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + writer.Int64Str(keyValue.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + writer.Uint64Str(keyValue.Uint()) + default: + return nil, fmt.Errorf("unsupported key type: %T", key) + } + } + + writer.RawByte(':') + // the error is checked at the end of the function + writer.Raw(json.Marshal(pair.Value)) + } + + writer.RawByte('}') + + return dumpWriter(&writer) +} + +func dumpWriter(writer *jwriter.Writer) ([]byte, error) { + if writer.Error != nil { + return nil, writer.Error + } + + var buf bytes.Buffer + buf.Grow(writer.Size()) + if _, err := writer.DumpTo(&buf); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (om *OrderedMap[K, V]) UnmarshalJSON(data []byte) error { + if om.list == nil { + om.initialize(0) + } + + return jsonparser.ObjectEach( + data, + func(keyData []byte, valueData []byte, dataType jsonparser.ValueType, offset int) error { + if dataType == jsonparser.String { + // jsonparser removes the enclosing quotes; we need to restore them to make a valid JSON + valueData = data[offset-len(valueData)-2 : offset] + } + + var key K + var value V + + switch typedKey := any(&key).(type) { + case *string: + s, err := decodeUTF8(keyData) + if err != nil { + return err + } + *typedKey = s + case encoding.TextUnmarshaler: + if err := typedKey.UnmarshalText(keyData); err != nil { + return err + } + case *int, *int8, *int16, *int32, *int64, *uint, *uint8, *uint16, *uint32, *uint64: + if err := json.Unmarshal(keyData, typedKey); err != nil { + return err + } + default: + // this switch takes care of wrapper types around primitive types, such as + // type myType string + switch reflect.TypeOf(key).Kind() { + case reflect.String: + s, err := decodeUTF8(keyData) + if err != nil { + return err + } + + convertedKeyData := reflect.ValueOf(s).Convert(reflect.TypeOf(key)) + reflect.ValueOf(&key).Elem().Set(convertedKeyData) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if err := json.Unmarshal(keyData, &key); err != nil { + return err + } + default: + return fmt.Errorf("unsupported key type: %T", key) + } + } + + if err := json.Unmarshal(valueData, &value); err != nil { + return err + } + + om.Set(key, value) + return nil + }) +} + +func decodeUTF8(input []byte) (string, error) { + remaining, offset := input, 0 + runes := make([]rune, 0, len(remaining)) + + for len(remaining) > 0 { + r, size := utf8.DecodeRune(remaining) + if r == utf8.RuneError && size <= 1 { + return "", fmt.Errorf("not a valid UTF-8 string (at position %d): %s", offset, string(input)) + } + + runes = append(runes, r) + remaining = remaining[size:] + offset += size + } + + return string(runes), nil +} diff --git a/vendor/github.com/wk8/go-ordered-map/v2/orderedmap.go b/vendor/github.com/wk8/go-ordered-map/v2/orderedmap.go new file mode 100644 index 000000000..45bf8622d --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/orderedmap.go @@ -0,0 +1,373 @@ +// Package orderedmap implements an ordered map, i.e. a map that also keeps track of +// the order in which keys were inserted. +// +// All operations are constant-time. +// +// Github repo: https://github.com/wk8/go-ordered-map +package orderedmap + +import ( + "fmt" + "iter" + + list "github.com/bahlo/generic-list-go" +) + +type Pair[K comparable, V any] struct { + Key K + Value V + + element *list.Element[*Pair[K, V]] +} + +type OrderedMap[K comparable, V any] struct { + pairs map[K]*Pair[K, V] + list *list.List[*Pair[K, V]] +} + +type initConfig[K comparable, V any] struct { + capacity int + initialData []Pair[K, V] +} + +type InitOption[K comparable, V any] func(config *initConfig[K, V]) + +// WithCapacity allows giving a capacity hint for the map, akin to the standard make(map[K]V, capacity). +func WithCapacity[K comparable, V any](capacity int) InitOption[K, V] { + return func(c *initConfig[K, V]) { + c.capacity = capacity + } +} + +// WithInitialData allows passing in initial data for the map. +func WithInitialData[K comparable, V any](initialData ...Pair[K, V]) InitOption[K, V] { + return func(c *initConfig[K, V]) { + c.initialData = initialData + if c.capacity < len(initialData) { + c.capacity = len(initialData) + } + } +} + +// New creates a new OrderedMap. +// options can either be one or several InitOption[K, V], or a single integer, +// which is then interpreted as a capacity hint, à la make(map[K]V, capacity). +func New[K comparable, V any](options ...any) *OrderedMap[K, V] { + orderedMap := &OrderedMap[K, V]{} + + var config initConfig[K, V] + for _, untypedOption := range options { + switch option := untypedOption.(type) { + case int: + if len(options) != 1 { + invalidOption() + } + config.capacity = option + + case InitOption[K, V]: + option(&config) + + default: + invalidOption() + } + } + + orderedMap.initialize(config.capacity) + orderedMap.AddPairs(config.initialData...) + + return orderedMap +} + +const invalidOptionMessage = `when using orderedmap.New[K,V]() with options, either provide one or several InitOption[K, V]; or a single integer which is then interpreted as a capacity hint, à la make(map[K]V, capacity).` //nolint:lll + +func invalidOption() { panic(invalidOptionMessage) } + +func (om *OrderedMap[K, V]) initialize(capacity int) { + om.pairs = make(map[K]*Pair[K, V], capacity) + om.list = list.New[*Pair[K, V]]() +} + +// Get looks for the given key, and returns the value associated with it, +// or V's nil value if not found. The boolean it returns says whether the key is present in the map. +func (om *OrderedMap[K, V]) Get(key K) (val V, present bool) { + if pair, present := om.pairs[key]; present { + return pair.Value, true + } + + return +} + +// Load is an alias for Get, mostly to present an API similar to `sync.Map`'s. +func (om *OrderedMap[K, V]) Load(key K) (V, bool) { + return om.Get(key) +} + +// Value returns the value associated with the given key or the zero value. +func (om *OrderedMap[K, V]) Value(key K) (val V) { + if pair, present := om.pairs[key]; present { + val = pair.Value + } + return +} + +// GetPair looks for the given key, and returns the pair associated with it, +// or nil if not found. The Pair struct can then be used to iterate over the ordered map +// from that point, either forward or backward. +func (om *OrderedMap[K, V]) GetPair(key K) *Pair[K, V] { + return om.pairs[key] +} + +// Set sets the key-value pair, and returns what `Get` would have returned +// on that key prior to the call to `Set`. +func (om *OrderedMap[K, V]) Set(key K, value V) (val V, present bool) { + if pair, present := om.pairs[key]; present { + oldValue := pair.Value + pair.Value = value + return oldValue, true + } + + pair := &Pair[K, V]{ + Key: key, + Value: value, + } + pair.element = om.list.PushBack(pair) + om.pairs[key] = pair + + return +} + +// AddPairs allows setting multiple pairs at a time. It's equivalent to calling +// Set on each pair sequentially. +func (om *OrderedMap[K, V]) AddPairs(pairs ...Pair[K, V]) { + for _, pair := range pairs { + om.Set(pair.Key, pair.Value) + } +} + +// Store is an alias for Set, mostly to present an API similar to `sync.Map`'s. +func (om *OrderedMap[K, V]) Store(key K, value V) (V, bool) { + return om.Set(key, value) +} + +// Delete removes the key-value pair, and returns what `Get` would have returned +// on that key prior to the call to `Delete`. +func (om *OrderedMap[K, V]) Delete(key K) (val V, present bool) { + if pair, present := om.pairs[key]; present { + om.list.Remove(pair.element) + delete(om.pairs, key) + return pair.Value, true + } + return +} + +// Len returns the length of the ordered map. +func (om *OrderedMap[K, V]) Len() int { + if om == nil || om.pairs == nil { + return 0 + } + return len(om.pairs) +} + +// Oldest returns a pointer to the oldest pair. It's meant to be used to iterate on the ordered map's +// pairs from the oldest to the newest, e.g.: +// for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) } +func (om *OrderedMap[K, V]) Oldest() *Pair[K, V] { + if om == nil || om.list == nil { + return nil + } + return listElementToPair(om.list.Front()) +} + +// Newest returns a pointer to the newest pair. It's meant to be used to iterate on the ordered map's +// pairs from the newest to the oldest, e.g.: +// for pair := orderedMap.Newest(); pair != nil; pair = pair.Prev() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) } +func (om *OrderedMap[K, V]) Newest() *Pair[K, V] { + if om == nil || om.list == nil { + return nil + } + return listElementToPair(om.list.Back()) +} + +// Next returns a pointer to the next pair. +func (p *Pair[K, V]) Next() *Pair[K, V] { + return listElementToPair(p.element.Next()) +} + +// Prev returns a pointer to the previous pair. +func (p *Pair[K, V]) Prev() *Pair[K, V] { + return listElementToPair(p.element.Prev()) +} + +func listElementToPair[K comparable, V any](element *list.Element[*Pair[K, V]]) *Pair[K, V] { + if element == nil { + return nil + } + return element.Value +} + +// KeyNotFoundError may be returned by functions in this package when they're called with keys that are not present +// in the map. +type KeyNotFoundError[K comparable] struct { + MissingKey K +} + +func (e *KeyNotFoundError[K]) Error() string { + return fmt.Sprintf("missing key: %v", e.MissingKey) +} + +// MoveAfter moves the value associated with key to its new position after the one associated with markKey. +// Returns an error iff key or markKey are not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveAfter(key, markKey K) error { + elements, err := om.getElements(key, markKey) + if err != nil { + return err + } + om.list.MoveAfter(elements[0], elements[1]) + return nil +} + +// MoveBefore moves the value associated with key to its new position before the one associated with markKey. +// Returns an error iff key or markKey are not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveBefore(key, markKey K) error { + elements, err := om.getElements(key, markKey) + if err != nil { + return err + } + om.list.MoveBefore(elements[0], elements[1]) + return nil +} + +func (om *OrderedMap[K, V]) getElements(keys ...K) ([]*list.Element[*Pair[K, V]], error) { + elements := make([]*list.Element[*Pair[K, V]], len(keys)) + for i, k := range keys { + pair, present := om.pairs[k] + if !present { + return nil, &KeyNotFoundError[K]{k} + } + elements[i] = pair.element + } + return elements, nil +} + +// MoveToBack moves the value associated with key to the back of the ordered map, +// i.e. makes it the newest pair in the map. +// Returns an error iff key is not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveToBack(key K) error { + _, err := om.GetAndMoveToBack(key) + return err +} + +// MoveToFront moves the value associated with key to the front of the ordered map, +// i.e. makes it the oldest pair in the map. +// Returns an error iff key is not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveToFront(key K) error { + _, err := om.GetAndMoveToFront(key) + return err +} + +// GetAndMoveToBack combines Get and MoveToBack in the same call. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) GetAndMoveToBack(key K) (val V, err error) { + if pair, present := om.pairs[key]; present { + val = pair.Value + om.list.MoveToBack(pair.element) + } else { + err = &KeyNotFoundError[K]{key} + } + + return +} + +// GetAndMoveToFront combines Get and MoveToFront in the same call. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) GetAndMoveToFront(key K) (val V, err error) { + if pair, present := om.pairs[key]; present { + val = pair.Value + om.list.MoveToFront(pair.element) + } else { + err = &KeyNotFoundError[K]{key} + } + + return +} + +// FromOldest returns an iterator over all the key-value pairs in the map, starting from the oldest pair. +func (om *OrderedMap[K, V]) FromOldest() iter.Seq2[K, V] { + return func(yield func(K, V) bool) { + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + if !yield(pair.Key, pair.Value) { + return + } + } + } +} + +// FromNewest returns an iterator over all the key-value pairs in the map, starting from the newest pair. +func (om *OrderedMap[K, V]) FromNewest() iter.Seq2[K, V] { + return func(yield func(K, V) bool) { + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + if !yield(pair.Key, pair.Value) { + return + } + } + } +} + +// KeysFromOldest returns an iterator over all the keys in the map, starting from the oldest pair. +func (om *OrderedMap[K, V]) KeysFromOldest() iter.Seq[K] { + return func(yield func(K) bool) { + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + if !yield(pair.Key) { + return + } + } + } +} + +// KeysFromNewest returns an iterator over all the keys in the map, starting from the newest pair. +func (om *OrderedMap[K, V]) KeysFromNewest() iter.Seq[K] { + return func(yield func(K) bool) { + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + if !yield(pair.Key) { + return + } + } + } +} + +// ValuesFromOldest returns an iterator over all the values in the map, starting from the oldest pair. +func (om *OrderedMap[K, V]) ValuesFromOldest() iter.Seq[V] { + return func(yield func(V) bool) { + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + if !yield(pair.Value) { + return + } + } + } +} + +// ValuesFromNewest returns an iterator over all the values in the map, starting from the newest pair. +func (om *OrderedMap[K, V]) ValuesFromNewest() iter.Seq[V] { + return func(yield func(V) bool) { + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + if !yield(pair.Value) { + return + } + } + } +} + +// From creates a new OrderedMap from an iterator over key-value pairs. +func From[K comparable, V any](i iter.Seq2[K, V]) *OrderedMap[K, V] { + oMap := New[K, V]() + + for k, v := range i { + oMap.Set(k, v) + } + + return oMap +} diff --git a/vendor/github.com/wk8/go-ordered-map/v2/yaml.go b/vendor/github.com/wk8/go-ordered-map/v2/yaml.go new file mode 100644 index 000000000..602247128 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/yaml.go @@ -0,0 +1,71 @@ +package orderedmap + +import ( + "fmt" + + "gopkg.in/yaml.v3" +) + +var ( + _ yaml.Marshaler = &OrderedMap[int, any]{} + _ yaml.Unmarshaler = &OrderedMap[int, any]{} +) + +// MarshalYAML implements the yaml.Marshaler interface. +func (om *OrderedMap[K, V]) MarshalYAML() (interface{}, error) { + if om == nil { + return []byte("null"), nil + } + + node := yaml.Node{ + Kind: yaml.MappingNode, + } + + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + key, value := pair.Key, pair.Value + + keyNode := &yaml.Node{} + + // serialize key to yaml, then deserialize it back into the node + // this is a hack to get the correct tag for the key + if err := keyNode.Encode(key); err != nil { + return nil, err + } + + valueNode := &yaml.Node{} + if err := valueNode.Encode(value); err != nil { + return nil, err + } + + node.Content = append(node.Content, keyNode, valueNode) + } + + return &node, nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (om *OrderedMap[K, V]) UnmarshalYAML(value *yaml.Node) error { + if value.Kind != yaml.MappingNode { + return fmt.Errorf("pipeline must contain YAML mapping, has %v", value.Kind) + } + + if om.list == nil { + om.initialize(0) + } + + for index := 0; index < len(value.Content); index += 2 { + var key K + var val V + + if err := value.Content[index].Decode(&key); err != nil { + return err + } + if err := value.Content[index+1].Decode(&val); err != nil { + return err + } + + om.Set(key, val) + } + + return nil +} diff --git a/vendor/golang.org/x/net/internal/socks/client.go b/vendor/golang.org/x/net/internal/socks/client.go deleted file mode 100644 index 3d6f516a5..000000000 --- a/vendor/golang.org/x/net/internal/socks/client.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package socks - -import ( - "context" - "errors" - "io" - "net" - "strconv" - "time" -) - -var ( - noDeadline = time.Time{} - aLongTimeAgo = time.Unix(1, 0) -) - -func (d *Dialer) connect(ctx context.Context, c net.Conn, address string) (_ net.Addr, ctxErr error) { - host, port, err := splitHostPort(address) - if err != nil { - return nil, err - } - if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() { - c.SetDeadline(deadline) - defer c.SetDeadline(noDeadline) - } - if ctx != context.Background() { - errCh := make(chan error, 1) - done := make(chan struct{}) - defer func() { - close(done) - if ctxErr == nil { - ctxErr = <-errCh - } - }() - go func() { - select { - case <-ctx.Done(): - c.SetDeadline(aLongTimeAgo) - errCh <- ctx.Err() - case <-done: - errCh <- nil - } - }() - } - - b := make([]byte, 0, 6+len(host)) // the size here is just an estimate - b = append(b, Version5) - if len(d.AuthMethods) == 0 || d.Authenticate == nil { - b = append(b, 1, byte(AuthMethodNotRequired)) - } else { - ams := d.AuthMethods - if len(ams) > 255 { - return nil, errors.New("too many authentication methods") - } - b = append(b, byte(len(ams))) - for _, am := range ams { - b = append(b, byte(am)) - } - } - if _, ctxErr = c.Write(b); ctxErr != nil { - return - } - - if _, ctxErr = io.ReadFull(c, b[:2]); ctxErr != nil { - return - } - if b[0] != Version5 { - return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0]))) - } - am := AuthMethod(b[1]) - if am == AuthMethodNoAcceptableMethods { - return nil, errors.New("no acceptable authentication methods") - } - if d.Authenticate != nil { - if ctxErr = d.Authenticate(ctx, c, am); ctxErr != nil { - return - } - } - - b = b[:0] - b = append(b, Version5, byte(d.cmd), 0) - if ip := net.ParseIP(host); ip != nil { - if ip4 := ip.To4(); ip4 != nil { - b = append(b, AddrTypeIPv4) - b = append(b, ip4...) - } else if ip6 := ip.To16(); ip6 != nil { - b = append(b, AddrTypeIPv6) - b = append(b, ip6...) - } else { - return nil, errors.New("unknown address type") - } - } else { - if len(host) > 255 { - return nil, errors.New("FQDN too long") - } - b = append(b, AddrTypeFQDN) - b = append(b, byte(len(host))) - b = append(b, host...) - } - b = append(b, byte(port>>8), byte(port)) - if _, ctxErr = c.Write(b); ctxErr != nil { - return - } - - if _, ctxErr = io.ReadFull(c, b[:4]); ctxErr != nil { - return - } - if b[0] != Version5 { - return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0]))) - } - if cmdErr := Reply(b[1]); cmdErr != StatusSucceeded { - return nil, errors.New("unknown error " + cmdErr.String()) - } - if b[2] != 0 { - return nil, errors.New("non-zero reserved field") - } - l := 2 - var a Addr - switch b[3] { - case AddrTypeIPv4: - l += net.IPv4len - a.IP = make(net.IP, net.IPv4len) - case AddrTypeIPv6: - l += net.IPv6len - a.IP = make(net.IP, net.IPv6len) - case AddrTypeFQDN: - if _, err := io.ReadFull(c, b[:1]); err != nil { - return nil, err - } - l += int(b[0]) - default: - return nil, errors.New("unknown address type " + strconv.Itoa(int(b[3]))) - } - if cap(b) < l { - b = make([]byte, l) - } else { - b = b[:l] - } - if _, ctxErr = io.ReadFull(c, b); ctxErr != nil { - return - } - if a.IP != nil { - copy(a.IP, b) - } else { - a.Name = string(b[:len(b)-2]) - } - a.Port = int(b[len(b)-2])<<8 | int(b[len(b)-1]) - return &a, nil -} - -func splitHostPort(address string) (string, int, error) { - host, port, err := net.SplitHostPort(address) - if err != nil { - return "", 0, err - } - portnum, err := strconv.Atoi(port) - if err != nil { - return "", 0, err - } - if 1 > portnum || portnum > 0xffff { - return "", 0, errors.New("port number out of range " + port) - } - return host, portnum, nil -} diff --git a/vendor/golang.org/x/net/internal/socks/socks.go b/vendor/golang.org/x/net/internal/socks/socks.go deleted file mode 100644 index 84fcc32b6..000000000 --- a/vendor/golang.org/x/net/internal/socks/socks.go +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package socks provides a SOCKS version 5 client implementation. -// -// SOCKS protocol version 5 is defined in RFC 1928. -// Username/Password authentication for SOCKS version 5 is defined in -// RFC 1929. -package socks - -import ( - "context" - "errors" - "io" - "net" - "strconv" -) - -// A Command represents a SOCKS command. -type Command int - -func (cmd Command) String() string { - switch cmd { - case CmdConnect: - return "socks connect" - case cmdBind: - return "socks bind" - default: - return "socks " + strconv.Itoa(int(cmd)) - } -} - -// An AuthMethod represents a SOCKS authentication method. -type AuthMethod int - -// A Reply represents a SOCKS command reply code. -type Reply int - -func (code Reply) String() string { - switch code { - case StatusSucceeded: - return "succeeded" - case 0x01: - return "general SOCKS server failure" - case 0x02: - return "connection not allowed by ruleset" - case 0x03: - return "network unreachable" - case 0x04: - return "host unreachable" - case 0x05: - return "connection refused" - case 0x06: - return "TTL expired" - case 0x07: - return "command not supported" - case 0x08: - return "address type not supported" - default: - return "unknown code: " + strconv.Itoa(int(code)) - } -} - -// Wire protocol constants. -const ( - Version5 = 0x05 - - AddrTypeIPv4 = 0x01 - AddrTypeFQDN = 0x03 - AddrTypeIPv6 = 0x04 - - CmdConnect Command = 0x01 // establishes an active-open forward proxy connection - cmdBind Command = 0x02 // establishes a passive-open forward proxy connection - - AuthMethodNotRequired AuthMethod = 0x00 // no authentication required - AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password - AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods - - StatusSucceeded Reply = 0x00 -) - -// An Addr represents a SOCKS-specific address. -// Either Name or IP is used exclusively. -type Addr struct { - Name string // fully-qualified domain name - IP net.IP - Port int -} - -func (a *Addr) Network() string { return "socks" } - -func (a *Addr) String() string { - if a == nil { - return "" - } - port := strconv.Itoa(a.Port) - if a.IP == nil { - return net.JoinHostPort(a.Name, port) - } - return net.JoinHostPort(a.IP.String(), port) -} - -// A Conn represents a forward proxy connection. -type Conn struct { - net.Conn - - boundAddr net.Addr -} - -// BoundAddr returns the address assigned by the proxy server for -// connecting to the command target address from the proxy server. -func (c *Conn) BoundAddr() net.Addr { - if c == nil { - return nil - } - return c.boundAddr -} - -// A Dialer holds SOCKS-specific options. -type Dialer struct { - cmd Command // either CmdConnect or cmdBind - proxyNetwork string // network between a proxy server and a client - proxyAddress string // proxy server address - - // ProxyDial specifies the optional dial function for - // establishing the transport connection. - ProxyDial func(context.Context, string, string) (net.Conn, error) - - // AuthMethods specifies the list of request authentication - // methods. - // If empty, SOCKS client requests only AuthMethodNotRequired. - AuthMethods []AuthMethod - - // Authenticate specifies the optional authentication - // function. It must be non-nil when AuthMethods is not empty. - // It must return an error when the authentication is failed. - Authenticate func(context.Context, io.ReadWriter, AuthMethod) error -} - -// DialContext connects to the provided address on the provided -// network. -// -// The returned error value may be a net.OpError. When the Op field of -// net.OpError contains "socks", the Source field contains a proxy -// server address and the Addr field contains a command target -// address. -// -// See func Dial of the net package of standard library for a -// description of the network and address parameters. -func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - if err := d.validateTarget(network, address); err != nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} - } - if ctx == nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} - } - var err error - var c net.Conn - if d.ProxyDial != nil { - c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress) - } else { - var dd net.Dialer - c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress) - } - if err != nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} - } - a, err := d.connect(ctx, c, address) - if err != nil { - c.Close() - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} - } - return &Conn{Conn: c, boundAddr: a}, nil -} - -// DialWithConn initiates a connection from SOCKS server to the target -// network and address using the connection c that is already -// connected to the SOCKS server. -// -// It returns the connection's local address assigned by the SOCKS -// server. -func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) { - if err := d.validateTarget(network, address); err != nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} - } - if ctx == nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} - } - a, err := d.connect(ctx, c, address) - if err != nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} - } - return a, nil -} - -// Dial connects to the provided address on the provided network. -// -// Unlike DialContext, it returns a raw transport connection instead -// of a forward proxy connection. -// -// Deprecated: Use DialContext or DialWithConn instead. -func (d *Dialer) Dial(network, address string) (net.Conn, error) { - if err := d.validateTarget(network, address); err != nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} - } - var err error - var c net.Conn - if d.ProxyDial != nil { - c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress) - } else { - c, err = net.Dial(d.proxyNetwork, d.proxyAddress) - } - if err != nil { - proxy, dst, _ := d.pathAddrs(address) - return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} - } - if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil { - c.Close() - return nil, err - } - return c, nil -} - -func (d *Dialer) validateTarget(network, address string) error { - switch network { - case "tcp", "tcp6", "tcp4": - default: - return errors.New("network not implemented") - } - switch d.cmd { - case CmdConnect, cmdBind: - default: - return errors.New("command not implemented") - } - return nil -} - -func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) { - for i, s := range []string{d.proxyAddress, address} { - host, port, err := splitHostPort(s) - if err != nil { - return nil, nil, err - } - a := &Addr{Port: port} - a.IP = net.ParseIP(host) - if a.IP == nil { - a.Name = host - } - if i == 0 { - proxy = a - } else { - dst = a - } - } - return -} - -// NewDialer returns a new Dialer that dials through the provided -// proxy server's network and address. -func NewDialer(network, address string) *Dialer { - return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect} -} - -const ( - authUsernamePasswordVersion = 0x01 - authStatusSucceeded = 0x00 -) - -// UsernamePassword are the credentials for the username/password -// authentication method. -type UsernamePassword struct { - Username string - Password string -} - -// Authenticate authenticates a pair of username and password with the -// proxy server. -func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error { - switch auth { - case AuthMethodNotRequired: - return nil - case AuthMethodUsernamePassword: - if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) > 255 { - return errors.New("invalid username/password") - } - b := []byte{authUsernamePasswordVersion} - b = append(b, byte(len(up.Username))) - b = append(b, up.Username...) - b = append(b, byte(len(up.Password))) - b = append(b, up.Password...) - // TODO(mikio): handle IO deadlines and cancelation if - // necessary - if _, err := rw.Write(b); err != nil { - return err - } - if _, err := io.ReadFull(rw, b[:2]); err != nil { - return err - } - if b[0] != authUsernamePasswordVersion { - return errors.New("invalid username/password version") - } - if b[1] != authStatusSucceeded { - return errors.New("username/password authentication failed") - } - return nil - } - return errors.New("unsupported authentication method " + strconv.Itoa(int(auth))) -} diff --git a/vendor/golang.org/x/net/proxy/dial.go b/vendor/golang.org/x/net/proxy/dial.go deleted file mode 100644 index 811c2e4e9..000000000 --- a/vendor/golang.org/x/net/proxy/dial.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proxy - -import ( - "context" - "net" -) - -// A ContextDialer dials using a context. -type ContextDialer interface { - DialContext(ctx context.Context, network, address string) (net.Conn, error) -} - -// Dial works like DialContext on net.Dialer but using a dialer returned by FromEnvironment. -// -// The passed ctx is only used for returning the Conn, not the lifetime of the Conn. -// -// Custom dialers (registered via RegisterDialerType) that do not implement ContextDialer -// can leak a goroutine for as long as it takes the underlying Dialer implementation to timeout. -// -// A Conn returned from a successful Dial after the context has been cancelled will be immediately closed. -func Dial(ctx context.Context, network, address string) (net.Conn, error) { - d := FromEnvironment() - if xd, ok := d.(ContextDialer); ok { - return xd.DialContext(ctx, network, address) - } - return dialContext(ctx, d, network, address) -} - -// WARNING: this can leak a goroutine for as long as the underlying Dialer implementation takes to timeout -// A Conn returned from a successful Dial after the context has been cancelled will be immediately closed. -func dialContext(ctx context.Context, d Dialer, network, address string) (net.Conn, error) { - var ( - conn net.Conn - done = make(chan struct{}, 1) - err error - ) - go func() { - conn, err = d.Dial(network, address) - close(done) - if conn != nil && ctx.Err() != nil { - conn.Close() - } - }() - select { - case <-ctx.Done(): - err = ctx.Err() - case <-done: - } - return conn, err -} diff --git a/vendor/golang.org/x/net/proxy/direct.go b/vendor/golang.org/x/net/proxy/direct.go deleted file mode 100644 index 3d66bdef9..000000000 --- a/vendor/golang.org/x/net/proxy/direct.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proxy - -import ( - "context" - "net" -) - -type direct struct{} - -// Direct implements Dialer by making network connections directly using net.Dial or net.DialContext. -var Direct = direct{} - -var ( - _ Dialer = Direct - _ ContextDialer = Direct -) - -// Dial directly invokes net.Dial with the supplied parameters. -func (direct) Dial(network, addr string) (net.Conn, error) { - return net.Dial(network, addr) -} - -// DialContext instantiates a net.Dialer and invokes its DialContext receiver with the supplied parameters. -func (direct) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - var d net.Dialer - return d.DialContext(ctx, network, addr) -} diff --git a/vendor/golang.org/x/net/proxy/per_host.go b/vendor/golang.org/x/net/proxy/per_host.go deleted file mode 100644 index d7d4b8b6e..000000000 --- a/vendor/golang.org/x/net/proxy/per_host.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proxy - -import ( - "context" - "net" - "strings" -) - -// A PerHost directs connections to a default Dialer unless the host name -// requested matches one of a number of exceptions. -type PerHost struct { - def, bypass Dialer - - bypassNetworks []*net.IPNet - bypassIPs []net.IP - bypassZones []string - bypassHosts []string -} - -// NewPerHost returns a PerHost Dialer that directs connections to either -// defaultDialer or bypass, depending on whether the connection matches one of -// the configured rules. -func NewPerHost(defaultDialer, bypass Dialer) *PerHost { - return &PerHost{ - def: defaultDialer, - bypass: bypass, - } -} - -// Dial connects to the address addr on the given network through either -// defaultDialer or bypass. -func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) { - host, _, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - - return p.dialerForRequest(host).Dial(network, addr) -} - -// DialContext connects to the address addr on the given network through either -// defaultDialer or bypass. -func (p *PerHost) DialContext(ctx context.Context, network, addr string) (c net.Conn, err error) { - host, _, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - d := p.dialerForRequest(host) - if x, ok := d.(ContextDialer); ok { - return x.DialContext(ctx, network, addr) - } - return dialContext(ctx, d, network, addr) -} - -func (p *PerHost) dialerForRequest(host string) Dialer { - if ip := net.ParseIP(host); ip != nil { - for _, net := range p.bypassNetworks { - if net.Contains(ip) { - return p.bypass - } - } - for _, bypassIP := range p.bypassIPs { - if bypassIP.Equal(ip) { - return p.bypass - } - } - return p.def - } - - for _, zone := range p.bypassZones { - if strings.HasSuffix(host, zone) { - return p.bypass - } - if host == zone[1:] { - // For a zone ".example.com", we match "example.com" - // too. - return p.bypass - } - } - for _, bypassHost := range p.bypassHosts { - if bypassHost == host { - return p.bypass - } - } - return p.def -} - -// AddFromString parses a string that contains comma-separated values -// specifying hosts that should use the bypass proxy. Each value is either an -// IP address, a CIDR range, a zone (*.example.com) or a host name -// (localhost). A best effort is made to parse the string and errors are -// ignored. -func (p *PerHost) AddFromString(s string) { - hosts := strings.Split(s, ",") - for _, host := range hosts { - host = strings.TrimSpace(host) - if len(host) == 0 { - continue - } - if strings.Contains(host, "/") { - // We assume that it's a CIDR address like 127.0.0.0/8 - if _, net, err := net.ParseCIDR(host); err == nil { - p.AddNetwork(net) - } - continue - } - if ip := net.ParseIP(host); ip != nil { - p.AddIP(ip) - continue - } - if strings.HasPrefix(host, "*.") { - p.AddZone(host[1:]) - continue - } - p.AddHost(host) - } -} - -// AddIP specifies an IP address that will use the bypass proxy. Note that -// this will only take effect if a literal IP address is dialed. A connection -// to a named host will never match an IP. -func (p *PerHost) AddIP(ip net.IP) { - p.bypassIPs = append(p.bypassIPs, ip) -} - -// AddNetwork specifies an IP range that will use the bypass proxy. Note that -// this will only take effect if a literal IP address is dialed. A connection -// to a named host will never match. -func (p *PerHost) AddNetwork(net *net.IPNet) { - p.bypassNetworks = append(p.bypassNetworks, net) -} - -// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of -// "example.com" matches "example.com" and all of its subdomains. -func (p *PerHost) AddZone(zone string) { - zone = strings.TrimSuffix(zone, ".") - if !strings.HasPrefix(zone, ".") { - zone = "." + zone - } - p.bypassZones = append(p.bypassZones, zone) -} - -// AddHost specifies a host name that will use the bypass proxy. -func (p *PerHost) AddHost(host string) { - host = strings.TrimSuffix(host, ".") - p.bypassHosts = append(p.bypassHosts, host) -} diff --git a/vendor/golang.org/x/net/proxy/proxy.go b/vendor/golang.org/x/net/proxy/proxy.go deleted file mode 100644 index 9ff4b9a77..000000000 --- a/vendor/golang.org/x/net/proxy/proxy.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package proxy provides support for a variety of protocols to proxy network -// data. -package proxy // import "golang.org/x/net/proxy" - -import ( - "errors" - "net" - "net/url" - "os" - "sync" -) - -// A Dialer is a means to establish a connection. -// Custom dialers should also implement ContextDialer. -type Dialer interface { - // Dial connects to the given address via the proxy. - Dial(network, addr string) (c net.Conn, err error) -} - -// Auth contains authentication parameters that specific Dialers may require. -type Auth struct { - User, Password string -} - -// FromEnvironment returns the dialer specified by the proxy-related -// variables in the environment and makes underlying connections -// directly. -func FromEnvironment() Dialer { - return FromEnvironmentUsing(Direct) -} - -// FromEnvironmentUsing returns the dialer specify by the proxy-related -// variables in the environment and makes underlying connections -// using the provided forwarding Dialer (for instance, a *net.Dialer -// with desired configuration). -func FromEnvironmentUsing(forward Dialer) Dialer { - allProxy := allProxyEnv.Get() - if len(allProxy) == 0 { - return forward - } - - proxyURL, err := url.Parse(allProxy) - if err != nil { - return forward - } - proxy, err := FromURL(proxyURL, forward) - if err != nil { - return forward - } - - noProxy := noProxyEnv.Get() - if len(noProxy) == 0 { - return proxy - } - - perHost := NewPerHost(proxy, forward) - perHost.AddFromString(noProxy) - return perHost -} - -// proxySchemes is a map from URL schemes to a function that creates a Dialer -// from a URL with such a scheme. -var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error) - -// RegisterDialerType takes a URL scheme and a function to generate Dialers from -// a URL with that scheme and a forwarding Dialer. Registered schemes are used -// by FromURL. -func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) { - if proxySchemes == nil { - proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error)) - } - proxySchemes[scheme] = f -} - -// FromURL returns a Dialer given a URL specification and an underlying -// Dialer for it to make network requests. -func FromURL(u *url.URL, forward Dialer) (Dialer, error) { - var auth *Auth - if u.User != nil { - auth = new(Auth) - auth.User = u.User.Username() - if p, ok := u.User.Password(); ok { - auth.Password = p - } - } - - switch u.Scheme { - case "socks5", "socks5h": - addr := u.Hostname() - port := u.Port() - if port == "" { - port = "1080" - } - return SOCKS5("tcp", net.JoinHostPort(addr, port), auth, forward) - } - - // If the scheme doesn't match any of the built-in schemes, see if it - // was registered by another package. - if proxySchemes != nil { - if f, ok := proxySchemes[u.Scheme]; ok { - return f(u, forward) - } - } - - return nil, errors.New("proxy: unknown scheme: " + u.Scheme) -} - -var ( - allProxyEnv = &envOnce{ - names: []string{"ALL_PROXY", "all_proxy"}, - } - noProxyEnv = &envOnce{ - names: []string{"NO_PROXY", "no_proxy"}, - } -) - -// envOnce looks up an environment variable (optionally by multiple -// names) once. It mitigates expensive lookups on some platforms -// (e.g. Windows). -// (Borrowed from net/http/transport.go) -type envOnce struct { - names []string - once sync.Once - val string -} - -func (e *envOnce) Get() string { - e.once.Do(e.init) - return e.val -} - -func (e *envOnce) init() { - for _, n := range e.names { - e.val = os.Getenv(n) - if e.val != "" { - return - } - } -} - -// reset is used by tests -func (e *envOnce) reset() { - e.once = sync.Once{} - e.val = "" -} diff --git a/vendor/golang.org/x/net/proxy/socks5.go b/vendor/golang.org/x/net/proxy/socks5.go deleted file mode 100644 index c91651f96..000000000 --- a/vendor/golang.org/x/net/proxy/socks5.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package proxy - -import ( - "context" - "net" - - "golang.org/x/net/internal/socks" -) - -// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given -// address with an optional username and password. -// See RFC 1928 and RFC 1929. -func SOCKS5(network, address string, auth *Auth, forward Dialer) (Dialer, error) { - d := socks.NewDialer(network, address) - if forward != nil { - if f, ok := forward.(ContextDialer); ok { - d.ProxyDial = func(ctx context.Context, network string, address string) (net.Conn, error) { - return f.DialContext(ctx, network, address) - } - } else { - d.ProxyDial = func(ctx context.Context, network string, address string) (net.Conn, error) { - return dialContext(ctx, forward, network, address) - } - } - } - if auth != nil { - up := socks.UsernamePassword{ - Username: auth.User, - Password: auth.Password, - } - d.AuthMethods = []socks.AuthMethod{ - socks.AuthMethodNotRequired, - socks.AuthMethodUsernamePassword, - } - d.Authenticate = up.Authenticate - } - return d, nil -} diff --git a/vendor/modernc.org/sqlite/CONTRIBUTORS b/vendor/modernc.org/sqlite/CONTRIBUTORS index 853cbb56e..e9ea3aff5 100644 --- a/vendor/modernc.org/sqlite/CONTRIBUTORS +++ b/vendor/modernc.org/sqlite/CONTRIBUTORS @@ -37,3 +37,4 @@ Steffen Butzer Toni Spets W. Michael Petullo Yaacov Akiba Slama +Prathyush PV diff --git a/vendor/modernc.org/sqlite/Makefile b/vendor/modernc.org/sqlite/Makefile index 67a56d389..8b7472ddd 100644 --- a/vendor/modernc.org/sqlite/Makefile +++ b/vendor/modernc.org/sqlite/Makefile @@ -57,7 +57,7 @@ clean: edit: @touch log - @if [ -f "Session.vim" ]; then novim -S & else novim -p Makefile go.mod builder.json all_test.go vendor_libsqlite3.go & fi + @if [ -f "Session.vim" ]; then gvim -S & else gvim -p Makefile go.mod builder.json all_test.go vendor_libsqlite3.go & fi editor: gofmt -l -s -w . 2>&1 | tee log-editor diff --git a/vendor/modernc.org/sqlite/README.md b/vendor/modernc.org/sqlite/README.md index f47ad3240..8f4acdb3b 100644 --- a/vendor/modernc.org/sqlite/README.md +++ b/vendor/modernc.org/sqlite/README.md @@ -1,81 +1,7 @@ -# sqlite +![logo](logo.png) -Package sqlite is a cgo-free port of SQLite. Although you could see mattn's driver (`github.com/mattn/go-sqlite3`) in go.mod file, we import it for tests only. +[![Go Reference](https://pkg.go.dev/badge/modernc.org/sqlite.svg)](https://pkg.go.dev/modernc.org/sqlite) -SQLite is an in-process implementation of a self-contained, serverless, -zero-configuration, transactional SQL database engine. - -## Thanks - -This project is sponsored by Schleibinger Geräte Teubert u. Greim GmbH by -allowing one of the maintainers to work on it also in office hours. - -## Installation - - $ go get modernc.org/sqlite - -## Documentation - -[pkg.go.dev/modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) - -## Builders - -[modern-c.appspot.com/-/builder/?importpath=modernc.org%2fsqlite](https://modern-c.appspot.com/-/builder/?importpath=modernc.org%2fsqlite) - -## Speedtest1 - -Numbers for the pure Go version were produced by - - ~/src/modernc.org/sqlite/speedtest1$ go build && ./speedtest1 - -Numbers for the pure C version were produced by - - ~/src/modernc.org/sqlite/testdata/sqlite-src-3410200/test$ gcc speedtest1.c ../../sqlite-amalgamation-3410200/sqlite3.c -lpthread -ldl && ./a.out - -The results are from Go version 1.20.4 and GCC version 10.2.1 on a -Linux/amd64 machine, CPU: AMD Ryzen 9 3900X 12-Core Processor × 24, 128GB -RAM. Shown are the best of 3 runs. - - Go C - - -- Speedtest1 for SQLite 3.41.2 2023-03-22 11:56:21 0d1fc92f94cb6b76bffe3ec34d69 -- Speedtest1 for SQLite 3.41.2 2023-03-22 11:56:21 0d1fc92f94cb6b76bffe3ec34d69 - 100 - 50000 INSERTs into table with no index...................... 0.071s 100 - 50000 INSERTs into table with no index...................... 0.077s - 110 - 50000 ordered INSERTS with one index/PK..................... 0.114s 110 - 50000 ordered INSERTS with one index/PK..................... 0.082s - 120 - 50000 unordered INSERTS with one index/PK................... 0.137s 120 - 50000 unordered INSERTS with one index/PK................... 0.099s - 130 - 25 SELECTS, numeric BETWEEN, unindexed...................... 0.083s 130 - 25 SELECTS, numeric BETWEEN, unindexed...................... 0.091s - 140 - 10 SELECTS, LIKE, unindexed................................. 0.210s 140 - 10 SELECTS, LIKE, unindexed................................. 0.120s - 142 - 10 SELECTS w/ORDER BY, unindexed............................ 0.276s 142 - 10 SELECTS w/ORDER BY, unindexed............................ 0.182s - 145 - 10 SELECTS w/ORDER BY and LIMIT, unindexed.................. 0.183s 145 - 10 SELECTS w/ORDER BY and LIMIT, unindexed.................. 0.099s - 150 - CREATE INDEX five times..................................... 0.172s 150 - CREATE INDEX five times..................................... 0.127s - 160 - 10000 SELECTS, numeric BETWEEN, indexed..................... 0.080s 160 - 10000 SELECTS, numeric BETWEEN, indexed..................... 0.078s - 161 - 10000 SELECTS, numeric BETWEEN, PK.......................... 0.080s 161 - 10000 SELECTS, numeric BETWEEN, PK.......................... 0.078s - 170 - 10000 SELECTS, text BETWEEN, indexed........................ 0.187s 170 - 10000 SELECTS, text BETWEEN, indexed........................ 0.169s - 180 - 50000 INSERTS with three indexes............................ 0.196s 180 - 50000 INSERTS with three indexes............................ 0.154s - 190 - DELETE and REFILL one table................................. 0.200s 190 - DELETE and REFILL one table................................. 0.155s - 200 - VACUUM...................................................... 0.180s 200 - VACUUM...................................................... 0.142s - 210 - ALTER TABLE ADD COLUMN, and query........................... 0.004s 210 - ALTER TABLE ADD COLUMN, and query........................... 0.005s - 230 - 10000 UPDATES, numeric BETWEEN, indexed..................... 0.093s 230 - 10000 UPDATES, numeric BETWEEN, indexed..................... 0.080s - 240 - 50000 UPDATES of individual rows............................ 0.153s 240 - 50000 UPDATES of individual rows............................ 0.137s - 250 - One big UPDATE of the whole 50000-row table................. 0.024s 250 - One big UPDATE of the whole 50000-row table................. 0.019s - 260 - Query added column after filling............................ 0.004s 260 - Query added column after filling............................ 0.005s - 270 - 10000 DELETEs, numeric BETWEEN, indexed..................... 0.278s 270 - 10000 DELETEs, numeric BETWEEN, indexed..................... 0.263s - 280 - 50000 DELETEs of individual rows............................ 0.188s 280 - 50000 DELETEs of individual rows............................ 0.180s - 290 - Refill two 50000-row tables using REPLACE................... 0.411s 290 - Refill two 50000-row tables using REPLACE................... 0.359s - 300 - Refill a 50000-row table using (b&1)==(a&1)................. 0.175s 300 - Refill a 50000-row table using (b&1)==(a&1)................. 0.151s - 310 - 10000 four-ways joins....................................... 0.427s 310 - 10000 four-ways joins....................................... 0.365s - 320 - subquery in result set...................................... 0.440s 320 - subquery in result set...................................... 0.521s - 400 - 70000 REPLACE ops on an IPK................................. 0.125s 400 - 70000 REPLACE ops on an IPK................................. 0.106s - 410 - 70000 SELECTS on an IPK..................................... 0.081s 410 - 70000 SELECTS on an IPK..................................... 0.078s - 500 - 70000 REPLACE on TEXT PK.................................... 0.174s 500 - 70000 REPLACE on TEXT PK.................................... 0.116s - 510 - 70000 SELECTS on a TEXT PK.................................. 0.153s 510 - 70000 SELECTS on a TEXT PK.................................. 0.117s - 520 - 70000 SELECT DISTINCT....................................... 0.083s 520 - 70000 SELECT DISTINCT....................................... 0.067s - 980 - PRAGMA integrity_check...................................... 0.436s 980 - PRAGMA integrity_check...................................... 0.377s - 990 - ANALYZE..................................................... 0.107s 990 - ANALYZE..................................................... 0.038s - TOTAL....................................................... 5.525s TOTAL....................................................... 4.637s - -This particular test executes 16.1% faster in the C version. - -## Troubleshooting - -* Q: **How can I write to a database concurrently without getting the `database is locked` error (or `SQLITE_BUSY`)?** - * A: You can't. The C sqlite implementation does not allow concurrent writes, and this libary does not modify that behaviour. You can, however, use [DB.SetMaxOpenConns(1)](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns) so that only 1 connection is ever used by the `DB`, allowing concurrent access to DB without making the writes concurrent. More information on issues [#65](https://gitlab.com/cznic/sqlite/-/issues/65) and [#106](https://gitlab.com/cznic/sqlite/-/issues/106). +[![LiberaPay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/jnml/donate) +[![receives](https://img.shields.io/liberapay/receives/jnml.svg?logo=liberapay)](https://liberapay.com/jnml/donate) +[![patrons](https://img.shields.io/liberapay/patrons/jnml.svg?logo=liberapay)](https://liberapay.com/jnml/donate) diff --git a/vendor/modernc.org/sqlite/doc.go b/vendor/modernc.org/sqlite/doc.go index c09d18590..02820b86b 100644 --- a/vendor/modernc.org/sqlite/doc.go +++ b/vendor/modernc.org/sqlite/doc.go @@ -8,6 +8,14 @@ // SQLite is an in-process implementation of a self-contained, serverless, // zero-configuration, transactional SQL database engine. // +// # Fragile modernc.org/libc dependency +// +// When you import this package you should use in your go.mod file the exact +// same version of modernc.org/libc as seen in the go.mod file of this +// repository. +// +// See the discussion at https://gitlab.com/cznic/sqlite/-/issues/177 for more details. +// // # Thanks // // This project is sponsored by Schleibinger Geräte Teubert u. Greim GmbH by @@ -41,175 +49,98 @@ // // https://modern-c.appspot.com/-/builder/?importpath=modernc.org%2fsqlite // -// # Fragile modernc.org/libc dependency -// -// When you import this package you should use in your go.mod file the exact -// same version of modernc.org/libc as seen in the go.mod file of this -// repository. -// -// See the discussion at https://gitlab.com/cznic/sqlite/-/issues/177 for more details. -// // # Changelog // -// 2024-07-22: v1.31.0 +// - 2024-11-16 v1.34.0: Implement ResetSession and IsValid methods in connection // -// Support windows/386. +// - 2024-07-22 v1.31.0: Support windows/386. // -// 2024-06-04: v1.30.0 +// - 2024-06-04 v1.30.0: Upgrade to SQLite 3.46.0, release notes at +// https://sqlite.org/releaselog/3_46_0.html. // -// Upgrade to SQLite 3.46.0, release notes at https://sqlite.org/releaselog/3_46_0.html. +// - 2024-02-13 v1.29.0: Upgrade to SQLite 3.45.1, release notes at +// https://sqlite.org/releaselog/3_45_1.html. // -// 2024-02-13: v1.29.0 +// - 2023-12-14: v1.28.0: Add (*Driver).RegisterConnectionHook, +// ConnectionHookFn, ExecQuerierContext, RegisterConnectionHook. // -// Upgrade to SQLite 3.45.1, release notes at https://sqlite.org/releaselog/3_45_1.html. +// - 2023-08-03 v1.25.0: enable SQLITE_ENABLE_DBSTAT_VTAB. // -// 2023-12-14 v1.28.0: +// - 2023-07-11 v1.24.0: Add +// (*conn).{Serialize,Deserialize,NewBackup,NewRestore} methods, add Backup +// type. // -// (*Driver).RegisterConnectionHook: added -// ConnectionHookFn: added -// ExecQuerierContext: added -// RegisterConnectionHook: added +// - 2023-06-01 v1.23.0: Allow registering aggregate functions. // -// 2023-08-03 v1.25.0: enable SQLITE_ENABLE_DBSTAT_VTAB. +// - 2023-04-22 v1.22.0: Support linux/s390x. // -// 2023-07-11 v1.24.0: +// - 2023-02-23 v1.21.0: Upgrade to SQLite 3.41.0, release notes at +// https://sqlite.org/releaselog/3_41_0.html. // -// Add (*conn).{Serialize,Deserialize,NewBackup,NewRestore} methods, add Backup type. +// - 2022-11-28 v1.20.0: Support linux/ppc64le. // -// 2023-06-01 v1.23.0: +// - 2022-09-16 v1.19.0: Support frebsd/arm64. // -// Allow registering aggregate functions. +// - 2022-07-26 v1.18.0: Add support for Go fs.FS based SQLite virtual +// filesystems, see function New in modernc.org/sqlite/vfs and/or TestVFS in +// all_test.go // -// 2023-04-22 v1.22.0: +// - 2022-04-24 v1.17.0: Support windows/arm64. // -// Support linux/s390x. +// - 2022-04-04 v1.16.0: Support scalar application defined functions written +// in Go. See https://www.sqlite.org/appfunc.html // -// 2023-02-23 v1.21.0: +// - 2022-03-13 v1.15.0: Support linux/riscv64. // -// Upgrade to SQLite 3.41.0, release notes at https://sqlite.org/releaselog/3_41_0.html. +// - 2021-11-13 v1.14.0: Support windows/amd64. This target had previously +// only experimental status because of a now resolved memory leak. // -// 2022-11-28 v1.20.0 +// - 2021-09-07 v1.13.0: Support freebsd/amd64. // -// Support linux/ppc64le. +// - 2021-06-23 v1.11.0: Upgrade to use sqlite 3.36.0, release notes at +// https://www.sqlite.org/releaselog/3_36_0.html. // -// 2022-09-16 v1.19.0: +// - 2021-05-06 v1.10.6: Fixes a memory corruption issue +// (https://gitlab.com/cznic/sqlite/-/issues/53). Versions since v1.8.6 were +// affected and should be updated to v1.10.6. // -// Support frebsd/arm64. +// - 2021-03-14 v1.10.0: Update to use sqlite 3.35.0, release notes at +// https://www.sqlite.org/releaselog/3_35_0.html. // -// 2022-07-26 v1.18.0: +// - 2021-03-11 v1.9.0: Support darwin/arm64. // -// Adds support for Go fs.FS based SQLite virtual filesystems, see function New -// in modernc.org/sqlite/vfs and/or TestVFS in all_test.go +// - 2021-01-08 v1.8.0: Support darwin/amd64. // -// 2022-04-24 v1.17.0: +// - 2020-09-13 v1.7.0: Support linux/arm and linux/arm64. // -// Support windows/arm64. +// - 2020-09-08 v1.6.0: Support linux/386. // -// 2022-04-04 v1.16.0: +// - 2020-09-03 v1.5.0: This project is now completely CGo-free, including +// the Tcl tests. // -// Support scalar application defined functions written in Go. +// - 2020-08-26 v1.4.0: First stable release for linux/amd64. The +// database/sql driver and its tests are CGo free. Tests of the translated +// sqlite3.c library still require CGo. // -// https://www.sqlite.org/appfunc.html +// - 2020-07-26 v1.4.0-beta1: The project has reached beta status while +// supporting linux/amd64 only at the moment. The 'extraquick' Tcl testsuite +// reports // -// 2022-03-13 v1.15.0: +// - 2019-12-28 v1.2.0-alpha.3: Third alpha fixes issue #19. // -// Support linux/riscv64. +// - 2019-12-26 v1.1.0-alpha.2: Second alpha release adds support for +// accessing a database concurrently by multiple goroutines and/or processes. +// v1.1.0 is now considered feature-complete. Next planed release should be a +// beta with a proper test suite. // -// 2021-11-13 v1.14.0: +// - 2019-12-18 v1.1.0-alpha.1: First alpha release using the new cc/v3, +// gocc, qbe toolchain. Some primitive tests pass on linux_{amd64,386}. Not +// yet safe for concurrent access by multiple goroutines. Next alpha release +// is planed to arrive before the end of this year. // -// Support windows/amd64. This target had previously only experimental status -// because of a now resolved memory leak. +// - 2017-06-10: Windows/Intel no more uses the VM (thanks Steffen Butzer). // -// 2021-09-07 v1.13.0: -// -// Support freebsd/amd64. -// -// 2021-06-23 v1.11.0: -// -// Upgrade to use sqlite 3.36.0, release notes at https://www.sqlite.org/releaselog/3_36_0.html. -// -// 2021-05-06 v1.10.6: -// -// Fixes a memory corruption issue -// (https://gitlab.com/cznic/sqlite/-/issues/53). Versions since v1.8.6 were -// affected and should be updated to v1.10.6. -// -// 2021-03-14 v1.10.0: -// -// Update to use sqlite 3.35.0, release notes at https://www.sqlite.org/releaselog/3_35_0.html. -// -// 2021-03-11 v1.9.0: -// -// Support darwin/arm64. -// -// 2021-01-08 v1.8.0: -// -// Support darwin/amd64. -// -// 2020-09-13 v1.7.0: -// -// Support linux/arm and linux/arm64. -// -// 2020-09-08 v1.6.0: -// -// Support linux/386. -// -// 2020-09-03 v1.5.0: -// -// This project is now completely CGo-free, including the Tcl tests. -// -// 2020-08-26 v1.4.0: -// -// First stable release for linux/amd64. The database/sql driver and its tests -// are CGo free. Tests of the translated sqlite3.c library still require CGo. -// -// $ make full -// -// ... -// -// SQLite 2020-08-14 13:23:32 fca8dc8b578f215a969cd899336378966156154710873e68b3d9ac5881b0ff3f -// 0 errors out of 928271 tests on 3900x Linux 64-bit little-endian -// WARNING: Multi-threaded tests skipped: Linked against a non-threadsafe Tcl build -// All memory allocations freed - no leaks -// Maximum memory usage: 9156360 bytes -// Current memory usage: 0 bytes -// Number of malloc() : -1 calls -// --- PASS: TestTclTest (1785.04s) -// PASS -// ok modernc.org/sqlite 1785.041s -// $ -// -// 2020-07-26 v1.4.0-beta1: -// -// The project has reached beta status while supporting linux/amd64 only at the -// moment. The 'extraquick' Tcl testsuite reports -// -// 630 errors out of 200177 tests on Linux 64-bit little-endian -// -// and some memory leaks -// -// Unfreed memory: 698816 bytes in 322 allocations -// -// 2019-12-28 v1.2.0-alpha.3: Third alpha fixes issue #19. -// -// It also bumps the minor version as the repository was wrongly already tagged -// with v1.1.0 before. Even though the tag was deleted there are proxies that -// cached that tag. Thanks /u/garaktailor for detecting the problem and -// suggesting this solution. -// -// 2019-12-26 v1.1.0-alpha.2: Second alpha release adds support for accessing a -// database concurrently by multiple goroutines and/or processes. v1.1.0 is now -// considered feature-complete. Next planed release should be a beta with a -// proper test suite. -// -// 2019-12-18 v1.1.0-alpha.1: First alpha release using the new cc/v3, gocc, -// qbe toolchain. Some primitive tests pass on linux_{amd64,386}. Not yet safe -// for concurrent access by multiple goroutines. Next alpha release is planed -// to arrive before the end of this year. -// -// 2017-06-10 Windows/Intel no more uses the VM (thanks Steffen Butzer). -// -// 2017-06-05 Linux/Intel no more uses the VM (cznic/virtual). +// - 2017-06-05 Linux/Intel no more uses the VM (cznic/virtual). // // # Connecting to a database // diff --git a/vendor/modernc.org/sqlite/lib/sqlite_darwin_amd64.go b/vendor/modernc.org/sqlite/lib/sqlite_darwin_amd64.go index 827d9b06d..e856b001a 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_darwin_amd64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_darwin_amd64.go @@ -230897,4 +230897,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_darwin_arm64.go b/vendor/modernc.org/sqlite/lib/sqlite_darwin_arm64.go index 97211c806..78f2aeb3e 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_darwin_arm64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_darwin_arm64.go @@ -230452,4 +230452,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_freebsd_amd64.go b/vendor/modernc.org/sqlite/lib/sqlite_freebsd_amd64.go index 5b7a72932..695a5b8c9 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_freebsd_amd64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_freebsd_amd64.go @@ -225512,4 +225512,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_freebsd_arm64.go b/vendor/modernc.org/sqlite/lib/sqlite_freebsd_arm64.go index ce9ed9ccb..1883ce6b3 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_freebsd_arm64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_freebsd_arm64.go @@ -225523,4 +225523,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_386.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_386.go index a454f2371..a099d0369 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_386.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_386.go @@ -231929,4 +231929,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_amd64.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_amd64.go index 172b9f296..e52d6a899 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_amd64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_amd64.go @@ -225728,4 +225728,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_arm.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_arm.go index 49eb1ec10..964a2676c 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_arm.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_arm.go @@ -232454,4 +232454,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_arm64.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_arm64.go index 57386d724..15ba9a251 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_arm64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_arm64.go @@ -231974,4 +231974,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_loong64.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_loong64.go index 2888a1c36..83675cadf 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_loong64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_loong64.go @@ -225817,4 +225817,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_ppc64le.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_ppc64le.go index e4915c3b1..a851859b8 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_ppc64le.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_ppc64le.go @@ -231955,4 +231955,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_riscv64.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_riscv64.go index 3aedd92be..dd2bf77e1 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_riscv64.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_riscv64.go @@ -231918,4 +231918,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_linux_s390x.go b/vendor/modernc.org/sqlite/lib/sqlite_linux_s390x.go index 2c276e6c3..81f1426a6 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_linux_s390x.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_linux_s390x.go @@ -231852,4 +231852,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_windows.go b/vendor/modernc.org/sqlite/lib/sqlite_windows.go index 49ff6d6d0..6afacb327 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_windows.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_windows.go @@ -94277,7 +94277,7 @@ func Xsqlite3_str_vappendf(tls *libc.TLS, pAccum uintptr, fmt uintptr, ap Tva_li _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ = bArgList, base, bufpt, c, cThousand, ch, ch1, cset, done, e2, escarg, exp, flag_alternateform, flag_altform2, flag_dp, flag_leftjustify, flag_long, flag_prefix, flag_rtz, flag_zeropad, i, i1, iRound, idx, ii, infop, isnull, ix, j, j1, k, length, longvalue, n, n1, nCopyBytes, nOut, nPad, nPrior, needQuote, nn, pArgList, pExpr, pItem, pSel, pToken, pre, precision, prefix, px, q, realvalue, szBufNeeded, v, width, wx, x, x1, xtype, z, zExtra, zOut, v10, v100, v101, v102, v103, v104, v106, v107, v108, v109, v11, v110, v111, v12, v14, v15, v16, v17, v18, v19, v2, v20, v21, v22, v23, v24, v3, v4, v45, v46, v47, v48, v49, v5, v51, v52, v54, v55, v56, v57, v58, v59, v6, v60, v61, v62, v64, v65, v66, v67, v68, v7, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79, v8, v80, v81, v82, v83, v85, v86, v87, v88, v89, v9, v90, v91, v93, v94, v96, v97, v98, p92 /* Thousands separator for %d and %u */ xtype = uint8(etINVALID) /* Size of the rendering buffer */ zExtra = uintptr(0) /* True if trailing zeros should be removed */ - pArgList = uintptr(0) /* Conversion buffer */ + pArgList = uintptr(0) /* Conversion buffer */ /* pAccum never starts out with an empty buffer that was obtained from ** malloc(). This precondition is required by the mprintf("%z...") ** optimization. */ @@ -97796,14 +97796,14 @@ func _sqlite3AtoF(tls *libc.TLS, z uintptr, pResult uintptr, length int32, enc T var _ /* rr at bp+0 */ [2]float64 _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ = d, e, eType, eValid, esign, i, incr, nDigit, r, s, s2, sign, zEnd, v2, v3, v4 /* sign * significand * (10 ^ (esign * exponent)) */ - sign = int32(1) /* sign of significand */ - s = uint64(0) /* significand */ - d = 0 /* adjust exponent for shifting decimal point */ - esign = int32(1) /* sign of exponent */ - e = 0 /* exponent */ - eValid = int32(1) /* True exponent is either not used or is well-formed */ - nDigit = 0 /* Number of digits processed */ - eType = int32(1) /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ + sign = int32(1) /* sign of significand */ + s = uint64(0) /* significand */ + d = 0 /* adjust exponent for shifting decimal point */ + esign = int32(1) /* sign of exponent */ + e = 0 /* exponent */ + eValid = int32(1) /* True exponent is either not used or is well-formed */ + nDigit = 0 /* Number of digits processed */ + eType = int32(1) /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ *(*float64)(unsafe.Pointer(pResult)) = float64(0) /* Default return value, in case of an error */ if length == 0 { return 0 @@ -103328,7 +103328,7 @@ func _winOpen(tls *libc.TLS, pVfs uintptr, zName uintptr, id uintptr, flags int3 ** a temporary file. Use this buffer to store the file name in. */ *(*uintptr)(unsafe.Pointer(bp + 8)) = uintptr(0) /* For temporary filename, if necessary. */ - rc = SQLITE_OK /* Function Return Code */ + rc = SQLITE_OK /* Function Return Code */ isExclusive = flags & int32(SQLITE_OPEN_EXCLUSIVE) isDelete = flags & int32(SQLITE_OPEN_DELETEONCLOSE) isCreate = flags & int32(SQLITE_OPEN_CREATE) @@ -111336,7 +111336,7 @@ func _readDbPage(tls *libc.TLS, pPg uintptr) (r int32) { _, _, _, _ = dbFileVers, iOffset, pPager, rc pPager = (*TPgHdr)(unsafe.Pointer(pPg)).FpPager /* Pager object associated with page pPg */ rc = SQLITE_OK /* Return code */ - *(*Tu32)(unsafe.Pointer(bp)) = uint32(0) /* Frame of WAL containing pgno */ + *(*Tu32)(unsafe.Pointer(bp)) = uint32(0) /* Frame of WAL containing pgno */ if (*TPager)(unsafe.Pointer(pPager)).FpWal != uintptr(0) { rc = _sqlite3WalFindFrame(tls, (*TPager)(unsafe.Pointer(pPager)).FpWal, (*TPgHdr)(unsafe.Pointer(pPg)).Fpgno, bp) if rc != 0 { @@ -130461,7 +130461,7 @@ func _btreeCreateTable(tls *libc.TLS, p uintptr, piTable uintptr, createTabFlags var _ /* pgnoRoot at bp+8 */ TPgno var _ /* rc at bp+12 */ int32 _, _ = pBt, ptfFlags - pBt = (*TBtree)(unsafe.Pointer(p)).FpBt /* Page-type flags for the root page of new table */ + pBt = (*TBtree)(unsafe.Pointer(p)).FpBt /* Page-type flags for the root page of new table */ if (*TBtShared)(unsafe.Pointer(pBt)).FautoVacuum != 0 { /* The page to move to. */ /* Creating a new table may probably require moving an existing database ** to make room for the new tables root page. In case this page turns @@ -136679,8 +136679,8 @@ func _sqlite3VdbeMakeReady(tls *libc.TLS, p uintptr, pParse uintptr) { ** opcode array. This extra memory will be reallocated for other elements ** of the prepared statement. */ - n = int32(libc.Uint64FromInt64(24) * uint64((*TVdbe)(unsafe.Pointer(p)).FnOp)) /* Bytes of opcode memory used */ - (*(*TReusableSpace)(unsafe.Pointer(bp + 8))).FpSpace = (*TVdbe)(unsafe.Pointer(p)).FaOp + uintptr(n) /* Unused opcode memory */ + n = int32(libc.Uint64FromInt64(24) * uint64((*TVdbe)(unsafe.Pointer(p)).FnOp)) /* Bytes of opcode memory used */ + (*(*TReusableSpace)(unsafe.Pointer(bp + 8))).FpSpace = (*TVdbe)(unsafe.Pointer(p)).FaOp + uintptr(n) /* Unused opcode memory */ (*(*TReusableSpace)(unsafe.Pointer(bp + 8))).FnFree = int64(((*TParse)(unsafe.Pointer(pParse)).FszOpAlloc - n) & ^libc.Int32FromInt32(7)) /* Bytes of unused memory */ _resolveP2Values(tls, p, bp) libc.SetBitFieldPtr16Uint32(p+200, uint32(libc.BoolUint8((*TParse)(unsafe.Pointer(pParse)).FisMultiWrite != 0 && (*TParse)(unsafe.Pointer(pParse)).FmayAbort != 0)), 5, 0x20) @@ -167288,7 +167288,7 @@ func _statInit(tls *libc.TLS, context uintptr, argc int32, argv uintptr) { nColUp = nCol nKeyCol = Xsqlite3_value_int(tls, *(*uintptr)(unsafe.Pointer(argv + 1*8))) /* Allocate the space required for the StatAccum object */ - n = int32(uint64(136) + uint64(8)*uint64(uint64(nColUp))) /* StatAccum.anDLt */ + n = int32(uint64(136) + uint64(8)*uint64(uint64(nColUp))) /* StatAccum.anDLt */ n = int32(uint64(n) + libc.Uint64FromInt64(8)*uint64(uint64(nColUp))) /* StatAccum.anEq */ if mxSample != 0 { n = int32(uint64(n) + (libc.Uint64FromInt64(8)*uint64(uint64(nColUp)) + libc.Uint64FromInt64(48)*uint64(nCol+mxSample) + libc.Uint64FromInt64(8)*libc.Uint64FromInt32(3)*uint64(uint64(nColUp))*uint64(nCol+mxSample))) @@ -174242,7 +174242,7 @@ func _sqlite3RefillIndex(tls *libc.TLS, pParse uintptr, pIndex uintptr, memRootP func _sqlite3AllocateIndexObject(tls *libc.TLS, db uintptr, nCol Ti16, nExtra int32, ppExtra uintptr) (r uintptr) { var nByte int32 var p, pExtra uintptr - _, _, _ = nByte, p, pExtra /* Bytes of space for Index object + arrays */ + _, _, _ = nByte, p, pExtra /* Bytes of space for Index object + arrays */ nByte = int32((libc.Uint64FromInt64(160)+libc.Uint64FromInt32(7))&uint64(^libc.Int32FromInt32(7)) + (uint64(8)*uint64(uint64(nCol))+uint64(7))&uint64(^libc.Int32FromInt32(7)) + (uint64(2)*uint64(int32(int32(nCol))+libc.Int32FromInt32(1))+uint64(2)*uint64(uint64(nCol))+uint64(1)*uint64(uint64(nCol))+uint64(7))&uint64(^libc.Int32FromInt32(7))) /* Index.aSortOrder */ p = _sqlite3DbMallocZero(tls, db, uint64(nByte+nExtra)) if p != 0 { @@ -183673,7 +183673,7 @@ func _autoIncBegin(tls *libc.TLS, pParse uintptr, iDb int32, pTab uintptr) (r in v3 = pToplevel + 56 *(*int32)(unsafe.Pointer(v3))++ v2 = *(*int32)(unsafe.Pointer(v3)) - (*TAutoincInfo)(unsafe.Pointer(pInfo)).FregCtr = v2 /* Max rowid register */ + (*TAutoincInfo)(unsafe.Pointer(pInfo)).FregCtr = v2 /* Max rowid register */ *(*int32)(unsafe.Pointer(pToplevel + 56)) += int32(2) /* Rowid in sqlite_sequence + orig max val */ } memId = (*TAutoincInfo)(unsafe.Pointer(pInfo)).FregCtr @@ -209650,7 +209650,7 @@ func _sqlite3WhereCodeOneLoopStart(tls *libc.TLS, pParse uintptr, v uintptr, pWI iCovCur = v44 v47 = pParse + 56 *(*int32)(unsafe.Pointer(v47))++ - v46 = *(*int32)(unsafe.Pointer(v47)) /* Cursor used for index scans (if any) */ + v46 = *(*int32)(unsafe.Pointer(v47)) /* Cursor used for index scans (if any) */ regReturn = v46 /* Register used with OP_Gosub */ regRowset = 0 /* Register for RowSet object */ regRowid = 0 /* Register holding rowid */ @@ -232092,14 +232092,14 @@ _137: *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)) = _sqlite3SrcListAppend(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(2))*24+8, yymsp+8) /*A-overwrites-X*/ goto _346 _138: - ; /* xfullname ::= nm DOT nm AS nm */ + ; /* xfullname ::= nm DOT nm AS nm */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*24 + 8)) = _sqlite3SrcListAppend(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(4))*24+8, yymsp+uintptr(-libc.Int32FromInt32(2))*24+8) /*A-overwrites-X*/ if *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*24 + 8)) != 0 { (*(*TSrcItem)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*24 + 8)) + 8))).FzAlias = _sqlite3NameFromToken(tls, (*TParse)(unsafe.Pointer(pParse)).Fdb, yymsp+8) } goto _346 _139: - ; /* xfullname ::= nm AS nm */ + ; /* xfullname ::= nm AS nm */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)) = _sqlite3SrcListAppend(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(2))*24+8, uintptr(0)) /*A-overwrites-X*/ if *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)) != 0 { (*(*TSrcItem)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)) + 8))).FzAlias = _sqlite3NameFromToken(tls, (*TParse)(unsafe.Pointer(pParse)).Fdb, yymsp+8) @@ -232157,7 +232157,7 @@ _151: _sqlite3ExprListSetSortOrder(tls, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*24 + 8)), *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*24 + 8)), *(*int32)(unsafe.Pointer(yymsp + 8))) goto _346 _152: - ; /* sortlist ::= expr sortorder nulls */ + ; /* sortlist ::= expr sortorder nulls */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)) = _sqlite3ExprListAppend(tls, pParse, uintptr(0), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8))) /*A-overwrites-Y*/ _sqlite3ExprListSetSortOrder(tls, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)), *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*24 + 8)), *(*int32)(unsafe.Pointer(yymsp + 8))) goto _346 @@ -232736,7 +232736,7 @@ _253: *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*24 + 8)) = _parserAddExprIdListTerm(tls, pParse, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*24 + 8)), yymsp+uintptr(-libc.Int32FromInt32(2))*24+8, *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*24 + 8)), *(*int32)(unsafe.Pointer(yymsp + 8))) goto _346 _254: - ; /* eidlist ::= nm collate sortorder */ + ; /* eidlist ::= nm collate sortorder */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)) = _parserAddExprIdListTerm(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(2))*24+8, *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*24 + 8)), *(*int32)(unsafe.Pointer(yymsp + 8))) /*A-overwrites-Y*/ goto _346 _255: @@ -232851,7 +232851,7 @@ _280: *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(8))*24 + 8)) = *(*uintptr)(unsafe.Pointer(&*(*TYYMINORTYPE)(unsafe.Pointer(bp)))) goto _346 _281: - ; /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + ; /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ *(*uintptr)(unsafe.Pointer(&*(*TYYMINORTYPE)(unsafe.Pointer(bp)))) = _sqlite3TriggerInsertStep(tls, pParse, yymsp+uintptr(-libc.Int32FromInt32(4))*24+8, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(3))*24 + 8)), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)), uint8(*(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(6))*24 + 8))), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*24 + 8)), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(7))*24 + 8)), *(*uintptr)(unsafe.Pointer(yymsp + 8))) /*yylhsminor.yy427-overwrites-yymsp[-6].minor.yy144*/ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(7))*24 + 8)) = *(*uintptr)(unsafe.Pointer(&*(*TYYMINORTYPE)(unsafe.Pointer(bp)))) goto _346 @@ -232980,7 +232980,7 @@ _311: *(*Tu8)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*24 + 8)) = uint8(M10d_No) goto _346 _312: - ; /* wqitem ::= withnm eidlist_opt wqas LP select RP */ + ; /* wqitem ::= withnm eidlist_opt wqas LP select RP */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(5))*24 + 8)) = _sqlite3CteNew(tls, pParse, yymsp+uintptr(-libc.Int32FromInt32(5))*24+8, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*24 + 8)), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*24 + 8)), *(*Tu8)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(3))*24 + 8))) /*A-overwrites-X*/ goto _346 _313: @@ -232988,7 +232988,7 @@ _313: (*TParse)(unsafe.Pointer(pParse)).FbHasWith = uint8(1) goto _346 _314: - ; /* wqlist ::= wqitem */ + ; /* wqlist ::= wqitem */ *(*uintptr)(unsafe.Pointer(yymsp + 8)) = _sqlite3WithAdd(tls, pParse, uintptr(0), *(*uintptr)(unsafe.Pointer(yymsp + 8))) /*A-overwrites-X*/ goto _346 _315: @@ -263572,7 +263572,7 @@ func _sessionSerializeValue(tls *libc.TLS, aBuf uintptr, pValue uintptr, pnWrite var _ /* i at bp+0 */ Tu64 var _ /* r at bp+8 */ float64 _, _, _, _, _ = eType, n, nByte, nVarint, z /* Size of serialized value in bytes */ - if pValue != 0 { /* Value type (SQLITE_NULL, TEXT etc.) */ + if pValue != 0 { /* Value type (SQLITE_NULL, TEXT etc.) */ eType = Xsqlite3_value_type(tls, pValue) if aBuf != 0 { *(*Tu8)(unsafe.Pointer(aBuf)) = uint8(uint8(eType)) @@ -291297,7 +291297,7 @@ func _sqlite3Fts5StorageOpen(tls *libc.TLS, pConfig uintptr, pIndex uintptr, bCr var nByte Tsqlite3_int64 var p, zCols, zDefn, v1 uintptr _, _, _, _, _, _, _, _, _ = i, iOff, nByte, nDefn, p, rc, zCols, zDefn, v1 - rc = SQLITE_OK /* Bytes of space to allocate */ + rc = SQLITE_OK /* Bytes of space to allocate */ nByte = int64(uint64(128) + uint64((*TFts5Config)(unsafe.Pointer(pConfig)).FnCol)*uint64(8)) /* Fts5Storage.aTotalSize[] */ v1 = Xsqlite3_malloc64(tls, uint64(uint64(nByte))) p = v1 @@ -300917,4 +300917,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/lib/sqlite_windows_386.go b/vendor/modernc.org/sqlite/lib/sqlite_windows_386.go index 2b71621cd..c0d66b916 100644 --- a/vendor/modernc.org/sqlite/lib/sqlite_windows_386.go +++ b/vendor/modernc.org/sqlite/lib/sqlite_windows_386.go @@ -94062,7 +94062,7 @@ func Xsqlite3_str_vappendf(tls *libc.TLS, pAccum uintptr, fmt uintptr, ap Tva_li _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ = bArgList, base, bufpt, c, cThousand, ch, ch1, cset, done, e2, escarg, exp, flag_alternateform, flag_altform2, flag_dp, flag_leftjustify, flag_long, flag_prefix, flag_rtz, flag_zeropad, i, i1, iRound, idx, ii, infop, isnull, ix, j, j1, k, length, longvalue, n, n1, nCopyBytes, nOut, nPad, nPrior, needQuote, nn, pArgList, pExpr, pItem, pSel, pToken, pre, precision, prefix, px, q, realvalue, szBufNeeded, v, width, wx, x, x1, xtype, z, zExtra, zOut, v10, v100, v101, v102, v103, v104, v106, v107, v108, v109, v11, v110, v111, v12, v14, v15, v16, v17, v18, v19, v2, v20, v21, v22, v23, v24, v3, v4, v45, v46, v47, v48, v49, v5, v51, v52, v54, v55, v56, v57, v58, v59, v6, v60, v61, v62, v64, v65, v66, v67, v68, v7, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79, v8, v80, v81, v82, v83, v85, v86, v87, v88, v89, v9, v90, v91, v93, v94, v96, v97, v98, p92 /* Thousands separator for %d and %u */ xtype = uint8(etINVALID) /* Size of the rendering buffer */ zExtra = uintptr(0) /* True if trailing zeros should be removed */ - pArgList = uintptr(0) /* Conversion buffer */ + pArgList = uintptr(0) /* Conversion buffer */ /* pAccum never starts out with an empty buffer that was obtained from ** malloc(). This precondition is required by the mprintf("%z...") ** optimization. */ @@ -97585,14 +97585,14 @@ func _sqlite3AtoF(tls *libc.TLS, z uintptr, pResult uintptr, length int32, enc T var _ /* rr at bp+0 */ [2]float64 _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ = d, e, eType, eValid, esign, i, incr, nDigit, r, s, s2, sign, zEnd, v2, v3, v4 /* sign * significand * (10 ^ (esign * exponent)) */ - sign = int32(1) /* sign of significand */ - s = uint64(0) /* significand */ - d = 0 /* adjust exponent for shifting decimal point */ - esign = int32(1) /* sign of exponent */ - e = 0 /* exponent */ - eValid = int32(1) /* True exponent is either not used or is well-formed */ - nDigit = 0 /* Number of digits processed */ - eType = int32(1) /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ + sign = int32(1) /* sign of significand */ + s = uint64(0) /* significand */ + d = 0 /* adjust exponent for shifting decimal point */ + esign = int32(1) /* sign of exponent */ + e = 0 /* exponent */ + eValid = int32(1) /* True exponent is either not used or is well-formed */ + nDigit = 0 /* Number of digits processed */ + eType = int32(1) /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ *(*float64)(unsafe.Pointer(pResult)) = float64(0) /* Default return value, in case of an error */ if length == 0 { return 0 @@ -103121,7 +103121,7 @@ func _winOpen(tls *libc.TLS, pVfs uintptr, zName uintptr, id uintptr, flags int3 ** a temporary file. Use this buffer to store the file name in. */ *(*uintptr)(unsafe.Pointer(bp + 8)) = uintptr(0) /* For temporary filename, if necessary. */ - rc = SQLITE_OK /* Function Return Code */ + rc = SQLITE_OK /* Function Return Code */ isExclusive = flags & int32(SQLITE_OPEN_EXCLUSIVE) isDelete = flags & int32(SQLITE_OPEN_DELETEONCLOSE) isCreate = flags & int32(SQLITE_OPEN_CREATE) @@ -111142,7 +111142,7 @@ func _readDbPage(tls *libc.TLS, pPg uintptr) (r int32) { _, _, _, _ = dbFileVers, iOffset, pPager, rc pPager = (*TPgHdr)(unsafe.Pointer(pPg)).FpPager /* Pager object associated with page pPg */ rc = SQLITE_OK /* Return code */ - *(*Tu32)(unsafe.Pointer(bp)) = uint32(0) /* Frame of WAL containing pgno */ + *(*Tu32)(unsafe.Pointer(bp)) = uint32(0) /* Frame of WAL containing pgno */ if (*TPager)(unsafe.Pointer(pPager)).FpWal != uintptr(0) { rc = _sqlite3WalFindFrame(tls, (*TPager)(unsafe.Pointer(pPager)).FpWal, (*TPgHdr)(unsafe.Pointer(pPg)).Fpgno, bp) if rc != 0 { @@ -130278,7 +130278,7 @@ func _btreeCreateTable(tls *libc.TLS, p uintptr, piTable uintptr, createTabFlags var _ /* pgnoRoot at bp+4 */ TPgno var _ /* rc at bp+8 */ int32 _, _ = pBt, ptfFlags - pBt = (*TBtree)(unsafe.Pointer(p)).FpBt /* Page-type flags for the root page of new table */ + pBt = (*TBtree)(unsafe.Pointer(p)).FpBt /* Page-type flags for the root page of new table */ if (*TBtShared)(unsafe.Pointer(pBt)).FautoVacuum != 0 { /* The page to move to. */ /* Creating a new table may probably require moving an existing database ** to make room for the new tables root page. In case this page turns @@ -136500,7 +136500,7 @@ func _sqlite3VdbeMakeReady(tls *libc.TLS, p uintptr, pParse uintptr) { */ n = int32((libc.Uint32FromInt64(20)*uint32((*TVdbe)(unsafe.Pointer(p)).FnOp) + libc.Uint32FromInt32(7)) & uint32(^libc.Int32FromInt32(7))) /* Bytes of opcode memory used */ (*(*TReusableSpace)(unsafe.Pointer(bp + 8))).FpSpace = (*TVdbe)(unsafe.Pointer(p)).FaOp + uintptr(n) /* Unused opcode memory */ - (*(*TReusableSpace)(unsafe.Pointer(bp + 8))).FnFree = int64(((*TParse)(unsafe.Pointer(pParse)).FszOpAlloc - n) & ^libc.Int32FromInt32(7)) /* Bytes of unused memory */ + (*(*TReusableSpace)(unsafe.Pointer(bp + 8))).FnFree = int64(((*TParse)(unsafe.Pointer(pParse)).FszOpAlloc - n) & ^libc.Int32FromInt32(7)) /* Bytes of unused memory */ _resolveP2Values(tls, p, bp) libc.SetBitFieldPtr16Uint32(p+152, uint32(libc.BoolUint8((*TParse)(unsafe.Pointer(pParse)).FisMultiWrite != 0 && (*TParse)(unsafe.Pointer(pParse)).FmayAbort != 0)), 5, 0x20) if (*TParse)(unsafe.Pointer(pParse)).Fexplain != 0 { @@ -167158,7 +167158,7 @@ func _statInit(tls *libc.TLS, context uintptr, argc int32, argv uintptr) { nColUp = nCol nKeyCol = Xsqlite3_value_int(tls, *(*uintptr)(unsafe.Pointer(argv + 1*4))) /* Allocate the space required for the StatAccum object */ - n = int32(uint32(120) + uint32(8)*uint32(uint32(nColUp))) /* StatAccum.anDLt */ + n = int32(uint32(120) + uint32(8)*uint32(uint32(nColUp))) /* StatAccum.anDLt */ n = int32(uint32(n) + libc.Uint32FromInt64(8)*uint32(uint32(nColUp))) /* StatAccum.anEq */ if mxSample != 0 { n = int32(uint32(n) + (libc.Uint32FromInt64(8)*uint32(uint32(nColUp)) + libc.Uint32FromInt64(40)*uint32(nCol+mxSample) + libc.Uint32FromInt64(8)*libc.Uint32FromInt32(3)*uint32(uint32(nColUp))*uint32(nCol+mxSample))) @@ -174112,7 +174112,7 @@ func _sqlite3RefillIndex(tls *libc.TLS, pParse uintptr, pIndex uintptr, memRootP func _sqlite3AllocateIndexObject(tls *libc.TLS, db uintptr, nCol Ti16, nExtra int32, ppExtra uintptr) (r uintptr) { var nByte int32 var p, pExtra uintptr - _, _, _ = nByte, p, pExtra /* Bytes of space for Index object + arrays */ + _, _, _ = nByte, p, pExtra /* Bytes of space for Index object + arrays */ nByte = int32((libc.Uint32FromInt64(104)+libc.Uint32FromInt32(7))&uint32(^libc.Int32FromInt32(7)) + (uint32(4)*uint32(uint32(nCol))+uint32(7))&uint32(^libc.Int32FromInt32(7)) + (uint32(2)*uint32(int32(int32(nCol))+libc.Int32FromInt32(1))+uint32(2)*uint32(uint32(nCol))+uint32(1)*uint32(uint32(nCol))+uint32(7))&uint32(^libc.Int32FromInt32(7))) /* Index.aSortOrder */ p = _sqlite3DbMallocZero(tls, db, uint64(nByte+nExtra)) if p != 0 { @@ -183549,7 +183549,7 @@ func _autoIncBegin(tls *libc.TLS, pParse uintptr, iDb int32, pTab uintptr) (r in v3 = pToplevel + 44 *(*int32)(unsafe.Pointer(v3))++ v2 = *(*int32)(unsafe.Pointer(v3)) - (*TAutoincInfo)(unsafe.Pointer(pInfo)).FregCtr = v2 /* Max rowid register */ + (*TAutoincInfo)(unsafe.Pointer(pInfo)).FregCtr = v2 /* Max rowid register */ *(*int32)(unsafe.Pointer(pToplevel + 44)) += int32(2) /* Rowid in sqlite_sequence + orig max val */ } memId = (*TAutoincInfo)(unsafe.Pointer(pInfo)).FregCtr @@ -209563,7 +209563,7 @@ func _sqlite3WhereCodeOneLoopStart(tls *libc.TLS, pParse uintptr, v uintptr, pWI iCovCur = v44 v47 = pParse + 44 *(*int32)(unsafe.Pointer(v47))++ - v46 = *(*int32)(unsafe.Pointer(v47)) /* Cursor used for index scans (if any) */ + v46 = *(*int32)(unsafe.Pointer(v47)) /* Cursor used for index scans (if any) */ regReturn = v46 /* Register used with OP_Gosub */ regRowset = 0 /* Register for RowSet object */ regRowid = 0 /* Register holding rowid */ @@ -232009,14 +232009,14 @@ _137: *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)) = _sqlite3SrcListAppend(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(2))*12+4, yymsp+4) /*A-overwrites-X*/ goto _346 _138: - ; /* xfullname ::= nm DOT nm AS nm */ + ; /* xfullname ::= nm DOT nm AS nm */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*12 + 4)) = _sqlite3SrcListAppend(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(4))*12+4, yymsp+uintptr(-libc.Int32FromInt32(2))*12+4) /*A-overwrites-X*/ if *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*12 + 4)) != 0 { (*(*TSrcItem)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*12 + 4)) + 8))).FzAlias = _sqlite3NameFromToken(tls, (*TParse)(unsafe.Pointer(pParse)).Fdb, yymsp+4) } goto _346 _139: - ; /* xfullname ::= nm AS nm */ + ; /* xfullname ::= nm AS nm */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)) = _sqlite3SrcListAppend(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(2))*12+4, uintptr(0)) /*A-overwrites-X*/ if *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)) != 0 { (*(*TSrcItem)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)) + 8))).FzAlias = _sqlite3NameFromToken(tls, (*TParse)(unsafe.Pointer(pParse)).Fdb, yymsp+4) @@ -232074,7 +232074,7 @@ _151: _sqlite3ExprListSetSortOrder(tls, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*12 + 4)), *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*12 + 4)), *(*int32)(unsafe.Pointer(yymsp + 4))) goto _346 _152: - ; /* sortlist ::= expr sortorder nulls */ + ; /* sortlist ::= expr sortorder nulls */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)) = _sqlite3ExprListAppend(tls, pParse, uintptr(0), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4))) /*A-overwrites-Y*/ _sqlite3ExprListSetSortOrder(tls, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)), *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*12 + 4)), *(*int32)(unsafe.Pointer(yymsp + 4))) goto _346 @@ -232653,7 +232653,7 @@ _253: *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*12 + 4)) = _parserAddExprIdListTerm(tls, pParse, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*12 + 4)), yymsp+uintptr(-libc.Int32FromInt32(2))*12+4, *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*12 + 4)), *(*int32)(unsafe.Pointer(yymsp + 4))) goto _346 _254: - ; /* eidlist ::= nm collate sortorder */ + ; /* eidlist ::= nm collate sortorder */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)) = _parserAddExprIdListTerm(tls, pParse, uintptr(0), yymsp+uintptr(-libc.Int32FromInt32(2))*12+4, *(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*12 + 4)), *(*int32)(unsafe.Pointer(yymsp + 4))) /*A-overwrites-Y*/ goto _346 _255: @@ -232768,7 +232768,7 @@ _280: *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(8))*12 + 4)) = *(*uintptr)(unsafe.Pointer(&*(*TYYMINORTYPE)(unsafe.Pointer(bp)))) goto _346 _281: - ; /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + ; /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ *(*uintptr)(unsafe.Pointer(&*(*TYYMINORTYPE)(unsafe.Pointer(bp)))) = _sqlite3TriggerInsertStep(tls, pParse, yymsp+uintptr(-libc.Int32FromInt32(4))*12+4, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(3))*12 + 4)), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)), uint8(*(*int32)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(6))*12 + 4))), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*12 + 4)), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(7))*12 + 4)), *(*uintptr)(unsafe.Pointer(yymsp + 4))) /*yylhsminor.yy427-overwrites-yymsp[-6].minor.yy144*/ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(7))*12 + 4)) = *(*uintptr)(unsafe.Pointer(&*(*TYYMINORTYPE)(unsafe.Pointer(bp)))) goto _346 @@ -232897,7 +232897,7 @@ _311: *(*Tu8)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(2))*12 + 4)) = uint8(M10d_No) goto _346 _312: - ; /* wqitem ::= withnm eidlist_opt wqas LP select RP */ + ; /* wqitem ::= withnm eidlist_opt wqas LP select RP */ *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(5))*12 + 4)) = _sqlite3CteNew(tls, pParse, yymsp+uintptr(-libc.Int32FromInt32(5))*12+4, *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(4))*12 + 4)), *(*uintptr)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(1))*12 + 4)), *(*Tu8)(unsafe.Pointer(yymsp + uintptr(-libc.Int32FromInt32(3))*12 + 4))) /*A-overwrites-X*/ goto _346 _313: @@ -232905,7 +232905,7 @@ _313: (*TParse)(unsafe.Pointer(pParse)).FbHasWith = uint8(1) goto _346 _314: - ; /* wqlist ::= wqitem */ + ; /* wqlist ::= wqitem */ *(*uintptr)(unsafe.Pointer(yymsp + 4)) = _sqlite3WithAdd(tls, pParse, uintptr(0), *(*uintptr)(unsafe.Pointer(yymsp + 4))) /*A-overwrites-X*/ goto _346 _315: @@ -263543,7 +263543,7 @@ func _sessionSerializeValue(tls *libc.TLS, aBuf uintptr, pValue uintptr, pnWrite var _ /* i at bp+0 */ Tu64 var _ /* r at bp+8 */ float64 _, _, _, _, _ = eType, n, nByte, nVarint, z /* Size of serialized value in bytes */ - if pValue != 0 { /* Value type (SQLITE_NULL, TEXT etc.) */ + if pValue != 0 { /* Value type (SQLITE_NULL, TEXT etc.) */ eType = Xsqlite3_value_type(tls, pValue) if aBuf != 0 { *(*Tu8)(unsafe.Pointer(aBuf)) = uint8(uint8(eType)) @@ -291384,7 +291384,7 @@ func _sqlite3Fts5StorageOpen(tls *libc.TLS, pConfig uintptr, pIndex uintptr, bCr var nByte Tsqlite3_int64 var p, zCols, zDefn, v1 uintptr _, _, _, _, _, _, _, _, _ = i, iOff, nByte, nDefn, p, rc, zCols, zDefn, v1 - rc = SQLITE_OK /* Bytes of space to allocate */ + rc = SQLITE_OK /* Bytes of space to allocate */ nByte = int64(uint32(72) + uint32((*TFts5Config)(unsafe.Pointer(pConfig)).FnCol)*uint32(8)) /* Fts5Storage.aTotalSize[] */ v1 = Xsqlite3_malloc64(tls, uint64(uint64(nByte))) p = v1 @@ -301017,4 +301017,3 @@ type Sqlite3_index_info = sqlite3_index_info type Sqlite3_module = sqlite3_module type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab_cursor = sqlite3_vtab_cursor - diff --git a/vendor/modernc.org/sqlite/logo.png b/vendor/modernc.org/sqlite/logo.png new file mode 100644 index 000000000..bdac02dff Binary files /dev/null and b/vendor/modernc.org/sqlite/logo.png differ diff --git a/vendor/modernc.org/sqlite/sqlite.go b/vendor/modernc.org/sqlite/sqlite.go index a785c6af9..1af3eea7b 100644 --- a/vendor/modernc.org/sqlite/sqlite.go +++ b/vendor/modernc.org/sqlite/sqlite.go @@ -18,6 +18,7 @@ import ( "net/url" "reflect" "runtime" + "sort" "strconv" "strings" "sync" @@ -819,7 +820,25 @@ func applyQueryParams(c *conn, query string) error { return err } + var a []string for _, v := range q["_pragma"] { + a = append(a, v) + } + // Push 'busy_timeout' first, the rest in lexicographic order, case insenstive. + // See https://gitlab.com/cznic/sqlite/-/issues/198#note_2233423463 for + // discussion. + sort.Slice(a, func(i, j int) bool { + x, y := strings.TrimSpace(strings.ToLower(a[i])), strings.TrimSpace(strings.ToLower(a[j])) + if strings.HasPrefix(x, "busy_timeout") { + return true + } + if strings.HasPrefix(y, "busy_timeout") { + return false + } + + return x < y + }) + for _, v := range a { cmd := "pragma " + v _, err := c.exec(context.Background(), cmd, nil) if err != nil { @@ -1390,6 +1409,27 @@ func (c *conn) closeV2(db uintptr) error { return nil } +// ResetSession is called prior to executing a query on the connection if the +// connection has been used before. If the driver returns ErrBadConn the +// connection is discarded. +func (c *conn) ResetSession(ctx context.Context) error { + if !c.usable() { + return driver.ErrBadConn + } + + return nil +} + +// IsValid is called prior to placing the connection into the connection pool. +// The connection will be discarded if false is returned. +func (c *conn) IsValid() bool { + return c.usable() +} + +func (c *conn) usable() bool { + return c.db != 0 && sqlite3.Xsqlite3_is_interrupted(c.tls, c.db) == 0 +} + // FunctionImpl describes an [application-defined SQL function]. If Scalar is // set, it is treated as a scalar function; otherwise, it is treated as an // aggregate function using MakeAggregate. diff --git a/vendor/modules.txt b/vendor/modules.txt index a5465ec96..ed9a35bd1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -24,7 +24,7 @@ codeberg.org/gruf/go-fastcopy # codeberg.org/gruf/go-fastpath/v2 v2.0.0 ## explicit; go 1.14 codeberg.org/gruf/go-fastpath/v2 -# codeberg.org/gruf/go-ffmpreg v0.6.0 +# codeberg.org/gruf/go-ffmpreg v0.6.1 ## explicit; go 1.22.0 codeberg.org/gruf/go-ffmpreg/embed codeberg.org/gruf/go-ffmpreg/wasm @@ -66,7 +66,7 @@ codeberg.org/gruf/go-storage/s3 # codeberg.org/gruf/go-structr v0.8.11 ## explicit; go 1.21 codeberg.org/gruf/go-structr -# codeberg.org/superseriousbusiness/exif-terminator v0.9.0 +# codeberg.org/superseriousbusiness/exif-terminator v0.9.1 ## explicit; go 1.21 codeberg.org/superseriousbusiness/exif-terminator # github.com/DmitriyVTitov/size v1.5.0 @@ -91,6 +91,9 @@ github.com/asaskevich/govalidator ## explicit github.com/aymerick/douceur/css github.com/aymerick/douceur/parser +# github.com/bahlo/generic-list-go v0.2.0 +## explicit; go 1.18 +github.com/bahlo/generic-list-go # github.com/beorn7/perks v1.0.1 ## explicit; go 1.11 github.com/beorn7/perks/quantile @@ -98,6 +101,9 @@ github.com/beorn7/perks/quantile ## explicit; go 1.14 github.com/buckket/go-blurhash github.com/buckket/go-blurhash/base83 +# github.com/buger/jsonparser v1.1.1 +## explicit; go 1.13 +github.com/buger/jsonparser # github.com/bytedance/sonic v1.11.6 ## explicit; go 1.16 github.com/bytedance/sonic @@ -300,7 +306,7 @@ github.com/go-playground/universal-translator # github.com/go-playground/validator/v10 v10.20.0 ## explicit; go 1.18 github.com/go-playground/validator/v10 -# github.com/go-swagger/go-swagger v0.31.0 +# github.com/go-swagger/go-swagger v0.31.0 => github.com/superseriousbusiness/go-swagger v0.31.0-gts-go1.23-fix ## explicit; go 1.21 github.com/go-swagger/go-swagger/cmd/swagger github.com/go-swagger/go-swagger/cmd/swagger/commands @@ -364,8 +370,8 @@ github.com/gorilla/securecookie # github.com/gorilla/sessions v1.2.2 ## explicit; go 1.20 github.com/gorilla/sessions -# github.com/gorilla/websocket v1.5.2 -## explicit; go 1.20 +# github.com/gorilla/websocket v1.5.3 +## explicit; go 1.12 github.com/gorilla/websocket # github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 ## explicit; go 1.21 @@ -482,7 +488,7 @@ github.com/miekg/dns # github.com/minio/md5-simd v1.1.2 ## explicit; go 1.14 github.com/minio/md5-simd -# github.com/minio/minio-go/v7 v7.0.80 +# github.com/minio/minio-go/v7 v7.0.81 ## explicit; go 1.22 github.com/minio/minio-go/v7 github.com/minio/minio-go/v7/pkg/cors @@ -514,7 +520,7 @@ github.com/modern-go/reflect2 # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 ## explicit github.com/munnerz/goautoneg -# github.com/ncruces/go-sqlite3 v0.20.2 +# github.com/ncruces/go-sqlite3 v0.20.3 ## explicit; go 1.21 github.com/ncruces/go-sqlite3 github.com/ncruces/go-sqlite3/driver @@ -631,9 +637,10 @@ github.com/spf13/viper/internal/encoding/json github.com/spf13/viper/internal/encoding/toml github.com/spf13/viper/internal/encoding/yaml github.com/spf13/viper/internal/features -# github.com/stretchr/testify v1.9.0 +# github.com/stretchr/testify v1.10.0 ## explicit; go 1.17 github.com/stretchr/testify/assert +github.com/stretchr/testify/assert/yaml github.com/stretchr/testify/require github.com/stretchr/testify/suite # github.com/subosito/gotenv v1.6.0 @@ -832,11 +839,11 @@ github.com/superseriousbusiness/oauth2/v4/generates github.com/superseriousbusiness/oauth2/v4/manage github.com/superseriousbusiness/oauth2/v4/models github.com/superseriousbusiness/oauth2/v4/server -# github.com/tdewolff/minify/v2 v2.21.1 +# github.com/tdewolff/minify/v2 v2.21.2 ## explicit; go 1.18 github.com/tdewolff/minify/v2 github.com/tdewolff/minify/v2/html -# github.com/tdewolff/parse/v2 v2.7.18 +# github.com/tdewolff/parse/v2 v2.7.19 ## explicit; go 1.13 github.com/tdewolff/parse/v2 github.com/tdewolff/parse/v2/buffer @@ -845,7 +852,7 @@ github.com/tdewolff/parse/v2/strconv # github.com/technologize/otel-go-contrib v1.1.1 ## explicit; go 1.17 github.com/technologize/otel-go-contrib/otelginmetrics -# github.com/tetratelabs/wazero v1.8.1 +# github.com/tetratelabs/wazero v1.8.2 ## explicit; go 1.21 github.com/tetratelabs/wazero github.com/tetratelabs/wazero/api @@ -918,8 +925,8 @@ github.com/ugorji/go/codec github.com/ulule/limiter/v3 github.com/ulule/limiter/v3/drivers/store/common github.com/ulule/limiter/v3/drivers/store/memory -# github.com/uptrace/bun v1.2.5 -## explicit; go 1.22 +# github.com/uptrace/bun v1.2.6 +## explicit; go 1.23 github.com/uptrace/bun github.com/uptrace/bun/dialect github.com/uptrace/bun/dialect/feature @@ -929,15 +936,16 @@ github.com/uptrace/bun/internal github.com/uptrace/bun/internal/parser github.com/uptrace/bun/internal/tagparser github.com/uptrace/bun/migrate +github.com/uptrace/bun/migrate/sqlschema github.com/uptrace/bun/schema -# github.com/uptrace/bun/dialect/pgdialect v1.2.5 -## explicit; go 1.22 +# github.com/uptrace/bun/dialect/pgdialect v1.2.6 +## explicit; go 1.23 github.com/uptrace/bun/dialect/pgdialect -# github.com/uptrace/bun/dialect/sqlitedialect v1.2.5 -## explicit; go 1.22 +# github.com/uptrace/bun/dialect/sqlitedialect v1.2.6 +## explicit; go 1.23 github.com/uptrace/bun/dialect/sqlitedialect -# github.com/uptrace/bun/extra/bunotel v1.2.5 -## explicit; go 1.22 +# github.com/uptrace/bun/extra/bunotel v1.2.6 +## explicit; go 1.23 github.com/uptrace/bun/extra/bunotel # github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 ## explicit; go 1.22 @@ -954,6 +962,9 @@ github.com/vmihailenco/tagparser/v2/internal/parser # github.com/wagslane/go-password-validator v0.3.0 ## explicit; go 1.16 github.com/wagslane/go-password-validator +# github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41 +## explicit; go 1.23 +github.com/wk8/go-ordered-map/v2 # github.com/yuin/goldmark v1.7.8 ## explicit; go 1.19 github.com/yuin/goldmark @@ -1109,11 +1120,9 @@ golang.org/x/net/http2/hpack golang.org/x/net/idna golang.org/x/net/internal/iana golang.org/x/net/internal/socket -golang.org/x/net/internal/socks golang.org/x/net/internal/timeseries golang.org/x/net/ipv4 golang.org/x/net/ipv6 -golang.org/x/net/proxy golang.org/x/net/publicsuffix golang.org/x/net/trace # golang.org/x/oauth2 v0.24.0 @@ -1332,8 +1341,8 @@ modernc.org/mathutil # modernc.org/memory v1.8.0 ## explicit; go 1.18 modernc.org/memory -# modernc.org/sqlite v0.0.0-00010101000000-000000000000 => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround -## explicit; go 1.20 +# modernc.org/sqlite v0.0.0-00010101000000-000000000000 => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.34.2-concurrency-workaround +## explicit; go 1.21 modernc.org/sqlite modernc.org/sqlite/lib # modernc.org/strutil v1.2.0 @@ -1345,7 +1354,8 @@ modernc.org/token # mvdan.cc/xurls/v2 v2.5.0 ## explicit; go 1.19 mvdan.cc/xurls/v2 -# modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround +# github.com/go-swagger/go-swagger => github.com/superseriousbusiness/go-swagger v0.31.0-gts-go1.23-fix +# modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.34.2-concurrency-workaround # go.opentelemetry.io/otel => go.opentelemetry.io/otel v1.29.0 # go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc => go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 # go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 diff --git a/web/assets/themes/blurple-auto.css b/web/assets/themes/blurple-auto.css new file mode 100644 index 000000000..817a07248 --- /dev/null +++ b/web/assets/themes/blurple-auto.css @@ -0,0 +1,10 @@ +/* + theme-title: Blurple (auto) + theme-description: Official blurple theme that adapts to system preferences +*/ + +/* Default to dark theme */ +@import url("blurple-dark.css"); + +@import url("blurple-light.css") screen and (prefers-color-scheme: light); +@import url("blurple-dark.css") screen and (prefers-color-scheme: dark); diff --git a/web/assets/themes/brutalist-auto.css b/web/assets/themes/brutalist-auto.css new file mode 100644 index 000000000..080360c87 --- /dev/null +++ b/web/assets/themes/brutalist-auto.css @@ -0,0 +1,10 @@ +/* + theme-title: Brutalist (auto) + theme-description: Official (Pseudo-)monochrome brutality theme that adapts to system preferences +*/ + +/* Default to brutalist theme */ +@import url("brutalist.css"); + +@import url("brutalist.css") screen and (prefers-color-scheme: light); +@import url("brutalist-dark.css") screen and (prefers-color-scheme: dark); diff --git a/web/assets/themes/solarized-auto.css b/web/assets/themes/solarized-auto.css new file mode 100644 index 000000000..8324ef5f7 --- /dev/null +++ b/web/assets/themes/solarized-auto.css @@ -0,0 +1,10 @@ +/* + theme-title: Solarized (auto) + theme-description: Solarized theme that adapts to system preferences +*/ + +/* Default to dark theme */ +@import url("solarized-dark.css"); + +@import url("solarized-light.css") screen and (prefers-color-scheme: light); +@import url("solarized-dark.css") screen and (prefers-color-scheme: dark); diff --git a/web/source/settings/lib/types/instance.ts b/web/source/settings/lib/types/instance.ts index 11f75032c..9abdc6a96 100644 --- a/web/source/settings/lib/types/instance.ts +++ b/web/source/settings/lib/types/instance.ts @@ -25,6 +25,7 @@ export interface InstanceV1 { description_text?: string; short_description: string; short_description_text?: string; + custom_css: string; email: string; version: string; debug?: boolean; diff --git a/web/source/settings/views/admin/instance/settings.tsx b/web/source/settings/views/admin/instance/settings.tsx index c769b11ec..fd5ceb1ee 100644 --- a/web/source/settings/views/admin/instance/settings.tsx +++ b/web/source/settings/views/admin/instance/settings.tsx @@ -46,7 +46,7 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) { const shortDescLimit = 500; const descLimit = 5000; const termsLimit = 5000; - + const form = { title: useTextInput("title", { source: instance, @@ -66,6 +66,10 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) { valueSelector: (s: InstanceV1) => s.description_text, validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less` }), + customCSS: useTextInput("custom_css", { + source: instance, + valueSelector: (s: InstanceV1) => s.custom_css + }), terms: useTextInput("terms", { source: instance, // Select "raw" text version of parsed field for editing. @@ -191,7 +195,16 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) { type="email" /> +