Repository: zeromicro/go-zero
Pull request #1630 (2022-03-09 14:25:49)

feat:Support for referencing types in different API files using format
maybe I just want to format api files


Patch:
diff --git a/tools/goctl/api/format/format.go b/tools/goctl/api/format/format.go
index 79672952..02cbd222 100644
--- a/tools/goctl/api/format/format.go
+++ b/tools/goctl/api/format/format.go
@@ -28,10 +28,11 @@ const (
 // GoFormatApi format api file
 func GoFormatApi(c *cli.Context) error {
 	useStdin := c.Bool("stdin")
+	skipCheckDeclare := c.Bool("declare")
 
 	var be errorx.BatchError
 	if useStdin {
-		if err := apiFormatByStdin(); err != nil {
+		if err := apiFormatByStdin(skipCheckDeclare); err != nil {
 			be.Add(err)
 		}
 	} else {
@@ -47,7 +48,7 @@ func GoFormatApi(c *cli.Context) error {
 
 		err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
 			if strings.HasSuffix(path, ".api") {
-				if err := ApiFormatByPath(path); err != nil {
+				if err := ApiFormatByPath(path, skipCheckDeclare); err != nil {
 					be.Add(util.WrapErr(err, fi.Name()))
 				}
 			}
@@ -64,13 +65,13 @@ func GoFormatApi(c *cli.Context) error {
 	return be.Err()
 }
 
-func apiFormatByStdin() error {
+func apiFormatByStdin(skipCheckDeclare bool) error {
 	data, err := ioutil.ReadAll(os.Stdin)
 	if err != nil {
 		return err
 	}
 
-	result, err := apiFormat(string(data))
+	result, err := apiFormat(string(data), skipCheckDeclare)
 	if err != nil {
 		return err
 	}
@@ -80,7 +81,7 @@ func apiFormatByStdin() error {
 }
 
 // ApiFormatByPath format api from file path
-func ApiFormatByPath(apiFilePath string) error {
+func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
 	data, err := ioutil.ReadFile(apiFilePath)
 	if err != nil {
 		return err
@@ -91,12 +92,12 @@ func ApiFormatByPath(apiFilePath string) error {
 		return err
 	}
 
-	result, err := apiFormat(string(data), abs)
+	result, err := apiFormat(string(data), skipCheckDeclare, abs)
 	if err != nil {
 		return err
 	}
 
-	_, err = parser.ParseContent(result, abs)
+	_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(result, abs)
 	if err != nil {
 		return err
 	}
@@ -104,8 +105,13 @@ func ApiFormatByPath(apiFilePath string) error {
 	return ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm)
 }
 
-func apiFormat(data string, filename ...string) (string, error) {
-	_, err := parser.ParseContent(data, filename...)
+func apiFormat(data string, skipCheckDeclare bool, filename ...string) (string, error) {
+	var err error
+	if skipCheckDeclare {
+		_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(data, filename...)
+	} else {
+		_, err = parser.ParseContent(data, filename...)
+	}
 	if err != nil {
 		return "", err
 	}
diff --git a/tools/goctl/api/gogen/gen.go b/tools/goctl/api/gogen/gen.go
index 5b767750..14b81fcd 100644
--- a/tools/goctl/api/gogen/gen.go
+++ b/tools/goctl/api/gogen/gen.go
@@ -86,7 +86,7 @@ func DoGenProject(apiFile, dir, style string) error {
 		return err
 	}
 
-	if err := apiformat.ApiFormatByPath(apiFile); err != nil {
+	if err := apiformat.ApiFormatByPath(apiFile, false); err != nil {
 		return err
 	}
 
diff --git a/tools/goctl/api/ktgen/gen.go b/tools/goctl/api/ktgen/gen.go
index 40703749..73969cf4 100644
--- a/tools/goctl/api/ktgen/gen.go
+++ b/tools/goctl/api/ktgen/gen.go
@@ -80,7 +80,7 @@ import com.google.gson.Gson
 object {{with .Info}}{{.Title}}{{end}}{
 	{{range .Types}}
 	data class {{.Name}}({{$length := (len .Members)}}{{range $i,$item := .Members}}
-		val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}}
+		val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type.Name}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}}
 	){{end}}
 	{{with .Service}}
 	{{range .Routes}}suspend fun {{routeToFuncName .Method .Path}}({{with .RequestType}}{{if ne .Name ""}}
diff --git a/tools/goctl/api/parser/g4/ast/apiparser.go b/tools/goctl/api/parser/g4/ast/apiparser.go
index 2cb0e21d..b19ba666 100644
--- a/tools/goctl/api/parser/g4/ast/apiparser.go
+++ b/tools/goctl/api/parser/g4/ast/apiparser.go
@@ -19,7 +19,8 @@ type (
 		debug      bool
 		log        console.Console
 		antlr.DefaultErrorListener
-		src string
+		src                      string
+		skipCheckTypeDeclaration bool
 	}
 
 	// ParserOption defines an function with argument Parser
@@ -136,9 +137,11 @@ func (p *Parser) parse(filename, content string) (*Api, error) {
 		apiAstList = append(apiAstList, nestedApi)
 	}
 
-	err = p.checkTypeDeclaration(apiAstList)
-	if err != nil {
-		return nil, err
+	if !p.skipCheckTypeDeclaration {
+		err = p.checkTypeDeclaration(apiAstList)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	allApi := p.memberFill(apiAstList)
@@ -483,3 +486,9 @@ func WithParserPrefix(prefix string) ParserOption {
 		p.linePrefix = prefix
 	}
 }
+
+func WithParserSkipCheckTypeDeclaration() ParserOption {
+	return func(p *Parser) {
+		p.skipCheckTypeDeclaration = true
+	}
+}
diff --git a/tools/goctl/api/parser/parser.go b/tools/goctl/api/parser/parser.go
index 8469605b..dadfe952 100644
--- a/tools/goctl/api/parser/parser.go
+++ b/tools/goctl/api/parser/parser.go
@@ -33,9 +33,13 @@ func Parse(filename string) (*spec.ApiSpec, error) {
 	return spec, nil
 }
 
-// ParseContent parses the api content
-func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
-	astParser := ast.NewParser()
+func parseContent(content string, skipCheckTypeDeclaration bool, filename ...string) (*spec.ApiSpec, error) {
+	var astParser *ast.Parser
+	if skipCheckTypeDeclaration {
+		astParser = ast.NewParser(ast.WithParserSkipCheckTypeDeclaration())
+	} else {
+		astParser = ast.NewParser()
+	}
 	ast, err := astParser.ParseContent(content, filename...)
 	if err != nil {
 		return nil, err
@@ -51,6 +55,16 @@ func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
 	return spec, nil
 }
 
+// ParseContent parses the api content
+func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
+	return parseContent(content, false, filename...)
+}
+
+// ParseContentWithParserSkipCheckTypeDeclaration parses the api content with skip check type declaration
+func ParseContentWithParserSkipCheckTypeDeclaration(content string, filename ...string) (*spec.ApiSpec, error) {
+	return parseContent(content, true, filename...)
+}
+
 func (p parser) convert2Spec() error {
 	p.fillInfo()
 	p.fillSyntax()
diff --git a/tools/goctl/docker/docker.go b/tools/goctl/docker/docker.go
index e4ac358a..d8292509 100644
--- a/tools/goctl/docker/docker.go
+++ b/tools/goctl/docker/docker.go
@@ -24,14 +24,17 @@ const (
 
 // Docker describes a dockerfile
 type Docker struct {
-	Chinese   bool
-	GoRelPath string
-	GoFile    string
-	ExeFile   string
-	HasPort   bool
-	Port      int
-	Argument  string
-	Version   string
+	Chinese     bool
+	GoRelPath   string
+	GoFile      string
+	ExeFile     string
+	Scratch     bool
+	HasPort     bool
+	Port        int
+	Argument    string
+	Version     string
+	HasTimezone bool
+	Timezone    string
 }
 
 // DockerCommand provides the entry for goctl docker
@@ -47,6 +50,7 @@ func DockerCommand(c *cli.Context) (err error) {
 	version := c.String("version")
 	remote := c.String("remote")
 	branch := c.String("branch")
+	timezone := c.String("tz")
 	if len(remote) > 0 {
 		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
@@ -70,9 +74,10 @@ func DockerCommand(c *cli.Context) (err error) {
 		return fmt.Errorf("file %q not found", goFile)
 	}
 
+	scratch := c.Bool("scratch")
 	port := c.Int("port")
 	if _, err := os.Stat(etcDir); os.IsNotExist(err) {
-		return generateDockerfile(goFile, port, version)
+		return generateDockerfile(goFile, scratch, port, version, timezone)
 	}
 
 	cfg, err := findConfig(goFile, etcDir)
@@ -80,7 +85,7 @@ func DockerCommand(c *cli.Context) (err error) {
 		return err
 	}
 
-	if err := generateDockerfile(goFile, port, version, "-f", "etc/"+cfg); err != nil {
+	if err := generateDockerfile(goFile, scratch, port, version, timezone, "-f", "etc/"+cfg); err != nil {
 		return err
 	}
 
@@ -121,7 +126,7 @@ func findConfig(file, dir string) (string, error) {
 	return files[0], nil
 }
 
-func generateDockerfile(goFile string, port int, version string, args ...string) error {
+func generateDockerfile(goFile string, scratch bool, port int, version, timezone string, args ...string) error {
 	projPath, err := getFilePath(filepath.Dir(goFile))
 	if err != nil {
 		return err
@@ -150,14 +155,17 @@ func generateDockerfile(goFile string, port int, version string, args ...string)
 	_, offset := time.Now().Zone()
 	t := template.Must(template.New("dockerfile").Parse(text))
 	return t.Execute(out, Docker{
-		Chinese:   offset == cstOffset,
-		GoRelPath: projPath,
-		GoFile:    goFile,
-		ExeFile:   pathx.FileNameWithoutExt(filepath.Base(goFile)),
-		HasPort:   port > 0,
-		Port:      port,
-		Argument:  builder.String(),
-		Version:   version,
+		Chinese:     offset == cstOffset,
+		GoRelPath:   projPath,
+		GoFile:      goFile,
+		ExeFile:     pathx.FileNameWithoutExt(filepath.Base(goFile)),
+		Scratch:     scratch,
+		HasPort:     port > 0,
+		Port:        port,
+		Argument:    builder.String(),
+		Version:     version,
+		HasTimezone: len(timezone) > 0,
+		Timezone:    timezone,
 	})
 }
 
diff --git a/tools/goctl/docker/template.go b/tools/goctl/docker/template.go
index b69b9722..6d21d3c6 100644
--- a/tools/goctl/docker/template.go
+++ b/tools/goctl/docker/template.go
@@ -15,6 +15,8 @@ LABEL stage=gobuilder
 ENV CGO_ENABLED 0
 ENV GOOS linux
 {{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct
+{{end}}{{if .HasTimezone}}
+RUN apk update --no-cache && apk add --no-cache tzdata
 {{end}}
 WORKDIR /build
 
@@ -26,11 +28,12 @@ COPY . .
 {{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoRelPath}}/{{.GoFile}}
 
 
-FROM alpine
-
-RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata
-ENV TZ Asia/Shanghai
+FROM {{if .Scratch}}scratch{{else}}alpine{{end}}
 
+{{if .Scratch}}COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt{{else}}RUN apk update --no-cache && apk add --no-cache ca-certificates{{end}}
+{{if .HasTimezone}}COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}}
+ENV TZ {{.Timezone}}
+{{end}}
 WORKDIR /app
 COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}}{{if .Argument}}
 COPY --from=builder /app/etc /app/etc{{end}}
diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go
index a9bacf8e..cccf38bd 100644
--- a/tools/goctl/goctl.go
+++ b/tools/goctl/goctl.go
@@ -160,6 +160,10 @@ var commands = []cli.Command{
 						Name:  "stdin",
 						Usage: "use stdin to input api doc content, press \"ctrl + d\" to send EOF",
 					},
+					cli.BoolFlag{
+						Name:  "declare",
+						Usage: "use to skip check api types already declare",
+					},
 				},
 				Action: format.GoFormatApi,
 			},
@@ -341,6 +345,10 @@ var commands = []cli.Command{
 				Name:  "go",
 				Usage: "the file that contains main function",
 			},
+			cli.BoolFlag{
+				Name:  "scratch",
+				Usage: "use scratch for the base docker image",
+			},
 			cli.IntFlag{
 				Name:  "port",
 				Usage: "the port to expose, default none",
@@ -365,6 +373,11 @@ var commands = []cli.Command{
 				Name:  "version",
 				Usage: "the goctl builder golang image version",
 			},
+			cli.StringFlag{
+				Name:  "tz",
+				Usage: "the timezone of the container",
+				Value: "Asia/Shanghai",
+			},
 		},
 		Action: docker.DockerCommand,
 	},


Test patch:
diff --git a/tools/goctl/api/format/format_test.go b/tools/goctl/api/format/format_test.go
index b3d2c64c..57bda187 100644
--- a/tools/goctl/api/format/format_test.go
+++ b/tools/goctl/api/format/format_test.go
@@ -13,6 +13,7 @@ type Request struct {
 }
 type Response struct {
   Message string ` + "`" + `json:"message"` + "`" + `
+  Students []Student ` + "`" + `json:"students"` + "`" + `
 }
 service A-api {
 @server(
@@ -26,7 +27,8 @@ handler: GreetHandler
 	Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
 }
 type Response {
-	Message string ` + "`" + `json:"message"` + "`" + `
+	Message  string    ` + "`" + `json:"message"` + "`" + `
+	Students []Student ` + "`" + `json:"students"` + "`" + `
 }
 service A-api {
 	@server(
@@ -37,7 +39,9 @@ service A-api {
 )
 
 func TestFormat(t *testing.T) {
-	r, err := apiFormat(notFormattedStr)
+	r, err := apiFormat(notFormattedStr, true)
 	assert.Nil(t, err)
 	assert.Equal(t, formattedStr, r)
+	_, err = apiFormat(notFormattedStr, false)
+	assert.Errorf(t, err, " line 7:13 can not found declaration 'Student' in context")
 }