private/apigen: Rename Endpoint fields

Rename the Endpoint fields MethodName and RequestName because they were
confusing for what they are used.

This commit also adds some validations for these fields values and other
validations for Endpoint and EndpointGroup to avoid generating invalid
code.

It also include some tests for these new validations.

Closes https://github.com/storj/storj/issues/6333

Change-Id: Iaabfc33935517889e3729c8b37be51a55eea366c
This commit is contained in:
Ivan Fraixedes 2023-09-27 18:48:01 +02:00
parent ac86eb397a
commit 956109a097
No known key found for this signature in database
GPG Key ID: FB6101AFB5CB5AD5
8 changed files with 449 additions and 142 deletions

View File

@ -47,7 +47,8 @@ func (a *API) Group(name, prefix string) *EndpointGroup {
if !groupNameRegExp.MatchString(name) { if !groupNameRegExp.MatchString(name) {
panic( panic(
fmt.Sprintf( fmt.Sprintf(
"invalid name for API Endpoint Group. name must fulfill the regular expression `^([A-Z0-9]\\w*)?$``, got %q", "invalid name for API Endpoint Group. name must fulfill the regular expression %q, got %q",
groupNameRegExp,
name, name,
), ),
) )
@ -55,8 +56,9 @@ func (a *API) Group(name, prefix string) *EndpointGroup {
if !groupPrefixRegExp.MatchString(prefix) { if !groupPrefixRegExp.MatchString(prefix) {
panic( panic(
fmt.Sprintf( fmt.Sprintf(
"invalid prefix for API Endpoint Group %q. prefix must fulfill the regular expression `^\\w*$`, got %q", "invalid prefix for API Endpoint Group %q. prefix must fulfill the regular expression %q, got %q",
name, name,
groupPrefixRegExp,
prefix, prefix,
), ),
) )

View File

@ -7,7 +7,17 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
"regexp"
"strings" "strings"
"github.com/zeebo/errs"
)
var (
errsEndpoint = errs.Class("Endpoint")
goNameRegExp = regexp.MustCompile(`^[A-Z]\w*$`)
typeScriptNameRegExp = regexp.MustCompile(`^[a-z][a-zA-Z0-9_$]*$`)
) )
// Endpoint represents endpoint's configuration. // Endpoint represents endpoint's configuration.
@ -17,18 +27,21 @@ type Endpoint struct {
Name string Name string
// Description is a free text to describe the endpoint for documentation purpose. // Description is a free text to describe the endpoint for documentation purpose.
Description string Description string
// MethodName is the name of method of the service interface which handles the business logic of // GoName is an identifier used by the Go generator to generate specific server side code for this
// this endpoint. // endpoint.
// It must fulfill the Go language specification for method names //
// (https://go.dev/ref/spec#MethodName) // It must start with an uppercase letter and fulfill the Go language specification for method
// TODO: Should we rename this field to be something like ServiceMethodName? // names (https://go.dev/ref/spec#MethodName).
MethodName string // It cannot be empty.
// RequestName is the name of the method used to name the method in the client side code. When not GoName string
// set, MethodName is used. // TypeScriptName is an identifier used by the TypeScript generator to generate specific client
// TODO: Should we delete this field in favor of always using MethodName? // code for this endpoint
RequestName string //
NoCookieAuth bool // It must start with a lowercase letter and can only contains letters, digits, _, and $.
NoAPIAuth bool // It cannot be empty.
TypeScriptName string
NoCookieAuth bool
NoAPIAuth bool
// Request is the type that defines the format of the request body. // Request is the type that defines the format of the request body.
Request interface{} Request interface{}
// Response is the type that defines the format of the response body. // Response is the type that defines the format of the response body.
@ -50,6 +63,27 @@ func (e *Endpoint) APIAuth() bool {
return !e.NoAPIAuth return !e.NoAPIAuth
} }
// Validate validates the endpoint fields values are correct according to the documented constraints.
func (e *Endpoint) Validate() error {
if e.Name == "" {
return errsEndpoint.New("Name cannot be empty")
}
if e.Description == "" {
return errsEndpoint.New("Description cannot be empty")
}
if !goNameRegExp.MatchString(e.GoName) {
return errsEndpoint.New("GoName doesn't match the regular expression %q", goNameRegExp)
}
if !typeScriptNameRegExp.MatchString(e.TypeScriptName) {
return errsEndpoint.New("TypeScriptName doesn't match the regular expression %q", typeScriptNameRegExp)
}
return nil
}
// fullEndpoint represents endpoint with path and method. // fullEndpoint represents endpoint with path and method.
type fullEndpoint struct { type fullEndpoint struct {
Endpoint Endpoint
@ -64,18 +98,13 @@ func (fe fullEndpoint) requestType() reflect.Type {
return t return t
} }
name := fe.RequestName
if name == "" {
name = fe.MethodName
}
switch k := t.Kind(); k { switch k := t.Kind(); k {
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
if t.Elem().Name() == "" { if t.Elem().Name() == "" {
t = typeCustomName{Type: t, name: compoundTypeName(name, "Request")} t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Request")}
} }
case reflect.Struct: case reflect.Struct:
t = typeCustomName{Type: t, name: compoundTypeName(name, "Request")} t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Request")}
default: default:
panic( panic(
fmt.Sprintf( fmt.Sprintf(
@ -98,10 +127,10 @@ func (fe fullEndpoint) responseType() reflect.Type {
switch k := t.Kind(); k { switch k := t.Kind(); k {
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
if t.Elem().Name() == "" { if t.Elem().Name() == "" {
t = typeCustomName{Type: t, name: compoundTypeName(fe.MethodName, "Response")} t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Response")}
} }
case reflect.Struct: case reflect.Struct:
t = typeCustomName{Type: t, name: compoundTypeName(fe.MethodName, "Response")} t = typeCustomName{Type: t, name: compoundTypeName(fe.TypeScriptName, "Response")}
default: default:
panic( panic(
fmt.Sprintf( fmt.Sprintf(
@ -148,7 +177,10 @@ func (eg *EndpointGroup) Delete(path string, endpoint *Endpoint) {
} }
// addEndpoint adds new endpoint to endpoints list. // addEndpoint adds new endpoint to endpoints list.
// It panics if path doesn't begin with '/'. // It panics if:
// - path doesn't begin with '/'.
// - endpoint.Validate() returns an error.
// - An Endpoint with the same path and method already exists.
func (eg *EndpointGroup) addEndpoint(path, method string, endpoint *Endpoint) { func (eg *EndpointGroup) addEndpoint(path, method string, endpoint *Endpoint) {
if !strings.HasPrefix(path, "/") { if !strings.HasPrefix(path, "/") {
panic( panic(
@ -161,11 +193,31 @@ func (eg *EndpointGroup) addEndpoint(path, method string, endpoint *Endpoint) {
) )
} }
if err := endpoint.Validate(); err != nil {
panic(err)
}
ep := &fullEndpoint{*endpoint, path, method} ep := &fullEndpoint{*endpoint, path, method}
for i, e := range eg.endpoints { for _, e := range eg.endpoints {
if e.Path == path && e.Method == method { if e.Path == path && e.Method == method {
eg.endpoints[i] = ep panic(fmt.Sprintf("there is already an endpoint defined with path %q and method %q", path, method))
return }
if e.GoName == ep.GoName {
panic(
fmt.Sprintf("GoName %q is already used by the endpoint with path %q and method %q", e.GoName, e.Path, e.Method),
)
}
if e.TypeScriptName == ep.TypeScriptName {
panic(
fmt.Sprintf(
"TypeScriptName %q is already used by the endpoint with path %q and method %q",
e.TypeScriptName,
e.Path,
e.Method,
),
)
} }
} }
eg.endpoints = append(eg.endpoints, ep) eg.endpoints = append(eg.endpoints, ep)
@ -191,7 +243,7 @@ func (p Param) namedType(ep Endpoint, where string) reflect.Type {
if p.Type.Name() == "" { if p.Type.Name() == "" {
return typeCustomName{ return typeCustomName{
Type: p.Type, Type: p.Type,
name: compoundTypeName(ep.MethodName, where, "param", p.Name), name: compoundTypeName(ep.GoName, where, "param", p.Name),
} }
} }

View File

@ -0,0 +1,253 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
package apigen
import (
"math/rand"
"net/http"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEndpoint_Validate(t *testing.T) {
validEndpoint := Endpoint{
Name: "Test Endpoint",
Description: "This is an Endpoint purely for testing purposes",
GoName: "GenTest",
TypeScriptName: "genTest",
}
tcases := []struct {
testName string
endpointFn func() *Endpoint
errMsg string
}{
{
testName: "valid endpoint",
endpointFn: func() *Endpoint {
return &validEndpoint
},
},
{
testName: "empty name",
endpointFn: func() *Endpoint {
e := validEndpoint
e.Name = ""
return &e
},
errMsg: "Name cannot be empty",
},
{
testName: "empty description",
endpointFn: func() *Endpoint {
e := validEndpoint
e.Description = ""
return &e
},
errMsg: "Description cannot be empty",
},
{
testName: "empty Go name",
endpointFn: func() *Endpoint {
e := validEndpoint
e.GoName = ""
return &e
},
errMsg: "GoName doesn't match the regular expression",
},
{
testName: "no capitalized Go name ",
endpointFn: func() *Endpoint {
e := validEndpoint
e.GoName = "genTest"
return &e
},
errMsg: "GoName doesn't match the regular expression",
},
{
testName: "symbol in Go name",
endpointFn: func() *Endpoint {
e := validEndpoint
e.GoName = "GenTe$t"
return &e
},
errMsg: "GoName doesn't match the regular expression",
},
{
testName: "empty TypeScript name",
endpointFn: func() *Endpoint {
e := validEndpoint
e.TypeScriptName = ""
return &e
},
errMsg: "TypeScriptName doesn't match the regular expression",
},
{
testName: "capitalized TypeScript name ",
endpointFn: func() *Endpoint {
e := validEndpoint
e.TypeScriptName = "GenTest"
return &e
},
errMsg: "TypeScriptName doesn't match the regular expression",
},
{
testName: "dash in TypeScript name",
endpointFn: func() *Endpoint {
e := validEndpoint
e.TypeScriptName = "genTest-2"
return &e
},
errMsg: "TypeScriptName doesn't match the regular expression",
},
}
for _, tc := range tcases {
t.Run(tc.testName, func(t *testing.T) {
ep := tc.endpointFn()
err := ep.Validate()
if tc.errMsg == "" {
require.NoError(t, err)
return
}
require.Error(t, err)
require.ErrorContains(t, err, tc.errMsg)
})
}
}
func TestEndpointGroup(t *testing.T) {
t.Run("add endpoints", func(t *testing.T) {
endpointFn := func(postfix string) *Endpoint {
return &Endpoint{
Name: "Test Endpoint",
Description: "This is an Endpoint purely for testing purposes",
GoName: "GenTest" + postfix,
TypeScriptName: "genTest" + postfix,
}
}
path := "/" + strconv.Itoa(rand.Int())
eg := EndpointGroup{}
assert.NotPanics(t, func() { eg.Get(path, endpointFn(http.MethodGet)) }, "Get")
assert.NotPanics(t, func() { eg.Patch(path, endpointFn(http.MethodPatch)) }, "Patch")
assert.NotPanics(t, func() { eg.Post(path, endpointFn(http.MethodPost)) }, "Post")
assert.NotPanics(t, func() { eg.Delete(path, endpointFn(http.MethodDelete)) }, "Delete")
require.Len(t, eg.endpoints, 4, "Group endpoints count")
for i, m := range []string{http.MethodGet, http.MethodPatch, http.MethodPost, http.MethodDelete} {
ep := eg.endpoints[i]
assert.Equal(t, m, ep.Method)
assert.Equal(t, path, ep.Path)
assert.EqualValues(t, endpointFn(m), &ep.Endpoint)
}
})
t.Run("path does not begin with slash", func(t *testing.T) {
endpointFn := func(postfix string) *Endpoint {
return &Endpoint{
Name: "Test Endpoint",
Description: "This is an Endpoint purely for testing purposes",
GoName: "GenTest" + postfix,
TypeScriptName: "genTest" + postfix,
}
}
path := strconv.Itoa(rand.Int())
eg := EndpointGroup{}
assert.Panics(t, func() { eg.Get(path, endpointFn(http.MethodGet)) }, "Get")
assert.Panics(t, func() { eg.Patch(path, endpointFn(http.MethodPatch)) }, "Patch")
assert.Panics(t, func() { eg.Post(path, endpointFn(http.MethodPost)) }, "Post")
assert.Panics(t, func() { eg.Delete(path, endpointFn(http.MethodDelete)) }, "Delete")
})
t.Run("invalid endpoint", func(t *testing.T) {
endpointFn := func(postfix string) *Endpoint {
return &Endpoint{
Name: "",
Description: "This is an Endpoint purely for testing purposes",
GoName: "GenTest" + postfix,
TypeScriptName: "genTest" + postfix,
}
}
path := "/" + strconv.Itoa(rand.Int())
eg := EndpointGroup{}
assert.Panics(t, func() { eg.Get(path, endpointFn(http.MethodGet)) }, "Get")
assert.Panics(t, func() { eg.Patch(path, endpointFn(http.MethodPatch)) }, "Patch")
assert.Panics(t, func() { eg.Post(path, endpointFn(http.MethodPost)) }, "Post")
assert.Panics(t, func() { eg.Delete(path, endpointFn(http.MethodDelete)) }, "Delete")
})
t.Run("endpoint duplicate path method", func(t *testing.T) {
endpointFn := func(postfix string) *Endpoint {
return &Endpoint{
Name: "Test Endpoint",
Description: "This is an Endpoint purely for testing purposes",
GoName: "GenTest" + postfix,
TypeScriptName: "genTest" + postfix,
}
}
path := "/" + strconv.Itoa(rand.Int())
eg := EndpointGroup{}
assert.NotPanics(t, func() { eg.Get(path, endpointFn(http.MethodGet)) }, "Get")
assert.NotPanics(t, func() { eg.Patch(path, endpointFn(http.MethodPatch)) }, "Patch")
assert.NotPanics(t, func() { eg.Post(path, endpointFn(http.MethodPost)) }, "Post")
assert.NotPanics(t, func() { eg.Delete(path, endpointFn(http.MethodDelete)) }, "Delete")
assert.Panics(t, func() { eg.Get(path, endpointFn(http.MethodGet)) }, "Get")
assert.Panics(t, func() { eg.Patch(path, endpointFn(http.MethodPatch)) }, "Patch")
assert.Panics(t, func() { eg.Post(path, endpointFn(http.MethodPost)) }, "Post")
assert.Panics(t, func() { eg.Delete(path, endpointFn(http.MethodDelete)) }, "Delete")
})
t.Run("endpoint duplicate GoName", func(t *testing.T) {
endpointFn := func(postfix string) *Endpoint {
return &Endpoint{
Name: "Test Endpoint",
Description: "This is an Endpoint purely for testing purposes",
GoName: "GenTest",
TypeScriptName: "genTest" + postfix,
}
}
path := "/" + strconv.Itoa(rand.Int())
eg := EndpointGroup{}
assert.NotPanics(t, func() { eg.Get(path, endpointFn(http.MethodGet)) }, "Get")
assert.Panics(t, func() { eg.Patch(path, endpointFn(http.MethodPatch)) }, "Patch")
assert.Panics(t, func() { eg.Post(path, endpointFn(http.MethodPost)) }, "Post")
assert.Panics(t, func() { eg.Delete(path, endpointFn(http.MethodDelete)) }, "Delete")
})
t.Run("endpoint duplicate TypeScriptName", func(t *testing.T) {
endpointFn := func(postfix string) *Endpoint {
return &Endpoint{
Name: "Test Endpoint",
Description: "This is an Endpoint purely for testing purposes",
GoName: "GenTest" + postfix,
TypeScriptName: "genTest",
}
}
path := "/" + strconv.Itoa(rand.Int())
eg := EndpointGroup{}
assert.NotPanics(t, func() { eg.Patch(path, endpointFn(http.MethodPatch)) }, "Patch")
assert.Panics(t, func() { eg.Get(path, endpointFn(http.MethodGet)) }, "Get")
assert.Panics(t, func() { eg.Post(path, endpointFn(http.MethodPost)) }, "Post")
assert.Panics(t, func() { eg.Delete(path, endpointFn(http.MethodDelete)) }, "Delete")
})
}

View File

@ -12,59 +12,59 @@ export class Document {
version: Version; version: Version;
} }
export class GetResponseItem {
id: UUID;
path: string;
date: Time;
metadata: Metadata;
last_retrievals?: GetResponseItemLastretrievals;
}
export class GetResponseItemLastretrievalsItem {
user: string;
when: Time;
}
export class Metadata { export class Metadata {
owner: string; owner: string;
tags?: string[][]; tags?: string[][];
} }
export class UpdateContentRequest {
content: string;
}
export class UpdateContentResponse {
id: UUID;
date: Time;
pathParam: string;
body: string;
}
export class Version { export class Version {
date: Time; date: Time;
number: number; number: number;
} }
export type GetResponse = Array<GetResponseItem> export class getResponseItem {
id: UUID;
path: string;
date: Time;
metadata: Metadata;
last_retrievals?: getResponseItemLastretrievals;
}
export type GetResponseItemLastretrievals = Array<GetResponseItemLastretrievalsItem> export class getResponseItemLastretrievalsItem {
user: string;
when: Time;
}
export class updateContentRequest {
content: string;
}
export class updateContentResponse {
id: UUID;
date: Time;
pathParam: string;
body: string;
}
export type getResponse = Array<getResponseItem>
export type getResponseItemLastretrievals = Array<getResponseItemLastretrievalsItem>
export class docsHttpApiV0 { export class docsHttpApiV0 {
private readonly http: HttpClient = new HttpClient(); private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/docs'; private readonly ROOT_PATH: string = '/api/v0/docs';
public async Get(): Promise<GetResponse> { public async get(): Promise<getResponse> {
const fullPath = `${this.ROOT_PATH}/`; const fullPath = `${this.ROOT_PATH}/`;
const response = await this.http.get(fullPath); const response = await this.http.get(fullPath);
if (response.ok) { if (response.ok) {
return response.json().then((body) => body as GetResponse); return response.json().then((body) => body as getResponse);
} }
const err = await response.json(); const err = await response.json();
throw new Error(err.error); throw new Error(err.error);
} }
public async GetOne(path: string): Promise<Document> { public async getOne(path: string): Promise<Document> {
const fullPath = `${this.ROOT_PATH}/${path}`; const fullPath = `${this.ROOT_PATH}/${path}`;
const response = await this.http.get(fullPath); const response = await this.http.get(fullPath);
if (response.ok) { if (response.ok) {
@ -74,7 +74,7 @@ export class docsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async GetTag(path: string, tagName: string): Promise<string[]> { public async getTag(path: string, tagName: string): Promise<string[]> {
const fullPath = `${this.ROOT_PATH}/${path}/${tagName}`; const fullPath = `${this.ROOT_PATH}/${path}/${tagName}`;
const response = await this.http.get(fullPath); const response = await this.http.get(fullPath);
if (response.ok) { if (response.ok) {
@ -84,7 +84,7 @@ export class docsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async GetVersions(path: string): Promise<Version[]> { public async getVersions(path: string): Promise<Version[]> {
const fullPath = `${this.ROOT_PATH}/${path}`; const fullPath = `${this.ROOT_PATH}/${path}`;
const response = await this.http.get(fullPath); const response = await this.http.get(fullPath);
if (response.ok) { if (response.ok) {
@ -94,14 +94,14 @@ export class docsHttpApiV0 {
throw new Error(err.error); throw new Error(err.error);
} }
public async UpdateContent(request: UpdateContentRequest, path: string, id: UUID, date: Time): Promise<UpdateContentResponse> { public async updateContent(request: updateContentRequest, path: string, id: UUID, date: Time): Promise<updateContentResponse> {
const u = new URL(`${this.ROOT_PATH}/${path}`); const u = new URL(`${this.ROOT_PATH}/${path}`);
u.searchParams.set('id', id); u.searchParams.set('id', id);
u.searchParams.set('date', date); u.searchParams.set('date', date);
const fullPath = u.toString(); const fullPath = u.toString();
const response = await this.http.post(fullPath, JSON.stringify(request)); const response = await this.http.post(fullPath, JSON.stringify(request));
if (response.ok) { if (response.ok) {
return response.json().then((body) => body as UpdateContentResponse); return response.json().then((body) => body as updateContentResponse);
} }
const err = await response.json(); const err = await response.json();
throw new Error(err.error); throw new Error(err.error);

View File

@ -21,9 +21,10 @@ func main() {
g := a.Group("Documents", "docs") g := a.Group("Documents", "docs")
g.Get("/", &apigen.Endpoint{ g.Get("/", &apigen.Endpoint{
Name: "Get Documents", Name: "Get Documents",
Description: "Get the paths to all the documents under the specified paths", Description: "Get the paths to all the documents under the specified paths",
MethodName: "Get", GoName: "Get",
TypeScriptName: "get",
Response: []struct { Response: []struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Path string `json:"path"` Path string `json:"path"`
@ -37,20 +38,22 @@ func main() {
}) })
g.Get("/{path}", &apigen.Endpoint{ g.Get("/{path}", &apigen.Endpoint{
Name: "Get One", Name: "Get One",
Description: "Get the document in the specified path", Description: "Get the document in the specified path",
MethodName: "GetOne", GoName: "GetOne",
Response: myapi.Document{}, TypeScriptName: "getOne",
Response: myapi.Document{},
PathParams: []apigen.Param{ PathParams: []apigen.Param{
apigen.NewParam("path", ""), apigen.NewParam("path", ""),
}, },
}) })
g.Get("/{path}/tag/{tagName}", &apigen.Endpoint{ g.Get("/{path}/tag/{tagName}", &apigen.Endpoint{
Name: "Get a tag", Name: "Get a tag",
Description: "Get the tag of the document in the specified path and tag label ", Description: "Get the tag of the document in the specified path and tag label ",
MethodName: "GetTag", GoName: "GetTag",
Response: [2]string{}, TypeScriptName: "getTag",
Response: [2]string{},
PathParams: []apigen.Param{ PathParams: []apigen.Param{
apigen.NewParam("path", ""), apigen.NewParam("path", ""),
apigen.NewParam("tagName", ""), apigen.NewParam("tagName", ""),
@ -58,19 +61,21 @@ func main() {
}) })
g.Get("/{path}/versions", &apigen.Endpoint{ g.Get("/{path}/versions", &apigen.Endpoint{
Name: "Get Version", Name: "Get Version",
Description: "Get all the version of the document in the specified path", Description: "Get all the version of the document in the specified path",
MethodName: "GetVersions", GoName: "GetVersions",
Response: []myapi.Version{}, TypeScriptName: "getVersions",
Response: []myapi.Version{},
PathParams: []apigen.Param{ PathParams: []apigen.Param{
apigen.NewParam("path", ""), apigen.NewParam("path", ""),
}, },
}) })
g.Post("/{path}", &apigen.Endpoint{ g.Post("/{path}", &apigen.Endpoint{
Name: "Update Content", Name: "Update Content",
Description: "Update the content of the document with the specified path and ID if the last update is before the indicated date", Description: "Update the content of the document with the specified path and ID if the last update is before the indicated date",
MethodName: "UpdateContent", GoName: "UpdateContent",
TypeScriptName: "updateContent",
Response: struct { Response: struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Date time.Time `json:"date"` Date time.Time `json:"date"`

View File

@ -142,9 +142,9 @@ func (a *API) generateGo() ([]byte, error) {
if !isNillableType(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.GoName, returnParam)
} else { } else {
pf("%s(ctx context.Context, "+paramStr+") (api.HTTPError)", e.MethodName) pf("%s(ctx context.Context, "+paramStr+") (api.HTTPError)", e.GoName)
} }
} }
pf("}") pf("}")
@ -180,7 +180,7 @@ func (a *API) generateGo() ([]byte, error) {
pf("") pf("")
pf("%sRouter := router.PathPrefix(\"%s/%s\").Subrouter()", group.Prefix, a.endpointBasePath(), group.Prefix) pf("%sRouter := router.PathPrefix(\"%s/%s\").Subrouter()", group.Prefix, a.endpointBasePath(), group.Prefix)
for _, endpoint := range group.endpoints { for _, endpoint := range group.endpoints {
handlerName := "handle" + endpoint.MethodName handlerName := "handle" + endpoint.GoName
pf( pf(
"%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")", "%sRouter.HandleFunc(\"%s\", handler.%s).Methods(\"%s\")",
group.Prefix, group.Prefix,
@ -199,7 +199,7 @@ func (a *API) generateGo() ([]byte, error) {
for _, endpoint := range group.endpoints { for _, endpoint := range group.endpoints {
i("net/http") i("net/http")
pf("") pf("")
handlerName := "handle" + endpoint.MethodName handlerName := "handle" + endpoint.GoName
pf("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", group.Name, handlerName) pf("func (h *%sHandler) %s(w http.ResponseWriter, r *http.Request) {", group.Name, handlerName)
pf("ctx := r.Context()") pf("ctx := r.Context()")
pf("var err error") pf("var err error")
@ -244,7 +244,7 @@ func (a *API) generateGo() ([]byte, error) {
} }
methodFormat += ")" methodFormat += ")"
pf(methodFormat, endpoint.MethodName) pf(methodFormat, endpoint.GoName)
pf("if httpErr.Err != nil {") pf("if httpErr.Err != nil {")
pf("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)") pf("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)")
if endpoint.Response == nil { if endpoint.Response == nil {
@ -261,7 +261,7 @@ func (a *API) generateGo() ([]byte, error) {
pf("if err != nil {") pf("if err != nil {")
pf( pf(
"h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))", "h.log.Debug(\"failed to write json %s response\", zap.Error(Err%sAPI.Wrap(err)))",
endpoint.MethodName, endpoint.GoName,
cases.Title(language.Und).String(group.Prefix), cases.Title(language.Und).String(group.Prefix),
) )
pf("}") pf("}")

View File

@ -102,12 +102,7 @@ func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
} }
returnStmt += ";" returnStmt += ";"
methodName := method.RequestName f.pf("\tpublic async %s(%s): Promise<%s> {", method.TypeScriptName, funcArgs, returnType)
if methodName == "" {
methodName = method.MethodName
}
f.pf("\tpublic async %s(%s): Promise<%s> {", methodName, funcArgs, returnType)
if len(method.QueryParams) > 0 { if len(method.QueryParams) > 0 {
f.pf("\t\tconst u = new URL(`%s`);", path) f.pf("\t\tconst u = new URL(`%s`);", path)
for _, p := range method.QueryParams { for _, p := range method.QueryParams {

View File

@ -31,50 +31,50 @@ func main() {
g := a.Group("ProjectManagement", "projects") g := a.Group("ProjectManagement", "projects")
g.Post("/create", &apigen.Endpoint{ g.Post("/create", &apigen.Endpoint{
Name: "Create new Project", Name: "Create new Project",
Description: "Creates new Project with given info", Description: "Creates new Project with given info",
MethodName: "GenCreateProject", GoName: "GenCreateProject",
RequestName: "createProject", TypeScriptName: "createProject",
Response: console.Project{}, Response: console.Project{},
Request: console.UpsertProjectInfo{}, Request: console.UpsertProjectInfo{},
}) })
g.Patch("/update/{id}", &apigen.Endpoint{ g.Patch("/update/{id}", &apigen.Endpoint{
Name: "Update Project", Name: "Update Project",
Description: "Updates project with given info", Description: "Updates project with given info",
MethodName: "GenUpdateProject", GoName: "GenUpdateProject",
RequestName: "updateProject", TypeScriptName: "updateProject",
Response: console.Project{}, Response: console.Project{},
Request: console.UpsertProjectInfo{}, Request: console.UpsertProjectInfo{},
PathParams: []apigen.Param{ PathParams: []apigen.Param{
apigen.NewParam("id", uuid.UUID{}), apigen.NewParam("id", uuid.UUID{}),
}, },
}) })
g.Delete("/delete/{id}", &apigen.Endpoint{ g.Delete("/delete/{id}", &apigen.Endpoint{
Name: "Delete Project", Name: "Delete Project",
Description: "Deletes project by id", Description: "Deletes project by id",
MethodName: "GenDeleteProject", GoName: "GenDeleteProject",
RequestName: "deleteProject", TypeScriptName: "deleteProject",
PathParams: []apigen.Param{ PathParams: []apigen.Param{
apigen.NewParam("id", uuid.UUID{}), apigen.NewParam("id", uuid.UUID{}),
}, },
}) })
g.Get("/", &apigen.Endpoint{ g.Get("/", &apigen.Endpoint{
Name: "Get Projects", Name: "Get Projects",
Description: "Gets all projects user has", Description: "Gets all projects user has",
MethodName: "GenGetUsersProjects", GoName: "GenGetUsersProjects",
RequestName: "getProjects", TypeScriptName: "getProjects",
Response: []console.Project{}, Response: []console.Project{},
}) })
g.Get("/bucket-rollup", &apigen.Endpoint{ g.Get("/bucket-rollup", &apigen.Endpoint{
Name: "Get Project's Single Bucket Usage", Name: "Get Project's Single Bucket Usage",
Description: "Gets project's single bucket usage by bucket ID", Description: "Gets project's single bucket usage by bucket ID",
MethodName: "GenGetSingleBucketUsageRollup", GoName: "GenGetSingleBucketUsageRollup",
RequestName: "getBucketRollup", TypeScriptName: "getBucketRollup",
Response: accounting.BucketUsageRollup{}, Response: accounting.BucketUsageRollup{},
QueryParams: []apigen.Param{ QueryParams: []apigen.Param{
apigen.NewParam("projectID", uuid.UUID{}), apigen.NewParam("projectID", uuid.UUID{}),
apigen.NewParam("bucket", ""), apigen.NewParam("bucket", ""),
@ -84,11 +84,11 @@ func main() {
}) })
g.Get("/bucket-rollups", &apigen.Endpoint{ g.Get("/bucket-rollups", &apigen.Endpoint{
Name: "Get Project's All Buckets Usage", Name: "Get Project's All Buckets Usage",
Description: "Gets project's all buckets usage", Description: "Gets project's all buckets usage",
MethodName: "GenGetBucketUsageRollups", GoName: "GenGetBucketUsageRollups",
RequestName: "getBucketRollups", TypeScriptName: "getBucketRollups",
Response: []accounting.BucketUsageRollup{}, Response: []accounting.BucketUsageRollup{},
QueryParams: []apigen.Param{ QueryParams: []apigen.Param{
apigen.NewParam("projectID", uuid.UUID{}), apigen.NewParam("projectID", uuid.UUID{}),
apigen.NewParam("since", time.Time{}), apigen.NewParam("since", time.Time{}),
@ -97,11 +97,11 @@ func main() {
}) })
g.Get("/apikeys/{projectID}", &apigen.Endpoint{ g.Get("/apikeys/{projectID}", &apigen.Endpoint{
Name: "Get Project's API Keys", Name: "Get Project's API Keys",
Description: "Gets API keys by project ID", Description: "Gets API keys by project ID",
MethodName: "GenGetAPIKeys", GoName: "GenGetAPIKeys",
RequestName: "getAPIKeys", TypeScriptName: "getAPIKeys",
Response: console.APIKeyPage{}, Response: console.APIKeyPage{},
PathParams: []apigen.Param{ PathParams: []apigen.Param{
apigen.NewParam("projectID", uuid.UUID{}), apigen.NewParam("projectID", uuid.UUID{}),
}, },
@ -119,19 +119,19 @@ func main() {
g := a.Group("APIKeyManagement", "apikeys") g := a.Group("APIKeyManagement", "apikeys")
g.Post("/create", &apigen.Endpoint{ g.Post("/create", &apigen.Endpoint{
Name: "Create new macaroon API key", Name: "Create new macaroon API key",
Description: "Creates new macaroon API key with given info", Description: "Creates new macaroon API key with given info",
MethodName: "GenCreateAPIKey", GoName: "GenCreateAPIKey",
RequestName: "createAPIKey", TypeScriptName: "createAPIKey",
Response: console.CreateAPIKeyResponse{}, Response: console.CreateAPIKeyResponse{},
Request: console.CreateAPIKeyRequest{}, Request: console.CreateAPIKeyRequest{},
}) })
g.Delete("/delete/{id}", &apigen.Endpoint{ g.Delete("/delete/{id}", &apigen.Endpoint{
Name: "Delete API Key", Name: "Delete API Key",
Description: "Deletes macaroon API key by id", Description: "Deletes macaroon API key by id",
MethodName: "GenDeleteAPIKey", GoName: "GenDeleteAPIKey",
RequestName: "deleteAPIKey", TypeScriptName: "deleteAPIKey",
PathParams: []apigen.Param{ PathParams: []apigen.Param{
apigen.NewParam("id", uuid.UUID{}), apigen.NewParam("id", uuid.UUID{}),
}, },
@ -142,11 +142,11 @@ func main() {
g := a.Group("UserManagement", "users") g := a.Group("UserManagement", "users")
g.Get("/", &apigen.Endpoint{ g.Get("/", &apigen.Endpoint{
Name: "Get User", Name: "Get User",
Description: "Gets User by request context", Description: "Gets User by request context",
MethodName: "GenGetUser", GoName: "GenGetUser",
RequestName: "getUser", TypeScriptName: "getUser",
Response: console.ResponseUser{}, Response: console.ResponseUser{},
}) })
} }