Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extends ts gen cmd args: url(base url) and body(custom http request body). #4630

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tools/goctl/api/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ func init() {
tsCmdFlags.StringVar(&tsgen.VarStringAPI, "api")
tsCmdFlags.StringVar(&tsgen.VarStringCaller, "caller")
tsCmdFlags.BoolVar(&tsgen.VarBoolUnWrap, "unwrap")
tsCmdFlags.StringVar(&tsgen.VarStringUrlPrefix, "url")
tsCmdFlags.BoolVar(&tsgen.VarBoolCustomBody, "body")

validateCmdFlags.StringVar(&validate.VarStringAPI, "api")

Expand Down
4 changes: 4 additions & 0 deletions tools/goctl/api/tsgen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ var (
VarStringCaller string
// VarBoolUnWrap describes whether wrap or not.
VarBoolUnWrap bool
// VarStringUrlPrefix request url prefix
VarStringUrlPrefix string
// VarBoolCustomBody request custom body
VarBoolCustomBody bool
)

// TsCommand provides the entry to generate typescript codes
Expand Down
36 changes: 30 additions & 6 deletions tools/goctl/api/tsgen/genpacket.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ func genAPI(api *spec.ApiSpec, caller string) (string, error) {
if len(comment) > 0 {
fmt.Fprintf(&builder, "%s\n", comment)
}
fmt.Fprintf(&builder, "export function %s(%s) {\n", handler, paramsForRoute(route))
genericsType := ""
if VarBoolCustomBody {
genericsType = "<T>"
}
fmt.Fprintf(&builder, "export function %s%s(%s) {\n", handler, genericsType, paramsForRoute(route))
writeIndent(&builder, 1)
responseGeneric := "<null>"
if len(route.ResponseTypeName()) > 0 {
Expand All @@ -101,6 +105,9 @@ func genAPI(api *spec.ApiSpec, caller string) (string, error) {

func paramsForRoute(route spec.Route) string {
if route.RequestType == nil {
if VarBoolCustomBody {
return "body?: T"
}
return ""
}
hasParams := pathHasParams(route)
Expand Down Expand Up @@ -141,6 +148,10 @@ func paramsForRoute(route spec.Route) string {
}
}
}

if VarBoolCustomBody {
params = append(params, "body?: T")
}
return strings.Join(params, ", ")
}

Expand Down Expand Up @@ -173,14 +184,27 @@ func callParamsForRoute(route spec.Route, group spec.Group) string {
var params = []string{pathForRoute(route, group)}
if hasParams {
params = append(params, "params")
} else {
params = append(params, "null")
}

configParams := []string{}

if hasBody {
params = append(params, "req")
if VarBoolCustomBody {
configParams = append(configParams, "body: JSON.stringify(body ?? req)")
} else {
configParams = append(configParams, "body: JSON.stringify(req)")
}
} else if VarBoolCustomBody {
configParams = append(configParams, "body: body ? JSON.stringify(body): null")
}
if hasHeader {
params = append(params, "headers")
configParams = append(configParams, "headers: headers")
}

params = append(params, fmt.Sprintf("{%s}", strings.Join(configParams, ", ")))

return strings.Join(params, ", ")
}

Expand All @@ -198,12 +222,12 @@ func pathForRoute(route spec.Route, group spec.Group) string {
routePath = strings.Join(pathSlice, "/")
}
if len(prefix) == 0 {
return "`" + routePath + "`"
return "`" + VarStringUrlPrefix + routePath + "`"
}

prefix = strings.TrimPrefix(prefix, `"`)
prefix = strings.TrimSuffix(prefix, `"`)
return fmt.Sprintf("`%s/%s`", prefix, strings.TrimPrefix(routePath, "/"))
return fmt.Sprintf("`%s%s/%s`", VarStringUrlPrefix, prefix, strings.TrimPrefix(routePath, "/"))
}

func pathHasParams(route spec.Route) bool {
Expand All @@ -212,7 +236,7 @@ func pathHasParams(route spec.Route) bool {
return false
}

return len(ds.Members) != len(ds.GetBodyMembers())
return len(ds.Members) != (len(ds.GetBodyMembers()) + len(ds.GetTagMembers(headerTagKey)) + len(ds.GetTagMembers(pathTagKey)))
}

func hasRequestBody(route spec.Route) bool {
Expand Down
5 changes: 0 additions & 5 deletions tools/goctl/api/tsgen/genrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
_ "embed"
"os"
"path/filepath"

"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)

//go:embed request.ts
Expand All @@ -18,9 +16,6 @@ func genRequest(dir string) error {
}

filename := filepath.Join(abs, "gocliRequest.ts")
if pathx.FileExists(filename) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why to remove the existance check?

return nil
}

return os.WriteFile(filename, []byte(requestTemplate), 0644)
}
140 changes: 77 additions & 63 deletions tools/goctl/api/tsgen/request.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
export type Method =
| 'get'
| 'GET'
| 'delete'
| 'DELETE'
| 'head'
| 'HEAD'
| 'options'
| 'OPTIONS'
| 'post'
| 'POST'
| 'put'
| 'PUT'
| 'patch'
| 'PATCH';
| "get"
| "GET"
| "delete"
| "DELETE"
| "head"
| "HEAD"
| "options"
| "OPTIONS"
| "post"
| "POST"
| "put"
| "PUT"
| "patch"
| "PATCH";

export type QueryParams = {
[key: string | symbol | number]: string|number;
} | null;

/**
* Parse route parameters for responseType
Expand All @@ -24,23 +28,23 @@ export function parseParams(url: string): Array<string> {
if (!ps) {
return [];
}
return ps.map((k) => k.replace(/:/, ''));
return ps.map((k) => k.replace(/:/, ""));
}

/**
* Generate url and parameters
* @param url
* @param params
*/
export function genUrl(url: string, params: any) {
export function genUrl(url: string, params: QueryParams) {
if (!params) {
return url;
}

const ps = parseParams(url);
ps.forEach((k) => {
const reg = new RegExp(`:${k}`);
url = url.replace(reg, params[k]);
url = url.replace(reg, params[k].toString());
});

const path: Array<string> = [];
Expand All @@ -50,77 +54,87 @@ export function genUrl(url: string, params: any) {
}
}

return url + (path.length > 0 ? `?${path.join('&')}` : '');
return url + (path.length > 0 ? `?${path.join("&")}` : "");
}

export async function request({
method,
url,
data,
config = {}
}: {
method: Method;
url: string;
data?: unknown;
config?: unknown;
}) {
export async function request(
method: Method,
url: string,
config?: RequestInit
) {
if (config?.body && /get|head/i.test(method)) {
throw new Error(
"Request with GET/HEAD method cannot have body. *.api service use other method, example: POST or PUT."
);
}
const response = await fetch(url, {
method: method.toLocaleUpperCase(),
credentials: 'include',
credentials: "include",
...config,
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json",
...config?.headers,
},
body: data ? JSON.stringify(data) : undefined,
// @ts-ignore
...config
});

return response.json();
if (response.headers.get('Content-Type') == 'application/json') {
return response.json();
} else {
return response.text();
}
}

function api<T>(
method: Method = 'get',
method: Method = "get",
url: string,
req: any,
config?: unknown
params?: QueryParams,
config?: RequestInit
): Promise<T> {
if (url.match(/:/) || method.match(/get|delete/i)) {
url = genUrl(url, req.params || req.forms);
if (params) {
url = genUrl(url, params);
}
method = method.toLocaleLowerCase() as Method;

switch (method) {
case 'get':
return request({method: 'get', url, data: req, config});
case 'delete':
return request({method: 'delete', url, data: req, config});
case 'put':
return request({method: 'put', url, data: req, config});
case 'post':
return request({method: 'post', url, data: req, config});
case 'patch':
return request({method: 'patch', url, data: req, config});
case "get":
return request("get", url, config);
case "delete":
return request("delete", url, config);
case "put":
return request("put", url, config);
case "post":
return request("post", url, config);
case "patch":
return request("patch", url, config);
default:
return request({method: 'post', url, data: req, config});
return request("post", url, config);
}
}

export const webapi = {
get<T>(url: string, req: unknown, config?: unknown): Promise<T> {
return api<T>('get', url, req, config);
get<T>(url: string, params?: QueryParams, config?: RequestInit): Promise<T> {
return api<T>("get", url, params, config);
},
delete<T>(url: string, req: unknown, config?: unknown): Promise<T> {
return api<T>('delete', url, req, config);
delete<T>(
url: string,
params?: QueryParams,
config?: RequestInit
): Promise<T> {
return api<T>("delete", url, params, config);
},
put<T>(url: string, req: unknown, config?: unknown): Promise<T> {
return api<T>('put', url, req, config);
put<T>(url: string, params?: QueryParams, config?: RequestInit): Promise<T> {
return api<T>("put", url, params, config);
},
post<T>(url: string, req: unknown, config?: unknown): Promise<T> {
return api<T>('post', url, req, config);
post<T>(url: string, params?: QueryParams, config?: RequestInit): Promise<T> {
return api<T>("post", url, params, config);
},
patch<T>(
url: string,
params?: QueryParams,
config?: RequestInit
): Promise<T> {
return api<T>("patch", url, params, config);
},
patch<T>(url: string, req: unknown, config?: unknown): Promise<T> {
return api<T>('patch', url, req, config);
}
};

export default webapi
export default webapi;
30 changes: 23 additions & 7 deletions tools/goctl/api/tsgen/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"strings"

"github.com/zeromicro/go-zero/core/stringx"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
apiutil "github.com/zeromicro/go-zero/tools/goctl/api/util"
"github.com/zeromicro/go-zero/tools/goctl/util"
Expand All @@ -18,6 +19,16 @@ const (
headerTagKey = "header"
)

func IsOptionalOrOmitEmpty(m spec.Member) bool {
tag := m.Tags()
for _, item := range tag {
if stringx.Contains(item.Options, "optional") || stringx.Contains(item.Options, "omitempty") {
return true
}
}
return false
}

func writeProperty(writer io.Writer, member spec.Member, indent int) error {
writeIndent(writer, indent)
ty, err := genTsType(member, indent)
Expand All @@ -26,13 +37,16 @@ func writeProperty(writer io.Writer, member spec.Member, indent int) error {
}

optionalTag := ""
if member.IsOptional() || member.IsOmitEmpty() {
if IsOptionalOrOmitEmpty(member) {
optionalTag = "?"
}
name, err := member.GetPropertyName()
if err != nil {
return err
}
if strings.Contains(name, "-") {
name = fmt.Sprintf("\"%s\"", name)
}

comment := member.GetComment()
if len(comment) > 0 {
Expand Down Expand Up @@ -150,7 +164,7 @@ func primitiveType(tp string) (string, bool) {
}

func writeType(writer io.Writer, tp spec.Type) error {
fmt.Fprintf(writer, "export interface %s {\n", util.Title(tp.Name()))
fmt.Fprintf(writer, "export type %s = {\n", util.Title(tp.Name()))
if err := writeMembers(writer, tp, false, 1); err != nil {
return err
}
Expand All @@ -170,14 +184,16 @@ func genParamsTypesIfNeed(writer io.Writer, tp spec.Type) error {
return nil
}

fmt.Fprintf(writer, "export interface %sParams {\n", util.Title(tp.Name()))
if err := writeTagMembers(writer, tp, formTagKey); err != nil {
return err
if len(definedType.GetTagMembers(formTagKey)) > 0 {
fmt.Fprintf(writer, "export type %sParams = {\n", util.Title(tp.Name()))
if err := writeTagMembers(writer, tp, formTagKey); err != nil {
return err
}
fmt.Fprintf(writer, "}\n")
}
fmt.Fprintf(writer, "}\n")

if len(definedType.GetTagMembers(headerTagKey)) > 0 {
fmt.Fprintf(writer, "export interface %sHeaders {\n", util.Title(tp.Name()))
fmt.Fprintf(writer, "export type %sHeaders = {\n", util.Title(tp.Name()))
if err := writeTagMembers(writer, tp, headerTagKey); err != nil {
return err
}
Expand Down
Loading