diff --git a/config/loader/loader.go b/config/loader/loader.go index cbf97c34..ba7bc56d 100644 --- a/config/loader/loader.go +++ b/config/loader/loader.go @@ -1,3 +1,8 @@ +// Package loader converts a parsed [config.Config] into running components +// by parsing each config section and registering the results into the +// global registries. Components are registered in dependency order so that +// when a component reads its dependencies from registries during parsing, +// those dependencies are already available. package loader import ( @@ -39,16 +44,24 @@ import ( "github.com/go-gost/x/registry" ) +// defaultLoader is a singleton loader used by the top-level Load function. var ( defaultLoader *loader = &loader{} ) +// Load parses all config sections from cfg and registers the resulting +// components into the global registries, then sets up the default logger +// and TLS config. func Load(cfg *config.Config) error { return defaultLoader.Load(cfg) } +// loader holds no state; its Load method is the entry point for converting +// a config into running components. type loader struct{} +// Load builds the default TLS config, registers all named components from +// cfg into the global registries, and sets the default logger and TLS config. func (l *loader) Load(cfg *config.Config) error { if cfg == nil { return nil diff --git a/config/loader/loader_test.go b/config/loader/loader_test.go new file mode 100644 index 00000000..776bf57d --- /dev/null +++ b/config/loader/loader_test.go @@ -0,0 +1,807 @@ +package loader + +import ( + "context" + "errors" + "io" + "net" + "testing" + + "github.com/go-gost/core/handler" + "github.com/go-gost/core/listener" + "github.com/go-gost/core/logger" + "github.com/go-gost/core/metadata" + reg "github.com/go-gost/core/registry" + "github.com/go-gost/x/config" + xlogger "github.com/go-gost/x/logger" + "github.com/go-gost/x/registry" +) + +func TestMain(m *testing.M) { + logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard))) + m.Run() +} + +// --- mock registry for registerGroup tests --- + +type mockRegistry[T any] struct { + m map[string]T +} + +func newMockRegistry[T any]() *mockRegistry[T] { + return &mockRegistry[T]{m: make(map[string]T)} +} + +func (r *mockRegistry[T]) Register(name string, v T) error { + if name == "" { + return nil + } + if _, ok := r.m[name]; ok { + return errors.New("duplicate") + } + r.m[name] = v + return nil +} + +func (r *mockRegistry[T]) Unregister(name string) { + delete(r.m, name) +} + +func (r *mockRegistry[T]) IsRegistered(name string) bool { + _, ok := r.m[name] + return ok +} + +func (r *mockRegistry[T]) Get(name string) T { + return r.m[name] +} + +func (r *mockRegistry[T]) GetAll() map[string]T { + result := make(map[string]T) + for k, v := range r.m { + result[k] = v + } + return result +} + +// Ensure mockRegistry satisfies reg.Registry[T]. +var _ reg.Registry[int] = (*mockRegistry[int])(nil) + +// --- registerGroup tests --- + +func TestRegisterGroup_EmptyEntries_EmptyRegistry(t *testing.T) { + r := newMockRegistry[int]() + err := registerGroup([]named[int]{}, r) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(r.GetAll()) != 0 { + t.Fatalf("expected empty registry, got %d entries", len(r.GetAll())) + } +} + +func TestRegisterGroup_EmptyEntries_ClearsExisting(t *testing.T) { + r := newMockRegistry[int]() + r.Register("a", 1) + r.Register("b", 2) + + err := registerGroup([]named[int]{}, r) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(r.GetAll()) != 0 { + t.Fatalf("expected empty registry, got %d entries", len(r.GetAll())) + } +} + +func TestRegisterGroup_SingleEntry(t *testing.T) { + r := newMockRegistry[int]() + err := registerGroup([]named[int]{{"x", 42}}, r) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered("x") { + t.Fatal("expected 'x' to be registered") + } + if v := r.Get("x"); v != 42 { + t.Fatalf("expected 42, got %v", v) + } +} + +func TestRegisterGroup_MultipleEntries(t *testing.T) { + r := newMockRegistry[int]() + err := registerGroup([]named[int]{ + {"x", 1}, + {"y", 2}, + {"z", 3}, + }, r) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + all := r.GetAll() + if len(all) != 3 { + t.Fatalf("expected 3 entries, got %d", len(all)) + } + for _, name := range []string{"x", "y", "z"} { + if !r.IsRegistered(name) { + t.Fatalf("expected '%s' to be registered", name) + } + } +} + +func TestRegisterGroup_ReplacesExisting(t *testing.T) { + r := newMockRegistry[int]() + r.Register("old", 1) + r.Register("keep", 2) // this one should be removed + + err := registerGroup([]named[int]{ + {"old", 99}, + {"new", 100}, + }, r) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if v := r.Get("old"); v != 99 { + t.Fatalf("expected 99, got %v", v) + } + if !r.IsRegistered("new") { + t.Fatal("expected 'new' to be registered") + } + if r.IsRegistered("keep") { + t.Fatal("expected 'keep' to be unregistered") + } +} + +func TestRegisterGroup_DuplicateNameInEntries(t *testing.T) { + r := newMockRegistry[int]() + err := registerGroup([]named[int]{ + {"dup", 1}, + {"dup", 2}, + }, r) + if err == nil { + t.Fatal("expected error for duplicate name") + } + // First entry should be registered, second failed. + if v := r.Get("dup"); v != 1 { + t.Fatalf("expected first entry (1), got %v", v) + } +} + +func TestRegisterGroup_ZeroValue(t *testing.T) { + r := newMockRegistry[int]() + // int zero value (0) is still registered + err := registerGroup([]named[int]{{"zero", 0}}, r) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered("zero") { + t.Fatal("expected 'zero' to be registered") + } +} + +// --- register tests --- + +func TestRegister_NilConfig(t *testing.T) { + if err := register(nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestRegister_EmptyConfig(t *testing.T) { + if err := register(&config.Config{}); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestRegister_Loggers(t *testing.T) { + reg := registry.LoggerRegistry() + name := "test-logger" + t.Cleanup(func() { reg.Unregister(name) }) + + cfg := &config.Config{ + Loggers: []*config.LoggerConfig{ + {Name: name, Log: &config.LogConfig{Level: "info"}}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reg.IsRegistered(name) { + t.Fatal("expected logger to be registered") + } +} + +func TestRegister_Authers(t *testing.T) { + r := registry.AutherRegistry() + name := "test-auther" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Authers: []*config.AutherConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected auther to be registered") + } +} + +func TestRegister_Admissions(t *testing.T) { + r := registry.AdmissionRegistry() + name := "test-admission" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Admissions: []*config.AdmissionConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected admission to be registered") + } +} + +func TestRegister_Bypasses(t *testing.T) { + r := registry.BypassRegistry() + name := "test-bypass" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Bypasses: []*config.BypassConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected bypass to be registered") + } +} + +func TestRegister_Resolvers(t *testing.T) { + r := registry.ResolverRegistry() + name := "test-resolver" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Resolvers: []*config.ResolverConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected resolver to be registered") + } +} + +func TestRegister_Hosts(t *testing.T) { + r := registry.HostsRegistry() + name := "test-hosts" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Hosts: []*config.HostsConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected host mapper to be registered") + } +} + +func TestRegister_Ingresses(t *testing.T) { + r := registry.IngressRegistry() + name := "test-ingress" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Ingresses: []*config.IngressConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected ingress to be registered") + } +} + +func TestRegister_Routers(t *testing.T) { + r := registry.RouterRegistry() + name := "test-router" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Routers: []*config.RouterConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected router to be registered") + } +} + +func TestRegister_SDs(t *testing.T) { + r := registry.SDRegistry() + name := "test-sd" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + SDs: []*config.SDConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected SD to be registered") + } +} + +func TestRegister_Observers(t *testing.T) { + r := registry.ObserverRegistry() + name := "test-observer" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Observers: []*config.ObserverConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected observer to be registered") + } +} + +func TestRegister_Recorders(t *testing.T) { + r := registry.RecorderRegistry() + name := "test-recorder" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Recorders: []*config.RecorderConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected recorder to be registered") + } +} + +func TestRegister_TrafficLimiters(t *testing.T) { + r := registry.TrafficLimiterRegistry() + name := "test-traffic-limiter" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Limiters: []*config.LimiterConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected traffic limiter to be registered") + } +} + +func TestRegister_ConnLimiters(t *testing.T) { + r := registry.ConnLimiterRegistry() + name := "test-conn-limiter" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + CLimiters: []*config.LimiterConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected conn limiter to be registered") + } +} + +func TestRegister_RateLimiters(t *testing.T) { + r := registry.RateLimiterRegistry() + name := "test-rate-limiter" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + RLimiters: []*config.LimiterConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected rate limiter to be registered") + } +} + +func TestRegister_Hops(t *testing.T) { + r := registry.HopRegistry() + name := "test-hop" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Hops: []*config.HopConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected hop to be registered") + } +} + +func TestRegister_Chains(t *testing.T) { + r := registry.ChainRegistry() + name := "test-chain" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Chains: []*config.ChainConfig{ + {Name: name}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected chain to be registered") + } +} + +// stubListener satisfies listener.Listener for testing. +type stubListener struct{} + +func (l *stubListener) Init(md metadata.Metadata) error { return nil } +func (l *stubListener) Accept() (net.Conn, error) { return nil, net.ErrClosed } +func (l *stubListener) Addr() net.Addr { return &net.TCPAddr{IP: net.IPv4zero, Port: 0} } +func (l *stubListener) Close() error { return nil } + +// stubHandler satisfies handler.Handler for testing. +type stubHandler struct{} + +func (h *stubHandler) Init(md metadata.Metadata) error { return nil } +func (h *stubHandler) Handle(_ context.Context, _ net.Conn, _ ...handler.HandleOption) error { return nil } + +func TestRegister_Services(t *testing.T) { + // ParseService needs "tcp" listener and "auto" handler in registries. + // These are registered via init() in their respective packages but those + // packages are not imported here, so the factories won't be found. + // Register stub factories so the test can exercise the service section. + // + // This save/restore pattern assumes no other test or transitive import + // has already registered "tcp" or "auto" factory functions. If those + // packages are ever imported by this test file, origListener/origHandler + // will be non-nil and the restore path will re-register the real + // factories, which is the desired behavior. + origListener := registry.ListenerRegistry().Get("tcp") + origHandler := registry.HandlerRegistry().Get("auto") + + registry.ListenerRegistry().Register("tcp", func(opts ...listener.Option) listener.Listener { + return &stubListener{} + }) + registry.HandlerRegistry().Register("auto", func(opts ...handler.Option) handler.Handler { + return &stubHandler{} + }) + t.Cleanup(func() { + registry.ListenerRegistry().Unregister("tcp") + registry.HandlerRegistry().Unregister("auto") + if origListener != nil { + registry.ListenerRegistry().Register("tcp", origListener) + } + if origHandler != nil { + registry.HandlerRegistry().Register("auto", origHandler) + } + }) + + r := registry.ServiceRegistry() + name := "test-service" + t.Cleanup(func() { r.Unregister(name) }) + + cfg := &config.Config{ + Services: []*config.ServiceConfig{ + {Name: name, Addr: ":0"}, + }, + } + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !r.IsRegistered(name) { + t.Fatal("expected service to be registered") + } +} + +func TestRegister_MultipleSections(t *testing.T) { + // Verify that multiple sections are all registered and in dependency order. + bypassReg := registry.BypassRegistry() + hopReg := registry.HopRegistry() + chainReg := registry.ChainRegistry() + + bypassName := "multi-bypass" + hopName := "multi-hop" + chainName := "multi-chain" + + t.Cleanup(func() { + bypassReg.Unregister(bypassName) + hopReg.Unregister(hopName) + chainReg.Unregister(chainName) + }) + + cfg := &config.Config{ + Bypasses: []*config.BypassConfig{{Name: bypassName}}, + Hops: []*config.HopConfig{{Name: hopName}}, + Chains: []*config.ChainConfig{{Name: chainName}}, + } + + if err := register(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !bypassReg.IsRegistered(bypassName) { + t.Fatal("expected bypass to be registered") + } + if !hopReg.IsRegistered(hopName) { + t.Fatal("expected hop to be registered") + } + if !chainReg.IsRegistered(chainName) { + t.Fatal("expected chain to be registered") + } +} + +func TestRegister_ComponentOrder(t *testing.T) { + // register() processes sections in dependency order: leaf components + // (loggers, authers, resolvers, etc.) first, then hops, then chains, + // then services. Hops look up loggers/resolvers from registries; + // chains look up hops; services look up chains. + // + // This test directly calls registerGroup in the canonical dependency + // sequence and verifies each layer can see the previous layer's + // registrations. + + // Use a sequence-tracking registry to verify registration order. + type seqEntry struct { + name string + seq int + } + var seq int + leafReg := newMockRegistry[seqEntry]() + hopReg := newMockRegistry[seqEntry]() + chainReg := newMockRegistry[seqEntry]() + svcReg := newMockRegistry[seqEntry]() + + seq++ + registerGroup([]named[seqEntry]{{"leaf", seqEntry{"logger", seq}}}, leafReg) + + // Hops should see leaf registrations. + if !leafReg.IsRegistered("leaf") { + t.Fatal("expected leaf to be registered before hops") + } + seq++ + registerGroup([]named[seqEntry]{{"hop", seqEntry{"hop", seq}}}, hopReg) + + // Chains should see hop registrations. + if !hopReg.IsRegistered("hop") { + t.Fatal("expected hop to be registered before chains") + } + seq++ + registerGroup([]named[seqEntry]{{"chain", seqEntry{"chain", seq}}}, chainReg) + + // Services should see chain registrations. + if !chainReg.IsRegistered("chain") { + t.Fatal("expected chain to be registered before services") + } + seq++ + registerGroup([]named[seqEntry]{{"svc", seqEntry{"svc", seq}}}, svcReg) + + // All should be registered. + for _, r := range []struct { + name string + reg *mockRegistry[seqEntry] + }{ + {"leaf", leafReg}, + {"hop", hopReg}, + {"chain", chainReg}, + {"svc", svcReg}, + } { + if len(r.reg.GetAll()) != 1 { + t.Fatalf("expected 1 entry in %s registry", r.name) + } + } + + // Verify registration sequence: leaf < hop < chain < svc. + if leafReg.Get("leaf").seq >= hopReg.Get("hop").seq { + t.Fatal("leaf must be registered before hop") + } + if hopReg.Get("hop").seq >= chainReg.Get("chain").seq { + t.Fatal("hop must be registered before chain") + } + if chainReg.Get("chain").seq >= svcReg.Get("svc").seq { + t.Fatal("chain must be registered before service") + } +} + +// --- Load tests --- + +func TestLoad_NilConfig(t *testing.T) { + if err := Load(nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestLoad_EmptyConfig(t *testing.T) { + cfg := &config.Config{} + if err := Load(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestLoad_WithTLS(t *testing.T) { + // BuildDefaultTLSConfig with nil TLS falls back to default cert/key files + // which don't exist, but it then generates a self-signed cert, so it + // should still succeed. + cfg := &config.Config{ + TLS: &config.TLSConfig{ + Validity: 3600, + }, + } + if err := Load(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestLoad_WithLog(t *testing.T) { + cfg := &config.Config{ + Log: &config.LogConfig{ + Level: "error", + }, + } + if err := Load(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestLoad_SetsDefaultLogger(t *testing.T) { + cfg := &config.Config{ + Log: &config.LogConfig{ + Level: "warn", + Format: "json", + }, + } + if err := Load(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + // After Load, logger.Default() should be non-nil. + if logger.Default() == nil { + t.Fatal("expected default logger to be set") + } +} + +// --- defaultLoader singleton tests --- + +func TestDefaultLoader_IsSet(t *testing.T) { + if defaultLoader == nil { + t.Fatal("defaultLoader should not be nil") + } + if err := defaultLoader.Load(nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +// --- registerGroup edge cases --- + +type countingRegistry[T any] struct { + m map[string]T + close int +} + +func newCountingRegistry[T any]() *countingRegistry[T] { + return &countingRegistry[T]{m: make(map[string]T)} +} + +func (r *countingRegistry[T]) Register(name string, v T) error { r.m[name] = v; return nil } +func (r *countingRegistry[T]) Unregister(name string) { delete(r.m, name); r.close++ } +func (r *countingRegistry[T]) IsRegistered(name string) bool { _, ok := r.m[name]; return ok } +func (r *countingRegistry[T]) Get(name string) T { return r.m[name] } +func (r *countingRegistry[T]) GetAll() map[string]T { return r.m } + +func TestRegisterGroup_UnregistersAllOldEntries(t *testing.T) { + r := newCountingRegistry[int]() + r.Register("a", 1) + r.Register("b", 2) + r.Register("c", 3) + + _ = registerGroup([]named[int]{{"x", 10}}, r) + + if r.close != 3 { + t.Fatalf("expected 3 unregister calls, got %d", r.close) + } + if !r.IsRegistered("x") { + t.Fatal("expected 'x' to be registered") + } + if r.IsRegistered("a") || r.IsRegistered("b") || r.IsRegistered("c") { + t.Fatal("old entries should be unregistered") + } +} + +// --- register error paths --- + +func TestRegister_ServiceBadTLS(t *testing.T) { + // A non-empty CertFile that doesn't exist causes + // tls_util.LoadServerConfig to fail before reaching the + // listener registry lookup. + // + // ParseService logs an error via the default logger when the + // TLS cert can't be loaded. We suppress that log here since + // the error is expected. + logger.SetDefault(xlogger.NewLogger(xlogger.OutputOption(io.Discard))) + discardLogger := xlogger.NewLogger(xlogger.OutputOption(io.Discard)) + t.Cleanup(func() { logger.SetDefault(discardLogger) }) + + cfg := &config.Config{ + Services: []*config.ServiceConfig{ + { + Name: "bad-tls", + Addr: ":0", + Listener: &config.ListenerConfig{ + TLS: &config.TLSConfig{ + CertFile: "/nonexistent/cert.pem", + }, + }, + }, + }, + } + if err := register(cfg); err == nil { + t.Fatal("expected error for bad TLS config, got nil") + } +} + +func TestRegister_ServiceUnknownListener(t *testing.T) { + // An unregistered listener type triggers the + // "unknown listener" error in ParseService. + cfg := &config.Config{ + Services: []*config.ServiceConfig{ + { + Name: "unknown-ln", + Addr: ":0", + Listener: &config.ListenerConfig{ + Type: "nonexistent", + }, + }, + }, + } + if err := register(cfg); err == nil { + t.Fatal("expected error for unknown listener type, got nil") + } +} diff --git a/sd/plugin/http.go b/sd/plugin/http.go index 87c0766b..1a38ece8 100644 --- a/sd/plugin/http.go +++ b/sd/plugin/http.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/http" @@ -81,7 +82,7 @@ func (p *httpPlugin) Register(ctx context.Context, service *sd.Service, opts ... defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return fmt.Errorf(resp.Status) + return errors.New(resp.Status) } return nil @@ -119,7 +120,7 @@ func (p *httpPlugin) Deregister(ctx context.Context, service *sd.Service) error defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return fmt.Errorf(resp.Status) + return errors.New(resp.Status) } return nil