032faefa4b
This change fixes an incorrect invocation of the URL object constructor in generated TypeScript HTTP clients. Change-Id: I9011bc535f2096374d20b74b401d4cc38a0451fb
168 lines
4.3 KiB
Go
168 lines
4.3 KiB
Go
// Copyright (C) 2022 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package apigen
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/zeebo/errs"
|
|
)
|
|
|
|
// MustWriteTS writes generated TypeScript code into a file indicated by path.
|
|
// The generated code is an API client to run in the browser.
|
|
//
|
|
// If an error occurs, it panics.
|
|
func (a *API) MustWriteTS(path string) {
|
|
f := newTSGenFile(path, a)
|
|
|
|
f.generateTS()
|
|
|
|
err := f.write()
|
|
if err != nil {
|
|
panic(errs.Wrap(err))
|
|
}
|
|
}
|
|
|
|
type tsGenFile struct {
|
|
result string
|
|
path string
|
|
api *API
|
|
types Types
|
|
}
|
|
|
|
func newTSGenFile(filepath string, api *API) *tsGenFile {
|
|
return &tsGenFile{
|
|
path: filepath,
|
|
api: api,
|
|
types: NewTypes(),
|
|
}
|
|
}
|
|
|
|
func (f *tsGenFile) pf(format string, a ...interface{}) {
|
|
f.result += fmt.Sprintf(format+"\n", a...)
|
|
}
|
|
|
|
func (f *tsGenFile) write() error {
|
|
content := strings.ReplaceAll(f.result, "\t", " ")
|
|
return os.WriteFile(f.path, []byte(content), 0644)
|
|
}
|
|
|
|
func (f *tsGenFile) generateTS() {
|
|
f.pf("// AUTOGENERATED BY private/apigen")
|
|
f.pf("// DO NOT EDIT.")
|
|
f.pf("")
|
|
f.pf("import { HttpClient } from '@/utils/httpClient';")
|
|
|
|
f.registerTypes()
|
|
f.result += f.types.GenerateTypescriptDefinitions()
|
|
|
|
f.result += `
|
|
class APIError extends Error {
|
|
constructor(
|
|
public readonly msg: string,
|
|
public readonly responseStatusCode?: number,
|
|
) {
|
|
super(msg);
|
|
}
|
|
}
|
|
`
|
|
|
|
for _, group := range f.api.EndpointGroups {
|
|
f.createAPIClient(group)
|
|
}
|
|
}
|
|
|
|
func (f *tsGenFile) registerTypes() {
|
|
for _, group := range f.api.EndpointGroups {
|
|
for _, method := range group.endpoints {
|
|
if method.Request != nil {
|
|
f.types.Register(method.requestType(group))
|
|
}
|
|
if method.Response != nil {
|
|
f.types.Register(method.responseType(group))
|
|
}
|
|
if len(method.QueryParams) > 0 {
|
|
for _, p := range method.QueryParams {
|
|
t := getElementaryType(p.namedType(method.Endpoint, "query"))
|
|
f.types.Register(t)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
|
|
f.pf("\nexport class %sHttpApi%s {", capitalize(group.Name), strings.ToUpper(f.api.Version))
|
|
f.pf("\tprivate readonly http: HttpClient = new HttpClient();")
|
|
f.pf("\tprivate readonly ROOT_PATH: string = '%s/%s';", f.api.endpointBasePath(), strings.ToLower(group.Prefix))
|
|
for _, method := range group.endpoints {
|
|
f.pf("")
|
|
|
|
funcArgs, path := f.getArgsAndPath(method, group)
|
|
|
|
returnStmt := "return"
|
|
returnType := "void"
|
|
if method.Response != nil {
|
|
respType := method.responseType(group)
|
|
returnType = TypescriptTypeName(respType)
|
|
returnStmt += fmt.Sprintf(" response.json().then((body) => body as %s)", returnType)
|
|
}
|
|
returnStmt += ";"
|
|
|
|
f.pf("\tpublic async %s(%s): Promise<%s> {", method.TypeScriptName, funcArgs, returnType)
|
|
if len(method.QueryParams) > 0 {
|
|
f.pf("\t\tconst u = new URL(`%s`, window.location.href);", path)
|
|
for _, p := range method.QueryParams {
|
|
f.pf("\t\tu.searchParams.set('%s', %s);", p.Name, p.Name)
|
|
}
|
|
f.pf("\t\tconst fullPath = u.toString();")
|
|
} else {
|
|
f.pf("\t\tconst fullPath = `%s`;", path)
|
|
}
|
|
|
|
if method.Request != nil {
|
|
f.pf("\t\tconst response = await this.http.%s(fullPath, JSON.stringify(request));", strings.ToLower(method.Method))
|
|
} else {
|
|
f.pf("\t\tconst response = await this.http.%s(fullPath);", strings.ToLower(method.Method))
|
|
}
|
|
|
|
f.pf("\t\tif (response.ok) {")
|
|
f.pf("\t\t\t%s", returnStmt)
|
|
f.pf("\t\t}")
|
|
f.pf("\t\tconst err = await response.json();")
|
|
f.pf("\t\tthrow new APIError(err.error, response.status);")
|
|
f.pf("\t}")
|
|
}
|
|
f.pf("}")
|
|
}
|
|
|
|
func (f *tsGenFile) getArgsAndPath(method *fullEndpoint, group *EndpointGroup) (funcArgs, path string) {
|
|
// remove path parameter placeholders
|
|
path = method.Path
|
|
i := strings.Index(path, "{")
|
|
if i > -1 {
|
|
path = method.Path[:i]
|
|
}
|
|
path = "${this.ROOT_PATH}" + path
|
|
|
|
if method.Request != nil {
|
|
funcArgs += fmt.Sprintf("request: %s, ", TypescriptTypeName(method.requestType(group)))
|
|
}
|
|
|
|
for _, p := range method.PathParams {
|
|
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.namedType(method.Endpoint, "path")))
|
|
path += fmt.Sprintf("/${%s}", p.Name)
|
|
}
|
|
|
|
for _, p := range method.QueryParams {
|
|
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.namedType(method.Endpoint, "query")))
|
|
}
|
|
|
|
path = strings.ReplaceAll(path, "//", "/")
|
|
|
|
return strings.Trim(funcArgs, ", "), path
|
|
}
|