diff --git a/app/server.go b/app/server.go index 11d04c3..a670c99 100644 --- a/app/server.go +++ b/app/server.go @@ -8,6 +8,7 @@ import ( "log" "os" "os/signal" + "strings" "syscall" "time" @@ -20,6 +21,7 @@ var ( port = flag.String("port", "6379", "listening port") dir = flag.String("dir", "/tmp/redis", "data directory") dbFilename = flag.String("dbfilename", "dump.rdb", "database filename") + replicaOf = flag.String("replicaof", "", "replica of another redis server") ) func run(ctx context.Context, _ io.Writer, _ []string) error { @@ -30,10 +32,14 @@ func run(ctx context.Context, _ io.Writer, _ []string) error { signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) defer signal.Stop(sigCh) cfg := config.NewConfig() + if *replicaOf != "" && len(strings.Split(*replicaOf, " ")) != 2 { + return fmt.Errorf("invalid replicaof flag, expected format: replicaof ") + } err := cfg.SetBatch(map[string]string{ config.ListenAddrKey: fmt.Sprintf(":%s", *port), config.DirKey: *dir, config.DBFilenameKey: *dbFilename, + config.ReplicaOfKey: *replicaOf, }) if err != nil { return err diff --git a/internal/app/server/config/config.go b/internal/app/server/config/config.go index a6b5a0e..de9fcf8 100644 --- a/internal/app/server/config/config.go +++ b/internal/app/server/config/config.go @@ -11,12 +11,14 @@ const ( ListenAddrKey = "LISTENADDR" DirKey = "DIR" DBFilenameKey = "DBFILENAME" + ReplicaOfKey = "REPLICAOF" ) var supportedOptions = map[string]struct{}{ ListenAddrKey: {}, DirKey: {}, DBFilenameKey: {}, + ReplicaOfKey: {}, } type Config struct { @@ -52,6 +54,9 @@ func (c *Config) SetBatch(pairs map[string]string) error { if _, exists := supportedOptions[strings.ToUpper(key)]; !exists { return fmt.Errorf("%s for %s", ErrOptionNotFound, key) } + if value == "" { + continue + } c.options[key] = value } return nil diff --git a/pkg/command/command.go b/pkg/command/command.go index d39306b..0b2797c 100644 --- a/pkg/command/command.go +++ b/pkg/command/command.go @@ -61,7 +61,7 @@ func NewCommandFactory(kv keyval.KV, cfg *config.Config) *CommandFactory { "set": &Set{kv: kv}, "get": &Get{kv: kv}, "hello": &Hello{}, - "info": &Info{}, + "info": &Info{cfg: cfg}, "client": &ClientCmd{}, "config": &ConfigCmd{cfg: cfg}, "keys": &Keys{kv: kv}, diff --git a/pkg/command/info.go b/pkg/command/info.go index 88560c5..81e1deb 100644 --- a/pkg/command/info.go +++ b/pkg/command/info.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/codecrafters-io/redis-starter-go/internal/app/server/config" "github.com/codecrafters-io/redis-starter-go/internal/client" "github.com/codecrafters-io/redis-starter-go/pkg/resp" ) @@ -13,22 +14,32 @@ const ( REPLICATION = "replication" ) -var ( - sections = map[string]map[string]string{ - SERVER: { +type DynamicFieldHandler func(*config.Config) string + +type SectionInfo struct { + StaticFields map[string]string + DynamicFields map[string]DynamicFieldHandler +} + +var sections = map[string]SectionInfo{ + SERVER: { + StaticFields: map[string]string{ "src_version": "1.0.0", "redis_version": "7.4-rc1", "redis_git_sha1": "random-sha1", "redis_build_id": "20240616", "redis_mode": "standalone", }, - REPLICATION: { - "role": "master", + }, + REPLICATION: { + DynamicFields: map[string]DynamicFieldHandler{ + "role": determineRole, }, - } -) + }, +} type Info struct { + cfg *config.Config } func (h *Info) Execute(c *client.Client, wr *resp.Writer, args []*resp.Resp) error { @@ -36,29 +47,49 @@ func (h *Info) Execute(c *client.Client, wr *resp.Writer, args []*resp.Resp) err if argsLen > 1 { return wr.WriteError(fmt.Errorf("wrong number of arguments for 'info' command")) } + str := strings.Builder{} + if argsLen == 1 { - str.WriteString(buildSectionString(args[0].String(), sections[args[0].String()])) - return wr.WriteString(str.String()) - } - for sectionName, section := range sections { - str.WriteString(buildSectionString(sectionName, section)) - str.WriteString("\r\n") + sectionName := args[0].String() + if sectionInfo, exists := sections[sectionName]; exists { + str.WriteString(buildSectionString(sectionName, sectionInfo, h.cfg)) + } + } else { + for sectionName, sectionInfo := range sections { + str.WriteString(buildSectionString(sectionName, sectionInfo, h.cfg)) + str.WriteString("\r\n") + } } return wr.WriteValue(str.String()) } -func buildSectionString(sectionName string, section map[string]string) string { +func buildSectionString(sectionName string, sectionInfo SectionInfo, cfg *config.Config) string { // # Server\r\nupstash_version:1.10.5\r\n,etc. var sb strings.Builder sb.WriteString(fmt.Sprintf("# %s\r\n", sectionName)) - for key, value := range section { + + for key, value := range sectionInfo.StaticFields { + sb.WriteString(fmt.Sprintf("%s:%s\r\n", key, value)) + } + + for key, handler := range sectionInfo.DynamicFields { + value := handler(cfg) sb.WriteString(fmt.Sprintf("%s:%s\r\n", key, value)) } + return sb.String() } func (h *Info) IsBlocking(_ []*resp.Resp) bool { return false } + +func determineRole(cfg *config.Config) string { + _, err := cfg.Get(config.ReplicaOfKey) + if err == nil { + return "slave" + } + return "master" +}