forked from zeromicro/go-zero
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:goctl support generate api client code zeromicro#4625
- Loading branch information
Showing
10 changed files
with
354 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{{if .hasDoc}}// {{.function}} {{.doc}}{{end}} | ||
func (c *ApiClient) {{.function}}(ctx context.Context, {{.request}}) {{.responseString}} { | ||
const {{.function}}Url= "{{.url}}" | ||
return call[{{.responseType}}](ctx, c, {{.httpMethod}}, {{.function}}Url{{if .hasRequest}}, req{{else}}, nil{{end}}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package client | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
{{.imports}} | ||
"github.com/zeromicro/go-zero/core/logx" | ||
"github.com/zeromicro/go-zero/rest/httpc" | ||
) | ||
|
||
type ApiClient struct { | ||
url string | ||
cs httpc.Service | ||
} | ||
|
||
// NewApiClientWithClient returns a http-api client with the given url. | ||
// opts are used to customize the *http.Client. | ||
func NewApiClientWithClient(url string, c *http.Client, opts ...httpc.Option) *ApiClient { | ||
return &ApiClient{ | ||
url: url, | ||
cs: httpc.NewServiceWithClient("{{.client}}", c, opts...), | ||
} | ||
} | ||
|
||
// NewApiClient returns a http-api client with the given url. | ||
// opts are used to customize the *http.Client. | ||
func NewApiClient(url string, opts ...httpc.Option) *ApiClient { | ||
return &ApiClient{ | ||
url: url, | ||
cs: httpc.NewService("{{.client}}", opts...), | ||
} | ||
} | ||
|
||
// Do calls the URL with the given requestBody | ||
func (cli *ApiClient) Do(ctx context.Context, method, url string, requestBody any) ([]byte, error) { | ||
return doRequest(ctx, method, cli.url+url, requestBody, cli.cs.Do) | ||
} | ||
|
||
// call makes an HTTP request and unmarshals the response into the specified type | ||
func call[T any](ctx context.Context, c *ApiClient, httpMethod string, url string, req any) (resp T, err error) { | ||
result, err := c.Do(ctx, httpMethod, url, req) | ||
if err != nil { | ||
logx.Error(err) | ||
return resp, err | ||
} | ||
if err = json.Unmarshal(result, &resp); err != nil { | ||
return resp, fmt.Errorf("json unmarshal failed. error: %v", err) | ||
} | ||
return resp, nil | ||
} | ||
|
||
// doRequest performs the HTTP request and handles the response | ||
func doRequest( | ||
ctx context.Context, method, url string, requestBody any, | ||
do func(ctx context.Context, method, url string, data any) (*http.Response, error)) ([]byte, error) { | ||
res, err := do(ctx, method, url, requestBody) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer res.Body.Close() | ||
|
||
// Log the request | ||
logx.Debugfn(func() any { | ||
param, _ := json.Marshal(requestBody) | ||
return fmt.Sprintf("call http api %s [%s] request: %s", method, url, string(param)) | ||
}) | ||
|
||
// Check the response status | ||
if res.StatusCode != http.StatusOK { | ||
return handleErrorResponse(res) | ||
} | ||
|
||
return handleSuccessResponse(res) | ||
} | ||
|
||
// handleErrorResponse handles non-OK HTTP responses | ||
func handleErrorResponse(res *http.Response) ([]byte, error) { | ||
var bz []byte | ||
var err error | ||
if res.Body != nil { | ||
bz, err = io.ReadAll(res.Body) | ||
logx.Error(string(bz), err) | ||
} else { | ||
logx.Error("server request failed", res.StatusCode, res.Status) | ||
} | ||
return nil, errors.New("server request failed, status: " + res.Status) | ||
} | ||
|
||
// handleSuccessResponse handles OK HTTP responses | ||
func handleSuccessResponse(res *http.Response) ([]byte, error) { | ||
responseData, err := io.ReadAll(res.Body) | ||
if err != nil { | ||
logx.Error(err) | ||
return nil, errors.New("server request failed, status: " + res.Status) | ||
} | ||
logx.Debugf("call http api %s [%s] response: %s", res.Request.Method, res.Request.URL, string(responseData)) | ||
return responseData, nil | ||
} | ||
|
||
{{.clientMethods}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package gogen | ||
|
||
import ( | ||
_ "embed" | ||
"fmt" | ||
"strings" | ||
"text/template" | ||
|
||
"github.com/zeromicro/go-zero/tools/goctl/api/spec" | ||
"github.com/zeromicro/go-zero/tools/goctl/config" | ||
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" | ||
"github.com/zeromicro/go-zero/tools/goctl/vars" | ||
) | ||
|
||
//go:embed client.tpl | ||
var clientTemplate string | ||
|
||
//go:embed client-method.tpl | ||
var clientMethodTemplate string | ||
|
||
func genClient(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error { | ||
// load client method template | ||
templateText, err := pathx.LoadTemplate(category, clientMethodTemplateFile, clientMethodTemplate) | ||
if err != nil { | ||
return err | ||
} | ||
gt := template.Must(template.New("groupTemplate").Parse(templateText)) | ||
// generate client method | ||
var builder strings.Builder | ||
for _, g := range api.Service.Groups { | ||
for _, r := range g.Routes { | ||
if err := generateClientMethod(gt, &builder, g, r); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
// generate client file | ||
return genClientFile(&builder, dir, rootPkg, api.Service.Name) | ||
} | ||
|
||
func generateClientMethod(gt *template.Template, builder *strings.Builder, g spec.Group, r spec.Route) error { | ||
client := getClientName(r) | ||
var responseString, responseType, returnString, requestString string | ||
if len(r.ResponseTypeName()) > 0 { | ||
responseType = responseGoTypeName(r, typesPacket) | ||
responseString = "(resp " + responseType + ", err error)" | ||
returnString = "return" | ||
} else { | ||
responseString = "error" | ||
returnString = "return nil" | ||
} | ||
if len(r.RequestTypeName()) > 0 { | ||
requestString = "req *" + requestGoTypeName(r, typesPacket) | ||
} | ||
|
||
data := map[string]any{ | ||
"client": strings.Title(client), | ||
"function": strings.Title(strings.TrimSuffix(client, "Client")), | ||
"responseString": responseString, | ||
"responseType": responseType, | ||
"httpMethod": mapping[r.Method], | ||
"hasRequest": len(r.RequestTypeName()) > 0, | ||
"returnString": returnString, | ||
"request": requestString, | ||
"hasDoc": len(r.JoinedDoc()) > 0, | ||
"doc": strings.Trim(r.JoinedDoc(), "\""), | ||
"url": g.Annotation.Properties["prefix"] + r.Path, | ||
} | ||
|
||
return gt.Execute(builder, data) | ||
} | ||
|
||
func genClientFile(builder *strings.Builder, dir, rootPkg string, name string) error { | ||
imports := genClientImports(rootPkg) | ||
subDir := clientDir | ||
return genFile(fileGenConfig{ | ||
dir: dir, | ||
subdir: subDir, | ||
filename: "client.go", | ||
templateName: "clientTemplate", | ||
category: category, | ||
templateFile: clientTemplateFile, | ||
builtinTemplate: clientTemplate, | ||
data: map[string]any{ | ||
"imports": imports, | ||
"client": strings.Title(name), | ||
"clientMethods": builder.String(), | ||
}, | ||
}) | ||
} | ||
|
||
func genClientImports(parentPkg string) string { | ||
var imports []string | ||
imports = append(imports, fmt.Sprintf("\"%s\"\n", pathx.JoinPackages(parentPkg, typesDir))) | ||
imports = append(imports, fmt.Sprintf("\"%s/core/logx\"", vars.ProjectOpenSourceURL)) | ||
return strings.Join(imports, "\n\t") | ||
} | ||
|
||
func getClientName(route spec.Route) string { | ||
handler, err := getHandlerBaseName(route) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
return handler + "Client" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package gogen | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path" | ||
"strings" | ||
"text/template" | ||
|
||
"github.com/zeromicro/go-zero/tools/goctl/api/spec" | ||
"github.com/zeromicro/go-zero/tools/goctl/config" | ||
"github.com/zeromicro/go-zero/tools/goctl/internal/version" | ||
"github.com/zeromicro/go-zero/tools/goctl/util/format" | ||
"github.com/zeromicro/go-zero/tools/goctl/util/pathx" | ||
) | ||
|
||
const ( | ||
interfaceFilename = "interface" | ||
interfaceTemplate = `package client | ||
import ( | ||
{{.importPackages}} | ||
) | ||
type Client interface { | ||
{{.methods}} | ||
} | ||
` | ||
interfaceMethodTemplate = ` | ||
{{if .hasDoc}}// {{.function}} {{.doc}}{{end}} | ||
{{.function}}(ctx context.Context,{{.request}}) {{.responseType}} | ||
` | ||
) | ||
|
||
func genMethod(gt *template.Template, builder *strings.Builder, route spec.Route) error { | ||
//{{.function}}({{.request}}) {{.responseType}} | ||
client := getClientName(route) | ||
request := "" | ||
requestType := requestGoTypeName(route, typesPacket) | ||
if len(requestType) > 0 { | ||
request = "req *" + requestType | ||
} | ||
var responseString string | ||
if len(route.ResponseTypeName()) > 0 { | ||
resp := responseGoTypeName(route, typesPacket) | ||
responseString = "(" + resp + ", error)" | ||
} else { | ||
responseString = "error" | ||
} | ||
data := map[string]any{ | ||
"client": strings.Title(client), | ||
"function": strings.Title(strings.TrimSuffix(client, "Client")), | ||
"responseType": responseString, | ||
"hasRequest": len(route.RequestTypeName()) > 0, | ||
"request": request, | ||
"hasDoc": len(route.JoinedDoc()) > 0, | ||
"doc": strings.Trim(route.JoinedDoc(), "\""), | ||
} | ||
return gt.Execute(builder, data) | ||
} | ||
|
||
func genInterface(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error { | ||
var builder strings.Builder | ||
templateText, err := pathx.LoadTemplate(category, interfaceMethodTemplateFile, interfaceMethodTemplate) | ||
if err != nil { | ||
return err | ||
} | ||
gt := template.Must(template.New("groupTemplate").Parse(templateText)) | ||
|
||
for _, g := range api.Service.Groups { | ||
for _, r := range g.Routes { | ||
err = genMethod(gt, &builder, r) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
var hasTimeout bool | ||
|
||
interfaceFilename, err := format.FileNamingFormat(cfg.NamingFormat, interfaceFilename) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
interfaceFilename = interfaceFilename + ".go" | ||
filename := path.Join(dir, clientDir, interfaceFilename) | ||
os.Remove(filename) | ||
|
||
return genFile(fileGenConfig{ | ||
dir: dir, | ||
subdir: clientDir, | ||
filename: interfaceFilename, | ||
templateName: "interfaceTemplate", | ||
category: category, | ||
templateFile: interfaceTemplateFile, | ||
builtinTemplate: interfaceTemplate, | ||
data: map[string]any{ | ||
"hasTimeout": hasTimeout, | ||
"importPackages": genInterfaceImports(rootPkg, api), | ||
"methods": strings.TrimSpace(builder.String()), | ||
"version": version.BuildVersion, | ||
}, | ||
}) | ||
} | ||
|
||
func genInterfaceImports(parentPkg string, api *spec.ApiSpec) string { | ||
var imports []string | ||
imports = append(imports, "\"context\"") | ||
imports = append(imports, "") | ||
imports = append(imports, fmt.Sprintf("\"%s\"\n", pathx.JoinPackages(parentPkg, typesDir))) | ||
return strings.Join(imports, "\n\t") | ||
} |
Oops, something went wrong.