better message formatting
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
This commit is contained in:
@@ -61,7 +61,7 @@ api:
|
|||||||
thread_id_env: TELEGRAM_THREAD_ID
|
thread_id_env: TELEGRAM_THREAD_ID
|
||||||
api_url: "https://api.telegram.org"
|
api_url: "https://api.telegram.org"
|
||||||
timeout_seconds: 10
|
timeout_seconds: 10
|
||||||
parse_mode: ""
|
parse_mode: markdown
|
||||||
|
|
||||||
localizer:
|
localizer:
|
||||||
path: "./i18n"
|
path: "./i18n"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (mb *MessageBuilderImp) AddData(key, value string) mmail.MailBuilder {
|
|||||||
|
|
||||||
func (mb *MessageBuilderImp) Build() (mmail.Message, error) {
|
func (mb *MessageBuilderImp) Build() (mmail.Message, error) {
|
||||||
if len(mb.message.recipients) == 0 {
|
if len(mb.message.recipients) == 0 {
|
||||||
return nil, merrors.InvalidArgument("Recipient not set")
|
return nil, merrors.InvalidArgument("Recipient not set", "recipients")
|
||||||
}
|
}
|
||||||
return mb.message, nil
|
return mb.message, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (c *Client) Send(r mmail.MailBuilder) error {
|
|||||||
c.logger.Warn("Malformed messge", zap.String("template_id", m.TemplateID()),
|
c.logger.Warn("Malformed messge", zap.String("template_id", m.TemplateID()),
|
||||||
zap.String("locale", m.Locale()), zap.Strings("recipients", m.Recipients()),
|
zap.String("locale", m.Locale()), zap.Strings("recipients", m.Recipients()),
|
||||||
zap.Int("body_size", len(body)))
|
zap.Int("body_size", len(body)))
|
||||||
return merrors.InvalidArgument("malformed message")
|
return merrors.InvalidArgument("malformed message", "message.body", "message.recipients")
|
||||||
}
|
}
|
||||||
subj, err := mailkey.Subject(c.l, m.Parameters(), m.TemplateID(), m.Locale())
|
subj, err := mailkey.Subject(c.l, m.Parameters(), m.TemplateID(), m.Locale())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package mail
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/tech/sendico/notification/interface/api/localizer"
|
"github.com/tech/sendico/notification/interface/api/localizer"
|
||||||
notification "github.com/tech/sendico/notification/interface/services/notification/config"
|
notification "github.com/tech/sendico/notification/interface/services/notification/config"
|
||||||
mi "github.com/tech/sendico/notification/internal/server/notificationimp/mail/internal"
|
mi "github.com/tech/sendico/notification/internal/server/notificationimp/mail/internal"
|
||||||
@@ -9,7 +10,6 @@ import (
|
|||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/messaging"
|
"github.com/tech/sendico/pkg/messaging"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ type Config = notification.Config
|
|||||||
|
|
||||||
func createMailClient(logger mlogger.Logger, producer messaging.Producer, l localizer.Localizer, dp domainprovider.DomainProvider, config *Config) (Client, error) {
|
func createMailClient(logger mlogger.Logger, producer messaging.Producer, l localizer.Localizer, dp domainprovider.DomainProvider, config *Config) (Client, error) {
|
||||||
if len(config.Driver) == 0 {
|
if len(config.Driver) == 0 {
|
||||||
return nil, merrors.InvalidArgument("Mail driver name must be provided")
|
return nil, merrors.InvalidArgument("Mail driver name must be provided", "config.driver")
|
||||||
}
|
}
|
||||||
logger.Info("Connecting mail client...", zap.String("driver", config.Driver))
|
logger.Info("Connecting mail client...", zap.String("driver", config.Driver))
|
||||||
if config.Driver == "dummy" {
|
if config.Driver == "dummy" {
|
||||||
@@ -45,7 +45,7 @@ func createMailClient(logger mlogger.Logger, producer messaging.Producer, l loca
|
|||||||
|
|
||||||
return mi.NewClient(logger, l, dp, &gsmconfing), nil
|
return mi.NewClient(logger, l, dp, &gsmconfing), nil
|
||||||
}
|
}
|
||||||
return nil, merrors.InvalidArgument("Unkwnown mail driver: " + config.Driver)
|
return nil, merrors.InvalidArgument("Unkwnown mail driver: "+config.Driver, "config.driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateMailClient(logger mlogger.Logger, sender string, producer messaging.Producer, l localizer.Localizer, dp domainprovider.DomainProvider, config *Config) (Client, error) {
|
func CreateMailClient(logger mlogger.Logger, sender string, producer messaging.Producer, l localizer.Localizer, dp domainprovider.DomainProvider, config *Config) (Client, error) {
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
|
|||||||
p.logger = a.Logger().Named(p.Name())
|
p.logger = a.Logger().Named(p.Name())
|
||||||
|
|
||||||
if a.Config().Notification == nil {
|
if a.Config().Notification == nil {
|
||||||
return nil, merrors.InvalidArgument("notification configuration is missing")
|
return nil, merrors.InvalidArgument("notification configuration is missing", "config.notification")
|
||||||
}
|
}
|
||||||
if a.Config().Notification.Telegram == nil {
|
if a.Config().Notification.Telegram == nil {
|
||||||
return nil, merrors.InvalidArgument("telegram configuration is missing")
|
return nil, merrors.InvalidArgument("telegram configuration is missing", "config.notification.telegram")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -47,15 +48,15 @@ type sendMessagePayload struct {
|
|||||||
|
|
||||||
func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, error) {
|
func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil, merrors.InvalidArgument("telegram configuration is not provided")
|
return nil, merrors.InvalidArgument("telegram configuration is not provided", "config.notification.telegram")
|
||||||
}
|
}
|
||||||
token := strings.TrimSpace(os.Getenv(cfg.BotTokenEnv))
|
token := strings.TrimSpace(os.Getenv(cfg.BotTokenEnv))
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return nil, merrors.InvalidArgument(fmt.Sprintf("telegram bot token env %s is empty", cfg.BotTokenEnv))
|
return nil, merrors.InvalidArgument(fmt.Sprintf("telegram bot token env %s is empty", cfg.BotTokenEnv), cfg.BotTokenEnv)
|
||||||
}
|
}
|
||||||
chatID := strings.TrimSpace(os.Getenv(cfg.ChatIDEnv))
|
chatID := strings.TrimSpace(os.Getenv(cfg.ChatIDEnv))
|
||||||
if chatID == "" {
|
if chatID == "" {
|
||||||
return nil, merrors.InvalidArgument(fmt.Sprintf("telegram chat id env %s is empty", cfg.ChatIDEnv))
|
return nil, merrors.InvalidArgument(fmt.Sprintf("telegram chat id env %s is empty", cfg.ChatIDEnv), cfg.ChatIDEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
var threadID *int64
|
var threadID *int64
|
||||||
@@ -64,7 +65,7 @@ func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, er
|
|||||||
if raw != "" {
|
if raw != "" {
|
||||||
val, err := strconv.ParseInt(raw, 10, 64)
|
val, err := strconv.ParseInt(raw, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, merrors.InvalidArgumentWrap(err, fmt.Sprintf("telegram thread id env %s is invalid", env))
|
return nil, merrors.InvalidArgumentWrap(err, fmt.Sprintf("telegram thread id env %s is invalid", env), env)
|
||||||
}
|
}
|
||||||
threadID = &val
|
threadID = &val
|
||||||
}
|
}
|
||||||
@@ -79,6 +80,10 @@ func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, er
|
|||||||
if apiURL == "" {
|
if apiURL == "" {
|
||||||
apiURL = defaultAPIURL
|
apiURL = defaultAPIURL
|
||||||
}
|
}
|
||||||
|
parseMode := strings.TrimSpace(cfg.ParseMode)
|
||||||
|
if parseMode == "" {
|
||||||
|
parseMode = "Markdown"
|
||||||
|
}
|
||||||
|
|
||||||
return &client{
|
return &client{
|
||||||
logger: logger.Named("telegram"),
|
logger: logger.Named("telegram"),
|
||||||
@@ -89,15 +94,15 @@ func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, er
|
|||||||
botToken: token,
|
botToken: token,
|
||||||
chatID: chatID,
|
chatID: chatID,
|
||||||
threadID: threadID,
|
threadID: threadID,
|
||||||
parseMode: strings.TrimSpace(cfg.ParseMode),
|
parseMode: parseMode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) SendDemoRequest(ctx context.Context, request *model.DemoRequest) error {
|
func (c *client) SendDemoRequest(ctx context.Context, request *model.DemoRequest) error {
|
||||||
if request == nil {
|
if request == nil {
|
||||||
return merrors.InvalidArgument("demo request payload is nil")
|
return merrors.InvalidArgument("demo request payload is nil", "request")
|
||||||
}
|
}
|
||||||
message := buildMessage(request)
|
message := buildMessage(request, c.parseMode)
|
||||||
payload := sendMessagePayload{
|
payload := sendMessagePayload{
|
||||||
ChatID: c.chatID,
|
ChatID: c.chatID,
|
||||||
Text: message,
|
Text: message,
|
||||||
@@ -157,16 +162,89 @@ func (c *client) endpoint() string {
|
|||||||
return fmt.Sprintf("%s/bot%s/sendMessage", c.apiURL, c.botToken)
|
return fmt.Sprintf("%s/bot%s/sendMessage", c.apiURL, c.botToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMessage(req *model.DemoRequest) string {
|
func buildMessage(req *model.DemoRequest, parseMode string) string {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
builder.WriteString("New demo request received\n")
|
builder.WriteString("New demo request received\n")
|
||||||
builder.WriteString(fmt.Sprintf("Name: %s\n", req.Name))
|
builder.WriteString("-----------------------------\n")
|
||||||
builder.WriteString(fmt.Sprintf("Organization: %s\n", req.OrganizationName))
|
|
||||||
builder.WriteString(fmt.Sprintf("Phone: %s\n", req.Phone))
|
formatter := selectValueFormatter(parseMode)
|
||||||
builder.WriteString(fmt.Sprintf("Work email: %s\n", req.WorkEmail))
|
appendMessageField(&builder, "Name", req.Name, formatter)
|
||||||
builder.WriteString(fmt.Sprintf("Payout volume: %s\n", req.PayoutVolume))
|
appendMessageField(&builder, "Organization", req.OrganizationName, formatter)
|
||||||
if req.Comment != "" {
|
appendMessageField(&builder, "Phone", req.Phone, formatter)
|
||||||
builder.WriteString(fmt.Sprintf("Comment: %s\n", req.Comment))
|
appendMessageField(&builder, "Work email", req.WorkEmail, formatter)
|
||||||
|
appendMessageField(&builder, "Payout volume", req.PayoutVolume, formatter)
|
||||||
|
if strings.TrimSpace(req.Comment) != "" {
|
||||||
|
appendMessageField(&builder, "Comment", req.Comment, formatter)
|
||||||
}
|
}
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type valueFormatter func(string) string
|
||||||
|
|
||||||
|
func appendMessageField(builder *strings.Builder, label, value string, formatter valueFormatter) {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
value = "—"
|
||||||
|
} else if formatter != nil {
|
||||||
|
value = formatter(value)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(builder, "• %s: %s\n", label, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectValueFormatter(parseMode string) valueFormatter {
|
||||||
|
switch strings.ToLower(parseMode) {
|
||||||
|
case "markdown":
|
||||||
|
return func(value string) string {
|
||||||
|
return fmt.Sprintf("*%s*", escapeMarkdown(value))
|
||||||
|
}
|
||||||
|
case "markdownv2":
|
||||||
|
return func(value string) string {
|
||||||
|
return fmt.Sprintf("*%s*", escapeMarkdownV2(value))
|
||||||
|
}
|
||||||
|
case "html":
|
||||||
|
return func(value string) string {
|
||||||
|
return fmt.Sprintf("<b>%s</b>", html.EscapeString(value))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var markdownEscaper = strings.NewReplacer(
|
||||||
|
"*", "\\*",
|
||||||
|
"_", "\\_",
|
||||||
|
"[", "\\[",
|
||||||
|
"]", "\\]",
|
||||||
|
"(", "\\(",
|
||||||
|
")", "\\)",
|
||||||
|
"`", "\\`",
|
||||||
|
)
|
||||||
|
|
||||||
|
var markdownV2Escaper = strings.NewReplacer(
|
||||||
|
"_", "\\_",
|
||||||
|
"*", "\\*",
|
||||||
|
"[", "\\[",
|
||||||
|
"]", "\\]",
|
||||||
|
"(", "\\(",
|
||||||
|
")", "\\)",
|
||||||
|
"~", "\\~",
|
||||||
|
"`", "\\`",
|
||||||
|
">", "\\>",
|
||||||
|
"#", "\\#",
|
||||||
|
"+", "\\+",
|
||||||
|
"-", "\\-",
|
||||||
|
"=", "\\=",
|
||||||
|
"|", "\\|",
|
||||||
|
"{", "\\{",
|
||||||
|
"}", "\\}",
|
||||||
|
".", "\\.",
|
||||||
|
"!", "\\!",
|
||||||
|
)
|
||||||
|
|
||||||
|
func escapeMarkdown(value string) string {
|
||||||
|
return markdownEscaper.Replace(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeMarkdownV2(value string) string {
|
||||||
|
return markdownV2Escaper.Replace(value)
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,5 +48,5 @@ func CreateAuth(
|
|||||||
}
|
}
|
||||||
return enforcer, manager, nil
|
return enforcer, manager, nil
|
||||||
}
|
}
|
||||||
return nil, nil, merrors.InvalidArgument("Unknown enforcer type: " + string(config.Driver))
|
return nil, nil, merrors.InvalidArgument("Unknown enforcer type: "+string(config.Driver), "config.driver")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ func stringToAction(actionStr string) (model.Action, error) {
|
|||||||
case string(model.ActionDelete):
|
case string(model.ActionDelete):
|
||||||
return model.ActionDelete, nil
|
return model.ActionDelete, nil
|
||||||
default:
|
default:
|
||||||
return "", merrors.InvalidArgument(fmt.Sprintf("invalid action: %s", actionStr))
|
return "", merrors.InvalidArgument(fmt.Sprintf("invalid action: %s", actionStr), "action")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func PrepareConfig(logger mlogger.Logger, config *Config) (*EnforcerConfig, erro
|
|||||||
|
|
||||||
if len(adapter.DatabaseName) == 0 {
|
if len(adapter.DatabaseName) == 0 {
|
||||||
logger.Error("Database name is not set")
|
logger.Error("Database name is not set")
|
||||||
return nil, merrors.InvalidArgument("database name must be provided")
|
return nil, merrors.InvalidArgument("database name must be provided", "adapter.databaseName")
|
||||||
}
|
}
|
||||||
|
|
||||||
path := getEnvValue(logger, "model_path", "model_path_env", config.ModelPath, config.ModelPathEnv)
|
path := getEnvValue(logger, "model_path", "model_path_env", config.ModelPath, config.ModelPathEnv)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func NewRoleManager(logger mlogger.Logger, enforcer *CasbinEnforcer, rolePermiss
|
|||||||
func (rm *RoleManager) validateObjectIDs(ids ...primitive.ObjectID) error {
|
func (rm *RoleManager) validateObjectIDs(ids ...primitive.ObjectID) error {
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
if id.IsZero() {
|
if id.IsZero() {
|
||||||
return merrors.InvalidArgument("Object references cannot be zero")
|
return merrors.InvalidArgument("Object references cannot be zero", "objectRef")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func NewRoleManager(logger mlogger.Logger, enforcer *Enforcer, rolePermissionRef
|
|||||||
func (rm *RoleManager) validateObjectIDs(ids ...primitive.ObjectID) error {
|
func (rm *RoleManager) validateObjectIDs(ids ...primitive.ObjectID) error {
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
if id.IsZero() {
|
if id.IsZero() {
|
||||||
return merrors.InvalidArgument("Object references cannot be zero")
|
return merrors.InvalidArgument("Object references cannot be zero", "objectRef")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ func (c *MongoConnection) Ping(ctx context.Context) error {
|
|||||||
// ConnectMongo returns a low-level MongoDB connection without constructing repositories.
|
// ConnectMongo returns a low-level MongoDB connection without constructing repositories.
|
||||||
func ConnectMongo(logger mlogger.Logger, config *Config) (*MongoConnection, error) {
|
func ConnectMongo(logger mlogger.Logger, config *Config) (*MongoConnection, error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return nil, merrors.InvalidArgument("database configuration is nil")
|
return nil, merrors.InvalidArgument("database configuration is nil", "config")
|
||||||
}
|
}
|
||||||
if config.Driver != Mongo {
|
if config.Driver != Mongo {
|
||||||
return nil, merrors.InvalidArgument("unsupported database driver: " + string(config.Driver))
|
return nil, merrors.InvalidArgument("unsupported database driver: "+string(config.Driver), "config.driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, _, settings, err := mongoimpl.ConnectClient(logger, config.Settings)
|
client, _, settings, err := mongoimpl.ConnectClient(logger, config.Settings)
|
||||||
|
|||||||
@@ -37,5 +37,5 @@ func NewConnection(logger mlogger.Logger, config *Config) (Factory, error) {
|
|||||||
if config.Driver == Mongo {
|
if config.Driver == Mongo {
|
||||||
return mongoimpl.NewConnection(logger, config.Settings)
|
return mongoimpl.NewConnection(logger, config.Settings)
|
||||||
}
|
}
|
||||||
return nil, merrors.InvalidArgument("unknown database driver: " + string(config.Driver))
|
return nil, merrors.InvalidArgument("unknown database driver: "+string(config.Driver), "config.driver")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func (db *OrganizationDB) Create(ctx context.Context, _, _ primitive.ObjectID, org *model.Organization) error {
|
func (db *OrganizationDB) Create(ctx context.Context, _, _ primitive.ObjectID, org *model.Organization) error {
|
||||||
if org == nil {
|
if org == nil {
|
||||||
return merrors.InvalidArgument("Organization object is nil")
|
return merrors.InvalidArgument("Organization object is nil", "organization")
|
||||||
}
|
}
|
||||||
org.SetID(primitive.NewObjectID())
|
org.SetID(primitive.NewObjectID())
|
||||||
// Organizaiton reference must be set to the same value as own organization reference
|
// Organizaiton reference must be set to the same value as own organization reference
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func (db *RefreshTokenDB) Create(ctx context.Context, rt *model.RefreshToken) er
|
|||||||
// First, try to find an existing token for this account/client/device combination
|
// First, try to find an existing token for this account/client/device combination
|
||||||
var existing model.RefreshToken
|
var existing model.RefreshToken
|
||||||
if rt.AccountRef == nil {
|
if rt.AccountRef == nil {
|
||||||
return merrors.InvalidArgument("Account reference must have a vaild value")
|
return merrors.InvalidArgument("Account reference must have a vaild value", "refreshToken.accountRef")
|
||||||
}
|
}
|
||||||
if err := db.FindOne(ctx, filterByAccount(*rt.AccountRef, &rt.SessionIdentifier), &existing); err != nil {
|
if err := db.FindOne(ctx, filterByAccount(*rt.AccountRef, &rt.SessionIdentifier), &existing); err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func (r *MongoRepository) CreateIndex(def *ri.Definition) error {
|
|||||||
return merrors.NoData("data collection is not set")
|
return merrors.NoData("data collection is not set")
|
||||||
}
|
}
|
||||||
if len(def.Keys) == 0 {
|
if len(def.Keys) == 0 {
|
||||||
return merrors.InvalidArgument("Index definition has no keys")
|
return merrors.InvalidArgument("Index definition has no keys", "index.keys")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- build BSON keys --------------------------------------------------
|
// ----- build BSON keys --------------------------------------------------
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (r *MongoRepository) findOneByFilterImp(ctx context.Context, filter bson.D,
|
|||||||
|
|
||||||
func (r *MongoRepository) Get(ctx context.Context, id primitive.ObjectID, result storable.Storable) error {
|
func (r *MongoRepository) Get(ctx context.Context, id primitive.ObjectID, result storable.Storable) error {
|
||||||
if id.IsZero() {
|
if id.IsZero() {
|
||||||
return merrors.InvalidArgument("zero id provided while fetching " + result.Collection())
|
return merrors.InvalidArgument("zero id provided while fetching "+result.Collection(), "id")
|
||||||
}
|
}
|
||||||
return r.findOneByFilterImp(ctx, idFilter(id), fmt.Sprintf("%s with ID = %s not found", result.Collection(), id.Hex()), result)
|
return r.findOneByFilterImp(ctx, idFilter(id), fmt.Sprintf("%s with ID = %s not found", result.Collection(), id.Hex()), result)
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ func (r *MongoRepository) Update(ctx context.Context, obj storable.Storable) err
|
|||||||
|
|
||||||
func (r *MongoRepository) Patch(ctx context.Context, id primitive.ObjectID, patch builder.Patch) error {
|
func (r *MongoRepository) Patch(ctx context.Context, id primitive.ObjectID, patch builder.Patch) error {
|
||||||
if id.IsZero() {
|
if id.IsZero() {
|
||||||
return merrors.InvalidArgument("zero id provided while patching")
|
return merrors.InvalidArgument("zero id provided while patching", "id")
|
||||||
}
|
}
|
||||||
_, err := r.collection.UpdateByID(ctx, id, patch.Build())
|
_, err := r.collection.UpdateByID(ctx, id, patch.Build())
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type TimeSeries struct {
|
|||||||
|
|
||||||
func NewMongoTimeSeriesCollection(ctx context.Context, db *mongo.Database, tsOpts *tsoptions.Options) (*TimeSeries, error) {
|
func NewMongoTimeSeriesCollection(ctx context.Context, db *mongo.Database, tsOpts *tsoptions.Options) (*TimeSeries, error) {
|
||||||
if tsOpts == nil {
|
if tsOpts == nil {
|
||||||
return nil, merrors.InvalidArgument("nil time-series options provided")
|
return nil, merrors.InvalidArgument("nil time-series options provided", "options")
|
||||||
}
|
}
|
||||||
// Configure time-series options
|
// Configure time-series options
|
||||||
granularity := tsOpts.Granularity.String()
|
granularity := tsOpts.Granularity.String()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package merrors
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
)
|
)
|
||||||
@@ -27,8 +28,8 @@ func Internal(msg string) error {
|
|||||||
|
|
||||||
var ErrInvalidArg = errors.New("invalidArgError")
|
var ErrInvalidArg = errors.New("invalidArgError")
|
||||||
|
|
||||||
func InvalidArgument(msg string) error {
|
func InvalidArgument(msg string, argumentNames ...string) error {
|
||||||
return fmt.Errorf("%w: %s", ErrInvalidArg, msg)
|
return fmt.Errorf("%w: %s", ErrInvalidArg, invalidArgumentMessage(msg, argumentNames...))
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrDataConflict = errors.New("DataConflict")
|
var ErrDataConflict = errors.New("DataConflict")
|
||||||
@@ -64,8 +65,8 @@ func NoMessagingTopic(topic string) error {
|
|||||||
return fmt.Errorf("%w: messaging topic '%s' not found", ErrNoMessagingTopic, topic)
|
return fmt.Errorf("%w: messaging topic '%s' not found", ErrNoMessagingTopic, topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InvalidArgumentWrap(err error, msg string) error {
|
func InvalidArgumentWrap(err error, msg string, argumentNames ...string) error {
|
||||||
return wrapError(ErrInvalidArg, msg, err)
|
return wrapError(ErrInvalidArg, invalidArgumentMessage(msg, argumentNames...), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InternalWrap(err error, msg string) error {
|
func InternalWrap(err error, msg string) error {
|
||||||
@@ -79,3 +80,23 @@ func wrapError(base error, msg string, err error) error {
|
|||||||
}
|
}
|
||||||
return errors.Join(baseErr, err)
|
return errors.Join(baseErr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invalidArgumentMessage(msg string, argumentNames ...string) string {
|
||||||
|
names := make([]string, 0, len(argumentNames))
|
||||||
|
for _, name := range argumentNames {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
names = append(names, fmt.Sprintf("%q", name))
|
||||||
|
}
|
||||||
|
if len(names) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := "broken argument"
|
||||||
|
if len(names) > 1 {
|
||||||
|
prefix = "broken arguments"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s: %s", prefix, strings.Join(names, ", "), msg)
|
||||||
|
}
|
||||||
|
|||||||
47
api/pkg/merrors/errors_test.go
Normal file
47
api/pkg/merrors/errors_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package merrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInvalidArgumentSupportsBrokenArgumentName(t *testing.T) {
|
||||||
|
t.Run("without argument name keeps old behavior", func(t *testing.T) {
|
||||||
|
err := InvalidArgument("value is missing")
|
||||||
|
expected := "invalidArgError: value is missing"
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("unexpected error message: %s", err)
|
||||||
|
}
|
||||||
|
if !errors.Is(err, ErrInvalidArg) {
|
||||||
|
t.Fatalf("error should wrap ErrInvalidArg")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("single argument name", func(t *testing.T) {
|
||||||
|
err := InvalidArgument("value is missing", "bot_token_env")
|
||||||
|
expected := `invalidArgError: broken argument "bot_token_env": value is missing`
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("unexpected error message: %s", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple argument names", func(t *testing.T) {
|
||||||
|
err := InvalidArgument("value mismatch", "bot_token_env", "chat_id_env", " ")
|
||||||
|
expected := `invalidArgError: broken arguments "bot_token_env", "chat_id_env": value mismatch`
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("unexpected error message: %s", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidArgumentWrapSupportsBrokenArgumentName(t *testing.T) {
|
||||||
|
base := errors.New("root cause")
|
||||||
|
err := InvalidArgumentWrap(base, "value is missing", "bot_token_env")
|
||||||
|
if !strings.Contains(err.Error(), `invalidArgError: broken argument "bot_token_env": value is missing`) {
|
||||||
|
t.Fatalf("wrapped error should include broken argument name: %s", err)
|
||||||
|
}
|
||||||
|
if !errors.Is(err, ErrInvalidArg) || !errors.Is(err, base) {
|
||||||
|
t.Fatalf("wrapped error should preserve all error layers")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ func (b *MessageBroker) Unsubscribe(event model.NotificationEvent, subChan <-cha
|
|||||||
|
|
||||||
func NewInProcessBroker(logger mlogger.Logger, bufferSize int) (*MessageBroker, error) {
|
func NewInProcessBroker(logger mlogger.Logger, bufferSize int) (*MessageBroker, error) {
|
||||||
if bufferSize < 1 {
|
if bufferSize < 1 {
|
||||||
return nil, merrors.InvalidArgument(fmt.Sprintf("Invelid buffer size %d. It must be greater than 1", bufferSize))
|
return nil, merrors.InvalidArgument(fmt.Sprintf("Invelid buffer size %d. It must be greater than 1", bufferSize), "bufferSize")
|
||||||
}
|
}
|
||||||
logger.Info("Created in-process logger", zap.Int("buffer_size", bufferSize))
|
logger.Info("Created in-process logger", zap.Int("buffer_size", bufferSize))
|
||||||
return &MessageBroker{
|
return &MessageBroker{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func loadEnv(settings *nc.Settings, l *zap.Logger) (*envConfig, error) {
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
l.Error(fmt.Sprintf("NATS %s not found in environment", label), zap.String("env_var", key))
|
l.Error(fmt.Sprintf("NATS %s not found in environment", label), zap.String("env_var", key))
|
||||||
return "", merrors.InvalidArgument(fmt.Sprintf("NATS %s not found in environment variable: %s", label, key))
|
return "", merrors.InvalidArgument(fmt.Sprintf("NATS %s not found in environment variable: %s", label, key), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := get(settings.UsernameEnv, "user name")
|
user, err := get(settings.UsernameEnv, "user name")
|
||||||
@@ -65,7 +65,7 @@ func loadEnv(settings *nc.Settings, l *zap.Logger) (*envConfig, error) {
|
|||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
if err != nil || port <= 0 || port > 65535 {
|
if err != nil || port <= 0 || port > 65535 {
|
||||||
l.Error("Invalid NATS port value", zap.String("port", portStr))
|
l.Error("Invalid NATS port value", zap.String("port", portStr))
|
||||||
return nil, merrors.InvalidArgument("Invalid NATS port: " + portStr)
|
return nil, merrors.InvalidArgument("Invalid NATS port: "+portStr, settings.PortEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &envConfig{
|
return &envConfig{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type DemoRequestNotification struct {
|
|||||||
|
|
||||||
func (drn *DemoRequestNotification) Serialize() ([]byte, error) {
|
func (drn *DemoRequestNotification) Serialize() ([]byte, error) {
|
||||||
if drn.request == nil {
|
if drn.request == nil {
|
||||||
return nil, merrors.InvalidArgument("demo request payload is empty")
|
return nil, merrors.InvalidArgument("demo request payload is empty", "request")
|
||||||
}
|
}
|
||||||
msg := gmessaging.DemoRequestEvent{
|
msg := gmessaging.DemoRequestEvent{
|
||||||
Name: drn.request.Name,
|
Name: drn.request.Name,
|
||||||
|
|||||||
@@ -32,22 +32,22 @@ func (dr *DemoRequest) Normalize() {
|
|||||||
// Validate ensures that all required fields are present.
|
// Validate ensures that all required fields are present.
|
||||||
func (dr *DemoRequest) Validate() error {
|
func (dr *DemoRequest) Validate() error {
|
||||||
if dr == nil {
|
if dr == nil {
|
||||||
return merrors.InvalidArgument("request payload is empty")
|
return merrors.InvalidArgument("request payload is empty", "request")
|
||||||
}
|
}
|
||||||
if dr.Name == "" {
|
if dr.Name == "" {
|
||||||
return merrors.InvalidArgument("name must not be empty")
|
return merrors.InvalidArgument("name must not be empty", "request.name")
|
||||||
}
|
}
|
||||||
if dr.OrganizationName == "" {
|
if dr.OrganizationName == "" {
|
||||||
return merrors.InvalidArgument("organization name must not be empty")
|
return merrors.InvalidArgument("organization name must not be empty", "request.organizationName")
|
||||||
}
|
}
|
||||||
if dr.Phone == "" {
|
if dr.Phone == "" {
|
||||||
return merrors.InvalidArgument("phone must not be empty")
|
return merrors.InvalidArgument("phone must not be empty", "request.phone")
|
||||||
}
|
}
|
||||||
if dr.WorkEmail == "" {
|
if dr.WorkEmail == "" {
|
||||||
return merrors.InvalidArgument("work email must not be empty")
|
return merrors.InvalidArgument("work email must not be empty", "request.workEmail")
|
||||||
}
|
}
|
||||||
if dr.PayoutVolume == "" {
|
if dr.PayoutVolume == "" {
|
||||||
return merrors.InvalidArgument("payout volume must not be empty")
|
return merrors.InvalidArgument("payout volume must not be empty", "request.payoutVolume")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ func IndexableRefs(items []model.IndexableRef, oldIndex, newIndex int) ([]model.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if targetIndex == -1 {
|
if targetIndex == -1 {
|
||||||
return nil, merrors.InvalidArgument("Item not found at specified index")
|
return nil, merrors.InvalidArgument("Item not found at specified index", "oldIndex")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate new index bounds
|
// Validate new index bounds
|
||||||
if newIndex < 0 || newIndex >= len(items) {
|
if newIndex < 0 || newIndex >= len(items) {
|
||||||
return nil, merrors.InvalidArgument("Invalid new index for reorder")
|
return nil, merrors.InvalidArgument("Invalid new index for reorder", "newIndex")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the item from its current position
|
// Remove the item from its current position
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ type App[T any] struct {
|
|||||||
|
|
||||||
func NewApp[T any](logger mlogger.Logger, name string, config *Config, debug bool, repoFactory RepositoryFactory[T], serviceFactory ServiceFactory[T], opts ...Option[T]) (*App[T], error) {
|
func NewApp[T any](logger mlogger.Logger, name string, config *Config, debug bool, repoFactory RepositoryFactory[T], serviceFactory ServiceFactory[T], opts ...Option[T]) (*App[T], error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, merrors.InvalidArgument("nil logger supplied")
|
return nil, merrors.InvalidArgument("nil logger supplied", "logger")
|
||||||
}
|
}
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return nil, merrors.InvalidArgument("nil config supplied")
|
return nil, merrors.InvalidArgument("nil config supplied", "config")
|
||||||
}
|
}
|
||||||
if serviceFactory == nil {
|
if serviceFactory == nil {
|
||||||
return nil, merrors.InvalidArgument("nil service factory supplied")
|
return nil, merrors.InvalidArgument("nil service factory supplied", "serviceFactory")
|
||||||
}
|
}
|
||||||
|
|
||||||
app := &App[T]{
|
app := &App[T]{
|
||||||
|
|||||||
Reference in New Issue
Block a user