Add --go-module-name flag to support generating  Go module compatible code (#7651)

* Add --go-module-name flag to support generating code for go modules

* Rename echo example folder

* Grammar

* Update readme for go-echo example

* Update readme for go-echo example

* Re-enable go modules after test is done
diff --git a/examples/go-echo/README.md b/examples/go-echo/README.md
new file mode 100644
index 0000000..acecee7
--- /dev/null
+++ b/examples/go-echo/README.md
@@ -0,0 +1,27 @@
+# Go Echo Example
+
+A simple example demonstrating how to send flatbuffers over the network in Go.
+
+## Generate flatbuffer code
+
+```
+flatc -g --gen-object-api --go-module-name echo hero.fbs net.fbs
+```
+
+## Running example
+
+1. Run go mod tidy to get dependencies
+```
+go mod tidy
+```
+
+2. Start a server
+```
+go run server/server.go
+```
+
+3. Run the client in another terminal
+```
+go run client/client.go
+```
+
diff --git a/examples/go-echo/client/client.go b/examples/go-echo/client/client.go
new file mode 100644
index 0000000..5d2c130
--- /dev/null
+++ b/examples/go-echo/client/client.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	"echo/hero"
+	"echo/net"
+
+	flatbuffers "github.com/google/flatbuffers/go"
+)
+
+func RequestBody() *bytes.Reader {
+	b := flatbuffers.NewBuilder(0)
+	r := net.RequestT{Player: &hero.WarriorT{Name: "Krull", Hp: 100}}
+	b.Finish(r.Pack(b))
+
+	// Encode builder head in last 4 bytes of request body
+	buf := make([]byte, 4)
+	flatbuffers.WriteUOffsetT(buf, b.Head())
+	buf = append(b.Bytes, buf...)
+
+	return bytes.NewReader(buf)
+}
+
+func ReadResponse(r *http.Response) {
+	body, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		fmt.Printf("Unable to read request body: %v\n", err)
+		return
+	}
+
+	// Last 4 bytes is offset.
+	off := flatbuffers.GetUOffsetT(body[len(body)-4:])
+	buf := body[:len(body) - 4] 
+
+	res := net.GetRootAsResponse(buf, off)
+	player := res.Player(nil)
+	
+	fmt.Printf("Got response (name: %v, hp: %v)\n", string(player.Name()), player.Hp())
+}
+
+func main() {
+
+	body := RequestBody()	
+	req, err := http.NewRequest("POST", "http://localhost:8080/echo", body)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	client := http.DefaultClient
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	ReadResponse(resp)
+}
diff --git a/examples/go-echo/go.mod b/examples/go-echo/go.mod
new file mode 100644
index 0000000..81abc9b
--- /dev/null
+++ b/examples/go-echo/go.mod
@@ -0,0 +1,5 @@
+module echo
+
+go 1.19
+
+require github.com/google/flatbuffers v22.10.26+incompatible
diff --git a/examples/go-echo/hero.fbs b/examples/go-echo/hero.fbs
new file mode 100644
index 0000000..91b9dca
--- /dev/null
+++ b/examples/go-echo/hero.fbs
@@ -0,0 +1,6 @@
+namespace hero;
+
+table Warrior {
+	name: string;
+	hp: uint32;
+}
diff --git a/examples/go-echo/hero/Warrior.go b/examples/go-echo/hero/Warrior.go
new file mode 100644
index 0000000..857697e
--- /dev/null
+++ b/examples/go-echo/hero/Warrior.go
@@ -0,0 +1,93 @@
+// Code generated by the FlatBuffers compiler. DO NOT EDIT.
+
+package hero
+
+import (
+	flatbuffers "github.com/google/flatbuffers/go"
+)
+
+type WarriorT struct {
+	Name string `json:"name"`
+	Hp uint32 `json:"hp"`
+}
+
+func (t *WarriorT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
+	if t == nil { return 0 }
+	nameOffset := builder.CreateString(t.Name)
+	WarriorStart(builder)
+	WarriorAddName(builder, nameOffset)
+	WarriorAddHp(builder, t.Hp)
+	return WarriorEnd(builder)
+}
+
+func (rcv *Warrior) UnPackTo(t *WarriorT) {
+	t.Name = string(rcv.Name())
+	t.Hp = rcv.Hp()
+}
+
+func (rcv *Warrior) UnPack() *WarriorT {
+	if rcv == nil { return nil }
+	t := &WarriorT{}
+	rcv.UnPackTo(t)
+	return t
+}
+
+type Warrior struct {
+	_tab flatbuffers.Table
+}
+
+func GetRootAsWarrior(buf []byte, offset flatbuffers.UOffsetT) *Warrior {
+	n := flatbuffers.GetUOffsetT(buf[offset:])
+	x := &Warrior{}
+	x.Init(buf, n+offset)
+	return x
+}
+
+func GetSizePrefixedRootAsWarrior(buf []byte, offset flatbuffers.UOffsetT) *Warrior {
+	n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
+	x := &Warrior{}
+	x.Init(buf, n+offset+flatbuffers.SizeUint32)
+	return x
+}
+
+func (rcv *Warrior) Init(buf []byte, i flatbuffers.UOffsetT) {
+	rcv._tab.Bytes = buf
+	rcv._tab.Pos = i
+}
+
+func (rcv *Warrior) Table() flatbuffers.Table {
+	return rcv._tab
+}
+
+func (rcv *Warrior) Name() []byte {
+	o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
+	if o != 0 {
+		return rcv._tab.ByteVector(o + rcv._tab.Pos)
+	}
+	return nil
+}
+
+func (rcv *Warrior) Hp() uint32 {
+	o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
+	if o != 0 {
+		return rcv._tab.GetUint32(o + rcv._tab.Pos)
+	}
+	return 0
+}
+
+func (rcv *Warrior) MutateHp(n uint32) bool {
+	return rcv._tab.MutateUint32Slot(6, n)
+}
+
+func WarriorStart(builder *flatbuffers.Builder) {
+	builder.StartObject(2)
+}
+func WarriorAddName(builder *flatbuffers.Builder, name flatbuffers.UOffsetT) {
+	builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(name), 0)
+}
+func WarriorAddHp(builder *flatbuffers.Builder, hp uint32) {
+	builder.PrependUint32Slot(1, hp, 0)
+}
+func WarriorEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
+	return builder.EndObject()
+}
diff --git a/examples/go-echo/net.fbs b/examples/go-echo/net.fbs
new file mode 100644
index 0000000..7293bb6
--- /dev/null
+++ b/examples/go-echo/net.fbs
@@ -0,0 +1,11 @@
+include "hero.fbs";
+
+namespace net;
+
+table Request {
+	player: hero.Warrior;
+}
+
+table Response  {
+	player: hero.Warrior;
+}
diff --git a/examples/go-echo/net/Request.go b/examples/go-echo/net/Request.go
new file mode 100644
index 0000000..b2449c1
--- /dev/null
+++ b/examples/go-echo/net/Request.go
@@ -0,0 +1,82 @@
+// Code generated by the FlatBuffers compiler. DO NOT EDIT.
+
+package net
+
+import (
+	flatbuffers "github.com/google/flatbuffers/go"
+
+	hero "echo/hero"
+)
+
+type RequestT struct {
+	Player *hero.WarriorT `json:"player"`
+}
+
+func (t *RequestT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
+	if t == nil { return 0 }
+	playerOffset := t.Player.Pack(builder)
+	RequestStart(builder)
+	RequestAddPlayer(builder, playerOffset)
+	return RequestEnd(builder)
+}
+
+func (rcv *Request) UnPackTo(t *RequestT) {
+	t.Player = rcv.Player(nil).UnPack()
+}
+
+func (rcv *Request) UnPack() *RequestT {
+	if rcv == nil { return nil }
+	t := &RequestT{}
+	rcv.UnPackTo(t)
+	return t
+}
+
+type Request struct {
+	_tab flatbuffers.Table
+}
+
+func GetRootAsRequest(buf []byte, offset flatbuffers.UOffsetT) *Request {
+	n := flatbuffers.GetUOffsetT(buf[offset:])
+	x := &Request{}
+	x.Init(buf, n+offset)
+	return x
+}
+
+func GetSizePrefixedRootAsRequest(buf []byte, offset flatbuffers.UOffsetT) *Request {
+	n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
+	x := &Request{}
+	x.Init(buf, n+offset+flatbuffers.SizeUint32)
+	return x
+}
+
+func (rcv *Request) Init(buf []byte, i flatbuffers.UOffsetT) {
+	rcv._tab.Bytes = buf
+	rcv._tab.Pos = i
+}
+
+func (rcv *Request) Table() flatbuffers.Table {
+	return rcv._tab
+}
+
+func (rcv *Request) Player(obj *hero.Warrior) *hero.Warrior {
+	o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
+	if o != 0 {
+		x := rcv._tab.Indirect(o + rcv._tab.Pos)
+		if obj == nil {
+			obj = new(hero.Warrior)
+		}
+		obj.Init(rcv._tab.Bytes, x)
+		return obj
+	}
+	return nil
+}
+
+func RequestStart(builder *flatbuffers.Builder) {
+	builder.StartObject(1)
+}
+func RequestAddPlayer(builder *flatbuffers.Builder, player flatbuffers.UOffsetT) {
+	builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(player), 0)
+}
+func RequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
+	return builder.EndObject()
+}
diff --git a/examples/go-echo/net/Response.go b/examples/go-echo/net/Response.go
new file mode 100644
index 0000000..57e6b35
--- /dev/null
+++ b/examples/go-echo/net/Response.go
@@ -0,0 +1,82 @@
+// Code generated by the FlatBuffers compiler. DO NOT EDIT.
+
+package net
+
+import (
+	flatbuffers "github.com/google/flatbuffers/go"
+
+	hero "echo/hero"
+)
+
+type ResponseT struct {
+	Player *hero.WarriorT `json:"player"`
+}
+
+func (t *ResponseT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
+	if t == nil { return 0 }
+	playerOffset := t.Player.Pack(builder)
+	ResponseStart(builder)
+	ResponseAddPlayer(builder, playerOffset)
+	return ResponseEnd(builder)
+}
+
+func (rcv *Response) UnPackTo(t *ResponseT) {
+	t.Player = rcv.Player(nil).UnPack()
+}
+
+func (rcv *Response) UnPack() *ResponseT {
+	if rcv == nil { return nil }
+	t := &ResponseT{}
+	rcv.UnPackTo(t)
+	return t
+}
+
+type Response struct {
+	_tab flatbuffers.Table
+}
+
+func GetRootAsResponse(buf []byte, offset flatbuffers.UOffsetT) *Response {
+	n := flatbuffers.GetUOffsetT(buf[offset:])
+	x := &Response{}
+	x.Init(buf, n+offset)
+	return x
+}
+
+func GetSizePrefixedRootAsResponse(buf []byte, offset flatbuffers.UOffsetT) *Response {
+	n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
+	x := &Response{}
+	x.Init(buf, n+offset+flatbuffers.SizeUint32)
+	return x
+}
+
+func (rcv *Response) Init(buf []byte, i flatbuffers.UOffsetT) {
+	rcv._tab.Bytes = buf
+	rcv._tab.Pos = i
+}
+
+func (rcv *Response) Table() flatbuffers.Table {
+	return rcv._tab
+}
+
+func (rcv *Response) Player(obj *hero.Warrior) *hero.Warrior {
+	o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
+	if o != 0 {
+		x := rcv._tab.Indirect(o + rcv._tab.Pos)
+		if obj == nil {
+			obj = new(hero.Warrior)
+		}
+		obj.Init(rcv._tab.Bytes, x)
+		return obj
+	}
+	return nil
+}
+
+func ResponseStart(builder *flatbuffers.Builder) {
+	builder.StartObject(1)
+}
+func ResponseAddPlayer(builder *flatbuffers.Builder, player flatbuffers.UOffsetT) {
+	builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(player), 0)
+}
+func ResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
+	return builder.EndObject()
+}
diff --git a/examples/go-echo/server/server.go b/examples/go-echo/server/server.go
new file mode 100644
index 0000000..46ff9e1
--- /dev/null
+++ b/examples/go-echo/server/server.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+	"echo/net"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	flatbuffers "github.com/google/flatbuffers/go"
+)
+
+func echo(w http.ResponseWriter, r *http.Request) {
+	body, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		fmt.Printf("Unable to read request body: %v\n", err)
+		return
+	}
+
+	// Last 4 bytes is offset. See client.go.
+	off := flatbuffers.GetUOffsetT(body[len(body)-4:])
+	buf := body[:len(body) - 4] 
+
+	req := net.GetRootAsRequest(buf, off)
+	player := req.Player(nil)
+
+	fmt.Printf("Got request (name: %v, hp: %v)\n", string(player.Name()), player.Hp())
+	w.Write(body)
+}
+
+func main() {
+	http.HandleFunc("/echo", echo)
+
+	fmt.Println("Listening on port :8080")
+	http.ListenAndServe(":8080", nil)
+}
diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h
index 19260e7..a07f62a 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -623,6 +623,7 @@
   bool binary_schema_gen_embed;
   std::string go_import;
   std::string go_namespace;
+  std::string go_module_name;
   bool protobuf_ascii_alike;
   bool size_prefixed;
   std::string root_type;
@@ -915,7 +916,7 @@
 
   // Returns the number of characters were consumed when parsing a JSON string.
   std::ptrdiff_t BytesConsumed() const;
-   
+
   // Set the root type. May override the one set in the schema.
   bool SetRootType(const char *name);
 
diff --git a/src/flatc.cpp b/src/flatc.cpp
index deb45ec..998d77b 100644
--- a/src/flatc.cpp
+++ b/src/flatc.cpp
@@ -152,6 +152,8 @@
   { "", "go-import", "IMPORT",
     "Generate the overriding import for flatbuffers in Golang (default is "
     "\"github.com/google/flatbuffers/go\")." },
+  { "", "go-module-name", "",
+    "Prefix local import paths of generated go code with the module name" },
   { "", "raw-binary", "",
     "Allow binaries without file_identifier to be read. This may crash flatc "
     "given a mismatched schema." },
@@ -448,6 +450,9 @@
       } else if (arg == "--go-import") {
         if (++argi >= argc) Error("missing golang import" + arg, true);
         opts.go_import = argv[argi];
+      } else if (arg == "--go-module-name") {
+        if (++argi >= argc) Error("missing golang module name" + arg, true);
+        opts.go_module_name = argv[argi];
       } else if (arg == "--defaults-json") {
         opts.output_default_scalars_in_json = true;
       } else if (arg == "--unknown-json") {
diff --git a/src/idl_gen_go.cpp b/src/idl_gen_go.cpp
index 54e8864..650450f 100644
--- a/src/idl_gen_go.cpp
+++ b/src/idl_gen_go.cpp
@@ -1532,7 +1532,12 @@
 
   // Create the full path for the imported namespace (format: A/B/C).
   std::string NamespaceImportPath(const Namespace *ns) const {
-    return namer_.Directories(*ns, SkipDir::OutputPathAndTrailingPathSeparator);
+    std::string path =
+        namer_.Directories(*ns, SkipDir::OutputPathAndTrailingPathSeparator);
+    if (!parser_.opts.go_module_name.empty()) {
+      path = parser_.opts.go_module_name + "/" + path;
+    }
+    return path;
   }
 
   // Ensure that a type is prefixed with its go package import name if it is
diff --git a/tests/GoTest.sh b/tests/GoTest.sh
index 8e73af2..b55cad5 100755
--- a/tests/GoTest.sh
+++ b/tests/GoTest.sh
@@ -72,3 +72,6 @@
     # enable this when enums are properly formated
     # exit 1
 fi
+
+# Re-enable go modules when done tests
+go env -w  GO111MODULE=on