storj/private/apigen/tsgen.go
Jeremy Wharton 4d823e8166 private/apigen: isolate TypeScript class generation
The code responsible for generating TypeScript classes has been
separated from the rest of the TypeScript generation code so that other
packages may take advantage of this functionality.

References #5494

Change-Id: I97eabd430bd6a5f748eafaf8b1d783977e75e660
2023-03-10 21:52:30 +00:00

158 lines
3.9 KiB
Go

// 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
}