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
This commit is contained in:
Jeremy Wharton 2023-02-22 04:08:34 -06:00 committed by Storj Robot
parent 4e94e6188c
commit 4d823e8166
6 changed files with 324 additions and 221 deletions

View File

@ -4,6 +4,10 @@
package apigen package apigen
import ( import (
"fmt"
"reflect"
"strings"
"storj.io/storj/private/api" "storj.io/storj/private/api"
) )
@ -27,3 +31,42 @@ func (a *API) Group(name, prefix string) *EndpointGroup {
return group return group
} }
// StringBuilder is an extension of strings.Builder that allows for writing formatted lines.
type StringBuilder struct{ strings.Builder }
// Writelnf formats arguments according to a format specifier
// and appends the resulting string to the StringBuilder's buffer.
func (s *StringBuilder) Writelnf(format string, a ...interface{}) {
s.WriteString(fmt.Sprintf(format+"\n", a...))
}
// getElementaryType simplifies a Go type.
func getElementaryType(t reflect.Type) reflect.Type {
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.Ptr, reflect.Slice:
return getElementaryType(t.Elem())
default:
return t
}
}
// filter returns a new slice of reflect.Type values that satisfy the given keep function.
func filter(types []reflect.Type, keep func(reflect.Type) bool) []reflect.Type {
filtered := make([]reflect.Type, 0, len(types))
for _, t := range types {
if keep(t) {
filtered = append(filtered, t)
}
}
return filtered
}
// isNillableType returns whether instances of the given type can be nil.
func isNillableType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Chan, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return true
}
return false
}

View File

@ -4,7 +4,6 @@
package apigen package apigen
import ( import (
"fmt"
"go/format" "go/format"
"os" "os"
"reflect" "reflect"
@ -37,11 +36,8 @@ func (a *API) MustWriteGo(path string) {
// generateGo generates api code and returns an output. // generateGo generates api code and returns an output.
func (a *API) generateGo() ([]byte, error) { func (a *API) generateGo() ([]byte, error) {
var result string result := &StringBuilder{}
pf := result.Writelnf
pf := func(format string, a ...interface{}) {
result += fmt.Sprintf(format+"\n", a...)
}
getPackageName := func(path string) string { getPackageName := func(path string) string {
pathPackages := strings.Split(path, "/") pathPackages := strings.Split(path, "/")
@ -81,13 +77,23 @@ func (a *API) generateGo() ([]byte, error) {
} }
} }
var getTypePackages func(t reflect.Type) []string
getTypePackages = func(t reflect.Type) []string {
t = getElementaryType(t)
if t.Kind() == reflect.Map {
pkgs := []string{getElementaryType(t.Key()).PkgPath()}
return append(pkgs, getTypePackages(t.Elem())...)
}
return []string{t.PkgPath()}
}
for _, group := range a.EndpointGroups { for _, group := range a.EndpointGroups {
for _, method := range group.endpoints { for _, method := range group.endpoints {
if method.Request != nil { if method.Request != nil {
i(getElementaryType(reflect.TypeOf(method.Request)).PkgPath()) i(getTypePackages(reflect.TypeOf(method.Request))...)
} }
if method.Response != nil { if method.Response != nil {
i(getElementaryType(reflect.TypeOf(method.Response)).PkgPath()) i(getTypePackages(reflect.TypeOf(method.Response))...)
} }
} }
} }
@ -122,7 +128,7 @@ func (a *API) generateGo() ([]byte, error) {
if e.Response != nil { if e.Response != nil {
responseType := reflect.TypeOf(e.Response) responseType := reflect.TypeOf(e.Response)
returnParam := a.handleTypesPackage(responseType) returnParam := a.handleTypesPackage(responseType)
if responseType == getElementaryType(responseType) { if !isNillableType(responseType) {
returnParam = "*" + returnParam returnParam = "*" + returnParam
} }
pf("%s(ctx context.Context, "+paramStr+") (%s, api.HTTPError)", e.MethodName, returnParam) pf("%s(ctx context.Context, "+paramStr+") (%s, api.HTTPError)", e.MethodName, returnParam)
@ -186,7 +192,7 @@ func (a *API) generateGo() ([]byte, error) {
pf("w.Header().Set(\"Content-Type\", \"application/json\")") pf("w.Header().Set(\"Content-Type\", \"application/json\")")
pf("") pf("")
if err := handleParams(pf, i, endpoint.PathParams, endpoint.QueryParams); err != nil { if err := handleParams(result, i, endpoint.PathParams, endpoint.QueryParams); err != nil {
return nil, err return nil, err
} }
@ -242,8 +248,9 @@ func (a *API) generateGo() ([]byte, error) {
} }
} }
fileBody := result fileBody := result.String()
result = "" result = &StringBuilder{}
pf = result.Writelnf
pf("// AUTOGENERATED BY private/apigen") pf("// AUTOGENERATED BY private/apigen")
pf("// DO NOT EDIT.") pf("// DO NOT EDIT.")
@ -271,9 +278,9 @@ func (a *API) generateGo() ([]byte, error) {
pf("") pf("")
} }
result += fileBody result.WriteString(fileBody)
output, err := format.Source([]byte(result)) output, err := format.Source([]byte(result.String()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -293,7 +300,8 @@ func (a *API) handleTypesPackage(t reflect.Type) string {
} }
// handleParams handles parsing of URL path parameters or query parameters. // handleParams handles parsing of URL path parameters or query parameters.
func handleParams(pf func(format string, a ...interface{}), i func(paths ...string), pathParams, queryParams []Param) error { func handleParams(builder *StringBuilder, i func(paths ...string), pathParams, queryParams []Param) error {
pf := builder.Writelnf
pErrCheck := func() { pErrCheck := func() {
pf("if err != nil {") pf("if err != nil {")
pf("api.ServeError(h.log, w, http.StatusBadRequest, err)") pf("api.ServeError(h.log, w, http.StatusBadRequest, err)")
@ -373,13 +381,3 @@ func handleBody(pf func(format string, a ...interface{}), body interface{}) {
pf("}") pf("}")
pf("") pf("")
} }
// getElementaryType simplifies a Go type.
func getElementaryType(t reflect.Type) reflect.Type {
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
return getElementaryType(t.Elem())
default:
return t
}
}

View File

@ -7,57 +7,11 @@ import (
"fmt" "fmt"
"os" "os"
"reflect" "reflect"
"sort"
"strings" "strings"
"github.com/zeebo/errs" "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. // MustWriteTS writes generated TypeScript code into a file.
func (a *API) MustWriteTS(path string) { func (a *API) MustWriteTS(path string) {
f := newTSGenFile(path, a) f := newTSGenFile(path, a)
@ -73,31 +27,16 @@ func (a *API) MustWriteTS(path string) {
type tsGenFile struct { type tsGenFile struct {
result string result string
path string path string
// types is a map of struct types and their struct type dependencies. api *API
// We use this to ensure all dependencies are written before their parent. types Types
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 { func newTSGenFile(filepath string, api *API) *tsGenFile {
f := &tsGenFile{ return &tsGenFile{
path: filepath, path: filepath,
types: make(map[reflect.Type][]reflect.Type), api: api,
typesWritten: make(map[reflect.Type]bool), types: NewTypes(),
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{}) { func (f *tsGenFile) pf(format string, a ...interface{}) {
@ -109,36 +48,14 @@ func (f *tsGenFile) write() error {
return os.WriteFile(f.path, []byte(content), 0644) return os.WriteFile(f.path, []byte(content), 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() { func (f *tsGenFile) generateTS() {
f.createClasses() 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 { for _, group := range f.api.EndpointGroups {
// Not sure if this is a good name // Not sure if this is a good name
@ -146,87 +63,38 @@ func (f *tsGenFile) generateTS() {
} }
} }
func (f *tsGenFile) emitStruct(t reflect.Type) { func (f *tsGenFile) registerTypes() {
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 _, group := range f.api.EndpointGroups {
for _, method := range group.endpoints { for _, method := range group.endpoints {
if method.Request != nil { if method.Request != nil {
reqType := reflect.TypeOf(method.Request) f.types.Register(reflect.TypeOf(method.Request))
f.getStructsFromType(reqType)
} }
if method.Response != nil { if method.Response != nil {
resType := reflect.TypeOf(method.Response) f.types.Register(reflect.TypeOf(method.Response))
f.getStructsFromType(resType)
} }
if len(method.QueryParams) > 0 { if len(method.QueryParams) > 0 {
for _, p := range method.QueryParams { for _, p := range method.QueryParams {
t := getBasicReflectType(p.Type) t := getElementaryType(p.Type)
f.getStructsFromType(t) f.types.Register(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) { func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
f.pf("export class %sHttpApi%s {", group.Prefix, strings.ToUpper(f.api.Version)) 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 http: HttpClient = new HttpClient();")
f.pf("\tprivate readonly ROOT_PATH: string = '/api/%s/%s';", f.api.Version, group.Prefix) f.pf("\tprivate readonly ROOT_PATH: string = '/api/%s/%s';", f.api.Version, group.Prefix)
f.pf("")
for _, method := range group.endpoints { for _, method := range group.endpoints {
f.pf("")
funcArgs, path := f.getArgsAndPath(method) funcArgs, path := f.getArgsAndPath(method)
returnStmt := "return" returnStmt := "return"
returnType := "void" returnType := "void"
if method.Response != nil { if method.Response != nil {
returnType = tsType(getBasicReflectType(reflect.TypeOf(method.Response))) returnType = TypescriptTypeName(getElementaryType(reflect.TypeOf(method.Response)))
if v := reflect.ValueOf(method.Response); v.Kind() == reflect.Array || v.Kind() == reflect.Slice { if v := reflect.ValueOf(method.Response); v.Kind() == reflect.Array || v.Kind() == reflect.Slice {
returnType = fmt.Sprintf("Array<%s>", returnType) returnType = fmt.Sprintf("Array<%s>", returnType)
} }
@ -246,9 +114,9 @@ func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
f.pf("\t\tif (response.ok) {") f.pf("\t\tif (response.ok) {")
f.pf("\t\t\t%s", returnStmt) f.pf("\t\t\t%s", returnStmt)
f.pf("\t\t}") f.pf("\t\t}")
f.pf("\t\tconst err = await response.json()") f.pf("\t\tconst err = await response.json();")
f.pf("\t\tthrow new Error(err.error)") f.pf("\t\tthrow new Error(err.error);")
f.pf("\t}\n") f.pf("\t}")
} }
f.pf("}") f.pf("}")
} }
@ -263,12 +131,12 @@ func (f *tsGenFile) getArgsAndPath(method *fullEndpoint) (funcArgs, path string)
path = "${this.ROOT_PATH}" + path path = "${this.ROOT_PATH}" + path
if method.Request != nil { if method.Request != nil {
t := getBasicReflectType(reflect.TypeOf(method.Request)) t := getElementaryType(reflect.TypeOf(method.Request))
funcArgs += fmt.Sprintf("request: %s, ", tsType(t)) funcArgs += fmt.Sprintf("request: %s, ", TypescriptTypeName(t))
} }
for _, p := range method.PathParams { for _, p := range method.PathParams {
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, tsType(p.Type)) funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.Type))
path += fmt.Sprintf("/${%s}", p.Name) path += fmt.Sprintf("/${%s}", p.Name)
} }
@ -279,7 +147,7 @@ func (f *tsGenFile) getArgsAndPath(method *fullEndpoint) (funcArgs, path string)
path += "&" path += "&"
} }
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, tsType(p.Type)) funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.Type))
path += fmt.Sprintf("%s=${%s}", p.Name, p.Name) path += fmt.Sprintf("%s=${%s}", p.Name, p.Name)
} }

189
private/apigen/tstypes.go Normal file
View File

@ -0,0 +1,189 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
package apigen
import (
"fmt"
"reflect"
"sort"
"strings"
"time"
"storj.io/common/memory"
"storj.io/common/uuid"
)
// commonPath is the path to the TypeScript module that common classes are imported from.
const commonPath = "@/types/common"
// commonClasses is a mapping of Go types to their corresponding TypeScript class names.
var commonClasses = map[reflect.Type]string{
reflect.TypeOf(memory.Size(0)): "MemorySize",
reflect.TypeOf(time.Time{}): "Time",
reflect.TypeOf(uuid.UUID{}): "UUID",
}
// NewTypes creates a new type definition generator.
func NewTypes() Types {
return Types{top: make(map[reflect.Type]struct{})}
}
// Types handles generating definitions from types.
type Types struct {
top map[reflect.Type]struct{}
}
// Register registers a type for generation.
func (types *Types) Register(t reflect.Type) {
types.top[t] = struct{}{}
}
// All returns a slice containing every top-level type and their dependencies.
func (types *Types) All() []reflect.Type {
seen := map[reflect.Type]struct{}{}
all := []reflect.Type{}
var walk func(t reflect.Type)
walk = func(t reflect.Type) {
if _, ok := seen[t]; ok {
return
}
seen[t] = struct{}{}
all = append(all, t)
if _, ok := commonClasses[t]; ok {
return
}
switch t.Kind() {
case reflect.Array, reflect.Ptr, reflect.Slice:
walk(t.Elem())
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
walk(t.Field(i).Type)
}
case reflect.Bool:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
case reflect.Float32, reflect.Float64:
case reflect.String:
break
default:
panic(fmt.Sprintf("type '%s' is not supported", t.Kind().String()))
}
}
for t := range types.top {
walk(t)
}
sort.Slice(all, func(i, j int) bool {
return strings.Compare(all[i].Name(), all[j].Name()) < 0
})
return all
}
// GenerateTypescriptDefinitions returns the TypeScript class definitions corresponding to the registered Go types.
func (types *Types) GenerateTypescriptDefinitions() string {
var out StringBuilder
pf := out.Writelnf
pf(types.getTypescriptImports())
all := filter(types.All(), func(t reflect.Type) bool {
if _, ok := commonClasses[t]; ok {
return false
}
return t.Kind() == reflect.Struct
})
for _, t := range all {
func() {
pf("\nexport class %s {", t.Name())
defer pf("}")
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
attributes := strings.Fields(field.Tag.Get("json"))
if len(attributes) == 0 || attributes[0] == "" {
pathParts := strings.Split(t.PkgPath(), "/")
pkg := pathParts[len(pathParts)-1]
panic(fmt.Sprintf("(%s.%s).%s missing json declaration", pkg, t.Name(), field.Name))
}
jsonField := attributes[0]
if jsonField == "-" {
continue
}
isOptional := ""
if isNillableType(t) {
isOptional = "?"
}
pf("\t%s%s: %s;", jsonField, isOptional, TypescriptTypeName(field.Type))
}
}()
}
return out.String()
}
// getTypescriptImports returns the TypeScript import directive for the registered Go types.
func (types *Types) getTypescriptImports() string {
classes := []string{}
all := types.All()
for _, t := range all {
if tsClass, ok := commonClasses[t]; ok {
classes = append(classes, tsClass)
}
}
if len(classes) == 0 {
return ""
}
sort.Slice(classes, func(i, j int) bool {
return strings.Compare(classes[i], classes[j]) < 0
})
return fmt.Sprintf("import { %s } from '%s';", strings.Join(classes, ", "), commonPath)
}
// TypescriptTypeName gets the corresponding TypeScript type for a provided reflect.Type.
func TypescriptTypeName(t reflect.Type) string {
if override, ok := commonClasses[t]; ok {
return override
}
switch t.Kind() {
case reflect.Ptr:
return TypescriptTypeName(t.Elem())
case reflect.Slice:
// []byte ([]uint8) is marshaled as a base64 string
elem := t.Elem()
if elem.Kind() == reflect.Uint8 {
return "string"
}
fallthrough
case reflect.Array:
return TypescriptTypeName(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())
}
}

View File

@ -2,16 +2,17 @@
// DO NOT EDIT. // DO NOT EDIT.
import { HttpClient } from '@/utils/httpClient'; import { HttpClient } from '@/utils/httpClient';
import { MemorySize, Time, UUID } from '@/types/common';
class APIKeyInfo { export class APIKeyInfo {
id: string; id: UUID;
projectId: string; projectId: UUID;
userAgent: string; userAgent: string;
name: string; name: string;
createdAt: string; createdAt: Time;
} }
class APIKeyPage { export class APIKeyPage {
apiKeys: APIKeyInfo[]; apiKeys: APIKeyInfo[];
search: string; search: string;
limit: number; limit: number;
@ -23,8 +24,8 @@ class APIKeyPage {
totalCount: number; totalCount: number;
} }
class BucketUsageRollup { export class BucketUsageRollup {
projectID: string; projectID: UUID;
bucketName: string; bucketName: string;
totalStoredData: number; totalStoredData: number;
totalSegments: number; totalSegments: number;
@ -33,49 +34,49 @@ class BucketUsageRollup {
repairEgress: number; repairEgress: number;
getEgress: number; getEgress: number;
auditEgress: number; auditEgress: number;
since: string; since: Time;
before: string; before: Time;
} }
class CreateAPIKeyRequest { export class CreateAPIKeyRequest {
projectID: string; projectID: string;
name: string; name: string;
} }
class CreateAPIKeyResponse { export class CreateAPIKeyResponse {
key: string; key: string;
keyInfo: APIKeyInfo; keyInfo: APIKeyInfo;
} }
class Project { export class Project {
id: string; id: UUID;
publicId: string; publicId: UUID;
name: string; name: string;
description: string; description: string;
userAgent: string; userAgent: string;
ownerId: string; ownerId: UUID;
rateLimit: number; rateLimit: number;
burstLimit: number; burstLimit: number;
maxBuckets: number; maxBuckets: number;
createdAt: string; createdAt: Time;
memberCount: number; memberCount: number;
storageLimit: string; storageLimit: MemorySize;
bandwidthLimit: string; bandwidthLimit: MemorySize;
userSpecifiedStorageLimit: string; userSpecifiedStorageLimit: MemorySize;
userSpecifiedBandwidthLimit: string; userSpecifiedBandwidthLimit: MemorySize;
segmentLimit: number; segmentLimit: number;
} }
class ProjectInfo { export class ProjectInfo {
name: string; name: string;
description: string; description: string;
storageLimit: string; storageLimit: MemorySize;
bandwidthLimit: string; bandwidthLimit: MemorySize;
createdAt: string; createdAt: Time;
} }
class ResponseUser { export class ResponseUser {
id: string; id: UUID;
fullName: string; fullName: string;
shortName: string; shortName: string;
email: string; email: string;
@ -105,7 +106,7 @@ export class projectsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async updateProject(request: ProjectInfo, id: string): Promise<Project> { public async updateProject(request: ProjectInfo, id: UUID): Promise<Project> {
const path = `${this.ROOT_PATH}/update/${id}`; const path = `${this.ROOT_PATH}/update/${id}`;
const response = await this.http.patch(path, JSON.stringify(request)); const response = await this.http.patch(path, JSON.stringify(request));
if (response.ok) { if (response.ok) {
@ -115,7 +116,7 @@ export class projectsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async deleteProject(id: string): Promise<void> { public async deleteProject(id: UUID): Promise<void> {
const path = `${this.ROOT_PATH}/delete/${id}`; const path = `${this.ROOT_PATH}/delete/${id}`;
const response = await this.http.delete(path); const response = await this.http.delete(path);
if (response.ok) { if (response.ok) {
@ -135,7 +136,7 @@ export class projectsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async getBucketRollup(projectID: string, bucket: string, since: string, before: string): Promise<BucketUsageRollup> { public async getBucketRollup(projectID: UUID, bucket: string, since: Time, before: Time): Promise<BucketUsageRollup> {
const path = `${this.ROOT_PATH}/bucket-rollup?projectID=${projectID}&bucket=${bucket}&since=${since}&before=${before}`; const path = `${this.ROOT_PATH}/bucket-rollup?projectID=${projectID}&bucket=${bucket}&since=${since}&before=${before}`;
const response = await this.http.get(path); const response = await this.http.get(path);
if (response.ok) { if (response.ok) {
@ -145,7 +146,7 @@ export class projectsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async getBucketRollups(projectID: string, since: string, before: string): Promise<Array<BucketUsageRollup>> { public async getBucketRollups(projectID: UUID, since: Time, before: Time): Promise<Array<BucketUsageRollup>> {
const path = `${this.ROOT_PATH}/bucket-rollups?projectID=${projectID}&since=${since}&before=${before}`; const path = `${this.ROOT_PATH}/bucket-rollups?projectID=${projectID}&since=${since}&before=${before}`;
const response = await this.http.get(path); const response = await this.http.get(path);
if (response.ok) { if (response.ok) {
@ -155,7 +156,7 @@ export class projectsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async getAPIKeys(projectID: string, search: string, limit: number, page: number, order: number, orderDirection: number): Promise<APIKeyPage> { public async getAPIKeys(projectID: UUID, search: string, limit: number, page: number, order: number, orderDirection: number): Promise<APIKeyPage> {
const path = `${this.ROOT_PATH}/apikeys/${projectID}?search=${search}&limit=${limit}&page=${page}&order=${order}&orderDirection=${orderDirection}`; const path = `${this.ROOT_PATH}/apikeys/${projectID}?search=${search}&limit=${limit}&page=${page}&order=${order}&orderDirection=${orderDirection}`;
const response = await this.http.get(path); const response = await this.http.get(path);
if (response.ok) { if (response.ok) {
@ -164,8 +165,8 @@ export class projectsHttpApiV0 {
const err = await response.json(); const err = await response.json();
throw new Error(err.error); throw new Error(err.error);
} }
} }
export class apikeysHttpApiV0 { export class apikeysHttpApiV0 {
private readonly http: HttpClient = new HttpClient(); private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/apikeys'; private readonly ROOT_PATH: string = '/api/v0/apikeys';
@ -180,7 +181,7 @@ export class apikeysHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async deleteAPIKey(id: string): Promise<void> { public async deleteAPIKey(id: UUID): Promise<void> {
const path = `${this.ROOT_PATH}/delete/${id}`; const path = `${this.ROOT_PATH}/delete/${id}`;
const response = await this.http.delete(path); const response = await this.http.delete(path);
if (response.ok) { if (response.ok) {
@ -189,8 +190,8 @@ export class apikeysHttpApiV0 {
const err = await response.json(); const err = await response.json();
throw new Error(err.error); throw new Error(err.error);
} }
} }
export class usersHttpApiV0 { export class usersHttpApiV0 {
private readonly http: HttpClient = new HttpClient(); private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/users'; private readonly ROOT_PATH: string = '/api/v0/users';
@ -204,5 +205,4 @@ export class usersHttpApiV0 {
const err = await response.json(); const err = await response.json();
throw new Error(err.error); throw new Error(err.error);
} }
} }

View File

@ -42,3 +42,8 @@ export enum PricingPlanType {
PARTNER = 'partner', PARTNER = 'partner',
PRO = 'pro', PRO = 'pro',
} }
// TODO: fully implement these types and their methods according to their Go counterparts
export type UUID = string
export type MemorySize = string
export type Time = string