fix: correct version number in Swagger documentation and enhance config loading with improved JSON parsing

This commit is contained in:
Daniel Lavrushin 2026-04-01 13:00:18 +02:00
parent 8a5821511c
commit d7d6933a0b
No known key found for this signature in database
GPG key ID: 57F1CAB57AD35056
5 changed files with 154 additions and 10 deletions

View file

@ -4,7 +4,7 @@
"description": "B4 network packet processor REST API",
"title": "B4 API",
"contact": {},
"version": "1.47.0"
"version": "1.0.0"
},
"basePath": "/api",
"paths": {

View file

@ -65,17 +65,37 @@ func (c *Config) LoadFromFile(path string) error {
if err != nil {
return log.Errorf("failed to read config file: %v", err)
}
err = json.Unmarshal(data, c)
if err != nil {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return log.Errorf("failed to parse config file: %v", err)
}
ApplyConfigDefaults(c)
for _, set := range c.Sets {
ApplySetDefaults(set)
rawSets := raw["sets"]
delete(raw, "sets")
withoutSets, _ := json.Marshal(raw)
if err := json.Unmarshal(withoutSets, c); err != nil {
return log.Errorf("failed to parse config file: %v", err)
}
if rawSets != nil {
var setArray []json.RawMessage
if err := json.Unmarshal(rawSets, &setArray); err != nil {
return log.Errorf("failed to parse sets: %v", err)
}
c.Sets = make([]*SetConfig, 0, len(setArray))
for _, rawSet := range setArray {
set := NewSetConfig()
if err := json.Unmarshal(rawSet, &set); err != nil {
return log.Errorf("failed to parse set: %v", err)
}
c.Sets = append(c.Sets, &set)
}
}
ApplyConfigDefaults(c)
return nil
}

View file

@ -443,15 +443,36 @@ func (c *Config) LoadWithMigration(path string) error {
return log.Errorf("failed to parse config file: %v", err)
}
if err := json.Unmarshal(data, c); err != nil {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return log.Errorf("failed to parse config file: %v", err)
}
ApplyConfigDefaults(c)
for _, set := range c.Sets {
ApplySetDefaults(set)
rawSets := raw["sets"]
delete(raw, "sets")
withoutSets, _ := json.Marshal(raw)
if err := json.Unmarshal(withoutSets, c); err != nil {
return log.Errorf("failed to parse config file: %v", err)
}
if rawSets != nil {
var setArray []json.RawMessage
if err := json.Unmarshal(rawSets, &setArray); err != nil {
return log.Errorf("failed to parse sets: %v", err)
}
c.Sets = make([]*SetConfig, 0, len(setArray))
for _, rs := range setArray {
set := NewSetConfig()
if err := json.Unmarshal(rs, &set); err != nil {
return log.Errorf("failed to parse set: %v", err)
}
c.Sets = append(c.Sets, &set)
}
}
ApplyConfigDefaults(c)
if c.Version < CurrentConfigVersion {
log.Infof("Config version %d is older than current version %d, migrating",
c.Version, CurrentConfigVersion)

View file

@ -1,12 +1,46 @@
package config
import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
func deepCompareFields(prefix string, expected, actual reflect.Value) []string {
var diffs []string
if expected.Kind() == reflect.Ptr {
if expected.IsNil() && actual.IsNil() {
return nil
}
if expected.IsNil() != actual.IsNil() {
return []string{fmt.Sprintf("%s: nil mismatch", prefix)}
}
return deepCompareFields(prefix, expected.Elem(), actual.Elem())
}
if expected.Kind() == reflect.Struct {
t := expected.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if !f.IsExported() {
continue
}
name := prefix + "." + f.Name
if prefix == "" {
name = f.Name
}
diffs = append(diffs, deepCompareFields(name, expected.Field(i), actual.Field(i))...)
}
return diffs
}
if !reflect.DeepEqual(expected.Interface(), actual.Interface()) {
diffs = append(diffs, fmt.Sprintf("%s: want %v, got %v", prefix, expected.Interface(), actual.Interface()))
}
return diffs
}
func TestLoadWithMigration(t *testing.T) {
tmpDir := t.TempDir()
@ -70,6 +104,31 @@ func TestLoadWithMigration(t *testing.T) {
t.Errorf("version should remain %d", CurrentConfigVersion)
}
})
t.Run("sparse roundtrip preserves all set defaults", func(t *testing.T) {
path := filepath.Join(tmpDir, "sparse_all.json")
cfg := NewConfig()
set := NewSetConfig()
set.Id = "all-defaults"
set.Name = "AllDefaults"
cfg.Sets = []*SetConfig{&set}
cfg.SaveToFile(path)
loaded := NewConfig()
if err := loaded.LoadWithMigration(path); err != nil {
t.Fatalf("LoadWithMigration failed: %v", err)
}
ls := loaded.Sets[0]
expected := NewSetConfig()
expected.Id = "all-defaults"
expected.Name = "AllDefaults"
diffs := deepCompareFields("", reflect.ValueOf(expected), reflect.ValueOf(*ls))
for _, d := range diffs {
t.Errorf("%s", d)
}
})
}
func TestDiscoverConfigPath(t *testing.T) {

View file

@ -202,6 +202,50 @@ func TestSparseSaveLoadRoundtrip_BoolDefaults(t *testing.T) {
}
}
func TestSparseSaveLoadRoundtrip_TrueBoolDefaultsSurvive(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "config.json")
cfg := NewConfig()
set := NewSetConfig()
set.Id = "bool-defaults"
set.Name = "BoolDefaults"
set.Faking.TTL = 8
cfg.Sets = []*SetConfig{&set}
if err := cfg.SaveToFile(path); err != nil {
t.Fatalf("SaveToFile failed: %v", err)
}
loaded := NewConfig()
if err := loaded.LoadFromFile(path); err != nil {
t.Fatalf("LoadFromFile failed: %v", err)
}
ls := loaded.Sets[0]
if ls.Faking.SNI != DefaultSetConfig.Faking.SNI {
t.Errorf("expected Faking.SNI=%v (default), got %v", DefaultSetConfig.Faking.SNI, ls.Faking.SNI)
}
if ls.Fragmentation.ReverseOrder != DefaultSetConfig.Fragmentation.ReverseOrder {
t.Errorf("expected ReverseOrder=%v (default), got %v", DefaultSetConfig.Fragmentation.ReverseOrder, ls.Fragmentation.ReverseOrder)
}
if ls.Fragmentation.MiddleSNI != DefaultSetConfig.Fragmentation.MiddleSNI {
t.Errorf("expected MiddleSNI=%v (default), got %v", DefaultSetConfig.Fragmentation.MiddleSNI, ls.Fragmentation.MiddleSNI)
}
if ls.Fragmentation.Combo.FirstByteSplit != DefaultSetConfig.Fragmentation.Combo.FirstByteSplit {
t.Errorf("expected FirstByteSplit=%v (default), got %v", DefaultSetConfig.Fragmentation.Combo.FirstByteSplit, ls.Fragmentation.Combo.FirstByteSplit)
}
if ls.Fragmentation.Combo.ExtensionSplit != DefaultSetConfig.Fragmentation.Combo.ExtensionSplit {
t.Errorf("expected ExtensionSplit=%v (default), got %v", DefaultSetConfig.Fragmentation.Combo.ExtensionSplit, ls.Fragmentation.Combo.ExtensionSplit)
}
if ls.Enabled != DefaultSetConfig.Enabled {
t.Errorf("expected Enabled=%v (default), got %v", DefaultSetConfig.Enabled, ls.Enabled)
}
if ls.UDP.FilterSTUN != DefaultSetConfig.UDP.FilterSTUN {
t.Errorf("expected FilterSTUN=%v (default), got %v", DefaultSetConfig.UDP.FilterSTUN, ls.UDP.FilterSTUN)
}
}
func TestMarshalSparse_OmitsDerivedDiscoveryMarks(t *testing.T) {
cfg := NewConfig()
set := NewSetConfig()