go implementation

This commit is contained in:
Илья Глазунов
2025-12-11 16:52:13 +03:00
parent c04ab283a6
commit 8f5b9a5cd1
20 changed files with 4146 additions and 1 deletions
+134
View File
@@ -0,0 +1,134 @@
package config
import (
"fmt"
"os"
"time"
"gopkg.in/yaml.v3"
)
type Config struct {
HTTP HTTPConfig `yaml:"http"`
Server ServerConfig `yaml:"server"`
SSL SSLConfig `yaml:"ssl"`
Logging LoggingConfig `yaml:"logging"`
Extensions []ExtensionConfig `yaml:"extensions"`
}
type HTTPConfig struct {
StaticDir string `yaml:"static_dir"`
TemplatesDir string `yaml:"templates_dir"`
}
type ServerConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Backlog int `yaml:"backlog"`
DefaultRoot bool `yaml:"default_root"`
ProxyTimeout time.Duration `yaml:"proxy_timeout"`
RedirectInstructions map[string]string `yaml:"redirect_instructions"`
}
type SSLConfig struct {
Enabled bool `yaml:"enabled"`
CertFile string `yaml:"cert_file"`
KeyFile string `yaml:"key_file"`
}
type LoggingConfig struct {
Level string `yaml:"level"`
ConsoleOutput bool `yaml:"console_output"`
Format LogFormatConfig `yaml:"format"`
Console *ConsoleLogConfig `yaml:"console"`
Files []FileLogConfig `yaml:"files"`
}
type LogFormatConfig struct {
Type string `yaml:"type"`
UseColors bool `yaml:"use_colors"`
ShowModule bool `yaml:"show_module"`
TimestampFormat string `yaml:"timestamp_format"`
}
type ConsoleLogConfig struct {
Format LogFormatConfig `yaml:"format"`
Level string `yaml:"level"`
}
type FileLogConfig struct {
Path string `yaml:"path"`
Level string `yaml:"level"`
Loggers []string `yaml:"loggers"`
Format LogFormatConfig `yaml:"format"`
MaxBytes int64 `yaml:"max_bytes"`
BackupCount int `yaml:"backup_count"`
}
type ExtensionConfig struct {
Type string `yaml:"type"`
Config map[string]interface{} `yaml:"config"`
}
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
cfg := Default()
if err := yaml.Unmarshal(data, cfg); err != nil {
return nil, fmt.Errorf("failed to parse config: %w", err)
}
return cfg, nil
}
func Default() *Config {
return &Config{
HTTP: HTTPConfig{
StaticDir: "./static",
TemplatesDir: "./templates",
},
Server: ServerConfig{
Host: "0.0.0.0",
Port: 8080,
Backlog: 5,
DefaultRoot: false,
ProxyTimeout: 30 * time.Second,
},
SSL: SSLConfig{
Enabled: false,
CertFile: "./ssl/cert.pem",
KeyFile: "./ssl/key.pem",
},
Logging: LoggingConfig{
Level: "INFO",
ConsoleOutput: true,
Format: LogFormatConfig{
Type: "standard",
UseColors: true,
ShowModule: true,
TimestampFormat: "2006-01-02 15:04:05",
},
},
Extensions: []ExtensionConfig{},
}
}
func (c *Config) Validate() error {
if c.Server.Port < 1 || c.Server.Port > 65535 {
return fmt.Errorf("invalid port: %d", c.Server.Port)
}
if c.SSL.Enabled {
if c.SSL.CertFile == "" {
return fmt.Errorf("SSL enabled but cert_file not specified")
}
if c.SSL.KeyFile == "" {
return fmt.Errorf("SSL enabled but key_file not specified")
}
}
return nil
}
+127
View File
@@ -0,0 +1,127 @@
package config
import (
"os"
"testing"
)
func TestDefault(t *testing.T) {
cfg := Default()
if cfg.Server.Host != "0.0.0.0" {
t.Errorf("Expected host 0.0.0.0, got %s", cfg.Server.Host)
}
if cfg.Server.Port != 8080 {
t.Errorf("Expected port 8080, got %d", cfg.Server.Port)
}
if cfg.SSL.Enabled {
t.Error("Expected SSL to be disabled by default")
}
}
func TestValidate(t *testing.T) {
tests := []struct {
name string
modify func(*Config)
wantErr bool
}{
{
name: "valid default config",
modify: func(c *Config) {},
wantErr: false,
},
{
name: "invalid port - too low",
modify: func(c *Config) {
c.Server.Port = 0
},
wantErr: true,
},
{
name: "invalid port - too high",
modify: func(c *Config) {
c.Server.Port = 70000
},
wantErr: true,
},
{
name: "SSL enabled without cert",
modify: func(c *Config) {
c.SSL.Enabled = true
c.SSL.CertFile = ""
},
wantErr: true,
},
{
name: "SSL enabled without key",
modify: func(c *Config) {
c.SSL.Enabled = true
c.SSL.CertFile = "cert.pem"
c.SSL.KeyFile = ""
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := Default()
tt.modify(cfg)
err := cfg.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestLoad(t *testing.T) {
// Create temporary config file
content := `
server:
host: 127.0.0.1
port: 3000
logging:
level: DEBUG
`
tmpfile, err := os.CreateTemp("", "config-*.yaml")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.Write([]byte(content)); err != nil {
t.Fatal(err)
}
if err := tmpfile.Close(); err != nil {
t.Fatal(err)
}
cfg, err := Load(tmpfile.Name())
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
if cfg.Server.Host != "127.0.0.1" {
t.Errorf("Expected host 127.0.0.1, got %s", cfg.Server.Host)
}
if cfg.Server.Port != 3000 {
t.Errorf("Expected port 3000, got %d", cfg.Server.Port)
}
if cfg.Logging.Level != "DEBUG" {
t.Errorf("Expected level DEBUG, got %s", cfg.Logging.Level)
}
}
func TestLoadNotFound(t *testing.T) {
_, err := Load("/nonexistent/config.yaml")
if err == nil {
t.Error("Expected error for non-existent file")
}
}