refactor: разнести пакеты и обновить валидацию

This commit is contained in:
Георгий
2026-07-14 22:00:34 +03:00
parent 3ed29fa8e6
commit 1e1008d506
20 changed files with 409 additions and 128 deletions

29
protocol/config.go Normal file
View File

@@ -0,0 +1,29 @@
package protocol
type Config struct {
MaxRouteLen int
MaxTextLen int
MaxTemplateLen int
}
func DefaultConfig() Config {
return Config{
MaxRouteLen: 3,
MaxTextLen: 200,
MaxTemplateLen: 200,
}
}
func NormalizeConfig(cfg Config) Config {
def := DefaultConfig()
if cfg.MaxRouteLen <= 0 {
cfg.MaxRouteLen = def.MaxRouteLen
}
if cfg.MaxTextLen <= 0 {
cfg.MaxTextLen = def.MaxTextLen
}
if cfg.MaxTemplateLen <= 0 {
cfg.MaxTemplateLen = def.MaxTemplateLen
}
return cfg
}

8
protocol/errors.go Normal file
View File

@@ -0,0 +1,8 @@
package protocol
import "errors"
var (
ErrNotReady = errors.New("mila: state is not ready")
ErrInvalidData = errors.New("mila: invalid content state")
)

15
protocol/state.go Normal file
View File

@@ -0,0 +1,15 @@
package protocol
import "time"
// ContentState is the current display state received from BVK.
type ContentState struct {
Route string `json:"route"`
Text string `json:"str"`
Template string `json:"tpl_name"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
func (s ContentState) IsZero() bool {
return s.Route == "" && s.Text == "" && s.Template == ""
}

8
protocol/store.go Normal file
View File

@@ -0,0 +1,8 @@
package protocol
import "context"
type Store interface {
Get(ctx context.Context) (ContentState, error)
Set(ctx context.Context, state ContentState) error
}

35
protocol/validation.go Normal file
View File

@@ -0,0 +1,35 @@
package protocol
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
)
var validate = validator.New(validator.WithRequiredStructEnabled())
func Validate(cfg Config, state ContentState) error {
cfg = NormalizeConfig(cfg)
route := strings.TrimSpace(state.Route)
template := strings.TrimSpace(state.Template)
if err := validate.Var(route, "required"); err != nil {
return fmt.Errorf("%w: route is required", ErrInvalidData)
}
if err := validate.Var([]rune(route), fmt.Sprintf("max=%d", cfg.MaxRouteLen)); err != nil {
return fmt.Errorf("%w: route must be at most %d characters", ErrInvalidData, cfg.MaxRouteLen)
}
if err := validate.Var([]rune(state.Text), fmt.Sprintf("max=%d", cfg.MaxTextLen)); err != nil {
return fmt.Errorf("%w: str must be at most %d characters", ErrInvalidData, cfg.MaxTextLen)
}
if err := validate.Var(template, "required"); err != nil {
return fmt.Errorf("%w: tpl_name is required", ErrInvalidData)
}
if err := validate.Var([]rune(template), fmt.Sprintf("max=%d", cfg.MaxTemplateLen)); err != nil {
return fmt.Errorf("%w: tpl_name must be at most %d characters", ErrInvalidData, cfg.MaxTemplateLen)
}
return nil
}

View File

@@ -0,0 +1,31 @@
package protocol
import (
"errors"
"testing"
)
func TestValidateAcceptsValidState(t *testing.T) {
err := Validate(DefaultConfig(), ContentState{
Route: "3",
Text: "Гостиный двор - Gostiny dvor",
Template: "Гостиный двор",
})
if err != nil {
t.Fatalf("Validate returned error: %v", err)
}
}
func TestValidateWrapsInvalidData(t *testing.T) {
err := Validate(DefaultConfig(), ContentState{
Route: "1234",
Text: "ok",
Template: "tpl",
})
if err == nil {
t.Fatal("expected error")
}
if !errors.Is(err, ErrInvalidData) {
t.Fatalf("expected ErrInvalidData, got %v", err)
}
}