gotosocial/internal/api/util/parsequery.go
kim cac9d65029
[performance] convert enum strings to ints (#3558)
* convert statuses.visibility and notifications.notification_type columns from type string -> int for performance / space savings

* fix test trying to compare string to int

* fix instance count query using string literal instead of gtsmodel const type

* ensure a default value is always set

* also migrate the account settings and sin bin status tables

* initialize maps outside loops and place into singular enum mapping creation func

* use int16 for enum types

* update sinbinstatus creation to be from a snapshot at initial creation

* add snapshot of poll type at creation time
2024-11-25 14:48:59 +01:00

411 lines
11 KiB
Go

// 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 <http://www.gnu.org/licenses/>.
package util
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
const (
/* API version keys */
APIVersionKey = "api_version"
APIv1 = "v1"
APIv2 = "v2"
/* Common keys */
IDKey = "id"
LimitKey = "limit"
LocalKey = "local"
MaxIDKey = "max_id"
SinceIDKey = "since_id"
MinIDKey = "min_id"
UsernameKey = "username"
AccountIDKey = "account_id"
TargetAccountIDKey = "target_account_id"
ResolvedKey = "resolved"
/* AP endpoint keys */
OnlyOtherAccountsKey = "only_other_accounts"
/* Search keys */
SearchExcludeUnreviewedKey = "exclude_unreviewed"
SearchFollowingKey = "following"
SearchLookupKey = "acct"
SearchOffsetKey = "offset"
SearchQueryKey = "q"
SearchResolveKey = "resolve"
SearchTypeKey = "type"
/* Tag keys */
TagNameKey = "tag_name"
/* Web endpoint keys */
WebStatusIDKey = "status"
/* Domain permission keys */
DomainPermissionExportKey = "export"
DomainPermissionImportKey = "import"
DomainPermissionSubscriptionIDKey = "subscription_id"
DomainPermissionPermTypeKey = "permission_type"
DomainPermissionDomainKey = "domain"
/* Admin query keys */
AdminRemoteKey = "remote"
AdminActiveKey = "active"
AdminPendingKey = "pending"
AdminDisabledKey = "disabled"
AdminSilencedKey = "silenced"
AdminSuspendedKey = "suspended"
AdminSensitizedKey = "sensitized"
AdminDisplayNameKey = "display_name"
AdminByDomainKey = "by_domain"
AdminEmailKey = "email"
AdminIPKey = "ip"
AdminStaffKey = "staff"
AdminOriginKey = "origin"
AdminStatusKey = "status"
AdminPermissionsKey = "permissions"
AdminRoleIDsKey = "role_ids[]"
AdminInvitedByKey = "invited_by"
/* Interaction policy + request keys */
InteractionStatusIDKey = "status_id"
InteractionFavouritesKey = "favourites"
InteractionRepliesKey = "replies"
InteractionReblogsKey = "reblogs"
)
/*
Parse functions for *OPTIONAL* parameters with default values.
*/
func ParseMaxID(value string, defaultValue string) string {
if value == "" {
return defaultValue
}
return value
}
func ParseSinceID(value string, defaultValue string) string {
if value == "" {
return defaultValue
}
return value
}
func ParseMinID(value string, defaultValue string) string {
if value == "" {
return defaultValue
}
return value
}
func ParseLimit(value string, defaultValue int, max, min int) (int, gtserror.WithCode) {
i, err := parseInt(value, defaultValue, max, min, LimitKey)
if err != nil {
return 0, err
}
return i, nil
}
func ParseLocal(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, LocalKey)
}
func ParseResolved(value string, defaultValue *bool) (*bool, gtserror.WithCode) {
return parseBoolPtr(value, defaultValue, ResolvedKey)
}
func ParseSearchExcludeUnreviewed(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, SearchExcludeUnreviewedKey)
}
func ParseSearchFollowing(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, SearchFollowingKey)
}
func ParseSearchOffset(value string, defaultValue int, max, min int) (int, gtserror.WithCode) {
return parseInt(value, defaultValue, max, min, SearchOffsetKey)
}
func ParseSearchResolve(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, SearchResolveKey)
}
func ParseDomainPermissionExport(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, DomainPermissionExportKey)
}
func ParseDomainPermissionImport(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, DomainPermissionImportKey)
}
func ParseOnlyOtherAccounts(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, OnlyOtherAccountsKey)
}
func ParseAdminRemote(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, AdminRemoteKey)
}
func ParseAdminActive(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, AdminActiveKey)
}
func ParseAdminPending(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, AdminPendingKey)
}
func ParseAdminDisabled(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, AdminDisabledKey)
}
func ParseAdminSilenced(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, AdminSilencedKey)
}
func ParseAdminSuspended(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, AdminSuspendedKey)
}
func ParseAdminStaff(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, AdminStaffKey)
}
func ParseInteractionFavourites(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, InteractionFavouritesKey)
}
func ParseInteractionReplies(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, InteractionRepliesKey)
}
func ParseInteractionReblogs(value string, defaultValue bool) (bool, gtserror.WithCode) {
return parseBool(value, defaultValue, InteractionReblogsKey)
}
func ParseNotificationType(value string) (gtsmodel.NotificationType, gtserror.WithCode) {
switch strings.ToLower(value) {
case "follow":
return gtsmodel.NotificationFollow, nil
case "follow_request":
return gtsmodel.NotificationFollowRequest, nil
case "mention":
return gtsmodel.NotificationMention, nil
case "reblog":
return gtsmodel.NotificationReblog, nil
case "favourite":
return gtsmodel.NotificationFave, nil
case "poll":
return gtsmodel.NotificationPoll, nil
case "status":
return gtsmodel.NotificationStatus, nil
case "admin.sign_up":
return gtsmodel.NotificationSignup, nil
case "pending.favourite":
return gtsmodel.NotificationPendingFave, nil
case "pending.reply":
return gtsmodel.NotificationPendingReply, nil
case "pending.reblog":
return gtsmodel.NotificationPendingReblog, nil
default:
text := fmt.Sprintf("unrecognized notification type %s", value)
return 0, gtserror.NewErrorBadRequest(errors.New(text), text)
}
}
func ParseNotificationTypes(values []string) ([]gtsmodel.NotificationType, gtserror.WithCode) {
if len(values) == 0 {
return nil, nil
}
ntypes := make([]gtsmodel.NotificationType, len(values))
for i, value := range values {
ntype, errWithCode := ParseNotificationType(value)
if errWithCode != nil {
return nil, errWithCode
}
ntypes[i] = ntype
}
return ntypes, nil
}
/*
Parse functions for *REQUIRED* parameters.
*/
func ParseAPIVersion(value string, availableVersion ...string) (string, gtserror.WithCode) {
key := APIVersionKey
if value == "" {
return "", requiredError(key)
}
for _, av := range availableVersion {
if value == av {
return value, nil
}
}
err := fmt.Errorf(
"invalid API version, valid versions for this path are [%s]",
strings.Join(availableVersion, ", "),
)
return "", gtserror.NewErrorBadRequest(err, err.Error())
}
func ParseID(value string) (string, gtserror.WithCode) {
key := IDKey
if value == "" {
return "", requiredError(key)
}
return value, nil
}
func ParseSearchLookup(value string) (string, gtserror.WithCode) {
key := SearchLookupKey
if value == "" {
return "", requiredError(key)
}
return value, nil
}
func ParseSearchQuery(value string) (string, gtserror.WithCode) {
key := SearchQueryKey
if value == "" {
return "", requiredError(key)
}
return value, nil
}
func ParseTagName(value string) (string, gtserror.WithCode) {
key := TagNameKey
if value == "" {
return "", requiredError(key)
}
return value, nil
}
func ParseUsername(value string) (string, gtserror.WithCode) {
key := UsernameKey
if value == "" {
return "", requiredError(key)
}
return value, nil
}
func ParseWebStatusID(value string) (string, gtserror.WithCode) {
key := WebStatusIDKey
if value == "" {
return "", requiredError(key)
}
return value, nil
}
/*
Internal functions
*/
func parseBool(value string, defaultValue bool, key string) (bool, gtserror.WithCode) {
if value == "" {
return defaultValue, nil
}
i, err := strconv.ParseBool(value)
if err != nil {
return defaultValue, parseError(key, value, defaultValue, err)
}
return i, nil
}
func parseBoolPtr(value string, defaultValue *bool, key string) (*bool, gtserror.WithCode) {
if value == "" {
return defaultValue, nil
}
i, err := strconv.ParseBool(value)
if err != nil {
return defaultValue, parseError(key, value, defaultValue, err)
}
return &i, nil
}
func parseInt(value string, defaultValue int, max int, min int, key string) (int, gtserror.WithCode) {
if value == "" {
return defaultValue, nil
}
i, err := strconv.Atoi(value)
if err != nil {
return defaultValue, parseError(key, value, defaultValue, err)
}
if i > max {
i = max
} else if i < min {
i = min
}
return i, nil
}
// parseError returns gtserror.WithCode set to 400 Bad Request, to indicate
// to the caller that a key was set to a value that could not be parsed.
func parseError(key string, value, defaultValue any, err error) gtserror.WithCode {
err = fmt.Errorf("error parsing key %s with value %s as %T: %w", key, value, defaultValue, err)
return gtserror.NewErrorBadRequest(err, err.Error())
}
// requiredError returns gtserror.WithCode set to 400 Bad Request, to indicate
// to the caller a required key value was not provided, or was empty.
func requiredError(key string) gtserror.WithCode {
err := fmt.Errorf("required key %s was not set or had empty value", key)
return gtserror.NewErrorBadRequest(err, err.Error())
}