You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
paopao-ce/internal/infra/settings/registry.go

691 lines
48 KiB

// Copyright 2026 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package settings
import (
stdjson "encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"github.com/alimy/tryst/cfg"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/pkg/xerror"
"github.com/sirupsen/logrus"
)
type (
ValueType string
ApplyMode string
)
const (
TypeBool ValueType = "bool"
TypeInt ValueType = "int"
TypeFloat ValueType = "float"
TypeString ValueType = "string"
ApplyModeLive ApplyMode = "live"
ApplyModeRestartRequired ApplyMode = "restart_required"
ApplyModeBootstrapOnly ApplyMode = "bootstrap_only"
)
type bootstrapSnapshot struct {
App confAppSnapshot
TweetSearch confTweetSearchSnapshot
Zinc confZincSnapshot
Meili confMeiliSnapshot
ObjectStorage confObjectStorageSnapshot
AliOSS confAliOSSSnapshot
COS confCOSSnapshot
HuaweiOBS confHuaweiOBSSnapshot
MinIO confMinIOSnapshot
S3 confS3Snapshot
LocalOSS confLocalOSSSnapshot
SmsJuhe confSmsJuheSnapshot
Alipay confAlipaySnapshot
WebProfile confWebProfileSnapshot
}
type confAppSnapshot struct {
AttachmentIncomeRate float64
MaxCommentCount int64
MaxWhisperDaily int64
MaxCaptchaTimes int
DefaultPageSize int
MaxPageSize int
}
type confTweetSearchSnapshot struct {
MaxUpdateQPS int
MinWorker int
}
type confZincSnapshot struct {
Host string
Index string
User string
Password string
Secure bool
}
type confMeiliSnapshot struct {
Host string
Index string
ApiKey string
Secure bool
}
type confObjectStorageSnapshot struct {
RetainInDays int
TempDir string
}
type confAliOSSSnapshot struct {
Endpoint string
AccessKeyID string
AccessKeySecret string
Bucket string
Domain string
}
type confCOSSnapshot struct {
SecretID string
SecretKey string
Region string
Bucket string
Domain string
}
type confHuaweiOBSSnapshot struct {
AccessKey string
SecretKey string
Endpoint string
Bucket string
Domain string
}
type confMinIOSnapshot struct {
AccessKey string
SecretKey string
Secure bool
Endpoint string
Bucket string
Domain string
}
type confS3Snapshot struct {
AccessKey string
SecretKey string
Secure bool
Endpoint string
Bucket string
Domain string
}
type confLocalOSSSnapshot struct {
SavePath string
Secure bool
Bucket string
Domain string
}
type confSmsJuheSnapshot struct {
Gateway string
Key string
TplID string
TplVal string
}
type confAlipaySnapshot struct {
AppID string
PrivateKey string
RootCertFile string
PublicCertFile string
AppPublicCertFile string
InProduction bool
}
type confWebProfileSnapshot struct {
UseFriendship bool
EnableTrendsBar bool
EnableWallet bool
AllowTweetAttachment bool
AllowTweetAttachmentPrice bool
AllowTweetVideo bool
AllowUserRegister bool
AllowPhoneBind bool
DefaultTweetMaxLength int
TweetWebEllipsisSize int
TweetMobileEllipsisSize int
DefaultTweetVisibility string
DefaultMsgLoopInterval int
CopyrightTop string
CopyrightLeft string
CopyrightLeftLink string
CopyrightRight string
CopyrightRightLink string
}
type Option struct {
Label string `json:"label"`
Value any `json:"value"`
}
type Definition struct {
Key string
Group string
Section string
Type ValueType
Label string
Description string
ApplyMode ApplyMode
Secret bool
Readonly bool
Options []Option
Active func() bool
CurrentValue func() any
BootstrapDefault func() any
ValidateValue func(any) (any, error)
BootstrapOverride func(any)
}
var (
ReadonlyFields = []string{"allow_user_register", "allow_phone_bind"}
bootstrapConfig *bootstrapSnapshot
visibilityOption = []Option{{Label: "Public", Value: "public"}, {Label: "Following", Value: "following"}, {Label: "Friend", Value: "friend"}, {Label: "Private", Value: "private"}}
)
func ensureBootstrapSnapshot() {
if bootstrapConfig != nil {
return
}
bootstrapConfig = &bootstrapSnapshot{}
if conf.AppSetting != nil {
bootstrapConfig.App = confAppSnapshot{
AttachmentIncomeRate: conf.AppSetting.AttachmentIncomeRate,
MaxCommentCount: conf.AppSetting.MaxCommentCount,
MaxWhisperDaily: conf.AppSetting.MaxWhisperDaily,
MaxCaptchaTimes: conf.AppSetting.MaxCaptchaTimes,
DefaultPageSize: conf.AppSetting.DefaultPageSize,
MaxPageSize: conf.AppSetting.MaxPageSize,
}
}
if conf.TweetSearchSetting != nil {
bootstrapConfig.TweetSearch = confTweetSearchSnapshot{MaxUpdateQPS: conf.TweetSearchSetting.MaxUpdateQPS, MinWorker: conf.TweetSearchSetting.MinWorker}
}
if conf.ZincSetting != nil {
bootstrapConfig.Zinc = confZincSnapshot{Host: conf.ZincSetting.Host, Index: conf.ZincSetting.Index, User: conf.ZincSetting.User, Password: conf.ZincSetting.Password, Secure: conf.ZincSetting.Secure}
}
if conf.MeiliSetting != nil {
bootstrapConfig.Meili = confMeiliSnapshot{Host: conf.MeiliSetting.Host, Index: conf.MeiliSetting.Index, ApiKey: conf.MeiliSetting.ApiKey, Secure: conf.MeiliSetting.Secure}
}
if conf.ObjectStorage != nil {
bootstrapConfig.ObjectStorage = confObjectStorageSnapshot{RetainInDays: conf.ObjectStorage.RetainInDays, TempDir: conf.ObjectStorage.TempDir}
}
if conf.AliOSSSetting != nil {
bootstrapConfig.AliOSS = confAliOSSSnapshot{Endpoint: conf.AliOSSSetting.Endpoint, AccessKeyID: conf.AliOSSSetting.AccessKeyID, AccessKeySecret: conf.AliOSSSetting.AccessKeySecret, Bucket: conf.AliOSSSetting.Bucket, Domain: conf.AliOSSSetting.Domain}
}
if conf.COSSetting != nil {
bootstrapConfig.COS = confCOSSnapshot{SecretID: conf.COSSetting.SecretID, SecretKey: conf.COSSetting.SecretKey, Region: conf.COSSetting.Region, Bucket: conf.COSSetting.Bucket, Domain: conf.COSSetting.Domain}
}
if conf.HuaweiOBSSetting != nil {
bootstrapConfig.HuaweiOBS = confHuaweiOBSSnapshot{AccessKey: conf.HuaweiOBSSetting.AccessKey, SecretKey: conf.HuaweiOBSSetting.SecretKey, Endpoint: conf.HuaweiOBSSetting.Endpoint, Bucket: conf.HuaweiOBSSetting.Bucket, Domain: conf.HuaweiOBSSetting.Domain}
}
if conf.MinIOSetting != nil {
bootstrapConfig.MinIO = confMinIOSnapshot{AccessKey: conf.MinIOSetting.AccessKey, SecretKey: conf.MinIOSetting.SecretKey, Secure: conf.MinIOSetting.Secure, Endpoint: conf.MinIOSetting.Endpoint, Bucket: conf.MinIOSetting.Bucket, Domain: conf.MinIOSetting.Domain}
}
if conf.S3Setting != nil {
bootstrapConfig.S3 = confS3Snapshot{AccessKey: conf.S3Setting.AccessKey, SecretKey: conf.S3Setting.SecretKey, Secure: conf.S3Setting.Secure, Endpoint: conf.S3Setting.Endpoint, Bucket: conf.S3Setting.Bucket, Domain: conf.S3Setting.Domain}
}
if conf.LocalOSSSetting != nil {
bootstrapConfig.LocalOSS = confLocalOSSSnapshot{SavePath: conf.LocalOSSSetting.SavePath, Secure: conf.LocalOSSSetting.Secure, Bucket: conf.LocalOSSSetting.Bucket, Domain: conf.LocalOSSSetting.Domain}
}
if conf.SmsJuheSetting != nil {
bootstrapConfig.SmsJuhe = confSmsJuheSnapshot{Gateway: conf.SmsJuheSetting.Gateway, Key: conf.SmsJuheSetting.Key, TplID: conf.SmsJuheSetting.TplID, TplVal: conf.SmsJuheSetting.TplVal}
}
if conf.AlipaySetting != nil {
bootstrapConfig.Alipay = confAlipaySnapshot{AppID: conf.AlipaySetting.AppID, PrivateKey: conf.AlipaySetting.PrivateKey, RootCertFile: conf.AlipaySetting.RootCertFile, PublicCertFile: conf.AlipaySetting.PublicCertFile, AppPublicCertFile: conf.AlipaySetting.AppPublicCertFile, InProduction: conf.AlipaySetting.InProduction}
}
if conf.WebProfileSetting != nil {
bootstrapConfig.WebProfile = confWebProfileSnapshot{
UseFriendship: conf.WebProfileSetting.UseFriendship,
EnableTrendsBar: conf.WebProfileSetting.EnableTrendsBar,
EnableWallet: conf.WebProfileSetting.EnableWallet,
AllowTweetAttachment: conf.WebProfileSetting.AllowTweetAttachment,
AllowTweetAttachmentPrice: conf.WebProfileSetting.AllowTweetAttachmentPrice,
AllowTweetVideo: conf.WebProfileSetting.AllowTweetVideo,
AllowUserRegister: conf.WebProfileSetting.AllowUserRegister,
AllowPhoneBind: conf.WebProfileSetting.AllowPhoneBind,
DefaultTweetMaxLength: conf.WebProfileSetting.DefaultTweetMaxLength,
TweetWebEllipsisSize: conf.WebProfileSetting.TweetWebEllipsisSize,
TweetMobileEllipsisSize: conf.WebProfileSetting.TweetMobileEllipsisSize,
DefaultTweetVisibility: conf.WebProfileSetting.DefaultTweetVisibility,
DefaultMsgLoopInterval: conf.WebProfileSetting.DefaultMsgLoopInterval,
CopyrightTop: conf.WebProfileSetting.CopyrightTop,
CopyrightLeft: conf.WebProfileSetting.CopyrightLeft,
CopyrightLeftLink: conf.WebProfileSetting.CopyrightLeftLink,
CopyrightRight: conf.WebProfileSetting.CopyrightRight,
CopyrightRightLink: conf.WebProfileSetting.CopyrightRightLink,
}
}
}
func CloneReadonlyFields() []string {
res := make([]string, len(ReadonlyFields))
copy(res, ReadonlyFields)
return res
}
func Registry() []Definition {
ensureBootstrapSnapshot()
return []Definition{
boolDef("web_profile.use_friendship", "web", "profile", "Use friendship", "Switch the frontend friendship model.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.UseFriendship }, func() any { return bootstrapConfig.WebProfile.UseFriendship }, func(v any) { conf.WebProfileSetting.UseFriendship = v.(bool) }),
boolDef("web_profile.enable_trends_bar", "web", "profile", "Enable trends bar", "Show the trends sidebar in the web UI.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.EnableTrendsBar }, func() any { return bootstrapConfig.WebProfile.EnableTrendsBar }, func(v any) { conf.WebProfileSetting.EnableTrendsBar = v.(bool) }),
boolDef("web_profile.enable_wallet", "web", "profile", "Enable wallet", "Enable wallet-related frontend features.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.EnableWallet }, func() any { return bootstrapConfig.WebProfile.EnableWallet }, func(v any) { conf.WebProfileSetting.EnableWallet = v.(bool) }),
boolDef("web_profile.allow_tweet_attachment", "web", "profile", "Allow attachments", "Allow file attachments on posts.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.AllowTweetAttachment }, func() any { return bootstrapConfig.WebProfile.AllowTweetAttachment }, func(v any) { conf.WebProfileSetting.AllowTweetAttachment = v.(bool) }),
boolDef("web_profile.allow_tweet_attachment_price", "web", "profile", "Allow paid attachments", "Allow post attachments to have a price.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.AllowTweetAttachmentPrice }, func() any { return bootstrapConfig.WebProfile.AllowTweetAttachmentPrice }, func(v any) { conf.WebProfileSetting.AllowTweetAttachmentPrice = v.(bool) }),
boolDef("web_profile.allow_tweet_video", "web", "profile", "Allow video posts", "Allow video uploads on posts.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.AllowTweetVideo }, func() any { return bootstrapConfig.WebProfile.AllowTweetVideo }, func(v any) { conf.WebProfileSetting.AllowTweetVideo = v.(bool) }),
boolDef("web_profile.allow_user_register", "web", "profile", "Allow user registration", "Bootstrap-only registration gate from YAML/features.", ApplyModeBootstrapOnly, false, false, func() any { return conf.WebProfileSetting.AllowUserRegister }, func() any { return bootstrapConfig.WebProfile.AllowUserRegister }, nil),
boolDef("web_profile.allow_phone_bind", "web", "profile", "Allow phone binding", "Bootstrap-only phone binding gate from YAML/features.", ApplyModeBootstrapOnly, false, false, func() any { return conf.WebProfileSetting.AllowPhoneBind }, func() any { return bootstrapConfig.WebProfile.AllowPhoneBind }, nil),
intDef("web_profile.default_tweet_max_length", "web", "profile", "Default tweet max length", "Maximum allowed tweet length.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.DefaultTweetMaxLength }, func() any { return bootstrapConfig.WebProfile.DefaultTweetMaxLength }, func(v int) error { return between(v, 1, 2000, "default_tweet_max_length") }, func(v any) { conf.WebProfileSetting.DefaultTweetMaxLength = v.(int) }),
intDef("web_profile.tweet_web_ellipsis_size", "web", "profile", "Web ellipsis size", "Truncated feed length on web.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.TweetWebEllipsisSize }, func() any { return bootstrapConfig.WebProfile.TweetWebEllipsisSize }, func(v int) error {
return between(v, 1, conf.WebProfileSetting.DefaultTweetMaxLength, "tweet_web_ellipsis_size")
}, func(v any) { conf.WebProfileSetting.TweetWebEllipsisSize = v.(int) }),
intDef("web_profile.tweet_mobile_ellipsis_size", "web", "profile", "Mobile ellipsis size", "Truncated feed length on mobile.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.TweetMobileEllipsisSize }, func() any { return bootstrapConfig.WebProfile.TweetMobileEllipsisSize }, func(v int) error {
return between(v, 1, conf.WebProfileSetting.DefaultTweetMaxLength, "tweet_mobile_ellipsis_size")
}, func(v any) { conf.WebProfileSetting.TweetMobileEllipsisSize = v.(int) }),
stringDef("web_profile.default_tweet_visibility", "web", "profile", "Default tweet visibility", "Default visibility for new posts.", ApplyModeLive, false, true, visibilityOption, func() any { return conf.WebProfileSetting.DefaultTweetVisibility }, func() any { return bootstrapConfig.WebProfile.DefaultTweetVisibility }, validateVisibility, func(v any) { conf.WebProfileSetting.DefaultTweetVisibility = v.(string) }),
intDef("web_profile.default_msg_loop_interval", "web", "profile", "Message polling interval", "Polling interval for unread messages in milliseconds.", ApplyModeLive, false, true, func() any { return conf.WebProfileSetting.DefaultMsgLoopInterval }, func() any { return bootstrapConfig.WebProfile.DefaultMsgLoopInterval }, func(v int) error { return between(v, 1000, 60000, "default_msg_loop_interval") }, func(v any) { conf.WebProfileSetting.DefaultMsgLoopInterval = v.(int) }),
stringDef("web_profile.copyright_top", "web", "profile", "Copyright top", "Top copyright line displayed in the UI.", ApplyModeLive, false, true, nil, func() any { return conf.WebProfileSetting.CopyrightTop }, func() any { return bootstrapConfig.WebProfile.CopyrightTop }, validateRequiredTrimmed("copyright_top", 255), func(v any) { conf.WebProfileSetting.CopyrightTop = v.(string) }),
stringDef("web_profile.copyright_left", "web", "profile", "Copyright left", "Left footer label.", ApplyModeLive, false, true, nil, func() any { return conf.WebProfileSetting.CopyrightLeft }, func() any { return bootstrapConfig.WebProfile.CopyrightLeft }, validateRequiredTrimmed("copyright_left", 255), func(v any) { conf.WebProfileSetting.CopyrightLeft = v.(string) }),
stringDef("web_profile.copyright_left_link", "web", "profile", "Copyright left link", "Optional left footer link.", ApplyModeLive, false, true, nil, func() any { return conf.WebProfileSetting.CopyrightLeftLink }, func() any { return bootstrapConfig.WebProfile.CopyrightLeftLink }, validateOptionalURL("copyright_left_link", 255), func(v any) { conf.WebProfileSetting.CopyrightLeftLink = v.(string) }),
stringDef("web_profile.copyright_right", "web", "profile", "Copyright right", "Right footer label.", ApplyModeLive, false, true, nil, func() any { return conf.WebProfileSetting.CopyrightRight }, func() any { return bootstrapConfig.WebProfile.CopyrightRight }, validateRequiredTrimmed("copyright_right", 255), func(v any) { conf.WebProfileSetting.CopyrightRight = v.(string) }),
stringDef("web_profile.copyright_right_link", "web", "profile", "Copyright right link", "Optional right footer link.", ApplyModeLive, false, true, nil, func() any { return conf.WebProfileSetting.CopyrightRightLink }, func() any { return bootstrapConfig.WebProfile.CopyrightRightLink }, validateOptionalURL("copyright_right_link", 255), func(v any) { conf.WebProfileSetting.CopyrightRightLink = v.(string) }),
int64Def("app.max_comment_count", "app", "general", "Max comment count", "Maximum comments allowed on a post.", ApplyModeLive, func() any { return conf.AppSetting.MaxCommentCount }, func() any { return bootstrapConfig.App.MaxCommentCount }, func(v int64) error { return betweenInt64(v, 1, 100000, "max_comment_count") }, func(v any) { conf.AppSetting.MaxCommentCount = v.(int64) }),
floatDef("app.attachment_income_rate", "app", "general", "Attachment income rate", "Revenue share for paid attachments.", ApplyModeLive, func() any { return conf.AppSetting.AttachmentIncomeRate }, func() any { return bootstrapConfig.App.AttachmentIncomeRate }, func(v float64) error {
if v < 0 || v > 1 {
return xerror.InvalidParams.WithDetails("attachment_income_rate must be between 0 and 1")
}
return nil
}, func(v any) { conf.AppSetting.AttachmentIncomeRate = v.(float64) }),
intDef("app.default_page_size", "app", "general", "Default page size", "Default pagination size.", ApplyModeLive, false, true, func() any { return conf.AppSetting.DefaultPageSize }, func() any { return bootstrapConfig.App.DefaultPageSize }, func(v int) error { return between(v, 1, conf.AppSetting.MaxPageSize, "default_page_size") }, func(v any) { conf.AppSetting.DefaultPageSize = v.(int) }),
intDef("app.max_page_size", "app", "general", "Max page size", "Maximum pagination size.", ApplyModeLive, false, true, func() any { return conf.AppSetting.MaxPageSize }, func() any { return bootstrapConfig.App.MaxPageSize }, func(v int) error {
return between(v, maxInt(conf.AppSetting.DefaultPageSize, 1), 1000, "max_page_size")
}, func(v any) { conf.AppSetting.MaxPageSize = v.(int) }),
int64Def("app.max_whisper_daily", "app", "limits", "Max daily whispers", "Daily whisper send limit. Restart required because servants cache it at startup.", ApplyModeRestartRequired, func() any { return conf.AppSetting.MaxWhisperDaily }, func() any { return bootstrapConfig.App.MaxWhisperDaily }, func(v int64) error { return betweenInt64(v, 1, 1000000, "max_whisper_daily") }, func(v any) { conf.AppSetting.MaxWhisperDaily = v.(int64) }),
intDef("app.max_captcha_times", "app", "limits", "Max captcha times", "Max captcha request count cached at startup by web servants.", ApplyModeRestartRequired, false, true, func() any { return conf.AppSetting.MaxCaptchaTimes }, func() any { return bootstrapConfig.App.MaxCaptchaTimes }, func(v int) error { return between(v, 1, 1000, "max_captcha_times") }, func(v any) { conf.AppSetting.MaxCaptchaTimes = v.(int) }),
intDef("tweet_search.max_update_qps", "search", "bridge", "Search update QPS", "Buffered update throughput for search indexing.", ApplyModeRestartRequired, false, true, func() any { return conf.TweetSearchSetting.MaxUpdateQPS }, func() any { return bootstrapConfig.TweetSearch.MaxUpdateQPS }, func(v int) error { return between(v, 10, 10000, "max_update_qps") }, func(v any) { conf.TweetSearchSetting.MaxUpdateQPS = v.(int) }),
intDef("tweet_search.min_worker", "search", "bridge", "Search worker count", "Background worker count for search indexing.", ApplyModeRestartRequired, false, true, func() any { return conf.TweetSearchSetting.MinWorker }, func() any { return bootstrapConfig.TweetSearch.MinWorker }, func(v int) error { return between(v, 5, 1000, "min_worker") }, func(v any) { conf.TweetSearchSetting.MinWorker = v.(int) }),
stringDefWithActive("meili.host", "search", "meili", "Meili host", "Meilisearch host.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Meili") }, func() any { return conf.MeiliSetting.Host }, func() any { return bootstrapConfig.Meili.Host }, validateTrimmedMax("meili.host", 255), func(v any) { conf.MeiliSetting.Host = v.(string) }),
stringDefWithActive("meili.index", "search", "meili", "Meili index", "Meilisearch index name.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Meili") }, func() any { return conf.MeiliSetting.Index }, func() any { return bootstrapConfig.Meili.Index }, validateTrimmedMax("meili.index", 255), func(v any) { conf.MeiliSetting.Index = v.(string) }),
stringDefWithActive("meili.api_key", "search", "meili", "Meili API key", "Meilisearch API key.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("Meili") }, func() any { return conf.MeiliSetting.ApiKey }, func() any { return bootstrapConfig.Meili.ApiKey }, validateTrimmedMax("meili.api_key", 512), func(v any) { conf.MeiliSetting.ApiKey = v.(string) }),
boolDefWithActive("meili.secure", "search", "meili", "Meili secure", "Use HTTPS for Meilisearch.", ApplyModeRestartRequired, false, true, func() bool { return cfg.If("Meili") }, func() any { return conf.MeiliSetting.Secure }, func() any { return bootstrapConfig.Meili.Secure }, func(v any) { conf.MeiliSetting.Secure = v.(bool) }),
stringDefWithActive("zinc.host", "search", "zinc", "Zinc host", "Zinc host.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Zinc") }, func() any { return conf.ZincSetting.Host }, func() any { return bootstrapConfig.Zinc.Host }, validateTrimmedMax("zinc.host", 255), func(v any) { conf.ZincSetting.Host = v.(string) }),
stringDefWithActive("zinc.index", "search", "zinc", "Zinc index", "Zinc index name.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Zinc") }, func() any { return conf.ZincSetting.Index }, func() any { return bootstrapConfig.Zinc.Index }, validateTrimmedMax("zinc.index", 255), func(v any) { conf.ZincSetting.Index = v.(string) }),
stringDefWithActive("zinc.user", "search", "zinc", "Zinc user", "Zinc username.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Zinc") }, func() any { return conf.ZincSetting.User }, func() any { return bootstrapConfig.Zinc.User }, validateTrimmedMax("zinc.user", 255), func(v any) { conf.ZincSetting.User = v.(string) }),
stringDefWithActive("zinc.password", "search", "zinc", "Zinc password", "Zinc password.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("Zinc") }, func() any { return conf.ZincSetting.Password }, func() any { return bootstrapConfig.Zinc.Password }, validateTrimmedMax("zinc.password", 512), func(v any) { conf.ZincSetting.Password = v.(string) }),
boolDefWithActive("zinc.secure", "search", "zinc", "Zinc secure", "Use HTTPS for Zinc.", ApplyModeRestartRequired, false, true, func() bool { return cfg.If("Zinc") }, func() any { return conf.ZincSetting.Secure }, func() any { return bootstrapConfig.Zinc.Secure }, func(v any) { conf.ZincSetting.Secure = v.(bool) }),
intDef("object_storage.retain_in_days", "storage", "common", "Object retention days", "Retention window for temporary objects.", ApplyModeRestartRequired, false, true, func() any { return conf.ObjectStorage.RetainInDays }, func() any { return bootstrapConfig.ObjectStorage.RetainInDays }, func(v int) error { return between(v, 0, 3650, "retain_in_days") }, func(v any) { conf.ObjectStorage.RetainInDays = v.(int) }),
stringDef("object_storage.temp_dir", "storage", "common", "Object temp dir", "Temporary directory/prefix for objects.", ApplyModeRestartRequired, false, true, nil, func() any { return conf.ObjectStorage.TempDir }, func() any { return bootstrapConfig.ObjectStorage.TempDir }, validateTrimmedMax("temp_dir", 255), func(v any) { conf.ObjectStorage.TempDir = v.(string) }),
stringDefWithActive("local_oss.save_path", "storage", "local_oss", "Local OSS save path", "Filesystem path for local object storage.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("LocalOSS") }, func() any { return conf.LocalOSSSetting.SavePath }, func() any { return bootstrapConfig.LocalOSS.SavePath }, validateTrimmedMax("local_oss.save_path", 1024), func(v any) { conf.LocalOSSSetting.SavePath = v.(string) }),
boolDefWithActive("local_oss.secure", "storage", "local_oss", "Local OSS secure", "Generate HTTPS URLs for local object storage.", ApplyModeRestartRequired, false, true, func() bool { return cfg.If("LocalOSS") }, func() any { return conf.LocalOSSSetting.Secure }, func() any { return bootstrapConfig.LocalOSS.Secure }, func(v any) { conf.LocalOSSSetting.Secure = v.(bool) }),
stringDefWithActive("local_oss.bucket", "storage", "local_oss", "Local OSS bucket", "Bucket folder name for local storage.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("LocalOSS") }, func() any { return conf.LocalOSSSetting.Bucket }, func() any { return bootstrapConfig.LocalOSS.Bucket }, validateTrimmedMax("local_oss.bucket", 255), func(v any) { conf.LocalOSSSetting.Bucket = v.(string) }),
stringDefWithActive("local_oss.domain", "storage", "local_oss", "Local OSS domain", "Public domain for local object storage.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("LocalOSS") }, func() any { return conf.LocalOSSSetting.Domain }, func() any { return bootstrapConfig.LocalOSS.Domain }, validateTrimmedMax("local_oss.domain", 255), func(v any) { conf.LocalOSSSetting.Domain = v.(string) }),
stringDefWithActive("minio.access_key", "storage", "minio", "MinIO access key", "MinIO access key.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("MinIO") }, func() any { return conf.MinIOSetting.AccessKey }, func() any { return bootstrapConfig.MinIO.AccessKey }, validateTrimmedMax("minio.access_key", 255), func(v any) { conf.MinIOSetting.AccessKey = v.(string) }),
stringDefWithActive("minio.secret_key", "storage", "minio", "MinIO secret key", "MinIO secret key.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("MinIO") }, func() any { return conf.MinIOSetting.SecretKey }, func() any { return bootstrapConfig.MinIO.SecretKey }, validateTrimmedMax("minio.secret_key", 512), func(v any) { conf.MinIOSetting.SecretKey = v.(string) }),
boolDefWithActive("minio.secure", "storage", "minio", "MinIO secure", "Use HTTPS for MinIO.", ApplyModeRestartRequired, false, true, func() bool { return cfg.If("MinIO") }, func() any { return conf.MinIOSetting.Secure }, func() any { return bootstrapConfig.MinIO.Secure }, func(v any) { conf.MinIOSetting.Secure = v.(bool) }),
stringDefWithActive("minio.endpoint", "storage", "minio", "MinIO endpoint", "MinIO endpoint.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("MinIO") }, func() any { return conf.MinIOSetting.Endpoint }, func() any { return bootstrapConfig.MinIO.Endpoint }, validateTrimmedMax("minio.endpoint", 255), func(v any) { conf.MinIOSetting.Endpoint = v.(string) }),
stringDefWithActive("minio.bucket", "storage", "minio", "MinIO bucket", "MinIO bucket name.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("MinIO") }, func() any { return conf.MinIOSetting.Bucket }, func() any { return bootstrapConfig.MinIO.Bucket }, validateTrimmedMax("minio.bucket", 255), func(v any) { conf.MinIOSetting.Bucket = v.(string) }),
stringDefWithActive("minio.domain", "storage", "minio", "MinIO domain", "MinIO public domain.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("MinIO") }, func() any { return conf.MinIOSetting.Domain }, func() any { return bootstrapConfig.MinIO.Domain }, validateTrimmedMax("minio.domain", 255), func(v any) { conf.MinIOSetting.Domain = v.(string) }),
stringDefWithActive("s3.access_key", "storage", "s3", "S3 access key", "Amazon S3 access key.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("S3") }, func() any { return conf.S3Setting.AccessKey }, func() any { return bootstrapConfig.S3.AccessKey }, validateTrimmedMax("s3.access_key", 255), func(v any) { conf.S3Setting.AccessKey = v.(string) }),
stringDefWithActive("s3.secret_key", "storage", "s3", "S3 secret key", "Amazon S3 secret key.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("S3") }, func() any { return conf.S3Setting.SecretKey }, func() any { return bootstrapConfig.S3.SecretKey }, validateTrimmedMax("s3.secret_key", 512), func(v any) { conf.S3Setting.SecretKey = v.(string) }),
boolDefWithActive("s3.secure", "storage", "s3", "S3 secure", "Use HTTPS for S3.", ApplyModeRestartRequired, false, true, func() bool { return cfg.If("S3") }, func() any { return conf.S3Setting.Secure }, func() any { return bootstrapConfig.S3.Secure }, func(v any) { conf.S3Setting.Secure = v.(bool) }),
stringDefWithActive("s3.endpoint", "storage", "s3", "S3 endpoint", "Amazon S3 endpoint.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("S3") }, func() any { return conf.S3Setting.Endpoint }, func() any { return bootstrapConfig.S3.Endpoint }, validateTrimmedMax("s3.endpoint", 255), func(v any) { conf.S3Setting.Endpoint = v.(string) }),
stringDefWithActive("s3.bucket", "storage", "s3", "S3 bucket", "Amazon S3 bucket name.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("S3") }, func() any { return conf.S3Setting.Bucket }, func() any { return bootstrapConfig.S3.Bucket }, validateTrimmedMax("s3.bucket", 255), func(v any) { conf.S3Setting.Bucket = v.(string) }),
stringDefWithActive("s3.domain", "storage", "s3", "S3 domain", "Amazon S3 public domain.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("S3") }, func() any { return conf.S3Setting.Domain }, func() any { return bootstrapConfig.S3.Domain }, validateTrimmedMax("s3.domain", 255), func(v any) { conf.S3Setting.Domain = v.(string) }),
stringDefWithActive("alioss.endpoint", "storage", "alioss", "AliOSS endpoint", "AliOSS endpoint.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("AliOSS") }, func() any { return conf.AliOSSSetting.Endpoint }, func() any { return bootstrapConfig.AliOSS.Endpoint }, validateTrimmedMax("alioss.endpoint", 255), func(v any) { conf.AliOSSSetting.Endpoint = v.(string) }),
stringDefWithActive("alioss.access_key_id", "storage", "alioss", "AliOSS access key ID", "AliOSS access key ID.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("AliOSS") }, func() any { return conf.AliOSSSetting.AccessKeyID }, func() any { return bootstrapConfig.AliOSS.AccessKeyID }, validateTrimmedMax("alioss.access_key_id", 255), func(v any) { conf.AliOSSSetting.AccessKeyID = v.(string) }),
stringDefWithActive("alioss.access_key_secret", "storage", "alioss", "AliOSS access key secret", "AliOSS secret key.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("AliOSS") }, func() any { return conf.AliOSSSetting.AccessKeySecret }, func() any { return bootstrapConfig.AliOSS.AccessKeySecret }, validateTrimmedMax("alioss.access_key_secret", 512), func(v any) { conf.AliOSSSetting.AccessKeySecret = v.(string) }),
stringDefWithActive("alioss.bucket", "storage", "alioss", "AliOSS bucket", "AliOSS bucket name.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("AliOSS") }, func() any { return conf.AliOSSSetting.Bucket }, func() any { return bootstrapConfig.AliOSS.Bucket }, validateTrimmedMax("alioss.bucket", 255), func(v any) { conf.AliOSSSetting.Bucket = v.(string) }),
stringDefWithActive("alioss.domain", "storage", "alioss", "AliOSS domain", "AliOSS public domain.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("AliOSS") }, func() any { return conf.AliOSSSetting.Domain }, func() any { return bootstrapConfig.AliOSS.Domain }, validateTrimmedMax("alioss.domain", 255), func(v any) { conf.AliOSSSetting.Domain = v.(string) }),
stringDefWithActive("cos.secret_id", "storage", "cos", "COS secret ID", "Tencent COS secret ID.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("COS") }, func() any { return conf.COSSetting.SecretID }, func() any { return bootstrapConfig.COS.SecretID }, validateTrimmedMax("cos.secret_id", 255), func(v any) { conf.COSSetting.SecretID = v.(string) }),
stringDefWithActive("cos.secret_key", "storage", "cos", "COS secret key", "Tencent COS secret key.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("COS") }, func() any { return conf.COSSetting.SecretKey }, func() any { return bootstrapConfig.COS.SecretKey }, validateTrimmedMax("cos.secret_key", 512), func(v any) { conf.COSSetting.SecretKey = v.(string) }),
stringDefWithActive("cos.region", "storage", "cos", "COS region", "Tencent COS region.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("COS") }, func() any { return conf.COSSetting.Region }, func() any { return bootstrapConfig.COS.Region }, validateTrimmedMax("cos.region", 255), func(v any) { conf.COSSetting.Region = v.(string) }),
stringDefWithActive("cos.bucket", "storage", "cos", "COS bucket", "Tencent COS bucket.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("COS") }, func() any { return conf.COSSetting.Bucket }, func() any { return bootstrapConfig.COS.Bucket }, validateTrimmedMax("cos.bucket", 255), func(v any) { conf.COSSetting.Bucket = v.(string) }),
stringDefWithActive("cos.domain", "storage", "cos", "COS domain", "Tencent COS public domain.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("COS") }, func() any { return conf.COSSetting.Domain }, func() any { return bootstrapConfig.COS.Domain }, validateTrimmedMax("cos.domain", 255), func(v any) { conf.COSSetting.Domain = v.(string) }),
stringDefWithActive("huawei_obs.access_key", "storage", "huawei_obs", "Huawei OBS access key", "Huawei OBS access key.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("HuaweiOBS") }, func() any { return conf.HuaweiOBSSetting.AccessKey }, func() any { return bootstrapConfig.HuaweiOBS.AccessKey }, validateTrimmedMax("huawei_obs.access_key", 255), func(v any) { conf.HuaweiOBSSetting.AccessKey = v.(string) }),
stringDefWithActive("huawei_obs.secret_key", "storage", "huawei_obs", "Huawei OBS secret key", "Huawei OBS secret key.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("HuaweiOBS") }, func() any { return conf.HuaweiOBSSetting.SecretKey }, func() any { return bootstrapConfig.HuaweiOBS.SecretKey }, validateTrimmedMax("huawei_obs.secret_key", 512), func(v any) { conf.HuaweiOBSSetting.SecretKey = v.(string) }),
stringDefWithActive("huawei_obs.endpoint", "storage", "huawei_obs", "Huawei OBS endpoint", "Huawei OBS endpoint.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("HuaweiOBS") }, func() any { return conf.HuaweiOBSSetting.Endpoint }, func() any { return bootstrapConfig.HuaweiOBS.Endpoint }, validateTrimmedMax("huawei_obs.endpoint", 255), func(v any) { conf.HuaweiOBSSetting.Endpoint = v.(string) }),
stringDefWithActive("huawei_obs.bucket", "storage", "huawei_obs", "Huawei OBS bucket", "Huawei OBS bucket.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("HuaweiOBS") }, func() any { return conf.HuaweiOBSSetting.Bucket }, func() any { return bootstrapConfig.HuaweiOBS.Bucket }, validateTrimmedMax("huawei_obs.bucket", 255), func(v any) { conf.HuaweiOBSSetting.Bucket = v.(string) }),
stringDefWithActive("huawei_obs.domain", "storage", "huawei_obs", "Huawei OBS domain", "Huawei OBS public domain.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("HuaweiOBS") }, func() any { return conf.HuaweiOBSSetting.Domain }, func() any { return bootstrapConfig.HuaweiOBS.Domain }, validateTrimmedMax("huawei_obs.domain", 255), func(v any) { conf.HuaweiOBSSetting.Domain = v.(string) }),
stringDefWithActive("sms_juhe.gateway", "notifications", "sms_juhe", "SMS gateway", "Juhe SMS gateway URL.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Sms") }, func() any { return conf.SmsJuheSetting.Gateway }, func() any { return bootstrapConfig.SmsJuhe.Gateway }, validateTrimmedMax("sms_juhe.gateway", 255), func(v any) { conf.SmsJuheSetting.Gateway = v.(string) }),
stringDefWithActive("sms_juhe.key", "notifications", "sms_juhe", "SMS key", "Juhe SMS key.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("Sms") }, func() any { return conf.SmsJuheSetting.Key }, func() any { return bootstrapConfig.SmsJuhe.Key }, validateTrimmedMax("sms_juhe.key", 255), func(v any) { conf.SmsJuheSetting.Key = v.(string) }),
stringDefWithActive("sms_juhe.tpl_id", "notifications", "sms_juhe", "SMS template ID", "Juhe SMS template ID.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Sms") }, func() any { return conf.SmsJuheSetting.TplID }, func() any { return bootstrapConfig.SmsJuhe.TplID }, validateTrimmedMax("sms_juhe.tpl_id", 255), func(v any) { conf.SmsJuheSetting.TplID = v.(string) }),
stringDefWithActive("sms_juhe.tpl_val", "notifications", "sms_juhe", "SMS template value", "Juhe SMS template value format.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Sms") }, func() any { return conf.SmsJuheSetting.TplVal }, func() any { return bootstrapConfig.SmsJuhe.TplVal }, validateTrimmedMax("sms_juhe.tpl_val", 255), func(v any) { conf.SmsJuheSetting.TplVal = v.(string) }),
stringDefWithActive("alipay.app_id", "payments", "alipay", "Alipay app ID", "Alipay application ID.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Alipay") }, func() any { return conf.AlipaySetting.AppID }, func() any { return bootstrapConfig.Alipay.AppID }, validateTrimmedMax("alipay.app_id", 255), func(v any) { conf.AlipaySetting.AppID = v.(string) }),
stringDefWithActive("alipay.private_key", "payments", "alipay", "Alipay private key", "Alipay private key PEM content.", ApplyModeRestartRequired, true, true, nil, func() bool { return cfg.If("Alipay") }, func() any { return conf.AlipaySetting.PrivateKey }, func() any { return bootstrapConfig.Alipay.PrivateKey }, validateTrimmedMax("alipay.private_key", 8192), func(v any) { conf.AlipaySetting.PrivateKey = v.(string) }),
stringDefWithActive("alipay.root_cert_file", "payments", "alipay", "Alipay root cert file", "Path to the Alipay root certificate file.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Alipay") }, func() any { return conf.AlipaySetting.RootCertFile }, func() any { return bootstrapConfig.Alipay.RootCertFile }, validateTrimmedMax("alipay.root_cert_file", 1024), func(v any) { conf.AlipaySetting.RootCertFile = v.(string) }),
stringDefWithActive("alipay.public_cert_file", "payments", "alipay", "Alipay public cert file", "Path to the Alipay public certificate file.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Alipay") }, func() any { return conf.AlipaySetting.PublicCertFile }, func() any { return bootstrapConfig.Alipay.PublicCertFile }, validateTrimmedMax("alipay.public_cert_file", 1024), func(v any) { conf.AlipaySetting.PublicCertFile = v.(string) }),
stringDefWithActive("alipay.app_public_cert_file", "payments", "alipay", "Alipay app public cert file", "Path to the app public certificate file.", ApplyModeRestartRequired, false, true, nil, func() bool { return cfg.If("Alipay") }, func() any { return conf.AlipaySetting.AppPublicCertFile }, func() any { return bootstrapConfig.Alipay.AppPublicCertFile }, validateTrimmedMax("alipay.app_public_cert_file", 1024), func(v any) { conf.AlipaySetting.AppPublicCertFile = v.(string) }),
boolDefWithActive("alipay.in_production", "payments", "alipay", "Alipay production mode", "Use Alipay production environment.", ApplyModeRestartRequired, false, true, func() bool { return cfg.If("Alipay") }, func() any { return conf.AlipaySetting.InProduction }, func() any { return bootstrapConfig.Alipay.InProduction }, func(v any) { conf.AlipaySetting.InProduction = v.(bool) }),
}
}
func registryMap() map[string]Definition {
defs := Registry()
res := make(map[string]Definition, len(defs))
for _, def := range defs {
res[def.Key] = def
}
return res
}
func boolDef(key, group, section, label, description string, applyMode ApplyMode, secret, editable bool, current func() any, bootstrap func() any, apply func(any)) Definition {
return boolDefWithActive(key, group, section, label, description, applyMode, secret, editable, func() bool { return true }, current, bootstrap, apply)
}
func boolDefWithActive(key, group, section, label, description string, applyMode ApplyMode, secret, editable bool, active func() bool, current func() any, bootstrap func() any, apply func(any)) Definition {
return Definition{Key: key, Group: group, Section: section, Type: TypeBool, Label: label, Description: description, ApplyMode: applyMode, Secret: secret, Readonly: !editable, Active: active, CurrentValue: current, BootstrapDefault: bootstrap, ValidateValue: func(v any) (any, error) {
b, ok := v.(bool)
if !ok {
return nil, invalidType(key, "bool")
}
return b, nil
}, BootstrapOverride: apply}
}
func intDef(key, group, section, label, description string, applyMode ApplyMode, secret, editable bool, current func() any, bootstrap func() any, validator func(int) error, apply func(any)) Definition {
return Definition{Key: key, Group: group, Section: section, Type: TypeInt, Label: label, Description: description, ApplyMode: applyMode, Secret: secret, Readonly: !editable, Active: func() bool { return true }, CurrentValue: current, BootstrapDefault: bootstrap, ValidateValue: func(v any) (any, error) {
i, ok := asInt(v)
if !ok {
return nil, invalidType(key, "int")
}
if validator != nil {
if err := validator(i); err != nil {
return nil, err
}
}
return i, nil
}, BootstrapOverride: apply}
}
func int64Def(key, group, section, label, description string, applyMode ApplyMode, current func() any, bootstrap func() any, validator func(int64) error, apply func(any)) Definition {
return Definition{Key: key, Group: group, Section: section, Type: TypeInt, Label: label, Description: description, ApplyMode: applyMode, Active: func() bool { return true }, CurrentValue: current, BootstrapDefault: bootstrap, ValidateValue: func(v any) (any, error) {
i, ok := asInt64(v)
if !ok {
return nil, invalidType(key, "int")
}
if validator != nil {
if err := validator(i); err != nil {
return nil, err
}
}
return i, nil
}, BootstrapOverride: apply}
}
func floatDef(key, group, section, label, description string, applyMode ApplyMode, current func() any, bootstrap func() any, validator func(float64) error, apply func(any)) Definition {
return Definition{Key: key, Group: group, Section: section, Type: TypeFloat, Label: label, Description: description, ApplyMode: applyMode, Active: func() bool { return true }, CurrentValue: current, BootstrapDefault: bootstrap, ValidateValue: func(v any) (any, error) {
f, ok := asFloat64(v)
if !ok {
return nil, invalidType(key, "float")
}
if validator != nil {
if err := validator(f); err != nil {
return nil, err
}
}
return f, nil
}, BootstrapOverride: apply}
}
func stringDef(key, group, section, label, description string, applyMode ApplyMode, secret, editable bool, options []Option, current func() any, bootstrap func() any, validator func(string) error, apply func(any)) Definition {
return stringDefWithActive(key, group, section, label, description, applyMode, secret, editable, options, func() bool { return true }, current, bootstrap, validator, apply)
}
func stringDefWithActive(key, group, section, label, description string, applyMode ApplyMode, secret, editable bool, options []Option, active func() bool, current func() any, bootstrap func() any, validator func(string) error, apply func(any)) Definition {
return Definition{Key: key, Group: group, Section: section, Type: TypeString, Label: label, Description: description, ApplyMode: applyMode, Secret: secret, Readonly: !editable, Options: options, Active: active, CurrentValue: current, BootstrapDefault: bootstrap, ValidateValue: func(v any) (any, error) {
s, ok := v.(string)
if !ok {
return nil, invalidType(key, "string")
}
s = strings.TrimSpace(s)
if validator != nil {
if err := validator(s); err != nil {
return nil, err
}
}
return s, nil
}, BootstrapOverride: apply}
}
func validateVisibility(v string) error {
for _, option := range visibilityOption {
if option.Value == v {
return nil
}
}
return xerror.InvalidParams.WithDetails("default_tweet_visibility must be one of public/following/friend/private")
}
func validateRequiredTrimmed(name string, max int) func(string) error {
return func(v string) error {
if v == "" {
return xerror.InvalidParams.WithDetails(name + " must not be empty")
}
if len(v) > max {
return xerror.InvalidParams.WithDetails(fmt.Sprintf("%s length must be <= %d", name, max))
}
return nil
}
}
func validateTrimmedMax(name string, max int) func(string) error {
return func(v string) error {
if len(v) > max {
return xerror.InvalidParams.WithDetails(fmt.Sprintf("%s length must be <= %d", name, max))
}
return nil
}
}
func validateOptionalURL(name string, max int) func(string) error {
return func(v string) error {
if len(v) > max {
return xerror.InvalidParams.WithDetails(fmt.Sprintf("%s length must be <= %d", name, max))
}
if v == "" {
return nil
}
parsed, err := url.ParseRequestURI(v)
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
return xerror.InvalidParams.WithDetails(name + " must be a valid URL")
}
return nil
}
}
func between(v, min, max int, name string) error {
if v < min || v > max {
return xerror.InvalidParams.WithDetails(fmt.Sprintf("%s must be between %d and %d", name, min, max))
}
return nil
}
func betweenInt64(v, min, max int64, name string) error {
if v < min || v > max {
return xerror.InvalidParams.WithDetails(fmt.Sprintf("%s must be between %d and %d", name, min, max))
}
return nil
}
func invalidType(key, want string) error {
return xerror.InvalidParams.WithDetails(fmt.Sprintf("%s must be %s", key, want))
}
func asInt(v any) (int, bool) {
switch n := v.(type) {
case int:
return n, true
case int32:
return int(n), true
case int64:
return int(n), true
case float64:
if n != float64(int(n)) {
return 0, false
}
return int(n), true
default:
return 0, false
}
}
func asInt64(v any) (int64, bool) {
switch n := v.(type) {
case int:
return int64(n), true
case int32:
return int64(n), true
case int64:
return n, true
case float64:
if n != float64(int64(n)) {
return 0, false
}
return int64(n), true
default:
return 0, false
}
}
func asFloat64(v any) (float64, bool) {
switch n := v.(type) {
case float64:
return n, true
case float32:
return float64(n), true
case int:
return float64(n), true
case int64:
return float64(n), true
default:
return 0, false
}
}
func parseRequestValue(def Definition, raw stdjson.RawMessage) (any, error) {
var target any
switch def.Type {
case TypeBool:
var v bool
if err := stdjson.Unmarshal(raw, &v); err != nil {
return nil, invalidType(def.Key, "bool")
}
target = v
case TypeInt:
var v float64
if err := stdjson.Unmarshal(raw, &v); err != nil {
return nil, invalidType(def.Key, "int")
}
target = v
case TypeFloat:
var v float64
if err := stdjson.Unmarshal(raw, &v); err != nil {
return nil, invalidType(def.Key, "float")
}
target = v
case TypeString:
var v string
if err := stdjson.Unmarshal(raw, &v); err != nil {
return nil, invalidType(def.Key, "string")
}
target = v
default:
return nil, xerror.ServerError.WithDetails("unsupported settings type")
}
return def.ValidateValue(target)
}
func parseStoredValue(def Definition, raw string) (any, error) {
switch def.Type {
case TypeBool:
return strconv.ParseBool(raw)
case TypeInt:
if i, err := strconv.ParseInt(raw, 10, 64); err == nil {
return def.ValidateValue(i)
} else {
return nil, err
}
case TypeFloat:
if f, err := strconv.ParseFloat(raw, 64); err == nil {
return def.ValidateValue(f)
} else {
return nil, err
}
case TypeString:
return def.ValidateValue(raw)
default:
return nil, xerror.ServerError.WithDetails("unsupported settings type")
}
}
func serializeValue(def Definition, value any) string {
switch def.Type {
case TypeBool:
return strconv.FormatBool(value.(bool))
case TypeInt:
switch v := value.(type) {
case int:
return strconv.Itoa(v)
case int64:
return strconv.FormatInt(v, 10)
default:
return fmt.Sprintf("%v", value)
}
case TypeFloat:
return strconv.FormatFloat(value.(float64), 'f', -1, 64)
default:
return value.(string)
}
}
func valuesEqual(left, right any) bool {
return fmt.Sprintf("%v", left) == fmt.Sprintf("%v", right)
}
func activeState(def Definition) bool {
if def.Active == nil {
return true
}
return def.Active()
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
func logBootstrapApplyError(def Definition, err error) {
logrus.WithError(err).WithField("key", def.Key).Warn("sitesetting: skip invalid persisted override")
}