fix: json marshal/unmarshal

This commit is contained in:
yusing 2025-04-24 19:57:27 +08:00
parent 080bbc18eb
commit 8363dfe257
5 changed files with 117 additions and 43 deletions

View file

@ -34,7 +34,7 @@ type sessionClaims struct {
type sessionID string type sessionID string
var oauthRefreshTokens jsonstore.Typed[oauthRefreshToken] var oauthRefreshTokens jsonstore.MapStore[oauthRefreshToken]
var ( var (
defaultRefreshTokenExpiry = 30 * 24 * time.Hour // 1 month defaultRefreshTokenExpiry = 30 * 24 * time.Hour // 1 month

View file

@ -1,6 +1,7 @@
package homepage package homepage
import ( import (
"maps"
"sync" "sync"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
@ -15,12 +16,19 @@ type OverrideConfig struct {
mu sync.RWMutex mu sync.RWMutex
} }
var overrideConfigInstance = jsonstore.Object[OverrideConfig](common.NamespaceHomepageOverrides) var overrideConfigInstance = jsonstore.Object[*OverrideConfig](common.NamespaceHomepageOverrides)
func GetOverrideConfig() *OverrideConfig { func GetOverrideConfig() *OverrideConfig {
return overrideConfigInstance return overrideConfigInstance
} }
func (c *OverrideConfig) Initialize() {
c.ItemOverrides = make(map[string]*ItemConfig)
c.DisplayOrder = make(map[string]int)
c.CategoryOrder = make(map[string]int)
c.ItemVisibility = make(map[string]bool)
}
func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) { func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -30,9 +38,7 @@ func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) {
func (c *OverrideConfig) OverrideItems(items map[string]*ItemConfig) { func (c *OverrideConfig) OverrideItems(items map[string]*ItemConfig) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
for key, value := range items { maps.Copy(c.ItemOverrides, items)
c.ItemOverrides[key] = value
}
} }
func (c *OverrideConfig) GetOverride(alias string, item *ItemConfig) *ItemConfig { func (c *OverrideConfig) GetOverride(alias string, item *ItemConfig) *ItemConfig {

View file

@ -4,8 +4,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path/filepath" "path/filepath"
"reflect"
"sync" "sync"
"maps"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
@ -16,16 +19,29 @@ import (
type namespace string type namespace string
type Typed[VT any] struct { type MapStore[VT any] struct {
*xsync.MapOf[string, VT] *xsync.MapOf[string, VT]
} }
type storesMap struct { type ObjectStore[Pointer Initializer] struct {
sync.RWMutex ptr Pointer
m map[namespace]any
} }
var stores = storesMap{m: make(map[namespace]any)} type Initializer interface {
Initialize()
}
type storeByNamespace struct {
sync.RWMutex
m map[namespace]store
}
type store interface {
json.Marshaler
json.Unmarshaler
}
var stores = storeByNamespace{m: make(map[namespace]store)}
var storesPath = common.DataDir var storesPath = common.DataDir
func init() { func init() {
@ -44,12 +60,16 @@ func load() error {
stores.Lock() stores.Lock()
defer stores.Unlock() defer stores.Unlock()
errs := gperr.NewBuilder("failed to load data stores") errs := gperr.NewBuilder("failed to load data stores")
for ns, store := range stores.m { for ns, obj := range stores.m {
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &store); err != nil { if init, ok := obj.(Initializer); ok {
init.Initialize()
}
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &obj); err != nil {
errs.Add(err) errs.Add(err)
} else { } else {
logging.Info().Str("name", string(ns)).Msg("store loaded") logging.Info().Str("name", string(ns)).Msg("store loaded")
} }
stores.m[ns] = obj
} }
return errs.Error() return errs.Error()
} }
@ -66,41 +86,42 @@ func save() error {
return errs.Error() return errs.Error()
} }
func Store[VT any](namespace namespace) Typed[VT] { func Store[VT any](namespace namespace) MapStore[VT] {
stores.Lock() stores.Lock()
defer stores.Unlock() defer stores.Unlock()
if s, ok := stores.m[namespace]; ok { if s, ok := stores.m[namespace]; ok {
return s.(Typed[VT]) v, ok := s.(*MapStore[VT])
} if !ok {
m := Typed[VT]{MapOf: xsync.NewMapOf[string, VT]()}
stores.m[namespace] = m
return m
}
func Object[VT any](namespace namespace) *VT {
stores.Lock()
defer stores.Unlock()
if s, ok := stores.m[namespace]; ok {
v, ok := s.(*VT)
if ok {
return v
}
panic(fmt.Errorf("type mismatch: %T != %T", s, v)) panic(fmt.Errorf("type mismatch: %T != %T", s, v))
} }
v := new(VT) return *v
stores.m[namespace] = v }
return v m := &MapStore[VT]{MapOf: xsync.NewMapOf[string, VT]()}
stores.m[namespace] = m
return *m
} }
func (s Typed[VT]) MarshalJSON() ([]byte, error) { func Object[Ptr Initializer](namespace namespace) Ptr {
tmp := make(map[string]VT, s.Size()) stores.Lock()
for k, v := range s.Range { defer stores.Unlock()
tmp[k] = v if s, ok := stores.m[namespace]; ok {
v, ok := s.(*ObjectStore[Ptr])
if !ok {
panic(fmt.Errorf("type mismatch: %T != %T", s, v))
} }
return json.Marshal(tmp) return v.ptr
}
obj := &ObjectStore[Ptr]{}
obj.init()
stores.m[namespace] = obj
return obj.ptr
} }
func (s Typed[VT]) UnmarshalJSON(data []byte) error { func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
return json.Marshal(maps.Collect(s.Range))
}
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
tmp := make(map[string]VT) tmp := make(map[string]VT)
if err := json.Unmarshal(data, &tmp); err != nil { if err := json.Unmarshal(data, &tmp); err != nil {
return err return err
@ -111,3 +132,17 @@ func (s Typed[VT]) UnmarshalJSON(data []byte) error {
} }
return nil return nil
} }
func (obj *ObjectStore[Ptr]) init() {
obj.ptr = reflect.New(reflect.TypeFor[Ptr]().Elem()).Interface().(Ptr)
obj.ptr.Initialize()
}
func (obj ObjectStore[Ptr]) MarshalJSON() ([]byte, error) {
return json.Marshal(obj.ptr)
}
func (obj *ObjectStore[Ptr]) UnmarshalJSON(data []byte) error {
obj.init()
return json.Unmarshal(data, obj.ptr)
}

View file

@ -12,19 +12,52 @@ func TestNewJSON(t *testing.T) {
} }
} }
func TestSaveLoad(t *testing.T) { func TestSaveLoadStore(t *testing.T) {
storesPath = t.TempDir() storesPath = t.TempDir()
store := Store[string]("test") store := Store[string]("test")
store.Store("a", "1") store.Store("a", "1")
if err := save(); err != nil { if err := save(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
stores.m = nil
if err := load(); err != nil { if err := load(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
store = Store[string]("test") loaded := Store[string]("test")
if v, _ := store.Load("a"); v != "1" { v, ok := loaded.Load("a")
t.Fatal("expected 1, got", v) if !ok {
t.Fatal("expected key exists")
}
if v != "1" {
t.Fatalf("expected 1, got %q", v)
}
if loaded.MapOf == store.MapOf {
t.Fatal("expected different objects")
}
}
type testObject struct {
I int `json:"i"`
S string `json:"s"`
}
func (*testObject) Initialize() {}
func TestSaveLoadObject(t *testing.T) {
storesPath = t.TempDir()
obj := Object[*testObject]("test")
obj.I = 1
obj.S = "1"
if err := save(); err != nil {
t.Fatal(err)
}
if err := load(); err != nil {
t.Fatal(err)
}
loaded := Object[*testObject]("test")
if loaded.I != 1 || loaded.S != "1" {
t.Fatalf("expected 1, got %d, %s", loaded.I, loaded.S)
}
if loaded == obj {
t.Fatal("expected different objects")
} }
} }

View file

@ -25,7 +25,7 @@ func getHealthInfo(r Route) map[string]string {
} }
type HealthInfoRaw struct { type HealthInfoRaw struct {
Status health.Status `json:"status,string"` Status health.Status `json:"status"`
Latency time.Duration `json:"latency"` Latency time.Duration `json:"latency"`
} }