{private,satellite}: add Typescript generation to REST API generation
github issue: https://github.com/storj/storj/issues/4984 Change-Id: Id3473a6642f7a4af80edda25a6242559efaf69e9
This commit is contained in:
parent
c255d6949f
commit
fa23e55654
@ -13,6 +13,7 @@ type Endpoint struct {
|
||||
Name string
|
||||
Description string
|
||||
MethodName string
|
||||
RequestName string
|
||||
NoCookieAuth bool
|
||||
NoAPIAuth bool
|
||||
Request interface{}
|
||||
|
@ -39,7 +39,7 @@ func (a *API) MustWriteGo(path string) {
|
||||
func (a *API) generateGo() ([]byte, error) {
|
||||
var result string
|
||||
|
||||
p := func(format string, a ...interface{}) {
|
||||
pf := func(format string, a ...interface{}) {
|
||||
result += fmt.Sprintf(format+"\n", a...)
|
||||
}
|
||||
|
||||
@ -94,15 +94,15 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
|
||||
for _, group := range a.EndpointGroups {
|
||||
i("github.com/zeebo/errs")
|
||||
p("var Err%sAPI = errs.Class(\"%s %s api\")", cases.Title(language.Und).String(group.Prefix), a.PackageName, group.Prefix)
|
||||
pf("var Err%sAPI = errs.Class(\"%s %s api\")", cases.Title(language.Und).String(group.Prefix), a.PackageName, group.Prefix)
|
||||
}
|
||||
|
||||
p("")
|
||||
pf("")
|
||||
|
||||
params := make(map[*fullEndpoint][]Param)
|
||||
|
||||
for _, group := range a.EndpointGroups {
|
||||
p("type %sService interface {", group.Name)
|
||||
pf("type %sService interface {", group.Name)
|
||||
for _, e := range group.endpoints {
|
||||
params[e] = append(e.QueryParams, e.PathParams...)
|
||||
|
||||
@ -121,82 +121,82 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
if responseType == getElementaryType(responseType) {
|
||||
returnParam = "*" + returnParam
|
||||
}
|
||||
p("%s(context.Context, "+paramStr+") (%s, api.HTTPError)", e.MethodName, returnParam)
|
||||
pf("%s(context.Context, "+paramStr+") (%s, api.HTTPError)", e.MethodName, returnParam)
|
||||
} else {
|
||||
p("%s(context.Context, "+paramStr+") (api.HTTPError)", e.MethodName)
|
||||
pf("%s(context.Context, "+paramStr+") (api.HTTPError)", e.MethodName)
|
||||
}
|
||||
}
|
||||
p("}")
|
||||
p("")
|
||||
pf("}")
|
||||
pf("")
|
||||
}
|
||||
|
||||
for _, group := range a.EndpointGroups {
|
||||
i("go.uber.org/zap", "github.com/spacemonkeygo/monkit/v3")
|
||||
p("// %sHandler is an api handler that exposes all %s related functionality.", group.Name, group.Prefix)
|
||||
p("type %sHandler struct {", group.Name)
|
||||
p("log *zap.Logger")
|
||||
p("mon *monkit.Scope")
|
||||
p("service %sService", group.Name)
|
||||
p("auth api.Auth")
|
||||
p("}")
|
||||
p("")
|
||||
pf("// %sHandler is an api handler that exposes all %s related functionality.", group.Name, group.Prefix)
|
||||
pf("type %sHandler struct {", group.Name)
|
||||
pf("log *zap.Logger")
|
||||
pf("mon *monkit.Scope")
|
||||
pf("service %sService", group.Name)
|
||||
pf("auth api.Auth")
|
||||
pf("}")
|
||||
pf("")
|
||||
}
|
||||
|
||||
for _, group := range a.EndpointGroups {
|
||||
i("github.com/gorilla/mux")
|
||||
p(
|
||||
pf(
|
||||
"func New%s(log *zap.Logger, mon *monkit.Scope, service %sService, router *mux.Router, auth api.Auth) *%sHandler {",
|
||||
group.Name,
|
||||
group.Name,
|
||||
group.Name,
|
||||
)
|
||||
p("handler := &%sHandler{", group.Name)
|
||||
p("log: log,")
|
||||
p("mon: mon,")
|
||||
p("service: service,")
|
||||
p("auth: auth,")
|
||||
p("}")
|
||||
p("")
|
||||
p("%sRouter := router.PathPrefix(\"/api/v0/%s\").Subrouter()", group.Prefix, group.Prefix)
|
||||
pf("handler := &%sHandler{", group.Name)
|
||||
pf("log: log,")
|
||||
pf("mon: mon,")
|
||||
pf("service: service,")
|
||||
pf("auth: auth,")
|
||||
pf("}")
|
||||
pf("")
|
||||
pf("%sRouter := router.PathPrefix(\"/api/v0/%s\").Subrouter()", group.Prefix, group.Prefix)
|
||||
for _, endpoint := range group.endpoints {
|
||||
handlerName := "handle" + endpoint.MethodName
|
||||
p("%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", group.Prefix, endpoint.Path, handlerName, endpoint.Method)
|
||||
pf("%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", group.Prefix, endpoint.Path, handlerName, endpoint.Method)
|
||||
}
|
||||
p("")
|
||||
p("return handler")
|
||||
p("}")
|
||||
p("")
|
||||
pf("")
|
||||
pf("return handler")
|
||||
pf("}")
|
||||
pf("")
|
||||
}
|
||||
|
||||
for _, group := range a.EndpointGroups {
|
||||
for _, endpoint := range group.endpoints {
|
||||
i("net/http")
|
||||
p("")
|
||||
pf("")
|
||||
handlerName := "handle" + endpoint.MethodName
|
||||
p("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", group.Name, handlerName)
|
||||
p("ctx := r.Context()")
|
||||
p("var err error")
|
||||
p("defer h.mon.Task()(&ctx)(&err)")
|
||||
p("")
|
||||
pf("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", group.Name, handlerName)
|
||||
pf("ctx := r.Context()")
|
||||
pf("var err error")
|
||||
pf("defer h.mon.Task()(&ctx)(&err)")
|
||||
pf("")
|
||||
|
||||
p("w.Header().Set(\"Content-Type\", \"application/json\")")
|
||||
p("")
|
||||
pf("w.Header().Set(\"Content-Type\", \"application/json\")")
|
||||
pf("")
|
||||
|
||||
if !endpoint.NoCookieAuth || !endpoint.NoAPIAuth {
|
||||
p("ctx, err = h.auth.IsAuthenticated(ctx, r, %v, %v)", !endpoint.NoCookieAuth, !endpoint.NoAPIAuth)
|
||||
p("if err != nil {")
|
||||
pf("ctx, err = h.auth.IsAuthenticated(ctx, r, %v, %v)", !endpoint.NoCookieAuth, !endpoint.NoAPIAuth)
|
||||
pf("if err != nil {")
|
||||
if !endpoint.NoCookieAuth {
|
||||
p("h.auth.RemoveAuthCookie(w)")
|
||||
pf("h.auth.RemoveAuthCookie(w)")
|
||||
}
|
||||
p("api.ServeError(h.log, w, http.StatusUnauthorized, err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
pf("api.ServeError(h.log, w, http.StatusUnauthorized, err)")
|
||||
pf("return")
|
||||
pf("}")
|
||||
pf("")
|
||||
}
|
||||
|
||||
handleParams(p, i, endpoint.QueryParams, endpoint.PathParams)
|
||||
handleParams(pf, i, endpoint.QueryParams, endpoint.PathParams)
|
||||
if endpoint.Request != nil {
|
||||
handleBody(p, endpoint.Request)
|
||||
handleBody(pf, endpoint.Request)
|
||||
}
|
||||
|
||||
var methodFormat string
|
||||
@ -214,54 +214,54 @@ func (a *API) generateGo() ([]byte, error) {
|
||||
}
|
||||
|
||||
methodFormat += ")"
|
||||
p(methodFormat, endpoint.MethodName)
|
||||
p("if httpErr.Err != nil {")
|
||||
p("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)")
|
||||
pf(methodFormat, endpoint.MethodName)
|
||||
pf("if httpErr.Err != nil {")
|
||||
pf("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)")
|
||||
if endpoint.Response == nil {
|
||||
p("}")
|
||||
p("}")
|
||||
pf("}")
|
||||
pf("}")
|
||||
continue
|
||||
}
|
||||
p("return")
|
||||
p("}")
|
||||
pf("return")
|
||||
pf("}")
|
||||
|
||||
i("encoding/json")
|
||||
p("")
|
||||
p("err = json.NewEncoder(w).Encode(retVal)")
|
||||
p("if err != nil {")
|
||||
p("h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", endpoint.MethodName, cases.Title(language.Und).String(group.Prefix))
|
||||
p("}")
|
||||
p("}")
|
||||
pf("")
|
||||
pf("err = json.NewEncoder(w).Encode(retVal)")
|
||||
pf("if err != nil {")
|
||||
pf("h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", endpoint.MethodName, cases.Title(language.Und).String(group.Prefix))
|
||||
pf("}")
|
||||
pf("}")
|
||||
}
|
||||
}
|
||||
|
||||
fileBody := result
|
||||
result = ""
|
||||
|
||||
p("// AUTOGENERATED BY private/apigen")
|
||||
p("// DO NOT EDIT.")
|
||||
p("")
|
||||
pf("// AUTOGENERATED BY private/apigen")
|
||||
pf("// DO NOT EDIT.")
|
||||
pf("")
|
||||
|
||||
p("package %s", a.PackageName)
|
||||
p("")
|
||||
pf("package %s", a.PackageName)
|
||||
pf("")
|
||||
|
||||
p("import (")
|
||||
pf("import (")
|
||||
slices := [][]string{imports.Standard, imports.External, imports.Internal}
|
||||
for sn, slice := range slices {
|
||||
sort.Strings(slice)
|
||||
for pn, path := range slice {
|
||||
p(`"%s"`, path)
|
||||
pf(`"%s"`, path)
|
||||
if pn == len(slice)-1 && sn < len(slices)-1 {
|
||||
p("")
|
||||
pf("")
|
||||
}
|
||||
}
|
||||
}
|
||||
p(")")
|
||||
p("")
|
||||
pf(")")
|
||||
pf("")
|
||||
|
||||
if _, ok := imports.All["time"]; ok {
|
||||
p("const dateLayout = \"%s\"", DateFormat)
|
||||
p("")
|
||||
pf("const dateLayout = \"%s\"", DateFormat)
|
||||
pf("")
|
||||
}
|
||||
|
||||
result += fileBody
|
||||
@ -286,7 +286,7 @@ func (a *API) handleTypesPackage(t reflect.Type) string {
|
||||
}
|
||||
|
||||
// handleParams handles parsing of URL query parameters or path parameters.
|
||||
func handleParams(p func(format string, a ...interface{}), i func(paths ...string), queryParams, pathParams []Param) {
|
||||
func handleParams(pf func(format string, a ...interface{}), i func(paths ...string), queryParams, pathParams []Param) {
|
||||
for _, params := range []*[]Param{&queryParams, &pathParams} {
|
||||
for _, param := range *params {
|
||||
varName := param.Name
|
||||
@ -296,50 +296,50 @@ func handleParams(p func(format string, a ...interface{}), i func(paths ...strin
|
||||
|
||||
switch params {
|
||||
case &queryParams:
|
||||
p("%s := r.URL.Query().Get(\"%s\")", varName, param.Name)
|
||||
p("if %s == \"\" {", varName)
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"parameter '%s' can't be empty\"))", param.Name)
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
pf("%s := r.URL.Query().Get(\"%s\")", varName, param.Name)
|
||||
pf("if %s == \"\" {", varName)
|
||||
pf("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"parameter '%s' can't be empty\"))", param.Name)
|
||||
pf("return")
|
||||
pf("}")
|
||||
pf("")
|
||||
case &pathParams:
|
||||
p("%s, ok := mux.Vars(r)[\"%s\"]", varName, param.Name)
|
||||
p("if !ok {")
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"missing %s route param\"))", param.Name)
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
pf("%s, ok := mux.Vars(r)[\"%s\"]", varName, param.Name)
|
||||
pf("if !ok {")
|
||||
pf("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"missing %s route param\"))", param.Name)
|
||||
pf("return")
|
||||
pf("}")
|
||||
pf("")
|
||||
}
|
||||
|
||||
switch param.Type {
|
||||
case reflect.TypeOf(uuid.UUID{}):
|
||||
i("storj.io/common/uuid")
|
||||
p("%s, err := uuid.FromString(%s)", param.Name, varName)
|
||||
pf("%s, err := uuid.FromString(%s)", param.Name, varName)
|
||||
case reflect.TypeOf(time.Time{}):
|
||||
i("time")
|
||||
p("%s, err := time.Parse(dateLayout, %s)", param.Name, varName)
|
||||
pf("%s, err := time.Parse(dateLayout, %s)", param.Name, varName)
|
||||
default:
|
||||
p("")
|
||||
pf("")
|
||||
continue
|
||||
}
|
||||
|
||||
p("if err != nil {")
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
pf("if err != nil {")
|
||||
pf("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
pf("return")
|
||||
pf("}")
|
||||
pf("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleBody handles request body.
|
||||
func handleBody(p func(format string, a ...interface{}), body interface{}) {
|
||||
p("payload := %s{}", reflect.TypeOf(body).String())
|
||||
p("if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {")
|
||||
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
p("return")
|
||||
p("}")
|
||||
p("")
|
||||
func handleBody(pf func(format string, a ...interface{}), body interface{}) {
|
||||
pf("payload := %s{}", reflect.TypeOf(body).String())
|
||||
pf("if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {")
|
||||
pf("api.ServeError(h.log, w, http.StatusBadRequest, err)")
|
||||
pf("return")
|
||||
pf("}")
|
||||
pf("")
|
||||
}
|
||||
|
||||
// getElementaryType simplifies a Go type.
|
||||
|
288
private/apigen/tsgen.go
Normal file
288
private/apigen/tsgen.go
Normal file
@ -0,0 +1,288 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package apigen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
// TODO maybe look at the Go structs MarshalJSON return type instead.
|
||||
var tsTypeOverrides = map[string]string{
|
||||
"uuid.UUID": "string",
|
||||
"time.Time": "string",
|
||||
"memory.Size": "string",
|
||||
"[]uint8": "string", // e.g. []byte
|
||||
}
|
||||
|
||||
// getBasicReflectType dereferences a pointer and gets the basic types from slices.
|
||||
func getBasicReflectType(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// tsType gets the corresponding typescript type for a provided reflect.Type.
|
||||
// Input is expected to be a (non pointer) struct or primitive.
|
||||
func tsType(t reflect.Type) string {
|
||||
override := tsTypeOverrides[t.String()]
|
||||
if len(override) > 0 {
|
||||
return override
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr:
|
||||
return tsType(t.Elem())
|
||||
case reflect.Slice, reflect.Array:
|
||||
return tsType(t.Elem()) + "[]"
|
||||
case reflect.String:
|
||||
return "string"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return "number"
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return "number"
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return "number"
|
||||
case reflect.Bool:
|
||||
return "boolean"
|
||||
case reflect.Struct:
|
||||
return t.Name()
|
||||
default:
|
||||
panic("unhandled type: " + t.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// types is a map of struct types and their struct type dependencies.
|
||||
// We use this to ensure all dependencies are written before their parent.
|
||||
types map[reflect.Type][]reflect.Type
|
||||
// typeList is a list of all struct types. We use this for sorting the types alphabetically
|
||||
// to ensure that the diff is minimized when the file is regenerated.
|
||||
typeList []reflect.Type
|
||||
typesWritten map[reflect.Type]bool
|
||||
api *API
|
||||
}
|
||||
|
||||
func newTSGenFile(filepath string, api *API) *tsGenFile {
|
||||
f := &tsGenFile{
|
||||
path: filepath,
|
||||
types: make(map[reflect.Type][]reflect.Type),
|
||||
typesWritten: make(map[reflect.Type]bool),
|
||||
api: api,
|
||||
}
|
||||
|
||||
f.pf("// AUTOGENERATED BY private/apigen")
|
||||
f.pf("// DO NOT EDIT.")
|
||||
f.pf("")
|
||||
f.pf("import { HttpClient } from '@/utils/httpClient'")
|
||||
f.pf("")
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *tsGenFile) pf(format string, a ...interface{}) {
|
||||
f.result += fmt.Sprintf(format+"\n", a...)
|
||||
}
|
||||
|
||||
func (f *tsGenFile) write() error {
|
||||
return os.WriteFile(f.path, []byte(f.result), 0644)
|
||||
}
|
||||
|
||||
func (f *tsGenFile) getStructsFromType(t reflect.Type) {
|
||||
t = getBasicReflectType(t)
|
||||
override := tsTypeOverrides[t.String()]
|
||||
if len(override) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// if it is a struct, get any types needed from the fields
|
||||
if t.Kind() == reflect.Struct {
|
||||
if _, ok := f.types[t]; !ok {
|
||||
f.types[t] = []reflect.Type{}
|
||||
f.typeList = append(f.typeList, t)
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
f.getStructsFromType(field.Type)
|
||||
|
||||
if field.Type.Kind() == reflect.Struct {
|
||||
deps := f.types[t]
|
||||
deps = append(deps, getBasicReflectType(field.Type))
|
||||
f.types[t] = deps
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (f *tsGenFile) generateTS() {
|
||||
f.createClasses()
|
||||
|
||||
for _, group := range f.api.EndpointGroups {
|
||||
// Not sure if this is a good name
|
||||
f.createAPIClient(group)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *tsGenFile) emitStruct(t reflect.Type) {
|
||||
override := tsTypeOverrides[t.String()]
|
||||
if len(override) > 0 {
|
||||
return
|
||||
}
|
||||
if f.typesWritten[t] {
|
||||
return
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
// TODO: handle slices
|
||||
// I'm not sure this is necessary. If it's not a struct then we don't need to create a TS class for it.
|
||||
// We just use a JS array of the class.
|
||||
return
|
||||
}
|
||||
|
||||
for _, d := range f.types[t] {
|
||||
if f.typesWritten[d] {
|
||||
continue
|
||||
}
|
||||
f.emitStruct(d)
|
||||
}
|
||||
|
||||
f.pf("class %s {", t.Name())
|
||||
defer f.pf("}\n")
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
attributes := strings.Fields(field.Tag.Get("json"))
|
||||
if len(attributes) == 0 || attributes[0] == "" {
|
||||
panic(t.Name() + " missing json declaration")
|
||||
}
|
||||
if attributes[0] == "-" {
|
||||
continue
|
||||
}
|
||||
f.pf("\t%s: %s;", attributes[0], tsType(field.Type))
|
||||
}
|
||||
|
||||
f.typesWritten[t] = true
|
||||
}
|
||||
|
||||
func (f *tsGenFile) createClasses() {
|
||||
for _, group := range f.api.EndpointGroups {
|
||||
for _, method := range group.endpoints {
|
||||
if method.Request != nil {
|
||||
reqType := reflect.TypeOf(method.Request)
|
||||
f.getStructsFromType(reqType)
|
||||
}
|
||||
if method.Response != nil {
|
||||
resType := reflect.TypeOf(method.Response)
|
||||
f.getStructsFromType(resType)
|
||||
}
|
||||
if len(method.QueryParams) > 0 {
|
||||
for _, p := range method.QueryParams {
|
||||
t := getBasicReflectType(p.Type)
|
||||
f.getStructsFromType(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(f.typeList, func(i, j int) bool {
|
||||
return strings.Compare(f.typeList[i].Name(), f.typeList[j].Name()) < 0
|
||||
})
|
||||
|
||||
for _, t := range f.typeList {
|
||||
f.emitStruct(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
|
||||
f.pf("export 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)
|
||||
f.pf("")
|
||||
for _, method := range group.endpoints {
|
||||
funcArgs, path := f.getArgsAndPath(method)
|
||||
|
||||
returnStmt := "return"
|
||||
returnType := "void"
|
||||
if method.Response != nil {
|
||||
returnType = tsType(getBasicReflectType(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}\n")
|
||||
}
|
||||
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 := getBasicReflectType(reflect.TypeOf(method.Request))
|
||||
funcArgs += fmt.Sprintf("request: %s, ", tsType(t))
|
||||
}
|
||||
|
||||
for _, p := range method.PathParams {
|
||||
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, tsType(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, tsType(p.Type))
|
||||
path += fmt.Sprintf("%s=${%s}", p.Name, p.Name)
|
||||
}
|
||||
|
||||
path = strings.ReplaceAll(path, "//", "/")
|
||||
|
||||
return strings.Trim(funcArgs, ", "), path
|
||||
}
|
@ -6,6 +6,7 @@ package main
|
||||
//go:generate go run ./
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
@ -29,6 +30,7 @@ func main() {
|
||||
Name: "Create new Project",
|
||||
Description: "Creates new Project with given info",
|
||||
MethodName: "GenCreateProject",
|
||||
RequestName: "createProject",
|
||||
Response: &console.Project{},
|
||||
Request: console.ProjectInfo{},
|
||||
})
|
||||
@ -37,6 +39,7 @@ func main() {
|
||||
Name: "Update Project",
|
||||
Description: "Updates project with given info",
|
||||
MethodName: "GenUpdateProject",
|
||||
RequestName: "updateProject",
|
||||
Response: console.Project{},
|
||||
Request: console.ProjectInfo{},
|
||||
PathParams: []apigen.Param{
|
||||
@ -48,6 +51,7 @@ func main() {
|
||||
Name: "Delete Project",
|
||||
Description: "Deletes project by id",
|
||||
MethodName: "GenDeleteProject",
|
||||
RequestName: "deleteProject",
|
||||
PathParams: []apigen.Param{
|
||||
apigen.NewParam("id", uuid.UUID{}),
|
||||
},
|
||||
@ -57,6 +61,7 @@ func main() {
|
||||
Name: "Get Projects",
|
||||
Description: "Gets all projects user has",
|
||||
MethodName: "GenGetUsersProjects",
|
||||
RequestName: "getProjects",
|
||||
Response: []console.Project{},
|
||||
})
|
||||
|
||||
@ -64,6 +69,7 @@ func main() {
|
||||
Name: "Get Project's Single Bucket Usage",
|
||||
Description: "Gets project's single bucket usage by bucket ID",
|
||||
MethodName: "GenGetSingleBucketUsageRollup",
|
||||
RequestName: "getBucketRollup",
|
||||
Response: accounting.BucketUsageRollup{},
|
||||
QueryParams: []apigen.Param{
|
||||
apigen.NewParam("projectID", uuid.UUID{}),
|
||||
@ -77,6 +83,7 @@ func main() {
|
||||
Name: "Get Project's All Buckets Usage",
|
||||
Description: "Gets project's all buckets usage",
|
||||
MethodName: "GenGetBucketUsageRollups",
|
||||
RequestName: "getBucketRollups",
|
||||
Response: []accounting.BucketUsageRollup{},
|
||||
QueryParams: []apigen.Param{
|
||||
apigen.NewParam("projectID", uuid.UUID{}),
|
||||
@ -93,6 +100,7 @@ func main() {
|
||||
Name: "Create new macaroon API key",
|
||||
Description: "Creates new macaroon API key with given info",
|
||||
MethodName: "GenCreateAPIKey",
|
||||
RequestName: "createAPIKey",
|
||||
Response: console.CreateAPIKeyResponse{},
|
||||
Request: console.CreateAPIKeyRequest{},
|
||||
})
|
||||
@ -105,9 +113,11 @@ func main() {
|
||||
Name: "Get User",
|
||||
Description: "Gets User by request context",
|
||||
MethodName: "GenGetUser",
|
||||
RequestName: "getUser",
|
||||
Response: console.ResponseUser{},
|
||||
})
|
||||
}
|
||||
|
||||
a.MustWriteGo("satellite/console/consoleweb/consoleapi/api.gen.go")
|
||||
a.MustWriteTS(fmt.Sprintf("web/satellite/src/api/%s.gen.ts", a.Version))
|
||||
}
|
||||
|
177
web/satellite/src/api/v0.gen.ts
Normal file
177
web/satellite/src/api/v0.gen.ts
Normal file
@ -0,0 +1,177 @@
|
||||
// AUTOGENERATED BY private/apigen
|
||||
// DO NOT EDIT.
|
||||
|
||||
import { HttpClient } from '@/utils/httpClient'
|
||||
|
||||
class APIKeyInfo {
|
||||
id: string;
|
||||
projectId: string;
|
||||
partnerId: string;
|
||||
userAgent: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
class BucketUsageRollup {
|
||||
projectID: string;
|
||||
bucketName: string;
|
||||
totalStoredData: number;
|
||||
totalSegments: number;
|
||||
objectCount: number;
|
||||
metadataSize: number;
|
||||
repairEgress: number;
|
||||
getEgress: number;
|
||||
auditEgress: number;
|
||||
since: string;
|
||||
before: string;
|
||||
}
|
||||
|
||||
class CreateAPIKeyRequest {
|
||||
projectID: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
class CreateAPIKeyResponse {
|
||||
key: string;
|
||||
keyInfo: APIKeyInfo;
|
||||
}
|
||||
|
||||
class Project {
|
||||
id: string;
|
||||
publicId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
partnerId: string;
|
||||
userAgent: string;
|
||||
ownerId: string;
|
||||
rateLimit: number;
|
||||
burstLimit: number;
|
||||
maxBuckets: number;
|
||||
createdAt: string;
|
||||
memberCount: number;
|
||||
storageLimit: string;
|
||||
bandwidthLimit: string;
|
||||
segmentLimit: number;
|
||||
}
|
||||
|
||||
class ProjectInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
storageLimit: string;
|
||||
bandwidthLimit: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
class ResponseUser {
|
||||
id: string;
|
||||
fullName: string;
|
||||
shortName: string;
|
||||
email: string;
|
||||
partnerId: string;
|
||||
userAgent: string;
|
||||
projectLimit: number;
|
||||
isProfessional: boolean;
|
||||
position: string;
|
||||
companyName: string;
|
||||
employeeCount: string;
|
||||
haveSalesContact: boolean;
|
||||
paidTier: boolean;
|
||||
isMFAEnabled: boolean;
|
||||
mfaRecoveryCodeCount: number;
|
||||
}
|
||||
|
||||
export class projectsHttpApiV0 {
|
||||
private readonly http: HttpClient = new HttpClient();
|
||||
private readonly ROOT_PATH: string = '/api/v0/projects';
|
||||
|
||||
public async createProject(request: ProjectInfo): Promise<Project> {
|
||||
const path = `${this.ROOT_PATH}/create`;
|
||||
const response = await this.http.post(path, JSON.stringify(request));
|
||||
if (response.ok) {
|
||||
return response.json().then((body) => body as Project);
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
public async updateProject(request: ProjectInfo, id: string): Promise<Project> {
|
||||
const path = `${this.ROOT_PATH}/update/${id}`;
|
||||
const response = await this.http.patch(path, JSON.stringify(request));
|
||||
if (response.ok) {
|
||||
return response.json().then((body) => body as Project);
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
public async deleteProject(id: string): Promise<void> {
|
||||
const path = `${this.ROOT_PATH}/delete/${id}`;
|
||||
const response = await this.http.delete(path);
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
public async getProjects(): Promise<Array<Project>> {
|
||||
const path = `${this.ROOT_PATH}/`;
|
||||
const response = await this.http.get(path);
|
||||
if (response.ok) {
|
||||
return response.json().then((body) => body as Array<Project>);
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
public async getBucketRollup(projectID: string, bucket: string, since: string, before: string): Promise<BucketUsageRollup> {
|
||||
const path = `${this.ROOT_PATH}/bucket-rollup?projectID=${projectID}&bucket=${bucket}&since=${since}&before=${before}`;
|
||||
const response = await this.http.get(path);
|
||||
if (response.ok) {
|
||||
return response.json().then((body) => body as BucketUsageRollup);
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
public async getBucketRollups(projectID: string, since: string, before: string): Promise<Array<BucketUsageRollup>> {
|
||||
const path = `${this.ROOT_PATH}/bucket-rollups?projectID=${projectID}&since=${since}&before=${before}`;
|
||||
const response = await this.http.get(path);
|
||||
if (response.ok) {
|
||||
return response.json().then((body) => body as Array<BucketUsageRollup>);
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
}
|
||||
export class apikeysHttpApiV0 {
|
||||
private readonly http: HttpClient = new HttpClient();
|
||||
private readonly ROOT_PATH: string = '/api/v0/apikeys';
|
||||
|
||||
public async createAPIKey(request: CreateAPIKeyRequest): Promise<CreateAPIKeyResponse> {
|
||||
const path = `${this.ROOT_PATH}/create`;
|
||||
const response = await this.http.post(path, JSON.stringify(request));
|
||||
if (response.ok) {
|
||||
return response.json().then((body) => body as CreateAPIKeyResponse);
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
}
|
||||
export class usersHttpApiV0 {
|
||||
private readonly http: HttpClient = new HttpClient();
|
||||
private readonly ROOT_PATH: string = '/api/v0/users';
|
||||
|
||||
public async getUser(): Promise<ResponseUser> {
|
||||
const path = `${this.ROOT_PATH}/`;
|
||||
const response = await this.http.get(path);
|
||||
if (response.ok) {
|
||||
return response.json().then((body) => body as ResponseUser);
|
||||
}
|
||||
const err = await response.json()
|
||||
throw new Error(err.error)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user