Repository: redis/go-redis

Issue:
Add support for MODULE LOADEX command
Redis MODULE LOADEX command was added in version 7.0.0 and is currently supported by the library. We should add support as documented [here](https://redis.io/commands/MODULE-LOADEX).

Discussion:
@ndyakov: closing as stale

Solution patch:
diff --git a/commands.go b/commands.go
index c94ccdf..3721df4 100644
--- a/commands.go
+++ b/commands.go
@@ -497,6 +497,8 @@ type Cmdable interface {
 	GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
 
 	ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd
+
+	ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd
 }
 
 type StatefulCmdable interface {
@@ -3888,3 +3890,32 @@ func (c cmdable) ACLDryRun(ctx context.Context, username string, command ...inte
 	_ = c(ctx, cmd)
 	return cmd
 }
+
+// ModuleLoadexConfig struct is used to specify the arguments for the MODULE LOADEX command of redis.
+// `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]`
+type ModuleLoadexConfig struct {
+	Path string
+	Conf map[string]interface{}
+	Args []interface{}
+}
+
+func (c *ModuleLoadexConfig) toArgs() []interface{} {
+	args := make([]interface{}, 3, 3+len(c.Conf)*3+len(c.Args)*2)
+	args[0] = "MODULE"
+	args[1] = "LOADEX"
+	args[2] = c.Path
+	for k, v := range c.Conf {
+		args = append(args, "CONFIG", k, v)
+	}
+	for _, arg := range c.Args {
+		args = append(args, "ARGS", arg)
+	}
+	return args
+}
+
+// ModuleLoadex Redis `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]` command.
+func (c cmdable) ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd {
+	cmd := NewStringCmd(ctx, conf.toArgs()...)
+	_ = c(ctx, cmd)
+	return cmd
+}



Test patch:
diff --git a/commands_test.go b/commands_test.go
index 89dcf93..e20470f 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1926,11 +1926,59 @@ var _ = Describe("Commands", func() {
 			Expect(replace.Val()).To(Equal(int64(1)))
 		})
 
-		It("should acl dryryn", func() {
+		It("should acl dryrun", func() {
 			dryRun := client.ACLDryRun(ctx, "default", "get", "randomKey")
 			Expect(dryRun.Err()).NotTo(HaveOccurred())
 			Expect(dryRun.Val()).To(Equal("OK"))
 		})
+
+		It("should fail module loadex", func() {
+			dryRun := client.ModuleLoadex(ctx, &redis.ModuleLoadexConfig{
+				Path: "/path/to/non-existent-library.so",
+				Conf: map[string]interface{}{
+					"param1": "value1",
+				},
+				Args: []interface{}{
+					"arg1",
+				},
+			})
+			Expect(dryRun.Err()).To(HaveOccurred())
+			Expect(dryRun.Err().Error()).To(Equal("ERR Error loading the extension. Please check the server logs."))
+		})
+
+		It("converts the module loadex configuration to a slice of arguments correctly", func() {
+			conf := &redis.ModuleLoadexConfig{
+				Path: "/path/to/your/module.so",
+				Conf: map[string]interface{}{
+					"param1": "value1",
+				},
+				Args: []interface{}{
+					"arg1",
+					"arg2",
+					3,
+				},
+			}
+
+			args := conf.ToArgs()
+
+			// Test if the arguments are in the correct order
+			expectedArgs := []interface{}{
+				"MODULE",
+				"LOADEX",
+				"/path/to/your/module.so",
+				"CONFIG",
+				"param1",
+				"value1",
+				"ARGS",
+				"arg1",
+				"ARGS",
+				"arg2",
+				"ARGS",
+				3,
+			}
+
+			Expect(args).To(Equal(expectedArgs))
+		})
 	})
 
 	Describe("hashes", func() {
diff --git a/export_test.go b/export_test.go
index 19a9ddd..3f92983 100644
--- a/export_test.go
+++ b/export_test.go
@@ -98,3 +98,7 @@ func (c *Ring) ShardByName(name string) *ringShard {
 	shard, _ := c.sharding.GetByName(name)
 	return shard
 }
+
+func (c *ModuleLoadexConfig) ToArgs() []interface{} {
+	return c.toArgs()
+}
diff --git a/main_test.go b/main_test.go
index b1a1f39..8e87d76 100644
--- a/main_test.go
+++ b/main_test.go
@@ -314,7 +314,7 @@ func startRedis(port string, args ...string) (*redisProcess, error) {
 		return nil, err
 	}
 
-	baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir}
+	baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir, "--enable-module-command", "yes"}
 	process, err := execCmd(redisServerBin, append(baseArgs, args...)...)
 	if err != nil {
 		return nil, err