storj/private/apigen/tsgen.go

158 lines
3.9 KiB
Go
Raw Normal View History

// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
package apigen
import (
"fmt"
"os"
"reflect"
"strings"
"github.com/zeebo/errs"
)
// MustWriteTS writes generated TypeScript code into a file.
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()
for _, group := range f.api.EndpointGroups {
// Not sure if this is a good name
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(reflect.TypeOf(method.Request))
}
if method.Response != nil {
f.types.Register(reflect.TypeOf(method.Response))
}
if len(method.QueryParams) > 0 {
for _, p := range method.QueryParams {
t := getElementaryType(p.Type)
f.types.Register(t)
}
}
}
}
}
func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
f.pf("\nexport class %sHttpApi%s {", group.Prefix, strings.ToUpper(f.api.Version))
f.pf("\tprivate readonly http: HttpClient = new HttpClient();")
f.pf("\tprivate readonly ROOT_PATH: string = '/api/%s/%s';", f.api.Version, group.Prefix)
for _, method := range group.endpoints {
f.pf("")
funcArgs, path := f.getArgsAndPath(method)
returnStmt := "return"
returnType := "void"
if method.Response != nil {
returnType = TypescriptTypeName(getElementaryType(reflect.TypeOf(method.Response)))
if v := reflect.ValueOf(method.Response); v.Kind() == reflect.Array || v.Kind() == reflect.Slice {
returnType = fmt.Sprintf("Array<%s>", returnType)
}
returnStmt += fmt.Sprintf(" response.json().then((body) => body as %s)", returnType)
}
returnStmt += ";"
f.pf("\tpublic async %s(%s): Promise<%s> {", method.RequestName, funcArgs, returnType)
f.pf("\t\tconst path = `%s`;", path)
if method.Request != nil {
f.pf("\t\tconst response = await this.http.%s(path, JSON.stringify(request));", strings.ToLower(method.Method))
} else {
f.pf("\t\tconst response = await this.http.%s(path);", 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 Error(err.error);")
f.pf("\t}")
}
f.pf("}")
}
func (f *tsGenFile) getArgsAndPath(method *fullEndpoint) (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 {
t := getElementaryType(reflect.TypeOf(method.Request))
funcArgs += fmt.Sprintf("request: %s, ", TypescriptTypeName(t))
}
for _, p := range method.PathParams {
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.Type))
path += fmt.Sprintf("/${%s}", p.Name)
}
for i, p := range method.QueryParams {
if i == 0 {
path += "?"
} else {
path += "&"
}
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.Type))
path += fmt.Sprintf("%s=${%s}", p.Name, p.Name)
}
path = strings.ReplaceAll(path, "//", "/")
return strings.Trim(funcArgs, ", "), path
}